From 957ae488b8582a6010dd433a2dd074e795b9900d Mon Sep 17 00:00:00 2001 From: Jackz Date: Mon, 9 Oct 2023 20:58:21 -0500 Subject: [PATCH] Forbidden check fixes --- scripting/GrabEnt.sp | 10 +- scripting/epi/director.sp | 240 ---------------- scripting/include/epi/director.sp | 425 +++++++++++++++++++++++++++++ scripting/include/hats/hats.sp | 7 +- scripting/include/hats/walls.sp | 3 +- scripting/l4d2_extraplayeritems.sp | 94 ++++--- 6 files changed, 496 insertions(+), 283 deletions(-) delete mode 100644 scripting/epi/director.sp create mode 100644 scripting/include/epi/director.sp diff --git a/scripting/GrabEnt.sp b/scripting/GrabEnt.sp index c4ea053..96bf8e6 100644 --- a/scripting/GrabEnt.sp +++ b/scripting/GrabEnt.sp @@ -36,7 +36,7 @@ int g_BeamSprite; int g_HaloSprite; int g_iLaserIndex; -#define MAX_FORBIDDEN_CLASSNAMES 7 +#define MAX_FORBIDDEN_CLASSNAMES 9 static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { // "env_physics_blocker", // "env_player_blocker", @@ -45,7 +45,8 @@ static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { "func_button", "func_elevator", "func_button_timed", - // "func_movelinear", + "func_movelinear", + "func_tracktrain", // "infected", "func_lod", "prop_ragdoll" @@ -149,6 +150,9 @@ public Action Cmd_Grab(client, args) { if(parent > 0) { ent = parent; } + if(!CheckBlacklist(ent)) { + return Plugin_Handled; + } float entOrigin[3], playerGrabOrigin[3]; GetEntPropVector(ent, Prop_Send, "m_vecOrigin", entOrigin); @@ -569,6 +573,7 @@ bool Filter_IgnoreForbidden(int entity, int mask, int data) { bool CheckBlacklist(int entity) { static char buffer[64]; GetEntityClassname(entity, buffer, sizeof(buffer)); + PrintToServer("GrabEnt:CheckBlacklist | classname=\"%s\"", buffer); for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { if(StrEqual(FORBIDDEN_CLASSNAMES[i], buffer)) { return false; @@ -577,6 +582,7 @@ bool CheckBlacklist(int entity) { if(StrContains(buffer, "prop_") > -1) { GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { + PrintToServer("GrabEnt:CheckBlacklist | model=\"%s\" FORBIDDEN_MODELS[%d] = \"%s\"", buffer, i, FORBIDDEN_MODELS[i]); if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { return false; } diff --git a/scripting/epi/director.sp b/scripting/epi/director.sp deleted file mode 100644 index d8b8117..0000000 --- a/scripting/epi/director.sp +++ /dev/null @@ -1,240 +0,0 @@ -// SETTINGS -#define DIRECTOR_WITCH_MIN_TIME 120 // The minimum amount of time to pass since last witch spawn for the next extra witch to spawn -#define DIRECTOR_WITCH_CHECK_TIME 30.0 // How often to check if a witch should be spawned -#define DIRECTOR_WITCH_MAX_WITCHES 6 // The maximum amount of extra witches to spawn -#define DIRECTOR_WITCH_ROLLS 2 // The number of dice rolls, increase if you want to increase freq -#define DIRECTOR_MIN_SPAWN_TIME 20.0 // Possibly randomized, per-special -#define DIRECTOR_SPAWN_CHANCE 30.0 // The raw chance of a spawn -#define DIRECTOR_CHANGE_LIMIT_CHANCE 0.10 // The chance that the maximum amount per-special is changed -#define DIRECTOR_SPECIAL_TANK_CHANCE 0.05 // The chance that specials can spawn when a tank is active -#define DIRECTOR_STRESS_CUTOFF 0.60 // The minimum chance a random cut off stress value is chosen [this, 1.0] - -/// DEFINITIONS -#define NUM_SPECIALS 6 -#define TOTAL_NUM_SPECIALS 8 -char SPECIAL_IDS[TOTAL_NUM_SPECIALS][] = { - "smoker", - "boomer", - "hunter", - "spitter", - "jockey", - "charger", - "witch", - "tank" -}; -enum specialType { - Special_Smoker, - Special_Boomer, - Special_Hunter, - Special_Spitter, - Special_Jockey, - Special_Charger, - Special_Witch, - Special_Tank, -}; - -static float highestFlowAchieved; -static float g_lastSpawnTime[TOTAL_NUM_SPECIALS]; -static int g_spawnLimit[TOTAL_NUM_SPECIALS]; -static int g_spawnCount[TOTAL_NUM_SPECIALS]; -static float g_minFlowSpawn; // The minimum flow for specials to start spawning (waiting for players to leave saferom) -static float g_minStressIntensity; // The minimum stress that specials arent allowed to spawn - -static int extraWitchCount; -static Handle witchSpawnTimer = null; - -float g_extraWitchFlowPositions[DIRECTOR_WITCH_MAX_WITCHES] = {}; - -/// EVENTS - -void Director_OnMapStart() { - if(cvEPISpecialSpawning.BoolValue && abmExtraCount > 4) { - InitExtraWitches(); - } - float time = GetGameTime(); - for(int i = 0; i < TOTAL_NUM_SPECIALS; i++) { - g_lastSpawnTime[i] = time; - g_spawnLimit[i] = 1; - g_spawnCount[i] = 0; - } -} -void Director_OnMapEnd() { - for(int i = 0; i <= DIRECTOR_WITCH_MAX_WITCHES; i++) { - g_extraWitchFlowPositions[i] = 0.0; - } - delete witchSpawnTimer; -} - -void Cvar_SpecialSpawningChange(ConVar convar, const char[] oldValue, const char[] newValue) { - if(convar.IntValue & 2 && abmExtraCount > 4) { - if(witchSpawnTimer == null) - witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT); - } else { - delete witchSpawnTimer; - } -} - -void Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) { - g_spawnCount[Special_Witch]++; -} -void Director_OnClientPutInServer(int client) { - if(client > 0 && GetClientTeam(client) == 3) { - int class = GetEntProp(client, Prop_Send, "m_zombieClass"); - // Ignore a hacky temp bot spawn - // To bypass director limits many plugins spawn an infected "bot" that immediately gets kicked, which allows a window to spawn a special - static char buf[32]; - GetClientName(special, buf, sizeof(buf)); - if(StrContains(buf, "bot", false) == -1) { - g_spawnCount[class]++; - } - } -} -void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - if(client > 0 && GetClientTeam(client) == 3) { - int class = GetEntProp(client, Prop_Send, "m_zombieClass"); - g_spawnCount[class]--; - } -} - -/// METHODS - -void InitExtraWitches() { - float flowMax = L4D2Direct_GetMapMaxFlowDistance() - FLOW_CUTOFF; - // Just in case we don't have max flow or the map is extremely tiny, don't run: - if(flowMax > 0.0) { - int count = abmExtraCount; - if(count < 4) count = 4; - // Calculate the number of witches we want to spawn. - // We bias the dice roll to the right. We slowly increase min based on player count to shift distribution to the right - int min = RoundToFloor(float(count - 4) / 4.0); - extraWitchCount = DiceRoll(min, DIRECTOR_WITCH_MAX_WITCHES, DIRECTOR_WITCH_ROLLS, BIAS_LEFT); - PrintDebug(DEBUG_SPAWNLOGIC, "InitExtraWitches: %d witches (min=%d, max=%d, rolls=%d) checkInterval=%f", extraWitchCount, min, DIRECTOR_WITCH_MAX_WITCHES, DIRECTOR_WITCH_ROLLS, DIRECTOR_WITCH_CHECK_TIME); - for(int i = 0; i <= extraWitchCount; i++) { - g_extraWitchFlowPositions[i] = GetURandomFloat() * (flowMax-FLOW_CUTOFF) + FLOW_CUTOFF; - PrintDebug(DEBUG_SPAWNLOGIC, "Witch position #%d: flow %.2f (%.0f%%)", i, g_extraWitchFlowPositions[i], g_extraWitchFlowPositions[i] / flowMax); - } - witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT); - } -} - -void Director_PrintDebug(int client) { - PrintToConsole(client, "===Extra Witches==="); - PrintToConsole(client, "Map Bounds: [%f, %f]", FLOW_CUTOFF, L4D2Direct_GetMapMaxFlowDistance() - (FLOW_CUTOFF*2.0)); - PrintToConsole(client, "Total Witches Spawned: %d | Target: %d", g_spawnCount[Special_Witch], extraWitchCount); - for(int i = 0; i < extraWitchCount && i < DIRECTOR_WITCH_MAX_WITCHES; i++) { - PrintToConsole(client, "%d. %f", i, g_extraWitchFlowPositions[i]); - } -} - -void Director_RandomizeLimits() { - // We add +1 to spice it up - int max = RoundToCeil(float(abmExtraCount - 4) / 4) + 1; - for(int i = 0; i < NUM_SPECIALS; i++) { - specialType special = view_as(i); - g_spawnLimit[i] = GetRandomInt(0, max); - } -} -void Director_RandomizeThings() { - g_minStressIntensity = GetRandomFloat(DIRECTOR_STRESS_CUTOFF, 1.0); - g_minFlowSpawn = GetRandomFloat(FLOW_CUTOFF, FLOW_CUTOFF * 2); - -} - -/// TIMERS - -Action Timer_Director(Handle h) { - if(abmExtraCount <= 4) return Plugin_Continue; - float time = GetGameTime(); - - // Calculate the new highest flow - int highestPlayer = L4D_GetHighestFlowSurvivor(); - float flow = L4D2Direct_GetFlowDistance(highestPlayer); - if(flow > highestFlowAchieved) { - highestFlowAchieved = flow; - } - // Only start spawning once they get to g_minFlowSpawn - a little past the start saferoom - if(highestFlowAchieved < g_minFlowSpawn) return Plugin_Continue; - float curAvgStress = L4D_GetAvgSurvivorIntensity(); - // Don't spawn specials when tanks active, but have a small chance (DIRECTOR_SPECIAL_TANK_CHANCE) to bypass - if(L4D2_IsTankInPlay() && GetURandomFloat() > DIRECTOR_SPECIAL_TANK_CHANCE) { - return Plugin_Continue; - } else { - // Stop spawning when players are stressed from a random value chosen by [DIRECTOR_STRESS_CUTOFF, 1.0] - if(curAvgStress >= g_minStressIntensity) return Plugin_Continue; - } - - // TODO: Scale spawning chance based on intensity? 0.0 = more likely, < g_minStressIntensity = less likely - // Scale the chance where stress = 0.0, the chance is 50% more, and stress = 1.0, the chance is 50% less - float spawnChance = DIRECTOR_SPAWN_CHANCE + (0.5 - curAvgStress) / 10 - for(int i = 0; i < NUM_SPECIALS; i++) { - specialType special = view_as(i); - // Skip if we hit our limit, or too soon: - if(g_spawnCount[i] >= g_spawnLimit[i]) continue; - if(time - g_lastSpawnTime[i] < DIRECTOR_MIN_SPAWN_TIME) continue; - - if(GetURandomFloat() < spawnChance) { - DirectorSpawn(special); - } - } - - if(GetURandomFloat() < DIRECTOR_CHANGE_LIMIT_CHANCE) { - Director_RandomizeLimits(); - } - - return Plugin_Continue; -} - - -Action Timer_DirectorWitch(Handle h) { - if(g_spawnCount[Special_Witch] < extraWitchCount) { //&& time - g_lastSpawnTimes.witch > DIRECTOR_WITCH_MIN_TIME - for(int i = 0; i <= extraWitchCount; i++) { - if(g_extraWitchFlowPositions[i] > 0.0 && highestFlowAchieved >= g_extraWitchFlowPositions[i]) { - // Reset the flow so we don't spawn another - g_extraWitchFlowPositions[i] = 0.0; - DirectorSpawn(Special_Witch); - break; - } - } - } - return Plugin_Continue; -} - -// UTIL functions -void DirectorSpawn(specialType special) { - PrintChatToAdmins("EPI: DirectorSpawn(%s) (dont worry about it)", SPECIAL_IDS[view_as(special)]); - int player = GetSuitableVictim(); - PrintDebug(DEBUG_SPAWNLOGIC, "Director: spawning %s from %N (cnt=%d,lim=%d)", SPECIAL_IDS[view_as(special)], player, g_spawnCount[view_as(special)], g_spawnLimit[view_as(special)]); - PrintToServer("[EPI] Spawning %s On %N", SPECIAL_IDS[view_as(special)], player); - if(special != Special_Witch && special != Special_Tank) { - // Bypass director - int bot = CreateFakeClient("EPI_BOT"); - if (bot != 0) { - ChangeClientTeam(bot, 3); - CreateTimer(0.1, Timer_Kick, bot); - } - } - CheatCommand(player, "z_spawn_old", SPECIAL_IDS[view_as(special)], "auto"); - g_lastSpawnTime[view_as(special)] = GetGameTime(); -} - -// TODO: make -void DirectSpawn(specialType special, const float pos[3]) { - -} -// Finds a player that is suitable (lowest intensity) -int GetSuitableVictim() { - int victim = -1; - float lowestIntensity = 0.0; - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { - float intensity = L4D_GetPlayerIntensity(i); - // TODO: possibly add perm health into calculations - if(intensity < lowestIntensity || victim == -1) { - lowestIntensity = intensity; - victim = i; - } - } - } - return victim; -} \ No newline at end of file diff --git a/scripting/include/epi/director.sp b/scripting/include/epi/director.sp new file mode 100644 index 0000000..1e45af4 --- /dev/null +++ b/scripting/include/epi/director.sp @@ -0,0 +1,425 @@ +// SETTINGS +// TODO: make cvars +#define DIRECTOR_TIMER_INTERVAL 3.0 +#define DIRECTOR_WITCH_MIN_TIME 120 // The minimum amount of time to pass since last witch spawn for the next extra witch to spawn +#define DIRECTOR_WITCH_CHECK_TIME 30.0 // How often to check if a witch should be spawned +#define DIRECTOR_WITCH_MAX_WITCHES 5 // The maximum amount of extra witches to spawn +#define DIRECTOR_WITCH_ROLLS 4 // The number of dice rolls, increase if you want to increase freq +#define DIRECTOR_MIN_SPAWN_TIME 12.0 // Possibly randomized, per-special +#define DIRECTOR_SPAWN_CHANCE 0.05 // The raw chance of a spawn +#define DIRECTOR_CHANGE_LIMIT_CHANCE 0.05 // The chance that the maximum amount per-special is changed +#define DIRECTOR_SPECIAL_TANK_CHANCE 0.05 // The chance that specials can spawn when a tank is active +#define DIRECTOR_STRESS_CUTOFF 0.75 // The minimum chance a random cut off stress value is chosen [this, 1.0] +#define DIRECTOR_REST_CHANCE 0.03 // The chance the director ceases spawning +#define DIRECTOR_REST_MAX_COUNT 10 // The maximum amount of rest given (this * DIRECTOR_TIMER_INTERVAL) + +#define DIRECTOR_DEBUG_SPAWN 1 // Dont actually spawn + +/// DEFINITIONS +#define NUM_SPECIALS 6 +#define TOTAL_NUM_SPECIALS 8 +char SPECIAL_IDS[TOTAL_NUM_SPECIALS][] = { + "invalid", + "smoker", + "boomer", + "hunter", + "spitter", + "jockey", + "charger", + "witch", + "tank" +}; +enum specialType { + Special_Smoker = 1, + Special_Boomer = 2, + Special_Hunter = 3, + Special_Spitter = 4, + Special_Jockey = 5, + Special_Charger = 6, + Special_Witch = 7, + Special_Tank = 8, +}; +enum directorState { + DState_Normal, + DState_NoPlayersOrNotCoop, + DState_PendingMinFlowOrDisabled, + DState_MaxSpecialTime, + DState_PlayerChance, + DState_Resting, + DState_TankInPlay, + DState_HighStress +} +char DIRECTOR_STATE[8][] = { + "normal", + "no players / not coop", + "pending minflow OR disabled", + "max special in window", + "player scaled chance", + "rest period", + "tank in play", + "high stress", +}; +directorState g_lastState; + +static float g_highestFlowAchieved; +static float g_lastSpawnTime[TOTAL_NUM_SPECIALS]; +static float g_lastSpecialSpawnTime; // for any special +static int g_spawnLimit[TOTAL_NUM_SPECIALS]; +static int g_spawnCount[TOTAL_NUM_SPECIALS]; +static float g_minFlowSpawn; // The minimum flow for specials to start spawning (waiting for players to leave saferom) +static float g_maxStressIntensity; // The max stress that specials arent allowed to spawn + +static int extraWitchCount; +static int g_infectedCount; +static int g_restCount; +static Handle witchSpawnTimer = null; + +float g_extraWitchFlowPositions[DIRECTOR_WITCH_MAX_WITCHES] = {}; + +/// EVENTS + +void Director_OnMapStart() { + if(cvEPISpecialSpawning.IntValue & 2 && abmExtraCount > 4) { + InitExtraWitches(); + } + float time = GetGameTime(); + for(int i = 0; i < TOTAL_NUM_SPECIALS; i++) { + g_lastSpawnTime[i] = time; + g_spawnLimit[i] = 1; + g_spawnCount[i] = 0; + } + g_highestFlowAchieved = 0.0; + g_lastSpecialSpawnTime = time; + g_infectedCount = 0; + g_restCount = 0; + Director_RandomizeThings(); +} + +void Director_OnMapEnd() { + for(int i = 0; i < DIRECTOR_WITCH_MAX_WITCHES; i++) { + g_extraWitchFlowPositions[i] = 0.0; + } + delete witchSpawnTimer; +} + +void Cvar_SpecialSpawningChange(ConVar convar, const char[] oldValue, const char[] newValue) { + if(convar.IntValue & 2 && abmExtraCount > 4) { + if(witchSpawnTimer == null) + witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT); + } else { + delete witchSpawnTimer; + } +} + +void Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) { + g_spawnCount[Special_Witch]++; +} +void Director_OnClientPutInServer(int client) { + // Wait a frame for the bot to be assigned a team + RequestFrame(Director_CheckClient, client); +} +void Director_CheckClient(int client) { + if(IsClientConnected(client) && GetClientTeam(client) == 3) { + // To bypass director limits many plugins spawn an infected "bot" that immediately gets kicked, which allows a window to spawn a special + // The fake bot's class is usually 9, an invalid + int class = GetEntProp(client, Prop_Send, "m_zombieClass"); + if(class > view_as(Special_Tank)) { + return; + } + if(IsFakeClient(client) && class == Special_Tank && abmExtraCount > 4 && cvEPISpecialSpawning.IntValue & 4) { + OnTankBotSpawn(client); + } + + g_spawnCount[class]++; + float time = GetGameTime(); + g_lastSpawnTime[class] = time; + g_lastSpecialSpawnTime = time; + g_infectedCount++; + + } +} + +void OnTankBotSpawn(int client) { + if(g_finaleStage = Stage_FinaleActive) { + + } else { + int health = GetEntProp(client, Prop_Send, "m_iHealth"); + int additionalHealth = float(abmExtraCount - 4) * cvEPITankHealth.FloatValue; + health += additionalHealth; + SetEntProp(client, Prop_Send, "m_iHealth", health); + } +} + +void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0) { + int team = GetClientTeam(client); + if(team == 3) { + int class = GetEntProp(client, Prop_Send, "m_zombieClass"); + if(class > view_as(Special_Tank)) return; + g_spawnCount[class]--; + if(g_spawnCount[class] < 0) { + g_spawnCount[class] = 0; + } + g_infectedCount--; + if(g_infectedCount < 0) { + g_infectedCount = 0; + } + } else if(team == 2) { + TryGrantRest(); + } + } +} + +void Event_PlayerIncapped(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0 && GetClientTeam(client) == 2) { + TryGrantRest(); + } +} +/// METHODS + +void InitExtraWitches() { + float flowMax = L4D2Direct_GetMapMaxFlowDistance() - FLOW_CUTOFF; + // Just in case we don't have max flow or the map is extremely tiny, don't run: + if(flowMax > 0.0) { + int count = abmExtraCount; + if(count < 4) count = 4; + // Calculate the number of witches we want to spawn. + // We bias the dice roll to the right. We slowly increase min based on player count to shift distribution to the right + int min = RoundToFloor(float(count - 5) / 4.0); + // TODO: max based on count + int max = RoundToFloor(float(count) / 4.0); + + extraWitchCount = DiceRoll(min, DIRECTOR_WITCH_MAX_WITCHES, DIRECTOR_WITCH_ROLLS, BIAS_LEFT); + PrintDebug(DEBUG_SPAWNLOGIC, "InitExtraWitches: %d witches (min=%d, max=%d, rolls=%d) checkInterval=%f", extraWitchCount, min, max, DIRECTOR_WITCH_ROLLS, DIRECTOR_WITCH_CHECK_TIME); + for(int i = 0; i <= extraWitchCount; i++) { + g_extraWitchFlowPositions[i] = GetURandomFloat() * (flowMax-FLOW_CUTOFF) + FLOW_CUTOFF; + PrintDebug(DEBUG_SPAWNLOGIC, "Witch position #%d: flow %.2f (%.0f%%)", i, g_extraWitchFlowPositions[i], g_extraWitchFlowPositions[i] / flowMax); + } + witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT); + } + // TODO: spawn them early instead +} + +void Director_PrintDebug(int client) { + PrintToConsole(client, "===Extra Witches==="); + PrintToConsole(client, "State: %s(%d)", DIRECTOR_STATE[g_lastState], g_lastState); + PrintToConsole(client, "Map Bounds: [%f, %f]", FLOW_CUTOFF, L4D2Direct_GetMapMaxFlowDistance() - (FLOW_CUTOFF*2.0)); + PrintToConsole(client, "Total Witches Spawned: %d | Target: %d", g_spawnCount[Special_Witch], extraWitchCount); + for(int i = 0; i < extraWitchCount && i < DIRECTOR_WITCH_MAX_WITCHES; i++) { + PrintToConsole(client, "%d. %f", i+1, g_extraWitchFlowPositions[i]); + } + PrintToConsole(client, "highestFlow = %f, g_minFlowSpawn = %f, current flow = %f", g_highestFlowAchieved, g_minFlowSpawn, L4D2Direct_GetFlowDistance(client)); + PrintToConsole(client, "g_maxStressIntensity = %f, current avg = %f", g_maxStressIntensity, L4D_GetAvgSurvivorIntensity()); + PrintToConsole(client, "TankInPlay=%b, FinaleEscapeReady=%b, DirectorTankCheck:%b", L4D2_IsTankInPlay(), g_isFinaleEnding, L4D2_IsTankInPlay() && !g_isFinaleEnding); + char buffer[128]; + float time = GetGameTime(); + PrintToConsole(client, "Last Spawn Deltas: (%.1f s) (min %f)", time - g_lastSpecialSpawnTime, DIRECTOR_MIN_SPAWN_TIME); + for(int i = 0; i < TOTAL_NUM_SPECIALS; i++) { + Format(buffer, sizeof(buffer), "%s %s=%.1f", buffer, SPECIAL_IDS[i], time-g_lastSpawnTime[i]); + } + PrintToConsole(client, "\t%s", buffer); + buffer[0] = '\0'; + PrintToConsole(client, "Spawn Counts: (%d/%d)", g_infectedCount, abmExtraCount); + for(int i = 0; i < TOTAL_NUM_SPECIALS; i++) { + Format(buffer, sizeof(buffer), "%s %s=%d/%d", buffer, SPECIAL_IDS[i], g_spawnCount[i], g_spawnLimit[i]); + } + PrintToConsole(client, "\t%s", buffer); + PrintToConsole(client, "timer interval=%.0f, rest count=%d", DIRECTOR_TIMER_INTERVAL, g_restCount); +} + +void Director_RandomizeLimits() { + // We add +1 to spice it up + int max = RoundToCeil(float(abmExtraCount - 4) / 4) + 1; + for(int i = 0; i < NUM_SPECIALS; i++) { + g_spawnLimit[i] = GetRandomInt(0, max); + // PrintDebug(DEBUG_SPAWNLOGIC, "new spawn limit (special=%d, b=[0,%d], limit=%d)", i, max, g_spawnLimit[i]); + } +} +void Director_RandomizeThings() { + g_maxStressIntensity = GetRandomFloat(DIRECTOR_STRESS_CUTOFF, 1.0); + g_minFlowSpawn = GetRandomFloat(FLOW_CUTOFF, FLOW_CUTOFF * 2); + +} + +bool Director_ShouldRest() { + if(g_restCount > 0) { + g_restCount--; + return true; + } + TryGrantRest(); + return false; +} + +void TryGrantRest() { + if(GetURandomFloat() <= DIRECTOR_REST_CHANCE) { + g_restCount = GetRandomInt(0, DIRECTOR_REST_MAX_COUNT); + if(g_restCount > 0) + PrintDebug(DEBUG_SPAWNLOGIC, "new rest period: %.1f s", g_restCount * DIRECTOR_TIMER_INTERVAL); + } +} + +// Little hacky, need to track when one leaves instead +void Director_CheckSpawnCounts() { + if(abmExtraCount <= 4 || !isCoop) return; + for(int i = 0; i < TOTAL_NUM_SPECIALS; i++) { + g_spawnCount[i] = 0; + } + g_infectedCount = 0; + for(int i = 1; i <= MaxClients; i++) { + if(IsClientInGame(i) && GetClientTeam(i) == 3) { + int class = GetEntProp(i, Prop_Send, "m_zombieClass") - 1; // make it 0-based + if(class == 8) continue; + g_spawnCount[class]++; + g_infectedCount++; + } + } +} + +/// TIMERS + +// TODO: maybe make specials spaw nmore during horde events (alarm car, etc) +// less during calms, making that the automatic rest periods? +directorState Director_Think() { + if(abmExtraCount <= 4 || !isCoop) return DState_NoPlayersOrNotCoop; + float time = GetGameTime(); + + // Calculate the new highest flow + int highestPlayer = L4D_GetHighestFlowSurvivor(); + if(highestPlayer <= 0) return DState_NoPlayersOrNotCoop; + float flow = L4D2Direct_GetFlowDistance(highestPlayer); + if(flow > g_highestFlowAchieved) { + g_highestFlowAchieved = flow; + } + + + // Only run once until: + // A. They reach minimum flow (little past start saferoom) + // B. Under the total limited (equal to player count) + // C. Special spawning is enabled + // TODO: scaling chance, low chance when hitting g_infectedCount, higher on 0 + if(g_highestFlowAchieved < g_minFlowSpawn || ~cvEPISpecialSpawning.IntValue & 1) return DState_PendingMinFlowOrDisabled; + + // Only spawn more than one special within 2s at 10% + if(time - g_lastSpecialSpawnTime < 2.0 && GetURandomFloat() > 0.5) return DState_MaxSpecialTime; + + if(GetURandomFloat() < DIRECTOR_CHANGE_LIMIT_CHANCE) { + Director_RandomizeLimits(); + } + + // Decrease chance of spawning based on how close to infected count + // abmExtraCount=6 g_infectedCount=0 chance=1.0 ((abmExtraCount-g_infectedCount)/abmExtraCount) + // abmExtraCount=6 g_infectedCount=1 chance=0.9 ((6-1)/6)) = (5/6) + // abmExtraCount=6 g_infectedCount=6 chance=0.2 + float eCount = float(abmExtraCount - 3); + float chance = (eCount - float(g_infectedCount)) / eCount; + // TODO: verify (abmExtraCount-4) + if(GetURandomFloat() > chance) return DState_PlayerChance; + + // Check if a rest period is given + if(Director_ShouldRest()) { + return DState_Resting; + } + + float curAvgStress = L4D_GetAvgSurvivorIntensity(); + // Don't spawn specials when tanks active, but have a small chance (DIRECTOR_SPECIAL_TANK_CHANCE) to bypass + if((L4D2_IsTankInPlay() && !g_isFinaleEnding) && GetURandomFloat() > DIRECTOR_SPECIAL_TANK_CHANCE) { + return DState_TankInPlay; + } else if(curAvgStress >= g_maxStressIntensity) { + // Stop spawning when players are stressed from a random value chosen by [DIRECTOR_STRESS_CUTOFF, 1.0] + return DState_HighStress; + } + // Scale the chance where stress = 0.0, the chance is 50% more, and stress = 1.0, the chance is 50% less + float spawnChance = DIRECTOR_SPAWN_CHANCE + ((0.5 - curAvgStress) / 10.0); + for(int i = 0; i < NUM_SPECIALS; i++) { + specialType special = view_as(i); + // Skip if we hit our limit, or too soon: + if(g_spawnCount[i] >= g_spawnLimit[i]) continue; + if(time - g_lastSpawnTime[i] < DIRECTOR_MIN_SPAWN_TIME) continue; + + if(GetURandomFloat() <= spawnChance) { + DirectorSpawn(special); + } + } + + return DState_Normal; +} + +Action Timer_Director(Handle h) { + g_lastState = Director_Think(); + return Plugin_Continue; +} + + +Action Timer_DirectorWitch(Handle h) { + if(g_spawnCount[Special_Witch] < extraWitchCount) { //&& time - g_lastSpawnTimes.witch > DIRECTOR_WITCH_MIN_TIME + for(int i = 0; i <= extraWitchCount; i++) { + if(g_extraWitchFlowPositions[i] > 0.0 && g_highestFlowAchieved >= g_extraWitchFlowPositions[i]) { + // Reset the flow so we don't spawn another + g_extraWitchFlowPositions[i] = 0.0; + int target = L4D_GetHighestFlowSurvivor(); + if(!target) return Plugin_Continue; + DirectorSpawn(Special_Witch, target); + break; + } + } + } + return Plugin_Continue; +} + +// UTIL functions +void DirectorSpawn(specialType special, int player = -1) { + if(player <= 0) + player = GetSuitableVictim(); + PrintDebug(DEBUG_SPAWNLOGIC, "Director: spawning %s(%d) around %N (cnt=%d,lim=%d)", SPECIAL_IDS[view_as(special)], special, player, g_spawnCount[view_as(special)], g_spawnLimit[view_as(special)]); + if(special != Special_Witch && special != Special_Tank) { + // Bypass director + int bot = CreateFakeClient("EPI_BOT"); + if (bot != 0) { + ChangeClientTeam(bot, 3); + CreateTimer(0.1, Timer_Kick, bot); + } + } + // TODO: dont use z_spawn_old, spawns too close!! + float pos[3]; + if(L4D_GetRandomPZSpawnPosition(player, view_as(special), 10, pos)) { + // They use 1-index + L4D2_SpawnSpecial(view_as(special) + 1, pos, NULL_VECTOR); + g_lastSpawnTime[view_as(special)] = GetGameTime(); + } +} + +// Finds a player that is suitable (lowest intensity) +// TODO: biased random (lower intensity : bias) +// dice roll, #sides = #players, sort list of players by intensity +// then use biased left dice, therefore lower intensity = higher random weight +int g_iLastVictim; +int GetSuitableVictim() { + // TODO: randomize? + return GetRandomSurvivor(1, -1); + // ArrayList survivors = new ArrayList(2); + // for(int i = 1; i <= MaxClients; i++) { + // if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { + // int index = survivors.Push(i); + // survivors.Set(index, 1, L4D_GetPlayerIntensity(i)); + // } + // } + // // Soe + // survivors.SortCustom() + + int victim = -1; + float lowestIntensity = 0.0; + for(int i = 1; i <= MaxClients; i++) { + if(g_iLastVictim != i && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { + float intensity = L4D_GetPlayerIntensity(i); + // TODO: possibly add perm health into calculations + if(intensity < lowestIntensity || victim == -1) { + lowestIntensity = intensity; + victim = i; + } + } + } + g_iLastVictim = victim; + return victim; +} \ No newline at end of file diff --git a/scripting/include/hats/hats.sp b/scripting/include/hats/hats.sp index dc77abb..2e4fadc 100644 --- a/scripting/include/hats/hats.sp +++ b/scripting/include/hats/hats.sp @@ -44,7 +44,7 @@ char ActivePreset[MAXPLAYERS+1][32]; StringMap g_HatPresets; int lastHatRequestTime[MAXPLAYERS+1]; -#define MAX_FORBIDDEN_CLASSNAMES 13 +#define MAX_FORBIDDEN_CLASSNAMES 14 char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { "prop_door_rotating_checkpoint", "env_physics_blocker", @@ -56,7 +56,7 @@ char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { "func_elevator", "func_button_timed", "func_tracktrain", - // "func_movelinear", + "func_movelinear", // "infected", "func_lod", "func_door", @@ -763,7 +763,8 @@ void EquipHat(int client, int entity, const char[] classname = "", int flags = H PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); } } else { - if(StrEqual(classname, "infected") || StrEqual(classname, "witch")) { + // TODO: freeze tank + if(StrEqual(classname, "infected") || StrEqual(classname, "witch") || (entity <= MaxClients && GetClientTeam(entity) == 3 && L4D2_GetPlayerZombieClass(entity) == L4D2ZombieClass_Tank)) { int eflags = GetEntityFlags(entity) | FL_FROZEN; SetEntityFlags(entity, eflags); hatData[client].offset[2] = 36.0; diff --git a/scripting/include/hats/walls.sp b/scripting/include/hats/walls.sp index d6523f3..9ba25e3 100644 --- a/scripting/include/hats/walls.sp +++ b/scripting/include/hats/walls.sp @@ -225,6 +225,7 @@ enum struct WallBuilderData { DispatchSpawn(entity); TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); this.entity = entity; + SetEntProp(entity, Prop_Send, "m_nSolidType", 2); return entity; } @@ -247,7 +248,7 @@ WallBuilderData WallBuilder[MAXPLAYERS+1]; // TODO: Stacker, copy tool, new command? public Action Command_MakeWall(int client, int args) { if(WallBuilder[client].IsActive()) { - ReplyToCommand(client, "\x04[Hats]\x01 You are already editing/building, either finish with \x05/wall build\x01 or cancel with \x04/wall cancel\x01"); + ReplyToCommand(client, "\x04[Hats]\x01 You are currently editing an entity. Finish editing your current entity with with \x05/edit done\x01 or cancel with \x04/edit cancel\x01"); } else { WallBuilder[client].Reset(); if(args > 0) { diff --git a/scripting/l4d2_extraplayeritems.sp b/scripting/l4d2_extraplayeritems.sp index 8bb729a..0a14d6f 100644 --- a/scripting/l4d2_extraplayeritems.sp +++ b/scripting/l4d2_extraplayeritems.sp @@ -29,7 +29,7 @@ //Sets abmExtraCount to this value if set // #define DEBUG_FORCE_PLAYERS 7 -#define FLOW_CUTOFF 100.0 // The cutoff of flow, so that witches / tanks don't spawn in saferooms / starting areas, [0 + FLOW_CUTOFF, MapMaxFlow - FLOW_CUTOFF] +#define FLOW_CUTOFF 500.0 // The cutoff of flow, so that witches / tanks don't spawn in saferooms / starting areas, [0 + FLOW_CUTOFF, MapMaxFlow - FLOW_CUTOFF] #define EXTRA_TANK_MIN_SEC 2.0 #define EXTRA_TANK_MAX_SEC 20.0 @@ -80,7 +80,7 @@ public Plugin myinfo = }; ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning; -int extraKitsAmount, extraKitsStarted, abmExtraCount, firstSaferoomDoorEntity, playersLoadedIn, playerstoWaitFor; +int extraKitsAmount, extraKitsStarted, abmExtraCount, firstSaferoomDoorEntity, playersLoadedIn, playerstoWaitFor, cvEPITankHealth; static int currentChapter; static bool isCheckpointReached, isLateLoaded, firstGiven, isFailureRound, areItemsPopulated; static ArrayList ammoPacks; @@ -88,8 +88,9 @@ static Handle updateHudTimer; static bool showHudPingMode; static int hudModeTicks; static char gamemode[32]; +bool g_isFinaleEnding; - +bool g_isSpeaking[MAXPLAYERS+1]; bool isCoop; enum Difficulty { @@ -264,6 +265,7 @@ public void OnPluginStart() { HookEvent("player_info", Event_PlayerInfo); HookEvent("player_disconnect", Event_PlayerDisconnect); HookEvent("player_death", Event_PlayerDeath); + HookEvent("player_incapacitated", Event_PlayerIncapped); HookEvent("charger_carry_start", Event_ChargerCarry); HookEvent("charger_carry_end", Event_ChargerCarry); @@ -280,22 +282,25 @@ public void OnPluginStart() { HookEvent("jockey_ride_end", Event_JockeyRide); HookEvent("witch_spawn", Event_WitchSpawn); + HookEvent("finale_vehicle_incoming", Event_FinaleVehicleIncoming); - hExtraItemBasePercentage = CreateConVar("l4d2_extraitems_chance", "0.056", "The base chance (multiplied by player count) of an extra item being spawned.", FCVAR_NONE, true, 0.0, true, 1.0); - hAddExtraKits = CreateConVar("l4d2_extraitems_kitmode", "0", "Decides how extra kits should be added.\n0 -> Overwrites previous extra kits, 1 -> Adds onto previous extra kits", FCVAR_NONE, true, 0.0, true, 1.0); - hUpdateMinPlayers = CreateConVar("l4d2_extraitems_updateminplayers", "1", "Should the plugin update abm\'s cvar min_players convar to the player count?\n 0 -> NO, 1 -> YES", FCVAR_NONE, true, 0.0, true, 1.0); - hMinPlayersSaferoomDoor = CreateConVar("l4d2_extraitems_doorunlock_percent", "0.75", "The percent of players that need to be loaded in before saferoom door is opened.\n 0 to disable", FCVAR_NONE, true, 0.0, true, 1.0); - hSaferoomDoorWaitSeconds = CreateConVar("l4d2_extraitems_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", FCVAR_NONE, true, 0.0); - hSaferoomDoorAutoOpen = CreateConVar("l4d2_extraitems_doorunlock_open", "0", "Controls when the door automatically opens after unlocked. Add bits together.\n0 = Never, 1 = When timer expires, 2 = When all players loaded in", FCVAR_NONE, true, 0.0); - hEPIHudState = CreateConVar("l4d2_extraitems_hudstate", "1", "Controls when the hud displays.\n0 -> OFF, 1 = When 5+ players, 2 = ALWAYS", FCVAR_NONE, true, 0.0, true, 3.0); - hExtraFinaleTank = CreateConVar("l4d2_extraitems_extra_tanks", "3", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 3.0); - hSplitTankChance = CreateConVar("l4d2_extraitems_splittank_chance", "0.80", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.0); - cvDropDisconnectTime = CreateConVar("l4d2_extraitems_disconnect_time", "120.0", "The amount of seconds after a player has actually disconnected, where their character slot will be void. 0 to disable", FCVAR_NONE, true, 0.0); - cvFFDecreaseRate = CreateConVar("l4d2_extraitems_ff_decrease_rate", "0.3", "The friendly fire factor is subtracted from the formula (playerCount-4) * this rate. Effectively reduces ff penalty when more players. 0.0 to subtract none", FCVAR_NONE, true, 0.0); - cvEPIHudFlags = CreateConVar("l4d2_extraitems_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", FCVAR_NONE, true, 0.0); - cvEPISpecialSpawning = CreateConVar("l4d2_extraitems_special_spawning", "2", "Determines what specials are spawned. Add bits together.\n1 = Normal specials\n2 = Witches\n4 = Tanks", FCVAR_NONE, true, 0.0); + hExtraItemBasePercentage = CreateConVar("epi_item_chance", "0.056", "The base chance (multiplied by player count) of an extra item being spawned.", FCVAR_NONE, true, 0.0, true, 1.0); + hAddExtraKits = CreateConVar("epi_kitmode", "0", "Decides how extra kits should be added.\n0 -> Overwrites previous extra kits\n1 -> Adds onto previous extra kits", FCVAR_NONE, true, 0.0, true, 1.0); + hUpdateMinPlayers = CreateConVar("epi_updateminplayers", "1", "Should the plugin update abm\'s cvar min_players convar to the player count?\n 0 -> NO\n1 -> YES", FCVAR_NONE, true, 0.0, true, 1.0); + hMinPlayersSaferoomDoor = CreateConVar("epi_doorunlock_percent", "0.75", "The percent of players that need to be loaded in before saferoom door is opened.\n 0 to disable", FCVAR_NONE, true, 0.0, true, 1.0); + hSaferoomDoorWaitSeconds = CreateConVar("epi_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", FCVAR_NONE, true, 0.0); + hSaferoomDoorAutoOpen = CreateConVar("epi_doorunlock_open", "0", "Controls when the door automatically opens after unlocked. Add bits together.\n0 = Never, 1 = When timer expires, 2 = When all players loaded in", FCVAR_NONE, true, 0.0); + hEPIHudState = CreateConVar("epi_hudstate", "1", "Controls when the hud displays.\n0 -> OFF, 1 = When 5+ players, 2 = ALWAYS", FCVAR_NONE, true, 0.0, true, 3.0); + hExtraFinaleTank = CreateConVar("epi_extra_tanks", "3", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 3.0); + hSplitTankChance = CreateConVar("epi_splittank_chance", "0.80", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.0); + cvDropDisconnectTime = CreateConVar("epi_disconnect_time", "120.0", "The amount of seconds after a player has actually disconnected, where their character slot will be void. 0 to disable", FCVAR_NONE, true, 0.0); + cvFFDecreaseRate = CreateConVar("epi_ff_decrease_rate", "0.3", "The friendly fire factor is subtracted from the formula (playerCount-4) * this rate. Effectively reduces ff penalty when more players. 0.0 to subtract none", FCVAR_NONE, true, 0.0); + cvEPIHudFlags = CreateConVar("epi_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", FCVAR_NONE, true, 0.0); + cvEPISpecialSpawning = CreateConVar("epi_sp_spawning", "2", "Determines what specials are spawned. Add bits together.\n1 = Normal specials\n2 = Witches\n4 = Tanks", FCVAR_NONE, true, 0.0); + cvEPITankHealth = CreateConVar("epi_tank_chunkhp", "2500", "The amount of health added to tank, for each extra player", FCVAR_NONE, true, 0.0); + // TODO: hook flags, reset name index / ping mode cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange); cvEPISpecialSpawning.AddChangeHook(Cvar_SpecialSpawningChange); @@ -358,12 +363,14 @@ public void OnPluginStart() { #endif RegAdminCmd("sm_epi_restore", Command_RestoreInventory, ADMFLAG_KICK); RegAdminCmd("sm_epi_save", Command_SaveInventory, ADMFLAG_KICK); - CreateTimer(2.0, Timer_Director, _, TIMER_REPEAT); + CreateTimer(DIRECTOR_TIMER_INTERVAL, Timer_Director, _, TIMER_REPEAT); CreateTimer(30.0, Timer_ForceUpdateInventories, _, TIMER_REPEAT); } - +void Event_FinaleVehicleIncoming(Event event, const char[] name, bool dontBroadcast) { + g_isFinaleEnding = true; +} Action Timer_ForceUpdateInventories(Handle h) { for(int i = 1; i <= MaxClients; i++) { @@ -371,6 +378,7 @@ Action Timer_ForceUpdateInventories(Handle h) { // SaveInventory(i); } } + Director_CheckSpawnCounts(); return Plugin_Continue; } @@ -393,6 +401,7 @@ public void OnClientPutInServer(int client) { public void OnClientDisconnect(int client) { if(!IsFakeClient(client) && IsClientInGame(client)) SaveInventory(client); + g_isSpeaking[client] = false; } public void OnPluginEnd() { @@ -557,6 +566,7 @@ Action Command_RestoreInventory(int client, int args) { } return Plugin_Handled; } +// TODO: allow sc for new sys public Action Command_SetSurvivorCount(int client, int args) { int oldCount = abmExtraCount; if(args > 0) { @@ -569,7 +579,6 @@ public Action Command_SetSurvivorCount(int client, int args) { return Plugin_Handled; } else { abmExtraCount = newCount; - hMinPlayers.IntValue = abmExtraCount; ReplyToCommand(client, "Changed extra survivor count to %d -> %d", oldCount, newCount); bool add = (newCount - oldCount) > 0; if(add) @@ -690,18 +699,18 @@ enum FinaleStage { Stage_InactiveFinale = -1 } int extraTankHP; -FinaleStage finaleStage; +FinaleStage g_finaleStage; public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) { if(finaleType == FINALE_STARTED && abmExtraCount > 4) { - finaleStage = Stage_FinaleActive; + g_finaleStage = Stage_FinaleActive; PrintToConsoleAll("[EPI] Finale started and over threshold"); } else if(finaleType == FINALE_TANK) { - if(finaleStage == Stage_FinaleActive) { - finaleStage = Stage_FinaleTank1; + if(g_finaleStage == Stage_FinaleActive) { + g_finaleStage = Stage_FinaleTank1; PrintToConsoleAll("[EPI] First tank stage has started"); - } else if(finaleStage == Stage_FinaleTank1) { - finaleStage = Stage_FinaleTank2; + } else if(g_finaleStage == Stage_FinaleTank1) { + g_finaleStage = Stage_FinaleTank2; PrintToConsoleAll("[EPI] Second stage started, waiting for tank"); } } @@ -709,20 +718,22 @@ public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) { } void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) { + // Only run when we aren't touching tanks (ABM in control) + if(cvEPISpecialSpawning.IntValue & 4) return; int user = event.GetInt("userid"); int tank = GetClientOfUserId(user); if(tank > 0 && IsFakeClient(tank) && abmExtraCount > 4 && hExtraFinaleTank.IntValue > 0) { PrintToConsoleAll("[EPI] Split tank is enabled, checking new spawned tank"); - if(finaleStage == Stage_FinaleTank2 && allowTankSplit && hExtraFinaleTank.IntValue & 2) { + if(g_finaleStage == Stage_FinaleTank2 && allowTankSplit && hExtraFinaleTank.IntValue & 2) { PrintToConsoleAll("[EPI] Second tank spawned, setting health."); // Sets health in half, sets finaleStage to health float duration = GetRandomFloat(EXTRA_TANK_MIN_SEC, EXTRA_TANK_MAX_SEC); CreateTimer(duration, Timer_SpawnFinaleTank, user); - } else if(finaleStage == Stage_FinaleDuplicatePending) { + } else if(g_finaleStage == Stage_FinaleDuplicatePending) { PrintToConsoleAll("[EPI] Third & final tank spawned"); RequestFrame(Frame_SetExtraTankHealth, user); - } else if(finaleStage == Stage_Inactive && allowTankSplit && hExtraFinaleTank.IntValue & 1 && GetSurvivorsCount() > 6) { - finaleStage = Stage_TankSplit; + } else if(g_finaleStage == Stage_Inactive && allowTankSplit && hExtraFinaleTank.IntValue & 1 && GetSurvivorsCount() > 6) { + g_finaleStage = Stage_TankSplit; if(GetRandomFloat() <= hSplitTankChance.FloatValue) { // Half their HP, assign half to self and for next tank int hp = GetEntProp(tank, Prop_Send, "m_iHealth") / 2; @@ -734,16 +745,16 @@ void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) { PrintToConsoleAll("[EPI] Random chance for split tank failed"); } // Then, summon the next tank - } else if(finaleStage == Stage_TankSplit) { + } else if(g_finaleStage == Stage_TankSplit) { CreateTimer(0.2, Timer_SetHealth, user); } } } Action Timer_SpawnFinaleTank(Handle t, int user) { - if(finaleStage == Stage_FinaleTank2) { + if(g_finaleStage == Stage_FinaleTank2) { DirectorSpawn(Special_Tank); // ServerCommand("sm_forcespecial tank"); - finaleStage = Stage_Inactive; + g_finaleStage = Stage_Inactive; } return Plugin_Handled; } @@ -762,9 +773,9 @@ Action Timer_SetHealth(Handle h, int user) { void Frame_SetExtraTankHealth(int user) { int tank = GetClientOfUserId(user); - if(tank > 0 && finaleStage == Stage_FinaleDuplicatePending) { + if(tank > 0 && g_finaleStage == Stage_FinaleDuplicatePending) { SetEntProp(tank, Prop_Send, "m_iHealth", extraTankHP); - finaleStage = Stage_InactiveFinale; + g_finaleStage = Stage_InactiveFinale; } } @@ -1183,7 +1194,7 @@ public void OnMapStart() { HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); playersLoadedIn = 0; - finaleStage = Stage_Inactive; + g_finaleStage = Stage_Inactive; L4D2_RunScript(HUD_SCRIPT_CLEAR); Director_OnMapStart(); @@ -1208,6 +1219,7 @@ public void OnConfigsExecuted() { public void OnMapEnd() { + g_isFinaleEnding = false; for(int i = 0; i < ammoPacks.Length; i++) { ArrayList clients = ammoPacks.Get(i, AMMOPACK_USERS); delete clients; @@ -1233,6 +1245,12 @@ public Action Timer_Populate(Handle h) { return Plugin_Continue; } +public void OnClientSpeaking(int client) { + g_isSpeaking[client] = true; +} +public void OnClientSpeakingEnd(int client) { + g_isSpeaking[client] = false; +} public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, int client, float time) { if(!isCheckpointReached && client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { @@ -1256,8 +1274,8 @@ public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, i extraKitsStarted = extraKitsAmount; hMinPlayers.IntValue = abmExtraCount; - PrintToConsoleAll("CHECKPOINT REACHED BY %N | EXTRA KITS: %d", client, extraPlayers); - PrintToServer("Player entered saferoom. Providing %d extra kits", extraKitsAmount); + PrintToConsoleAll("[EPI] CHECKPOINT REACHED BY %N | EXTRA KITS: %d", client, extraPlayers); + PrintToServer("[EPI] Player entered saferoom. Providing %d extra kits", extraKitsAmount); } } } @@ -1489,6 +1507,8 @@ Action Timer_UpdateHud(Handle h) { } else { Format(prefix, HUD_NAME_LENGTH, "%s", playerData[client].nameCache[playerData[client].scrollIndex]); } + if(g_isSpeaking[i]) + Format(prefix, HUD_NAME_LENGTH, "🔊%s", prefix); playerData[client].AdvanceScroll();