diff --git a/plugins/l4d2_extraplayeritems.smx b/plugins/l4d2_extraplayeritems.smx index 53cac8c..32d3b91 100644 Binary files a/plugins/l4d2_extraplayeritems.smx and b/plugins/l4d2_extraplayeritems.smx differ diff --git a/plugins/l4d2_guesswho.smx b/plugins/l4d2_guesswho.smx index 80d6926..a0f2b16 100644 Binary files a/plugins/l4d2_guesswho.smx and b/plugins/l4d2_guesswho.smx differ diff --git a/scripting/include/l4d2_weapon_stocks.inc b/scripting/include/l4d2_weapon_stocks.inc index f47be67..a4a9824 100644 --- a/scripting/include/l4d2_weapon_stocks.inc +++ b/scripting/include/l4d2_weapon_stocks.inc @@ -3,14 +3,6 @@ #endif #define l4d2_weapons_inc_ -#define GETWEAPONNAME(%0) (IsValidWeaponId(WeaponId (%0)) ? (WeaponNames[(%0)]) : "") -#define GETLONGWEAPONNAME(%0) (IsValidWeaponId(WeaponId (%0)) ? (LongWeaponNames[(%0)]) : "") -#define GETMELEEWEAPONNAME(%0) (IsValidWeaponId(MeleeWeaponId (%0)) ? (MeleeWeaponNames[(%0)]) : "") -#define GETLONGMELEEWEAPONNAME(%0) (IsValidWeaponId(MeleeWeaponId (%0)) ? (LongMeleeWeaponNames[(%0)]) : "") -#define GETWEAPONMODEL(%0) (HasValidWeaponModel(WeaponId (%0)) ? (WeaponModels[(%0)]) : "") -#define GETMELEEWEAPONMODEL(%0) (HasValidWeaponModel(MeleeWeaponId (%0)) ? (MeleeWeaponModels[(%0)]) : "") - - // Weapon ID enumerations. // These values are *NOT* arbitrary! // They are used in game as the weaponid for weapon_spawn entities @@ -69,7 +61,8 @@ enum WeaponId { WEPID_ROCK, // 52 WEPID_PHYSICS, // 53 WEPID_AMMO, // 54 - WEPID_UPGRADE_ITEM // 55 + WEPID_UPGRADE_ITEM, // 55 + WEPID_COUNT }; // These values are arbitrary @@ -89,7 +82,8 @@ enum MeleeWeaponId WEPID_KATANA, WEPID_MACHETE, WEPID_RIOT_SHIELD, - WEPID_TONFA + WEPID_TONFA, + WEPID_MELEE_COUNT }; // Weapon names for each of the weapons, used in identification. @@ -139,7 +133,7 @@ char LongWeaponNames[56][] = { }; // Internal names for melee weapons -char MeleeWeaponNames[MeleeWeaponId][] = +char MeleeWeaponNames[WEPID_MELEE_COUNT][] = { "", "knife", @@ -159,7 +153,7 @@ char MeleeWeaponNames[MeleeWeaponId][] = }; // Long melee weapon names -char LongMeleeWeaponNames[MeleeWeaponId][] = +char LongMeleeWeaponNames[WEPID_MELEE_COUNT][] = { "None", "Knife", @@ -332,13 +326,13 @@ static Handle hMeleeWeaponModelsTrie = INVALID_HANDLE; stock void InitWeaponNamesTrie() { hWeaponNamesTrie = CreateTrie(); - for(int i = 0; i < view_as(WeaponId); i++) { + for(int i = 0; i < view_as(WEPID_COUNT); i++) { SetTrieValue(hWeaponNamesTrie, WeaponNames[i], i); } hMeleeWeaponNamesTrie = CreateTrie(); hMeleeWeaponModelsTrie = CreateTrie(); - for (int i = 0; i < view_as(MeleeWeaponId); ++i) + for (int i = 0; i < view_as(WEPID_MELEE_COUNT); ++i) { SetTrieValue(hMeleeWeaponNamesTrie, MeleeWeaponNames[i], i); SetTrieString(hMeleeWeaponModelsTrie, MeleeWeaponModels[i], MeleeWeaponNames[i]); @@ -357,7 +351,7 @@ stock bool IsValidWeaponId(WeaponId wepid){ } stock bool IsValidMeleeWeaponId(MeleeWeaponId wepid) { - return MeleeWeaponId:wepid >= WEPID_MELEE_NONE && MeleeWeaponId:wepid < MeleeWeaponId; + return wepid >= WEPID_MELEE_NONE && wepid < WEPID_MELEE_COUNT; } /** @@ -382,9 +376,7 @@ stock bool HasValidWeaponModel(WeaponId wepid) { } stock bool HasValidMeleeWeaponModel(MeleeWeaponId wepid) { - if (tagType == tagof(MeleeWeaponId)) { - return IsValidWeaponId(MeleeWeaponId:wepid) && MeleeWeaponModels[MeleeWeaponId:wepid][0] != '\0'; - } + return IsValidMeleeWeaponId(wepid) && MeleeWeaponModels[wepid][0] != '\0'; } /** @@ -429,11 +421,13 @@ stock int GetMeleeWeaponName(WeaponId wepid, char[] nameBuffer, int length) { * @return Number of bytes written to buffer, or 0 for invalid weaponId. */ stock int GetLongWeaponName(WeaponId wepid, char[] nameBuffer, int length) { - strcopy(nameBuffer, length, GETLONGMELEEWEAPONNAME(wepid)); + if(!IsValidWeaponId(wepid)) return 0; + return strcopy(nameBuffer, length, LongWeaponNames[wepid]); } -stock int GetLongMeleeWeaponName(WeaponId wepid, char[] nameBuffer, int length) { - strcopy(nameBuffer, length, GETLONGWEAPONNAME(wepid)); +stock int GetLongMeleeWeaponName(MeleeWeaponId wepid, char[] nameBuffer, int length) { + if(!IsValidMeleeWeaponId(wepid)) return 0; + return strcopy(nameBuffer, length, LongMeleeWeaponNames[wepid]); } /** @@ -445,12 +439,14 @@ stock int GetLongMeleeWeaponName(WeaponId wepid, char[] nameBuffer, int length) * @param length Max length which can be written to the buffer. * @return Number of bytes written to buffer, or 0 for invalid weaponid or no weapon model available. */ -stock int GetWeaponModel(MeleeWeaponId wepid, char[] modelBuffer, int length) { - strcopy(modelBuffer, length, GETWEAPONMODEL(wepid)); +stock int GetWeaponModel(WeaponId wepid, char[] modelBuffer, int length) { + if(!HasValidWeaponModel(wepid)) return 0; + return strcopy(modelBuffer, length, WeaponModels[wepid]); } stock int GetMeleeWeaponModel(MeleeWeaponId wepid, char[] modelBuffer, int length) { - strcopy(modelBuffer, length, GETMELEEWEAPONMODEL(wepid)); + if(!HasValidMeleeWeaponModel(wepid)) return 0; + return strcopy(modelBuffer, length, MeleeWeaponModels[wepid]); } /** @@ -533,7 +529,7 @@ stock MeleeWeaponId IdentifyMeleeWeapon(int entity) { int id; if(GetTrieValue(hMeleeWeaponNamesTrie, sName, id)) { - return id; + return view_as(id); } return WEPID_MELEE_NONE; } @@ -549,14 +545,14 @@ stock MeleeWeaponId IdentifyMeleeWeapon(int entity) { * @param model World model to use for the weapon spawn * @return entity of the new weapon spawn, or -1 on errors. */ -stock int ConvertWeaponSpawn(int entity, WeaponId wepid, int count = 5, const char model[] = "") +stock int ConvertWeaponSpawn(int entity, WeaponId wepid, int count = 5, const char[] model = "") { if(!IsValidEntity(entity)) return -1; if(!IsValidWeaponId(wepid)) return -1; if(model[0] == '\0' && !HasValidWeaponModel(wepid)) return -1; - new Float:origins[3], Float:angles[3]; + float origins[3], angles[3]; GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origins); GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); @@ -567,7 +563,7 @@ stock int ConvertWeaponSpawn(int entity, WeaponId wepid, int count = 5, const ch SetEntProp(entity, Prop_Send, "m_weaponID", wepid); - decl String:buf[64]; + char buf[64]; if(model[0] == '\0') { SetEntityModel(entity, model); } else { diff --git a/scripting/include/left4dhooks_stocks.inc b/scripting/include/left4dhooks_stocks.inc index 6e7810c..5e674e9 100644 --- a/scripting/include/left4dhooks_stocks.inc +++ b/scripting/include/left4dhooks_stocks.inc @@ -661,7 +661,7 @@ stock int L4D_GetPendingTankPlayer() * @return True if glow was set, false if entity does not support glow. */ // L4D2 only. -stock bool L4D2_SetEntityGlow(int entity, L4D2GlowType type, int range, int minRange, colorOverride[3], bool flashing) +stock bool L4D2_SetEntityGlow(int entity, L4D2GlowType type, int range, int minRange, int colorOverride[3], bool flashing) { if (!IsValidEntity(entity)) { diff --git a/scripting/l4d2_extraplayeritems.sp b/scripting/l4d2_extraplayeritems.sp index c19f560..ba9a8fd 100644 --- a/scripting/l4d2_extraplayeritems.sp +++ b/scripting/l4d2_extraplayeritems.sp @@ -463,15 +463,18 @@ public Action Timer_SpawnFinaleTank(Handle t, int user) { ServerCommand("sm_forcespecial tank"); finaleStage = Stage_Inactive; } + return Plugin_Handled; } public Action Timer_SpawnSplitTank(Handle t, int user) { ServerCommand("sm_forcespecial tank"); + return Plugin_Handled; } public Action Timer_SetHealth(Handle h, int user) { int client = GetClientOfUserId(user); if(client > 0 ) { SetEntProp(client, Prop_Send, "m_iHealth", extraTankHP); } + return Plugin_Handled; } public void Frame_SetExtraTankHealth(int user) { @@ -591,6 +594,7 @@ public Action Timer_CheckInventory(Handle h, int client) { PrintToConsoleAll("[EPI] Detected mismatch inventory for %N, restoring", client); RestoreInventory(client); } + return Plugin_Handled; } public void Event_PlayerTeam(Event event, const char[] name, bool dontBroadcast) { @@ -626,6 +630,7 @@ public Action Timer_DropSurvivor(Handle h, int client) { } DropDroppedInventories(); } + return Plugin_Handled; } /*public Action Timer_DropSurvivor(Handle h, DataPack pack) { @@ -725,8 +730,10 @@ public void Frame_SetupNewClient(int client) { if(tier2Weapons.Length > 0) { tier2Weapons.GetString(GetRandomInt(0, tier2Weapons.Length), weaponName, sizeof(weaponName)); Format(weaponName, sizeof(weaponName), "weapon_%s", weaponName); + PrintToServer("[EPI/debug] Giving new client (%N) tier 2: %s", client, weaponName); } else { Format(weaponName, sizeof(weaponName), "weapon_%s", TIER1_WEAPONS[GetRandomInt(0, TIER1_WEAPON_COUNT)]); + PrintToServer("[EPI/debug] Giving new client (%N) tier 1: %s", client, weaponName); } int item = GivePlayerItem(client, weaponName); if(lowestClient > 0) { @@ -744,6 +751,7 @@ public void Frame_SetupNewClient(int client) { } public Action Timer_RemoveInvincibility(Handle h, int client) { SDKUnhook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken); + return Plugin_Handled; } public Action OnInvincibleDamageTaken(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3]) { damage = 0.0; diff --git a/scripting/l4d2_guesswho.sp b/scripting/l4d2_guesswho.sp index 0cb5b96..f6933b2 100644 --- a/scripting/l4d2_guesswho.sp +++ b/scripting/l4d2_guesswho.sp @@ -4,12 +4,14 @@ #define DEBUG #define DEBUG_SHOW_POINTS #define DEBUG_BOT_MOVE +#define DEBUG_BLOCKERS +// #define DEBUG_MOVE_ATTEMPTS // #define DEBUG_SEEKER_PATH_CREATION 1 #define PLUGIN_VERSION "1.0" -#define BOT_MOVE_RANDOM_MIN_TIME 6.0 // The minimum random time for Timer_BotMove to activate (set per bot, per round) -#define BOT_MOVE_RANDOM_MAX_TIME 8.6 // The maximum random time for Timer_BotMove to activate (set per bot, per round) +#define BOT_MOVE_RANDOM_MIN_TIME 2.0 // The minimum random time for Timer_BotMove to activate (set per bot, per round) +#define BOT_MOVE_RANDOM_MAX_TIME 3.0 // The maximum random time for Timer_BotMove to activate (set per bot, per round) #define BOT_MOVE_CHANCE 0.96 // The chance the bot will move each Timer_BotMove #define BOT_MOVE_AVOID_FLOW_DIST 12.0 // The flow range of flow distance that triggers avoid #define BOT_MOVE_AVOID_SEEKER_CHANCE 0.50 // The chance that if the bot gets too close to the seeker, it runs away @@ -18,10 +20,17 @@ #define BOT_MOVE_JUMP_CHANCE 0.001 #define BOT_MOVE_SHOVE_CHANCE 0.0015 #define BOT_MOVE_RUN_CHANCE 0.15 +#define BOT_MOVE_NOT_REACHED_DISTANCE 60.0 // The distance that determines if a bot reached a point +#define BOT_MOVE_NOT_REACHED_ATTEMPT_RUNJUMP 6 // The minimum amount of attempts where bot will run or jump to dest +#define BOT_MOVE_NOT_REACHED_ATTEMPT_RETRY 9 // The minimum amount of attempts where bot gives up and picks new #define DOOR_TOGGLE_INTERVAL 5.0 // Interval that loops throuh all doors to randomly toggle #define DOOR_TOGGLE_CHANCE 0.01 // Chance that every Timer_DoorToggles triggers a door to toggle state #define HIDER_SWAP_COOLDOWN 30.0 // Amount of seconds until they can swap +#define HIDER_SWAP_LIMIT 3 // Amount of times a hider can swap per round #define FLOW_BOUND_BUFFER 200.0 // Amount to add to calculated bounds (Make it very generous) +#define HIDER_MIN_AVG_DISTANCE_AUTO_VOCALIZE 300.0 // The average minimum distance a hider is from the player that triggers auto vocalizating +#define HIDER_AUTO_VOCALIZE_GRACE_TIME 20.0 // Number of seconds between auto vocalizations +#define DEFAULT_MAP_TIME 480 #if defined DEBUG #define SEED_TIME 1.0 @@ -39,6 +48,8 @@ float DEBUG_POINT_VIEW_MIN[3] = { -5.0, -5.0, 0.0 }; float DEBUG_POINT_VIEW_MAX[3] = { 5.0, 5.0, 2.0 }; +int SEEKER_GLOW_COLOR[3] = { 128, 0, 0 }; +int PLAYER_GLOW_COLOR[3] = { 0, 255, 0 }; #include #include @@ -49,20 +60,22 @@ float DEBUG_POINT_VIEW_MAX[3] = { 5.0, 5.0, 2.0 }; #include char SURVIVOR_MODELS[8][] = { - "models/survivors/survivor_gambler.mdl", - "models/survivors/survivor_producer.mdl", - "models/survivors/survivor_coach.mdl", - "models/survivors/survivor_mechanic.mdl", "models/survivors/survivor_namvet.mdl", "models/survivors/survivor_teenangst.mdl", "models/survivors/survivor_biker.mdl", - "models/survivors/survivor_manager.mdl" + "models/survivors/survivor_manager.mdl", + "models/survivors/survivor_gambler.mdl", + "models/survivors/survivor_producer.mdl", + "models/survivors/survivor_coach.mdl", + "models/survivors/survivor_mechanic.mdl" }; enum struct LocationMeta { float pos[3]; float ang[3]; bool runto; + bool jump; + int attempts; // # of attempts player has moved until they will try to manage } // Game settings @@ -82,10 +95,14 @@ int currentSeeker; bool hasBeenSeeker[MAXPLAYERS+1]; bool ignoreSeekerBalance; int hiderSwapTime[MAXPLAYERS+1]; +int hiderSwapCount[MAXPLAYERS+1]; +bool isStarting; // Temp Ent Materials & Timers Handle spawningTimer; +Handle hiderCheckTimer; Handle recordTimer; +Handle timesUpTimer; Handle acquireLocationsTimer; Handle moveTimers[MAXPLAYERS+1]; UserMsg g_FadeUserMsgId; @@ -99,7 +116,7 @@ ConVar cvar_seekerFailDamageAmount; // Bot Movement specifics float flowMin, flowMax; -static float seekerPos[3]; +float seekerPos[3]; float seekerFlow = 0.0; float vecLastLocation[MAXPLAYERS+1][3]; @@ -166,12 +183,14 @@ public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[ HookEvent("round_start", Event_RoundStart); HookEvent("player_death", Event_PlayerDeath); HookEvent("player_bot_replace", Event_PlayerToBot); + HookEvent("player_ledge_grab", Event_LedgeGrab); AddCommandListener(OnGoAwayFromKeyboard, "go_away_from_keyboard"); } else if(!lateLoaded) { UnsetCvars(); UnhookEvent("round_start", Event_RoundStart); UnhookEvent("player_death", Event_PlayerDeath); UnhookEvent("player_bot_replace", Event_PlayerToBot); + UnhookEvent("player_ledge_grab", Event_LedgeGrab); Cleanup(); PrintToChatAll("[GuessWho] Gamemode unloaded but cvars have not been reset."); RemoveCommandListener(OnGoAwayFromKeyboard, "go_away_from_keyboard"); @@ -183,6 +202,13 @@ public Action OnGoAwayFromKeyboard(int client, const char[] command, int argc) { return Plugin_Handled; } +void Event_LedgeGrab(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0) { + L4D_ReviveSurvivor(client); + } +} + void Event_PlayerToBot(Event event, const char[] name, bool dontBroadcast) { int player = GetClientOfUserId(event.GetInt("player")); int bot = GetClientOfUserId(event.GetInt("bot")); @@ -197,8 +223,6 @@ void Event_PlayerToBot(Event event, const char[] name, bool dontBroadcast) { } } -int SEEKER_GLOW_COLOR[3] = { 128, 0, 0 }; -int PLAYER_GLOW_COLOR[3] = { 0, 255, 0 }; void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); @@ -207,19 +231,7 @@ void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { if(client == currentSeeker) { PrintToChatAll("The seeker, %N, has died. Hiders win!", currentSeeker); SetState(State_HidersWin); - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && IsFakeClient(i)) { - if(IsFakeClient(i)) { - ClearInventory(i); - PrintToServer("PlayerDeath: Seeker kill %d", i); - KickClient(i); - } else { - L4D2_SetEntityGlow(i, L4D2Glow_Constant, 0, 20, PLAYER_GLOW_COLOR, false); - L4D2_SetPlayerSurvivorGlowState(i, true); - } - } - } - CreateTimer(5.0, Timer_ResetAll); + EndGame(State_HidersWin); } else if(!IsFakeClient(client)) { if(attacker == currentSeeker) { PrintToChatAll("%N was killed", client); @@ -235,46 +247,13 @@ void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { if(GetPlayersLeftAlive() == 0) { if(GetState() == State_Active) { PrintToChatAll("Everyone has died. %N wins!", currentSeeker); - CreateTimer(5.0, Timer_ResetAll); - SetState(State_SeekerWon); + EndGame(State_SeekerWon); } } } -Action Timer_ResetAll(Handle h) { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { - ForcePlayerSuicide(i); - } - } - return Plugin_Handled; -} -bool isStarting; - -void StartGame() { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i)) { - if(isPendingPlay[i]) { - ChangeClientTeam(i, 2); - } else if(IsFakeClient(i)) { - KickClient(i); - } - } - } - if(!isStarting) { - isStarting = true; - CreateTimer(5.0, Timer_Start); - } -} - void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { - StartGame(); -} - -Action Timer_Start(Handle h) { - if(isStarting) - InitGamemode(); - return Plugin_Handled; + CreateTimer(5.0, Timer_WaitForPlayers, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } public void OnMapStart() { @@ -324,7 +303,7 @@ public void OnMapStart() { SDKHook(i, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); } } - CreateTimer(0.1, Timer_Start); + InitGamemode(); } SetState(State_Unknown); } @@ -356,25 +335,20 @@ public void OnClientPutInServer(int client) { isPendingPlay[client] = true; PrintToChatAll("%N will play next round", client); TeleportToSpawn(client); - if(!IsPendingPlayers()) { - StartGame(); - } } } + public void OnClientDisconnect(int client) { if(!isEnabled) return; if(client == currentSeeker) { - if(acquireLocationsTimer != null) delete acquireLocationsTimer; PrintToChatAll("The seeker has disconnected"); - CreateTimer(1.0, Timer_ResetAll); - currentSeeker = 0; + EndGame(State_HidersWin); } else if(!IsFakeClient(client) && GetState() == State_Active) { PrintToChatAll("A hider has left (%N)", client); if(GetPlayersLeftAlive() == 0 && GetState() == State_Active) { PrintToChatAll("Game Over. %N wins!", currentSeeker); - CreateTimer(5.0, Timer_ResetAll); - SetState(State_SeekerWon); + EndGame(State_SeekerWon); } } } @@ -423,6 +397,7 @@ void SetCvars(bool record = false) { SetCvarValue(cvar_sbPushScale, 0, record); SetCvarValue(FindConVar("sb_battlestation_give_up_range_from_human"), 5000.0, record); SetCvarValue(FindConVar("sb_max_battlestation_range_from_human"), 5000.0, record); + SetCvarValue(FindConVar("sb_enforce_proximity_range"), 10000, record); } void UnsetCvars() { @@ -459,11 +434,17 @@ void InitGamemode() { ArrayList validPlayerIds = new ArrayList(); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { - if(IsFakeClient(i)) KickClient(i); - else { + ChangeClientTeam(i, 2); + activeBotLocations[i].attempts = 0; + if(IsFakeClient(i)) { + ClearInventory(i); + KickClient(i); + } else { if(!IsPlayerAlive(i)) { L4D_RespawnPlayer(i); } + hiderSwapCount[i] = 0; + distQueue[i].Clear(); ChangeClientTeam(i, 2); if(!hasBeenSeeker[i] || ignoreSeekerBalance) validPlayerIds.Push(GetClientUserId(i)); @@ -477,11 +458,12 @@ void InitGamemode() { ignoreSeekerBalance = false; int newSeeker = GetClientOfUserId(validPlayerIds.Get(GetURandomInt() % validPlayerIds.Length)); delete validPlayerIds; - if(newSeeker > 0) { + if(newSeeker > 0) { hasBeenSeeker[newSeeker] = true; PrintToChatAll("%N is the seeker", newSeeker); - SetPlayerBlind(newSeeker, 255); SetSeeker(newSeeker); + SetPlayerBlind(newSeeker, 255); + SetEntPropFloat(newSeeker, Prop_Send, "m_flLaggedMovementValue", 0.0); // L4D2_SetPlayerSurvivorGlowState(newSeeker, true); L4D2_SetEntityGlow(newSeeker, L4D2Glow_Constant, 0, 10, SEEKER_GLOW_COLOR, false); } @@ -513,13 +495,24 @@ Action Timer_SpawnPost(Handle h) { PrintToChatAll("Timer_SpawnPost(): activating"); bool isL4D1 = L4D2_GetSurvivorSetMap() == 1; int remainingSeekers; + int survivorMaxIndex = isL4D1 ? 3 : 7; + int survivorIndexBot; for(int i = 1; i <= MaxClients; i++) { if(i != currentSeeker && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { - if(!IsFakeClient(i)) { + int survivor; + if(IsFakeClient(i)) { + // Set bot models uniformly + survivor = survivorIndexBot; + if(++survivorIndexBot > survivorMaxIndex) { + survivorIndexBot = 0; + } + } else { + // Set hiders models randomly + survivor = GetURandomInt() % survivorMaxIndex; if(!hasBeenSeeker[i]) { remainingSeekers++; } - PrintToChat(i, "You can change your model by looking at a player and pressing RELOAD"); + PrintToChat(i, "You can change your model %d times by looking at a player and pressing RELOAD", HIDER_SWAP_LIMIT); } SDKHook(i, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); SDKHook(i, SDKHook_WeaponDrop, OnWeaponDrop); @@ -527,9 +520,10 @@ Action Timer_SpawnPost(Handle h) { ClearInventory(i); int item = GivePlayerItem(i, "weapon_gnome"); EquipPlayerWeapon(i, item); - int survivor = GetRandomInt(isL4D1 ? 5 : 0, 7); + SetEntityModel(i, SURVIVOR_MODELS[survivor]); - SetEntProp(i, Prop_Send, "m_survivorCharacter", isL4D1 ? (survivor - 4) : survivor); + SetEntProp(i, Prop_Send, "m_survivorCharacter", survivor); + } } @@ -554,13 +548,15 @@ Action Timer_WaitForStart(Handle h) { } seekerFlow = L4D2Direct_GetFlowDistance(currentSeeker); acquireLocationsTimer = CreateTimer(0.5, Timer_AcquireLocations, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); + hiderCheckTimer = CreateTimer(5.0, Timer_CheckHiders, _, TIMER_REPEAT); CreateTimer(DOOR_TOGGLE_INTERVAL, Timer_DoorToggles, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); for(int i = 1; i <= MaxClients; i++) { if(i != currentSeeker && IsClientConnected(i) && IsClientInGame(i)) { TeleportEntity(i, seekerPos, NULL_VECTOR, NULL_VECTOR); if(IsFakeClient(i)) { moveTimers[i] = CreateTimer(GetRandomFloat(BOT_MOVE_RANDOM_MIN_TIME, BOT_MOVE_RANDOM_MAX_TIME), Timer_BotMove, GetClientUserId(i), TIMER_REPEAT); - TriggerTimer(moveTimers[i]); + validLocations.GetArray(GetURandomInt() % validLocations.Length, activeBotLocations[i]); + TeleportEntity(i, activeBotLocations[i].pos, activeBotLocations[i].ang, NULL_VECTOR); } } } @@ -581,10 +577,21 @@ Action Timer_StartSeeker(Handle h) { SetPlayerBlind(currentSeeker, 0); SetState(State_Active); SetTick(0); - SetMapTime(1000); + SetEntPropFloat(currentSeeker, Prop_Send, "m_flLaggedMovementValue", 1.0); + if(mapConfig.mapTime == 0) { + mapConfig.mapTime = DEFAULT_MAP_TIME; + } + SetMapTime(mapConfig.mapTime); + timesUpTimer = CreateTimer(float(mapConfig.mapTime), Timer_TimesUp, _, TIMER_FLAG_NO_MAPCHANGE); return Plugin_Continue; } +Action Timer_TimesUp(Handle h) { + PrintToChatAll("The seeker ran out of time. Hiders win!"); + EndGame(State_HidersWin); + return Plugin_Handled; +} + Action OnWeaponDrop(int client, int weapon) { return Plugin_Handled; } @@ -595,7 +602,7 @@ Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damag ClearInventory(victim); if(IsFakeClient(victim)) { PrintToChat(attacker, "That was a bot! -%.0f health", cvar_seekerFailDamageAmount.FloatValue); - SDKHooks_TakeDamage(attacker, 0, 0, cvar_seekerFailDamageAmount.FloatValue, DMG_BURN); + SDKHooks_TakeDamage(attacker, 0, 0, cvar_seekerFailDamageAmount.FloatValue, DMG_DIRECT); } return Plugin_Changed; } else if(attacker > 0 && attacker <= MaxClients) { @@ -613,6 +620,7 @@ Action Timer_DoorToggles(Handle h) { if(GetURandomFloat() < DOOR_TOGGLE_CHANCE) AcceptEntityInput(entity, "Toggle"); } + return Plugin_Handled; } Action Timer_AcquireLocations(Handle h) { @@ -644,6 +652,15 @@ Action Timer_AcquireLocations(Handle h) { return Plugin_Continue; } +void GetMovePoint(int i) { + activeBotLocations[i].runto = GetURandomFloat() < BOT_MOVE_RUN_CHANCE; + activeBotLocations[i].attempts = 0; + validLocations.GetArray(GetURandomInt() % validLocations.Length, activeBotLocations[i]); + #if defined DEBUG_SHOW_POINTS + Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 255, 120}, 0); + #endif +} + Action Timer_BotMove(Handle h, int userid) { int i = GetClientOfUserId(userid); if(i == 0) return Plugin_Stop; @@ -659,6 +676,7 @@ Action Timer_BotMove(Handle h, int userid) { } float botFlow = L4D2Direct_GetFlowDistance(i); + static float pos[3]; if(botFlow < flowMin || botFlow > flowMax) { activeBotLocations[i].runto = GetURandomFloat() > 0.90; TE_SetupBeamLaser(i, currentSeeker, g_iLaserIndex, 0, 0, 0, 8.0, 0.5, 0.1, 0, 1.0, {255, 255, 0, 125}, 1); @@ -667,37 +685,83 @@ Action Timer_BotMove(Handle h, int userid) { #if defined DEBUG_BOT_MOVE PrintToConsoleAll("[gw/debug] BOT %N TOO FAR (%f) BOUNDS (%f, %f)-> Moving to seeker (%f %f %f)", i, botFlow, flowMin, flowMax, seekerPos[0], seekerPos[1], seekerPos[2]); #endif + activeBotLocations[i].attempts = 0; } else if(validLocations.Length > 0) { - if(mapConfig.hasSpawnpoint && FloatAbs(botFlow - seekerFlow) < BOT_MOVE_AVOID_FLOW_DIST && GetURandomFloat() < BOT_MOVE_AVOID_SEEKER_CHANCE) { - if(!FindPointAway(seekerPos, activeBotLocations[i].pos, BOT_MOVE_AVOID_MIN_DISTANCE)) { - #if defined DEBUG_BOT_MOVE - PrintToConsoleAll("[gw/debug] BOT %N TOO CLOSE -> Failed to find far point, falling back to spawn", i); + GetAbsOrigin(i, pos); + float distanceToPoint = GetVectorDistance(pos, activeBotLocations[i].pos); + if(distanceToPoint < BOT_MOVE_NOT_REACHED_DISTANCE || GetURandomFloat() < 0.20) { + activeBotLocations[i].attempts = 0; + #if defined DEBUG_BOT_MOVE + L4D2_SetPlayerSurvivorGlowState(i, false); + L4D2_RemoveEntityGlow(i); + #endif + // Has reached destination + if(mapConfig.hasSpawnpoint && FloatAbs(botFlow - seekerFlow) < BOT_MOVE_AVOID_FLOW_DIST && GetURandomFloat() < BOT_MOVE_AVOID_SEEKER_CHANCE) { + if(!FindPointAway(seekerPos, activeBotLocations[i].pos, BOT_MOVE_AVOID_MIN_DISTANCE)) { + #if defined DEBUG_BOT_MOVE + PrintToConsoleAll("[gw/debug] BOT %N TOO CLOSE -> Failed to find far point, falling back to spawn", i); + #endif + activeBotLocations[i].pos = mapConfig.spawnpoint; + } else { + #if defined DEBUG_BOT_MOVE + PrintToConsoleAll("[gw/debug] BOT %N TOO CLOSE -> Moving to far point (%f %f %f) (%f units away)", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2], GetVectorDistance(seekerPos, activeBotLocations[i].pos)); + #endif + } + activeBotLocations[i].runto = GetURandomFloat() < 0.75; + #if defined DEBUG_SHOW_POINTS + Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.2, 0.1, 0, 0.0, {255, 255, 255, 255}, 0); #endif - activeBotLocations[i].pos = mapConfig.spawnpoint; } else { - #if defined DEBUG_BOT_MOVE - PrintToConsoleAll("[gw/debug] BOT %N TOO CLOSE -> Moving to far point (%f %f %f) (%f units away)", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2], GetVectorDistance(seekerPos, activeBotLocations[i].pos)); - #endif + GetMovePoint(i); + } + if(!L4D2_IsReachable(i, activeBotLocations[i].pos)) { + #if defined DEBUG_BOT_MOVE + PrintToChatAll("[gw/debug] POINT UNREACHABLE (Bot:%d) (%f %f %f)", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]); + PrintToServer("[gw/debug] POINT UNREACHABLE (Bot:%d) (%f %f %f)", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]); + Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_POINT_VIEW_MIN, view_as({ 10.0, 10.0, 100.0 }), NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 400.0, 2.0, 3.0, 0, 0.0, {255, 0, 0, 255}, 0); + #endif + GetMovePoint(i); } - activeBotLocations[i].runto = GetURandomFloat() < 0.75; - #if defined DEBUG_SHOW_POINTS - Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.2, 0.1, 0, 0.0, {255, 255, 255, 255}, 0); - #endif } else { - activeBotLocations[i].runto = GetURandomFloat() < BOT_MOVE_RUN_CHANCE; - validLocations.GetArray(GetURandomInt() % validLocations.Length, activeBotLocations[i]); + // Has not reached dest + activeBotLocations[i].attempts++; + #if defined DEBUG_MOVE_ATTEMPTS + PrintToConsoleAll("[gw/debug] Bot %d - move attempt %d - dist: %f", i, activeBotLocations[i].attempts, distanceToPoint); + #endif + if(activeBotLocations[i].attempts == BOT_MOVE_NOT_REACHED_ATTEMPT_RUNJUMP) { + if(distanceToPoint <= (BOT_MOVE_NOT_REACHED_DISTANCE * 2)) { + #if defined DEBUG_BOT_MOVE + PrintToConsoleAll("[gw/debug] Bot %d still has not reached point (%f %f %f), jumping", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]); + L4D2_SetPlayerSurvivorGlowState(i, true); + L4D2_SetEntityGlow(i, L4D2Glow_Constant, 0, 10, PLAYER_GLOW_COLOR, true); + #endif + activeBotLocations[i].jump = true; + } else { + activeBotLocations[i].runto = true; + #if defined DEBUG_BOT_MOVE + PrintToConsoleAll("[gw/debug] Bot %d not reached point (%f %f %f), running", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]); + L4D2_SetEntityGlow(i, L4D2Glow_Constant, 0, 10, PLAYER_GLOW_COLOR, true); + L4D2_SetPlayerSurvivorGlowState(i, true); + #endif + } + } else if(activeBotLocations[i].attempts > BOT_MOVE_NOT_REACHED_ATTEMPT_RETRY) { + #if defined DEBUG_BOT_MOVE + PrintToConsoleAll("[gw/debug] Bot %d giving up at reaching point (%f %f %f)", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]); + L4D2_SetEntityGlow(i, L4D2Glow_Constant, 0, 10, SEEKER_GLOW_COLOR, true); + L4D2_SetPlayerSurvivorGlowState(i, true); + #endif + GetMovePoint(i); + } #if defined DEBUG_SHOW_POINTS - Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 255, 255}, 0); + int color[4]; + color[0] = 255; + color[2] = 255; + color[3] = 120 + activeBotLocations[i].attempts * 45; + Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, color, 0); #endif } - #if defined DEBUG_BOT_MOVE - if(!L4D2_IsReachable(i, activeBotLocations[i].pos)) { - PrintToChatAll("[gw/debug] POINT UNREACHABLE (Bot:%d) (%f %f %f)", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]); - PrintToServer("[gw/debug] POINT UNREACHABLE (Bot:%d) (%f %f %f)", i, activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]); - Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 0, 255}, 0); - } - #endif - + + LookAtPoint(i, activeBotLocations[i].pos); L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", GetClientUserId(i), activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2] @@ -709,6 +773,11 @@ Action Timer_BotMove(Handle h, int userid) { public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) { if(!isEnabled) return Plugin_Continue; if(IsFakeClient(client)) { + if(activeBotLocations[client].jump) { + activeBotLocations[client].jump = false; + buttons |= (IN_WALK | IN_JUMP | IN_FORWARD); + return Plugin_Changed; + } buttons |= (activeBotLocations[client].runto ? IN_WALK : IN_SPEED); if(GetURandomFloat() < BOT_MOVE_USE_CHANCE) { buttons |= IN_USE; @@ -721,28 +790,35 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 } return Plugin_Changed; } else if(client != currentSeeker && buttons & IN_RELOAD) { - int target = GetClientAimTarget(client, true); - if(target > 0) { - int time = GetTime(); - float diff = float(time - hiderSwapTime[client]); - if(diff > HIDER_SWAP_COOLDOWN) { - hiderSwapTime[client] = GetTime(); + if(hiderSwapCount[client] >= HIDER_SWAP_LIMIT) { + PrintHintText(client, "Swap limit reached"); + } else { + int target = GetClientAimTarget(client, true); + + if(target > 0) { + int time = GetTime(); + float diff = float(time - hiderSwapTime[client]); + if(diff > HIDER_SWAP_COOLDOWN) { + hiderSwapTime[client] = GetTime(); + hiderSwapCount[client]++; - /*float pos[3], pos2[3]; - GetClientAbsOrigin(client, pos); - GetClientEyePosition(client, pos2); - TE_SetupParticle(g_iSmokeParticle, pos, pos2, .iEntity = client); - TE_SendToAllInRange(pos, RangeType_Audibility, 0.0);*/ + /*float pos[3], pos2[3]; + GetClientAbsOrigin(client, pos); + GetClientEyePosition(client, pos2); + TE_SetupParticle(g_iSmokeParticle, pos, pos2, .iEntity = client); + TE_SendToAllInRange(pos, RangeType_Audibility, 0.0);*/ - char modelName[64]; - GetClientModel(target, modelName, sizeof(modelName)); - int type = GetEntProp(target, Prop_Send, "m_survivorCharacter"); - SetEntityModel(client, modelName); - SetEntProp(client, Prop_Send, "m_survivorCharacter", type); + char modelName[64]; + GetClientModel(target, modelName, sizeof(modelName)); + int type = GetEntProp(target, Prop_Send, "m_survivorCharacter"); + SetEntityModel(client, modelName); + SetEntProp(client, Prop_Send, "m_survivorCharacter", type); - EmitSoundToAll("ui/pickup_secret01.wav", client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } else { - PrintHintText(client, "You can swap in %.0f seconds", HIDER_SWAP_COOLDOWN - diff); + EmitSoundToAll("ui/pickup_secret01.wav", client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + PrintHintText(client, "You have %d swaps remaining", HIDER_SWAP_LIMIT - hiderSwapCount[client]); + } else { + PrintHintText(client, "You can swap in %.0f seconds", HIDER_SWAP_COOLDOWN - diff); + } } } } @@ -755,7 +831,8 @@ void ClearInventory(int client) { int item = GetPlayerWeaponSlot(client, i); if(item > 0) { RemovePlayerItem(client, item); - AcceptEntityInput(item, "Kill"); + RemoveEdict(item); + // AcceptEntityInput(item, "Kill"); } } } @@ -776,13 +853,15 @@ bool AddSurvivor() { } } - CreateTimer(0.2, Timer_Kick, i); + CreateTimer(0.2, Timer_Kick, GetClientUserId(i)); } return result; } -Action Timer_Kick(Handle h, int i) { - KickClient(i); +Action Timer_Kick(Handle h, int u) { + int i = GetClientOfUserId(u); + if(i > 0) KickClient(i); + return Plugin_Handled; } stock void L4D2_RunScript(const char[] sCode, any ...) {