Cleanup, add extra witch spawning

This commit is contained in:
Jackz 2023-09-29 18:00:01 -05:00
parent be9011d6a4
commit 6e323b26d2
No known key found for this signature in database
GPG key ID: E0BBD94CF657F603
2 changed files with 259 additions and 109 deletions

Binary file not shown.

View file

@ -28,7 +28,11 @@
#define EXTRA_PLAYER_HUD_UPDATE_INTERVAL 0.8 #define EXTRA_PLAYER_HUD_UPDATE_INTERVAL 0.8
//Sets abmExtraCount to this value if set //Sets abmExtraCount to this value if set
// #define DEBUG_FORCE_PLAYERS 7 // #define DEBUG_FORCE_PLAYERS 7
#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 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 EXTRA_TANK_MIN_SEC 2.0 #define EXTRA_TANK_MIN_SEC 2.0
#define EXTRA_TANK_MAX_SEC 20.0 #define EXTRA_TANK_MAX_SEC 20.0
@ -78,10 +82,11 @@ public Plugin myinfo =
url = "https://github.com/Jackzmc/sourcemod-plugins" url = "https://github.com/Jackzmc/sourcemod-plugins"
}; };
static ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags; static ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning;
static int extraKitsAmount, extraKitsStarted, abmExtraCount, firstSaferoomDoorEntity, playersLoadedIn, playerstoWaitFor; static int extraKitsAmount, extraKitsStarted, abmExtraCount, firstSaferoomDoorEntity, playersLoadedIn, playerstoWaitFor;
static int currentChapter; static int currentChapter;
static bool isCheckpointReached, isLateLoaded, firstGiven, isFailureRound, areItemsPopulated; static bool isCheckpointReached, isLateLoaded, firstGiven, isFailureRound, areItemsPopulated;
static float highestFlowAchieved;
static ArrayList ammoPacks; static ArrayList ammoPacks;
static Handle updateHudTimer; static Handle updateHudTimer;
static bool showHudPingMode; static bool showHudPingMode;
@ -89,6 +94,11 @@ static int hudModeTicks;
static char gamemode[32]; static char gamemode[32];
static int witchSpawnCount, witchLastSpawnTime, extraWitchCount;
float ExtraWitchFlowPositions[DIRECTOR_WITCH_MAX_WITCHES] = {};
static Handle witchSpawnTimer = null;
bool isCoop; bool isCoop;
enum Difficulty { enum Difficulty {
@ -255,6 +265,7 @@ public void OnPluginStart() {
HookEvent("game_end", Event_GameStart); HookEvent("game_end", Event_GameStart);
HookEvent("round_freeze_end", Event_RoundFreezeEnd); HookEvent("round_freeze_end", Event_RoundFreezeEnd);
HookEvent("tank_spawn", Event_TankSpawn); HookEvent("tank_spawn", Event_TankSpawn);
HookEvent("round_start", Event_RoundStart);
//Special Event Tracking //Special Event Tracking
HookEvent("player_info", Event_PlayerInfo); HookEvent("player_info", Event_PlayerInfo);
@ -274,6 +285,9 @@ public void OnPluginStart() {
HookEvent("jockey_ride", Event_JockeyRide); HookEvent("jockey_ride", Event_JockeyRide);
HookEvent("jockey_ride_end", Event_JockeyRide); HookEvent("jockey_ride_end", Event_JockeyRide);
HookEvent("witch_spawn", Event_WitchSpawn);
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); 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); 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);
@ -283,14 +297,14 @@ public void OnPluginStart() {
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); 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); 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); 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.75", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.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); 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); 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); 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);
// TODO: hook flags, reset name index / ping mode // TODO: hook flags, reset name index / ping mode
cvEPIHudFlags.AddChangeHook(Cvar_HudFlagChange);
cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange); cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange);
cvEPISpecialSpawning.AddChangeHook(Cvar_SpecialSpawningChange);
if(hUpdateMinPlayers.BoolValue) { if(hUpdateMinPlayers.BoolValue) {
hMinPlayers = FindConVar("abm_minplayers"); hMinPlayers = FindConVar("abm_minplayers");
@ -342,14 +356,49 @@ public void OnPluginStart() {
RegAdminCmd("sm_epi_kits", Command_GetKitAmount, ADMFLAG_CHEATS); RegAdminCmd("sm_epi_kits", Command_GetKitAmount, ADMFLAG_CHEATS);
RegAdminCmd("sm_epi_items", Command_RunExtraItems, ADMFLAG_CHEATS); RegAdminCmd("sm_epi_items", Command_RunExtraItems, ADMFLAG_CHEATS);
RegConsoleCmd("sm_epi_stats", Command_DebugStats); RegConsoleCmd("sm_epi_stats", Command_DebugStats);
RegConsoleCmd("sm_epi_debug", Command_Debug);
// TODO: Should we have sm_epi_value or use CVAR for state
// no cvar
// RegAdminCmd("sm_epi_val", Command_EPIValue);
RegAdminCmd("sm_epi_trigger", Command_Trigger, ADMFLAG_CHEATS);
#endif #endif
RegAdminCmd("sm_epi_restore", Command_RestoreInventory, ADMFLAG_KICK); RegAdminCmd("sm_epi_restore", Command_RestoreInventory, ADMFLAG_KICK);
RegAdminCmd("sm_epi_save", Command_SaveInventory, ADMFLAG_KICK); RegAdminCmd("sm_epi_save", Command_SaveInventory, ADMFLAG_KICK);
CreateTimer(2.0, Timer_Director, _, TIMER_REPEAT);
CreateTimer(30.0, Timer_ForceUpdateInventories, _, TIMER_REPEAT); CreateTimer(30.0, Timer_ForceUpdateInventories, _, TIMER_REPEAT);
} }
public Action Timer_ForceUpdateInventories(Handle h) {
Action Timer_Director(Handle h) {
if(abmExtraCount <= 4) return Plugin_Continue;
// Calculate the new highest flow
int highestPlayer = L4D_GetHighestFlowSurvivor();
float flow = L4D2Direct_GetFlowDistance(highestPlayer);
if(flow > highestFlowAchieved) {
highestFlowAchieved = flow;
}
return Plugin_Continue;
}
Action Timer_DirectorWitch(Handle h) {
int time = GetTime();
if(witchSpawnCount < extraWitchCount && time - witchLastSpawnTime > DIRECTOR_WITCH_MIN_TIME) {
for(int i = 0; i <= extraWitchCount; i++) {
if(ExtraWitchFlowPositions[i] > 0.0 && highestFlowAchieved >= ExtraWitchFlowPositions[i]) {
PrintChatToAdmins("EPI: (ignore me) DirectorSpawn(Special_Witch)");
PrintDebug(DEBUG_SPAWNLOGIC, "DirectorSpawn(Special_Witch)");
// Reset
ExtraWitchFlowPositions[i] = 0.0;
break;
}
}
}
return Plugin_Continue;
}
Action Timer_ForceUpdateInventories(Handle h) {
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
// SaveInventory(i); // SaveInventory(i);
@ -366,7 +415,10 @@ public void OnClientPutInServer(int client) {
if(!StrEqual(gamemode, "hideandseek")) { if(!StrEqual(gamemode, "hideandseek")) {
// CreateTimer(0.2, Timer_CheckInventory, client); // CreateTimer(0.2, Timer_CheckInventory, client);
} }
} } else if(abmExtraCount >= 4 && GetClientTeam(client) == 0) {
// TODO: revert revert
// L4D_TakeOverBot(client);
}
} }
} }
@ -418,7 +470,12 @@ public Action Event_JockeyRide(Event event, const char[] name, bool dontBroadcas
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// CVAR HOOKS // CVAR HOOKS
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
public void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char[] newValue) { void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char[] newValue) {
hudModeTicks = 0;
showHudPingMode = false;
for(int i = 0; i <= MaxClients; i++) {
playerData[i].ResetScroll();
}
if(convar.IntValue == 0) { if(convar.IntValue == 0) {
if(updateHudTimer != null) { if(updateHudTimer != null) {
PrintToServer("[EPI] Stopping timer externally: Cvar changed to 0"); PrintToServer("[EPI] Stopping timer externally: Cvar changed to 0");
@ -439,11 +496,12 @@ public void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char
} }
} }
} }
void Cvar_HudFlagChange(ConVar convar, const char[] oldValue, const char[] newValue) { void Cvar_SpecialSpawningChange(ConVar convar, const char[] oldValue, const char[] newValue) {
hudModeTicks = 0; if(convar.IntValue & 2 && abmExtraCount > 4) {
showHudPingMode = false; if(witchSpawnTimer == null)
for(int i = 0; i <= MaxClients; i++) { witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT);
playerData[i].ResetScroll(); } else {
delete witchSpawnTimer;
} }
} }
@ -483,6 +541,17 @@ public void Event_DifficultyChange(ConVar cvar, const char[] oldValue, const cha
///////////////////////////////////// /////////////////////////////////////
/// COMMANDS /// COMMANDS
//////////////////////////////////// ////////////////////////////////////
Action Command_Trigger(int client, int args) {
char arg[32];
GetCmdArg(1, arg, sizeof(arg));
if(StrEqual(arg, "witches")) {
InitExtraWitches();
ReplyToCommand(client, "Extra witches active.");
} else {
ReplyToCommand(client, "Unknown trigger");
}
return Plugin_Handled;
}
Action Command_SaveInventory(int client, int args) { Action Command_SaveInventory(int client, int args) {
if(args == 0) { if(args == 0) {
ReplyToCommand(client, "Syntax: /epi_save <player>"); ReplyToCommand(client, "Syntax: /epi_save <player>");
@ -497,6 +566,7 @@ Action Command_SaveInventory(int client, int args) {
} }
SaveInventory(player); SaveInventory(player);
ReplyToCommand(client, "Saved inventory for %N", player); ReplyToCommand(client, "Saved inventory for %N", player);
return Plugin_Handled;
} }
Action Command_RestoreInventory(int client, int args) { Action Command_RestoreInventory(int client, int args) {
if(args == 0) { if(args == 0) {
@ -556,7 +626,7 @@ public Action Command_SetSurvivorCount(int client, int args) {
return Plugin_Handled; return Plugin_Handled;
} }
#if defined DEBUG_LEVEL #if defined DEBUG_LEVEL
public Action Command_SetKitAmount(int client, int args) { Action Command_SetKitAmount(int client, int args) {
char arg[32]; char arg[32];
GetCmdArg(1, arg, sizeof(arg)); GetCmdArg(1, arg, sizeof(arg));
int number = StringToInt(arg); int number = StringToInt(arg);
@ -570,7 +640,7 @@ public Action Command_SetKitAmount(int client, int args) {
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_ToggleDoorLocks(int client, int args) { Action Command_ToggleDoorLocks(int client, int args) {
for(int i = MaxClients + 1; i < GetMaxEntities(); i++) { for(int i = MaxClients + 1; i < GetMaxEntities(); i++) {
if(HasEntProp(i, Prop_Send, "m_bLocked")) { if(HasEntProp(i, Prop_Send, "m_bLocked")) {
int state = GetEntProp(i, Prop_Send, "m_bLocked"); int state = GetEntProp(i, Prop_Send, "m_bLocked");
@ -580,17 +650,27 @@ public Action Command_ToggleDoorLocks(int client, int args) {
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_GetKitAmount(int client, int args) { Action Command_GetKitAmount(int client, int args) {
ReplyToCommand(client, "Extra kits available: %d (%d) | Survivors: %d", extraKitsAmount, extraKitsStarted, GetSurvivorsCount()); ReplyToCommand(client, "Extra kits available: %d (%d) | Survivors: %d", extraKitsAmount, extraKitsStarted, GetSurvivorsCount());
ReplyToCommand(client, "isCheckpointReached %b, isLateLoaded %b, firstGiven %b", isCheckpointReached, isLateLoaded, firstGiven); ReplyToCommand(client, "isCheckpointReached %b, isLateLoaded %b, firstGiven %b", isCheckpointReached, isLateLoaded, firstGiven);
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_RunExtraItems(int client, int args) { Action Command_RunExtraItems(int client, int args) {
ReplyToCommand(client, "Running extra item count increaser..."); ReplyToCommand(client, "Running extra item count increaser...");
PopulateItems(); PopulateItems();
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_DebugStats(int client, int args) { Action Command_Debug(int client, int args) {
PrintToConsole(client, "abmExtraCount = %d", abmExtraCount);
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", witchSpawnCount, extraWitchCount);
for(int i = 0; i < extraWitchCount && i < DIRECTOR_WITCH_MAX_WITCHES; i++) {
PrintToConsole(client, "%d. %f", i, ExtraWitchFlowPositions[i]);
}
return Plugin_Handled;
}
Action Command_DebugStats(int client, int args) {
if(args == 0) { if(args == 0) {
ReplyToCommand(client, "Player Statuses:"); ReplyToCommand(client, "Player Statuses:");
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
@ -673,7 +753,7 @@ public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) {
return Plugin_Continue; return Plugin_Continue;
} }
public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) { void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) {
int user = event.GetInt("userid"); int user = event.GetInt("userid");
int tank = GetClientOfUserId(user); int tank = GetClientOfUserId(user);
if(tank > 0 && IsFakeClient(tank) && abmExtraCount > 4 && hExtraFinaleTank.IntValue > 0) { if(tank > 0 && IsFakeClient(tank) && abmExtraCount > 4 && hExtraFinaleTank.IntValue > 0) {
@ -704,18 +784,20 @@ public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast)
} }
} }
} }
public Action Timer_SpawnFinaleTank(Handle t, int user) { Action Timer_SpawnFinaleTank(Handle t, int user) {
if(finaleStage == Stage_FinaleTank2) { if(finaleStage == Stage_FinaleTank2) {
ServerCommand("sm_forcespecial tank"); DirectorSpawn(Special_Tank);
// ServerCommand("sm_forcespecial tank");
finaleStage = Stage_Inactive; finaleStage = Stage_Inactive;
} }
return Plugin_Handled; return Plugin_Handled;
} }
public Action Timer_SpawnSplitTank(Handle t, int user) { Action Timer_SpawnSplitTank(Handle t, int user) {
ServerCommand("sm_forcespecial tank"); DirectorSpawn(Special_Tank);
// ServerCommand("sm_forcespecial tank");
return Plugin_Handled; return Plugin_Handled;
} }
public Action Timer_SetHealth(Handle h, int user) { Action Timer_SetHealth(Handle h, int user) {
int client = GetClientOfUserId(user); int client = GetClientOfUserId(user);
if(client > 0 ) { if(client > 0 ) {
SetEntProp(client, Prop_Send, "m_iHealth", extraTankHP); SetEntProp(client, Prop_Send, "m_iHealth", extraTankHP);
@ -723,7 +805,7 @@ public Action Timer_SetHealth(Handle h, int user) {
return Plugin_Handled; return Plugin_Handled;
} }
public void Frame_SetExtraTankHealth(int user) { void Frame_SetExtraTankHealth(int user) {
int tank = GetClientOfUserId(user); int tank = GetClientOfUserId(user);
if(tank > 0 && finaleStage == Stage_FinaleDuplicatePending) { if(tank > 0 && finaleStage == Stage_FinaleDuplicatePending) {
SetEntProp(tank, Prop_Send, "m_iHealth", extraTankHP); SetEntProp(tank, Prop_Send, "m_iHealth", extraTankHP);
@ -739,17 +821,21 @@ public void OnGetWeaponsInfo(int pThis, const char[] classname) {
if(maxClipSize > 0) if(maxClipSize > 0)
weaponMaxClipSizes.SetValue(classname, maxClipSize); weaponMaxClipSizes.SetValue(classname, maxClipSize);
} }
void Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) {
witchSpawnCount++;
witchLastSpawnTime = GetTime();
}
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
//// PLAYER STATE MANAGEMENT //// PLAYER STATE MANAGEMENT
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
//Called on the first spawn in a mission. //Called on the first spawn in a mission.
public void Event_GameStart(Event event, const char[] name, bool dontBroadcast) { void Event_GameStart(Event event, const char[] name, bool dontBroadcast) {
firstGiven = false; firstGiven = false;
extraKitsAmount = 0; extraKitsAmount = 0;
extraKitsStarted = 0; extraKitsStarted = 0;
abmExtraCount = 4; abmExtraCount = 0;
hMinPlayers.IntValue = 4; hMinPlayers.IntValue = 4;
currentChapter = 0; currentChapter = 0;
pInv.Clear(); pInv.Clear();
@ -758,7 +844,7 @@ public void Event_GameStart(Event event, const char[] name, bool dontBroadcast)
} }
} }
public void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) {
int userid = event.GetInt("userid"); int userid = event.GetInt("userid");
int client = GetClientOfUserId(userid); int client = GetClientOfUserId(userid);
if(GetClientTeam(client) != 2) return; if(GetClientTeam(client) != 2) return;
@ -800,6 +886,7 @@ public void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroa
int newCount = GetRealSurvivorsCount(); int newCount = GetRealSurvivorsCount();
if(newCount > abmExtraCount && abmExtraCount > 4) { if(newCount > abmExtraCount && abmExtraCount > 4) {
abmExtraCount = newCount; abmExtraCount = newCount;
PrintDebug(DEBUG_GENERIC, "New client, setting abmExtraCount to %d", newCount);
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = abmExtraCount;
ConVar friendlyFireFactor = GetActiveFriendlyFireFactor(); ConVar friendlyFireFactor = GetActiveFriendlyFireFactor();
@ -816,7 +903,7 @@ public void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroa
} }
} }
} }
public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
if(!StrEqual(gamemode, "coop") && !StrEqual(gamemode, "realism")) return; if(!StrEqual(gamemode, "coop") && !StrEqual(gamemode, "realism")) return;
int user = event.GetInt("userid"); int user = event.GetInt("userid");
@ -853,15 +940,7 @@ public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast
} }
// public Action Timer_CheckInventory(Handle h, int client) { void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
// if(IsClientConnected(client) && IsClientInGame(client) && GetClientTeam(client) == 2 && DoesInventoryDiffer(client)) {
// PrintToConsoleAll("[EPI] Detected mismatch inventory for %N, restoring", client);
// RestoreInventory(client);
// }
// return Plugin_Handled;
// }
public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
int userid = event.GetInt("userid"); int userid = event.GetInt("userid");
int client = GetClientOfUserId(userid); int client = GetClientOfUserId(userid);
if(client > 0 && IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client) && GetClientTeam(client) == 2) { //TODO: re-add && !event.GetBool("isbot") if(client > 0 && IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client) && GetClientTeam(client) == 2) { //TODO: re-add && !event.GetBool("isbot")
@ -877,14 +956,14 @@ public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroa
} }
} }
public void Event_PlayerInfo(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerInfo(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid")); int client = GetClientOfUserId(event.GetInt("userid"));
if(client && !IsFakeClient(client)) { if(client && !IsFakeClient(client)) {
playerData[client].Setup(client); playerData[client].Setup(client);
} }
} }
public Action Timer_DropSurvivor(Handle h, int client) { Action Timer_DropSurvivor(Handle h, int client) {
if(playerData[client].state == State_PendingEmpty) { if(playerData[client].state == State_PendingEmpty) {
playerData[client].state = State_Empty; playerData[client].state = State_Empty;
if(hMinPlayers != null) { if(hMinPlayers != null) {
@ -900,28 +979,11 @@ public Action Timer_DropSurvivor(Handle h, int client) {
return Plugin_Handled; return Plugin_Handled;
} }
/*public Action Timer_DropSurvivor(Handle h, DataPack pack) {
pack.Reset();
int userid = pack.ReadCell();
int client = pack.ReadCell();
// If the userid occupying client index is diff (or 0)
if(GetClientOfUserId(userid) != client) {
// If player was not replaced
if(!IsClientConnected(client)) {
PrintToConsoleAll("Dropping disconnected player after inactivity. UID:%d, index:%d, new MinPlayers: %d", userid, client, hMinPlayers.IntValue-1);
//playerData[client].active = false;
abmExtraCount--;
hMinPlayers.IntValue--;
}
}
DropDroppedInventories();
}*/
///////////////////////////////////////// /////////////////////////////////////////
/////// Events /////// Events
///////////////////////////////////////// /////////////////////////////////////////
public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid")); int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0) { if(client > 0) {
UpdatePlayerInventory(client); UpdatePlayerInventory(client);
@ -929,7 +991,6 @@ public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast)
} }
public Action L4D_OnIsTeamFull(int team, bool &full) { public Action L4D_OnIsTeamFull(int team, bool &full) {
if(team == 2 && full) { if(team == 2 && full) {
full = false; full = false;
@ -938,15 +999,6 @@ public Action L4D_OnIsTeamFull(int team, bool &full) {
return Plugin_Continue; return Plugin_Continue;
} }
#define TIER1_WEAPON_COUNT 5
char TIER1_WEAPONS[TIER1_WEAPON_COUNT][] = {
"shotgun_chrome",
"pumpshotgun",
"smg",
"smg_silenced",
"smg_mp5"
};
#define TIER2_WEAPON_COUNT 9 #define TIER2_WEAPON_COUNT 9
char TIER2_WEAPONS[9][] = { char TIER2_WEAPONS[9][] = {
"weapon_autoshotgun", "weapon_autoshotgun",
@ -960,7 +1012,7 @@ char TIER2_WEAPONS[9][] = {
"weapon_shotgun_spas" "weapon_shotgun_spas"
}; };
public Action Timer_SetupNewClient(Handle h, int userid) { Action Timer_SetupNewClient(Handle h, int userid) {
int client = GetClientOfUserId(userid); int client = GetClientOfUserId(userid);
if(client == 0) return Plugin_Handled; if(client == 0) return Plugin_Handled;
if(!DoesClientHaveKit(client)) { if(!DoesClientHaveKit(client)) {
@ -1057,7 +1109,7 @@ void GiveWeapon(int client, const char[] weaponName, float delay = 0.3, int clea
pack.WriteString(weaponName); pack.WriteString(weaponName);
} }
public Action Timer_GiveWeapon(Handle h, DataPack pack) { Action Timer_GiveWeapon(Handle h, DataPack pack) {
pack.Reset(); pack.Reset();
int userid = pack.ReadCell(); int userid = pack.ReadCell();
int client = GetClientOfUserId(userid); int client = GetClientOfUserId(userid);
@ -1071,7 +1123,7 @@ public Action Timer_GiveWeapon(Handle h, DataPack pack) {
} }
return Plugin_Handled; return Plugin_Handled;
} }
public Action Timer_RemoveInvincibility(Handle h, int userid) { Action Timer_RemoveInvincibility(Handle h, int userid) {
int client = GetClientOfUserId(userid); int client = GetClientOfUserId(userid);
if(client > 0) { if(client > 0) {
SetEntProp(client, Prop_Send, "m_iHealth", 100); SetEntProp(client, Prop_Send, "m_iHealth", 100);
@ -1079,11 +1131,11 @@ public Action Timer_RemoveInvincibility(Handle h, int userid) {
} }
return Plugin_Handled; return Plugin_Handled;
} }
public Action OnInvincibleDamageTaken(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3]) { Action OnInvincibleDamageTaken(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3]) {
damage = 0.0; damage = 0.0;
return Plugin_Stop; return Plugin_Stop;
} }
public Action Timer_GiveClientKit(Handle hdl, int user) { Action Timer_GiveClientKit(Handle hdl, int user) {
int client = GetClientOfUserId(user); int client = GetClientOfUserId(user);
if(client > 0 && !DoesClientHaveKit(client)) { if(client > 0 && !DoesClientHaveKit(client)) {
UseExtraKit(client); UseExtraKit(client);
@ -1091,15 +1143,13 @@ public Action Timer_GiveClientKit(Handle hdl, int user) {
return Plugin_Continue; return Plugin_Continue;
} }
public Action Timer_UpdateMinPlayers(Handle hdl) { Action Timer_UpdateMinPlayers(Handle hdl) {
//Set abm's min players to the amount of real survivors. Ran AFTER spawned incase they are pending joining //Set abm's min players to the amount of real survivors. Ran AFTER spawned incase they are pending joining
int newPlayerCount = GetRealSurvivorsCount(); int newPlayerCount = GetRealSurvivorsCount();
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) { if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
if(newPlayerCount > 4 && hMinPlayers.IntValue < newPlayerCount && newPlayerCount < 18) { if(newPlayerCount > 4 && hMinPlayers.IntValue < newPlayerCount && newPlayerCount < 18) {
abmExtraCount = newPlayerCount; abmExtraCount = newPlayerCount;
#if defined DEBUG
PrintDebug(DEBUG_GENERIC, "update abm_minplayers -> %d", abmExtraCount); PrintDebug(DEBUG_GENERIC, "update abm_minplayers -> %d", abmExtraCount);
#endif
//Create the extra player hud //Create the extra player hud
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = abmExtraCount;
} }
@ -1107,13 +1157,21 @@ public Action Timer_UpdateMinPlayers(Handle hdl) {
return Plugin_Continue; return Plugin_Continue;
} }
public Action Timer_GiveKits(Handle timer) { Action Timer_GiveKits(Handle timer) {
GiveStartingKits(); GiveStartingKits();
return Plugin_Continue; return Plugin_Continue;
} }
void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) {
PrintDebug(DEBUG_GENERIC, "round_start");
// TODO: check for any non-assigned specs
}
public void OnMapStart() { public void OnMapStart() {
PrintDebug(DEBUG_GENERIC, "OnMapStart");
isCheckpointReached = false; isCheckpointReached = false;
witchSpawnCount = 0;
witchLastSpawnTime = GetTime();
//If previous round was a failure, restore the amount of kits that were left directly after map transition //If previous round was a failure, restore the amount of kits that were left directly after map transition
if(isFailureRound) { if(isFailureRound) {
extraKitsAmount = extraKitsStarted; extraKitsAmount = extraKitsStarted;
@ -1179,8 +1237,41 @@ public void OnMapStart() {
finaleStage = Stage_Inactive; finaleStage = Stage_Inactive;
L4D2_RunScript(HUD_SCRIPT_CLEAR); L4D2_RunScript(HUD_SCRIPT_CLEAR);
if(cvEPISpecialSpawning.BoolValue && abmExtraCount > 4) {
InitExtraWitches();
}
} }
void InitExtraWitches() {
float flowMax = L4D2Direct_GetMapMaxFlowDistance() - (FLOW_CUTOFF*2); // *2 to calculate (max-min), save a minus
// 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, "Extra witch count: %d (%d min)", extraWitchCount, min);
for(int i = 0; i <= extraWitchCount; i++) {
ExtraWitchFlowPositions[i] = GetURandomFloat() * flowMax + FLOW_CUTOFF;
PrintDebug(DEBUG_SPAWNLOGIC, "Spawn location #%d: %f", i, ExtraWitchFlowPositions[i]);
}
witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT);
}
}
/*
Extra Witch Algo:
On map start, knowing # of total players, compute a random number of witches.
The random number calculated by DiceRoll with 2 rolls and biased to the left. [min, 6]
The minimum number in the dice is shifted to the right by the # of players (abmExtraCount-4)/4 (1 extra=0, 10 extra=2)
Then, with the # of witches, as N, calculate N different flow values between [0, L4D2Direct_GetMapMaxFlowDistance()]
Timer_Director then checks if highest flow achieved (never decreases) is >= each flow value, if one found, a witch is spawned
(the witch herself is not spawned at the flow, just her spawning is triggered)
*/
public void OnConfigsExecuted() { public void OnConfigsExecuted() {
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) { if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = abmExtraCount;
@ -1199,10 +1290,15 @@ public void OnMapEnd() {
cabinets[i].items[b] = 0; cabinets[i].items[b] = 0;
} }
} }
for(int i = 0; i <= DIRECTOR_WITCH_MAX_WITCHES; i++) {
ExtraWitchFlowPositions[i] = 0.0;
}
ammoPacks.Clear(); ammoPacks.Clear();
playersLoadedIn = 0; playersLoadedIn = 0;
abmExtraCount = 4; highestFlowAchieved = 0.0;
// abmExtraCount = 0;
delete updateHudTimer; delete updateHudTimer;
delete witchSpawnTimer;
} }
public void Event_RoundFreezeEnd(Event event, const char[] name, bool dontBroadcast) { public void Event_RoundFreezeEnd(Event event, const char[] name, bool dontBroadcast) {
@ -1409,12 +1505,11 @@ public Action Timer_OpenSaferoomDoor(Handle h) {
return Plugin_Continue; return Plugin_Continue;
} }
void UnlockDoor(int flag) { void UnlockDoor(int flag) {
int entity = EntRefToEntIndex(firstSaferoomDoorEntity); int entity = EntRefToEntIndex(firstSaferoomDoorEntity);
if(entity > 0) { if(entity > 0) {
PrintDebug(DEBUG_GENERIC, "Door unlocked, flag %d", flag); PrintDebug(DEBUG_GENERIC, "Door unlocked, flag %d", flag);
AcceptEntityInput(entity, "Unlock"); AcceptEntityInput(entity, "Unlock");
SetEntProp(entity, Prop_Send, "m_bLocked", 0); SetEntProp(entity, Prop_Send, "m_bLocked", 0);
SDKUnhook(entity, SDKHook_Use, Hook_Use); SDKUnhook(entity, SDKHook_Use, Hook_Use);
if(hSaferoomDoorAutoOpen.IntValue & flag) { if(hSaferoomDoorAutoOpen.IntValue & flag) {
@ -1427,7 +1522,7 @@ void UnlockDoor(int flag) {
} }
public Action Timer_UpdateHud(Handle h) { Action Timer_UpdateHud(Handle h) {
if(hEPIHudState.IntValue == 1 && !isCoop) { if(hEPIHudState.IntValue == 1 && !isCoop) {
PrintToServer("[EPI] Gamemode no longer coop, stopping (hudState=%d, abmExtraCount=%d)", hEPIHudState.IntValue, abmExtraCount); PrintToServer("[EPI] Gamemode no longer coop, stopping (hudState=%d, abmExtraCount=%d)", hEPIHudState.IntValue, abmExtraCount);
L4D2_RunScript(HUD_SCRIPT_CLEAR); L4D2_RunScript(HUD_SCRIPT_CLEAR);
@ -1435,13 +1530,13 @@ public Action Timer_UpdateHud(Handle h) {
return Plugin_Stop; return Plugin_Stop;
} }
// TODO: Turn it off when state == 1 // TODO: Turn it off when state == 1
int threshold = hEPIHudState.IntValue == 1 ? 4 : 0; // int threshold = hEPIHudState.IntValue == 1 ? 4 : 0;
if(hEPIHudState.IntValue == 1 && abmExtraCount < threshold) { //|| broke && abmExtraCount < threshold // if(hEPIHudState.IntValue == 1 && abmExtraCount < threshold) { //|| broke && abmExtraCount < threshold
PrintToServer("[EPI] Less than threshold (%d), stopping hud timer (hudState=%d, abmExtraCount=%d)", threshold, hEPIHudState.IntValue, abmExtraCount); // PrintToServer("[EPI] Less than threshold (%d), stopping hud timer (hudState=%d, abmExtraCount=%d)", threshold, hEPIHudState.IntValue, abmExtraCount);
L4D2_RunScript(HUD_SCRIPT_CLEAR); // L4D2_RunScript(HUD_SCRIPT_CLEAR);
updateHudTimer = null; // updateHudTimer = null;
return Plugin_Stop; // return Plugin_Stop;
} // }
if(cvEPIHudFlags.IntValue & 2) { if(cvEPIHudFlags.IntValue & 2) {
hudModeTicks++; hudModeTicks++;
@ -1491,12 +1586,14 @@ public Action Timer_UpdateHud(Handle h) {
Format(players, sizeof(players), "%s%s %s\\n", players, prefix, data); Format(players, sizeof(players), "%s%s %s\\n", players, prefix, data);
} }
} }
if(players[0] == '\0') { if(players[0] == '\0') {
PrintToServer("[EPI] No players online", hEPIHudState.IntValue, abmExtraCount); PrintToServer("[EPI] No players online", hEPIHudState.IntValue, abmExtraCount);
L4D2_RunScript(HUD_SCRIPT_CLEAR); L4D2_RunScript(HUD_SCRIPT_CLEAR);
updateHudTimer = null; updateHudTimer = null;
return Plugin_Stop; return Plugin_Stop;
} }
if(hEPIHudState.IntValue < 3) { if(hEPIHudState.IntValue < 3) {
// PrintToConsoleAll(HUD_SCRIPT_DATA, players); // PrintToConsoleAll(HUD_SCRIPT_DATA, players);
RunVScriptLong(HUD_SCRIPT_DATA, players); RunVScriptLong(HUD_SCRIPT_DATA, players);
@ -1512,7 +1609,7 @@ public Action Timer_UpdateHud(Handle h) {
// Methods // Methods
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
public void PopulateItems() { void PopulateItems() {
int survivors = GetRealSurvivorsCount(); int survivors = GetRealSurvivorsCount();
if(survivors <= 4) return; if(survivors <= 4) return;
@ -1806,18 +1903,16 @@ stock float GetAverageHP() {
stock int GetClientRealHealth(int client) { stock int GetClientRealHealth(int client) {
//First filter -> Must be a valid client, successfully in-game and not an spectator (The dont have health). //First filter -> Must be a valid client, successfully in-game and not an spectator (The dont have health).
if(!client if(!client || !IsValidEntity(client)
|| !IsValidEntity(client) || !IsClientInGame(client)
|| !IsClientInGame(client) || !IsPlayerAlive(client)
|| !IsPlayerAlive(client) || IsClientObserver(client)
|| IsClientObserver(client)) ) {
{
return -1; return -1;
} }
//If the client is not on the survivors team, then just return the normal client health. //If the client is not on the survivors team, then just return the normal client health.
if(GetClientTeam(client) != 2) if(GetClientTeam(client) != 2) {
{
return GetClientHealth(client); return GetClientHealth(client);
} }
@ -1829,14 +1924,10 @@ stock int GetClientRealHealth(int client) {
int PermHealth = GetClientHealth(client); int PermHealth = GetClientHealth(client);
//In case the buffer is 0 or less, we set the temporal health as 0, because the client has not used any pills or adrenaline yet //In case the buffer is 0 or less, we set the temporal health as 0, because the client has not used any pills or adrenaline yet
if(buffer <= 0.0) if(buffer <= 0.0) {
{
TempHealth = 0.0; TempHealth = 0.0;
} } else {
//In case it is higher than 0, we proceed to calculate the temporl health
//In case it is higher than 0, we proceed to calculate the temporl health
else
{
//This is the difference between the time we used the temporal item, and the current time //This is the difference between the time we used the temporal item, and the current time
float difference = GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime"); float difference = GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime");
@ -1845,15 +1936,14 @@ stock int GetClientRealHealth(int client) {
//This is a constant we create to determine the amount of health. This is the amount of time it has to pass //This is a constant we create to determine the amount of health. This is the amount of time it has to pass
//before 1 Temporal HP is consumed. //before 1 Temporal HP is consumed.
float constant = 1.0/decay; float constant = 1.0 / decay;
//Then we do the calcs //Then we do the calcs
TempHealth = buffer - (difference / constant); TempHealth = buffer - (difference / constant);
} }
//If the temporal health resulted less than 0, then it is just 0. //If the temporal health resulted less than 0, then it is just 0.
if(TempHealth < 0.0) if(TempHealth < 0.0) {
{
TempHealth = 0.0; TempHealth = 0.0;
} }
@ -1958,6 +2048,7 @@ stock int GetLowestFlowSurvivor(int ignoreTarget = 0) {
} }
} }
// Get the farthest ahead survivor, but ignoring ignoreTarget
stock int GetHighestFlowSurvivor(int ignoreTarget = 0) { stock int GetHighestFlowSurvivor(int ignoreTarget = 0) {
int client = L4D_GetHighestFlowSurvivor(); int client = L4D_GetHighestFlowSurvivor();
if(client != ignoreTarget) { if(client != ignoreTarget) {
@ -1984,4 +2075,63 @@ stock float GetSurvivorFlowDifference() {
float highestFlow = L4D2Direct_GetFlowDistance(client); float highestFlow = L4D2Direct_GetFlowDistance(client);
client = GetLowestFlowSurvivor(); client = GetLowestFlowSurvivor();
return highestFlow - L4D2Direct_GetFlowDistance(client); return highestFlow - L4D2Direct_GetFlowDistance(client);
}
char SPECIAL_IDS[8][] = {
"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,
}
void DirectorSpawn(SpecialType special) {
int player = L4D_GetHighestFlowSurvivor();
PrintToServer("[EPI] Spawning %s On %N", SPECIAL_IDS[view_as<int>(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<int>(special)], "auto");
}
Action Timer_Kick(Handle h, int bot) {
KickClient(bot);
return Plugin_Handled;
}
enum DiceBias {
BIAS_LEFT = -1,
BIAS_RIGHT = 1
}
int DiceRoll(int min, int max, int dices = 2, DiceBias bias) {
int compValue = -1;
for(int i = 0; i < dices; i++) {
int value = RoundToFloor(GetURandomFloat() * (max - min) + min);
if(bias == BIAS_LEFT) {
if(value < compValue || compValue == -1) {
compValue = value;
}
} else {
if(value > compValue || compValue == -1) {
compValue = value;
}
}
}
return compValue;
} }