This commit is contained in:
Jackzie 2023-10-09 21:00:03 -05:00
parent 3206e04cc1
commit 1d0f89c602

View file

@ -79,15 +79,18 @@ public Plugin myinfo =
url = "https://github.com/Jackzmc/sourcemod-plugins" url = "https://github.com/Jackzmc/sourcemod-plugins"
}; };
ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning; ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning, cvEPIGamemodes, hGamemode;
int extraKitsAmount, extraKitsStarted, abmExtraCount, firstSaferoomDoorEntity, playersLoadedIn, playerstoWaitFor; int g_extraKitsAmount, g_extraKitsStart, abmExtraCount, g_saferoomDoorEnt, playersLoadedIn, g_prevPlayerCount;
static int currentChapter; static int g_currentChapter;
static bool isCheckpointReached, isLateLoaded, firstGiven, isFailureRound, areItemsPopulated; static bool g_isCheckpointReached, isLateLoaded, g_startCampaignGiven, g_isFailureRound, g_areItemsPopulated;
static ArrayList ammoPacks; static ArrayList g_ammoPacks;
static Handle updateHudTimer; static Handle updateHudTimer;
static bool showHudPingMode; static bool showHudPingMode;
static int hudModeTicks; static int hudModeTicks;
static char gamemode[32]; static char g_currentGamemode[32];
static bool g_isGamemodeAllowed;
static int g_survivorCount;
static ArrayList g_allowedGamemodes;
bool isCoop; bool isCoop;
@ -101,11 +104,12 @@ enum Difficulty {
Difficulty zDifficulty; Difficulty zDifficulty;
static bool allowTankSplit = true; static bool g_tankSplitEnabled = true;
enum State { enum State {
State_Empty, State_Empty,
State_PendingEmpty, State_PendingEmpty,
State_Pending,
State_Active State_Active
} }
#if defined DEBUG_LEVEL #if defined DEBUG_LEVEL
@ -244,7 +248,7 @@ public void OnPluginStart() {
weaponMaxClipSizes = new StringMap(); weaponMaxClipSizes = new StringMap();
pInv = new StringMap(); pInv = new StringMap();
ammoPacks = new ArrayList(2); //<int entityID, ArrayList clients> g_ammoPacks = new ArrayList(2); //<int entityID, ArrayList clients>
HookEvent("player_spawn", Event_PlayerSpawn); HookEvent("player_spawn", Event_PlayerSpawn);
HookEvent("player_first_spawn", Event_PlayerFirstSpawn); HookEvent("player_first_spawn", Event_PlayerFirstSpawn);
@ -258,7 +262,6 @@ 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);
@ -282,8 +285,7 @@ public void OnPluginStart() {
HookEvent("witch_spawn", Event_WitchSpawn); HookEvent("witch_spawn", Event_WitchSpawn);
hExtraItemBasePercentage = CreateConVar("l4d2_extraitems_chance", "0.059", "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);
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); 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); 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);
@ -295,7 +297,8 @@ public void OnPluginStart() {
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); cvEPISpecialSpawning = CreateConVar("l4d2_extraitems_director_spawns", "2", "Determines what specials are spawned. Add bits together.\n1 = Normal specials\n2 = Witches\n4 = Tanks", FCVAR_NONE, true, 0.0);
cvEPIGamemodes = CreateConVar("l4d2_epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE);
// TODO: hook flags, reset name index / ping mode // TODO: hook flags, reset name index / ping mode
cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange); cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange);
cvEPISpecialSpawning.AddChangeHook(Cvar_SpecialSpawningChange); cvEPISpecialSpawning.AddChangeHook(Cvar_SpecialSpawningChange);
@ -315,30 +318,20 @@ public void OnPluginStart() {
playerData[i].Setup(i); playerData[i].Setup(i);
} }
} }
UpdateSurvivorCount();
int count = GetRealSurvivorsCount(); TryStartHud();
abmExtraCount = count;
int threshold = hEPIHudState.IntValue == 1 ? 5 : 0;
if(hEPIHudState.IntValue > 0 && count > threshold && updateHudTimer == null) {
PrintToServer("[EPI] Creating new hud timer");
updateHudTimer = CreateTimer(EXTRA_PLAYER_HUD_UPDATE_INTERVAL, Timer_UpdateHud, _, TIMER_REPEAT);
}
} }
#if defined DEBUG_FORCE_PLAYERS
abmExtraCount = DEBUG_FORCE_PLAYERS;
#endif
char buffer[16]; char buffer[16];
cvZDifficulty = FindConVar("z_difficulty"); cvZDifficulty = FindConVar("z_difficulty");
cvZDifficulty.GetString(buffer, sizeof(buffer)); cvZDifficulty.GetString(buffer, sizeof(buffer));
cvZDifficulty.AddChangeHook(Event_DifficultyChange); cvZDifficulty.AddChangeHook(Event_DifficultyChange);
Event_DifficultyChange(cvZDifficulty, buffer, buffer); Event_DifficultyChange(cvZDifficulty, buffer, buffer);
ConVar hGamemode = FindConVar("mp_gamemode"); hGamemode = FindConVar("mp_gamemode");
hGamemode.GetString(gamemode, sizeof(gamemode)); hGamemode.GetString(g_currentGamemode, sizeof(g_currentGamemode));
hGamemode.AddChangeHook(Event_GamemodeChange); hGamemode.AddChangeHook(Event_GamemodeChange);
Event_GamemodeChange(hGamemode, gamemode, gamemode); Event_GamemodeChange(hGamemode, g_currentGamemode, g_currentGamemode);
AutoExecConfig(true, "l4d2_extraplayeritems"); AutoExecConfig(true, "l4d2_extraplayeritems");
@ -351,8 +344,6 @@ public void OnPluginStart() {
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); 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_val", Command_EPIValue);
RegAdminCmd("sm_epi_trigger", Command_Trigger, ADMFLAG_CHEATS); RegAdminCmd("sm_epi_trigger", Command_Trigger, ADMFLAG_CHEATS);
#endif #endif
@ -364,8 +355,6 @@ public void OnPluginStart() {
} }
Action Timer_ForceUpdateInventories(Handle h) { 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) {
@ -381,13 +370,13 @@ public void OnClientPutInServer(int client) {
playerData[client].Setup(client); playerData[client].Setup(client);
if(GetClientTeam(client) == 2) { if(GetClientTeam(client) == 2) {
if(!StrEqual(gamemode, "hideandseek")) { if(g_isGamemodeAllowed) {
// CreateTimer(0.2, Timer_CheckInventory, client); // CreateTimer(0.2, Timer_CheckInventory, client);
} }
} else if(abmExtraCount >= 4 && GetClientTeam(client) == 0) { }/* else if(abmExtraCount >= 4 && GetClientTeam(client) == 0) {
// TODO: revert revert // TODO: revert revert
// L4D_TakeOverBot(client); // L4D_TakeOverBot(client);
} } */
} }
} }
@ -398,7 +387,7 @@ public void OnClientDisconnect(int client) {
public void OnPluginEnd() { public void OnPluginEnd() {
delete weaponMaxClipSizes; delete weaponMaxClipSizes;
delete ammoPacks; delete g_ammoPacks;
L4D2_ExecVScriptCode(HUD_SCRIPT_CLEAR); L4D2_ExecVScriptCode(HUD_SCRIPT_CLEAR);
} }
@ -447,28 +436,30 @@ void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char[] newV
} }
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");
delete updateHudTimer; delete updateHudTimer;
} }
} else { } else {
int count = GetRealSurvivorsCount(); TryStartHud();
int threshold = 0; }
// Default to 0 for state == 2 (force) }
if(hEPIHudState.IntValue == 1) { void TryStartHud() {
// On L4D1 map start if 5 players, on L4D2 start with 6 int threshold = 0;
// On L4D1 more chance of duplicate models, so can't see health // Default to 0 for state == 2 (force)
threshold = L4D2_GetSurvivorSetMap() == 2 ? 4 : 5; if(hEPIHudState.IntValue == 1) {
} // On L4D1 map start if 5 players, on L4D2 start with 6
if(count > threshold && updateHudTimer == null) { // On L4D1 more chance of duplicate models, so can't see health
PrintToServer("[EPI] Creating new hud timer"); threshold = L4D2_GetSurvivorSetMap() == 2 ? 4 : 5;
updateHudTimer = CreateTimer(EXTRA_PLAYER_HUD_UPDATE_INTERVAL, Timer_UpdateHud, _, TIMER_REPEAT); }
} if(g_realSurvivorCount > threshold && updateHudTimer == null) {
PrintToServer("[EPI] Creating new hud timer");
updateHudTimer = CreateTimer(EXTRA_PLAYER_HUD_UPDATE_INTERVAL, Timer_UpdateHud, _, TIMER_REPEAT);
} }
} }
public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[] newValue) { public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[] newValue) {
cvar.GetString(gamemode, sizeof(gamemode)); cvar.GetString(g_currentGamemode, sizeof(g_currentGamemode));
isCoop = StrEqual(gamemode, "coop", false); g_isGamemodeAllowed = IsGamemodeAllowed();
} }
ConVar GetActiveFriendlyFireFactor() { ConVar GetActiveFriendlyFireFactor() {
@ -559,7 +550,7 @@ Action Command_RestoreInventory(int client, int args) {
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_SetSurvivorCount(int client, int args) { public Action Command_SetSurvivorCount(int client, int args) {
int oldCount = abmExtraCount; int oldCount = g_realSurvivorCount;
if(args > 0) { if(args > 0) {
static char arg1[8]; static char arg1[8];
GetCmdArg(1, arg1, sizeof(arg1)); GetCmdArg(1, arg1, sizeof(arg1));
@ -569,7 +560,7 @@ public Action Command_SetSurvivorCount(int client, int args) {
ReplyToCommand(client, "Invalid survivor count. Must be between 0 and %d", MaxClients); ReplyToCommand(client, "Invalid survivor count. Must be between 0 and %d", MaxClients);
return Plugin_Handled; return Plugin_Handled;
} else { } else {
abmExtraCount = newCount; g_realSurvivorCount = g_survivorCount = newCount;
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = abmExtraCount;
ReplyToCommand(client, "Changed extra survivor count to %d -> %d", oldCount, newCount); ReplyToCommand(client, "Changed extra survivor count to %d -> %d", oldCount, newCount);
bool add = (newCount - oldCount) > 0; bool add = (newCount - oldCount) > 0;
@ -592,8 +583,8 @@ Action Command_SetKitAmount(int client, int args) {
GetCmdArg(1, arg, sizeof(arg)); GetCmdArg(1, arg, sizeof(arg));
int number = StringToInt(arg); int number = StringToInt(arg);
if(number > 0 || number == -1) { if(number > 0 || number == -1) {
extraKitsAmount = number; g_extraKitsAmount = number;
extraKitsStarted = extraKitsAmount; g_extraKitsStart = g_extraKitsAmount;
ReplyToCommand(client, "Set extra kits amount to %d", number); ReplyToCommand(client, "Set extra kits amount to %d", number);
} else { } else {
ReplyToCommand(client, "Must be a number greater than 0. -1 to disable"); ReplyToCommand(client, "Must be a number greater than 0. -1 to disable");
@ -612,8 +603,8 @@ Action Command_ToggleDoorLocks(int client, int args) {
} }
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", g_extraKitsAmount, g_extraKitsStart, GetSurvivorsCount());
ReplyToCommand(client, "isCheckpointReached %b, isLateLoaded %b, firstGiven %b", isCheckpointReached, isLateLoaded, firstGiven); ReplyToCommand(client, "isCheckpointReached %b, isLateLoaded %b, firstGiven %b", g_isCheckpointReached, isLateLoaded, g_startCampaignGiven);
return Plugin_Handled; return Plugin_Handled;
} }
Action Command_RunExtraItems(int client, int args) { Action Command_RunExtraItems(int client, int args) {
@ -623,6 +614,7 @@ Action Command_RunExtraItems(int client, int args) {
} }
Action Command_Debug(int client, int args) { Action Command_Debug(int client, int args) {
PrintToConsole(client, "abmExtraCount = %d", abmExtraCount); PrintToConsole(client, "abmExtraCount = %d", abmExtraCount);
PrintToConsole(client, "g_survivorCount = %d | g_realSurvivorCount = %d", g_survivorCount, g_realSurvivorCount);
Director_PrintDebug(client); Director_PrintDebug(client);
return Plugin_Handled; return Plugin_Handled;
} }
@ -691,18 +683,18 @@ enum FinaleStage {
Stage_InactiveFinale = -1 Stage_InactiveFinale = -1
} }
int extraTankHP; int extraTankHP;
FinaleStage finaleStage; FinaleStage g_finaleStage;
public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) { public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) {
if(finaleType == FINALE_STARTED && abmExtraCount > 4) { if(finaleType == FINALE_STARTED && g_realSurvivorCount > 4) {
finaleStage = Stage_FinaleActive; g_finaleStage = Stage_FinaleActive;
PrintToConsoleAll("[EPI] Finale started and over threshold"); PrintToConsoleAll("[EPI] Finale started and over threshold");
} else if(finaleType == FINALE_TANK) { } else if(finaleType == FINALE_TANK) {
if(finaleStage == Stage_FinaleActive) { if(g_finaleStage == Stage_FinaleActive) {
finaleStage = Stage_FinaleTank1; g_finaleStage = Stage_FinaleTank1;
PrintToConsoleAll("[EPI] First tank stage has started"); PrintToConsoleAll("[EPI] First tank stage has started");
} else if(finaleStage == Stage_FinaleTank1) { } else if(g_finaleStage == Stage_FinaleTank1) {
finaleStage = Stage_FinaleTank2; g_finaleStage = Stage_FinaleTank2;
PrintToConsoleAll("[EPI] Second stage started, waiting for tank"); PrintToConsoleAll("[EPI] Second stage started, waiting for tank");
} }
} }
@ -712,18 +704,18 @@ public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) {
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) && g_realSurvivorCount > 4 && hExtraFinaleTank.IntValue > 0) {
PrintToConsoleAll("[EPI] Split tank is enabled, checking new spawned tank"); PrintToConsoleAll("[EPI] Split tank is enabled, checking new spawned tank");
if(finaleStage == Stage_FinaleTank2 && allowTankSplit && hExtraFinaleTank.IntValue & 2) { if(g_finaleStage == Stage_FinaleTank2 && g_tankSplitEnabled && hExtraFinaleTank.IntValue & 2) {
PrintToConsoleAll("[EPI] Second tank spawned, setting health."); PrintToConsoleAll("[EPI] Second tank spawned, setting health.");
// Sets health in half, sets finaleStage to health // Sets health in half, sets finaleStage to health
float duration = GetRandomFloat(EXTRA_TANK_MIN_SEC, EXTRA_TANK_MAX_SEC); float duration = GetRandomFloat(EXTRA_TANK_MIN_SEC, EXTRA_TANK_MAX_SEC);
CreateTimer(duration, Timer_SpawnFinaleTank, user); CreateTimer(duration, Timer_SpawnFinaleTank, user);
} else if(finaleStage == Stage_FinaleDuplicatePending) { } else if(g_finaleStage == Stage_FinaleDuplicatePending) {
PrintToConsoleAll("[EPI] Third & final tank spawned"); PrintToConsoleAll("[EPI] Third & final tank spawned");
RequestFrame(Frame_SetExtraTankHealth, user); RequestFrame(Frame_SetExtraTankHealth, user);
} else if(finaleStage == Stage_Inactive && allowTankSplit && hExtraFinaleTank.IntValue & 1 && GetSurvivorsCount() > 6) { } else if(g_finaleStage == Stage_Inactive && g_tankSplitEnabled && hExtraFinaleTank.IntValue & 1 && GetSurvivorsCount() > 6) {
finaleStage = Stage_TankSplit; g_finaleStage = Stage_TankSplit;
if(GetRandomFloat() <= hSplitTankChance.FloatValue) { if(GetRandomFloat() <= hSplitTankChance.FloatValue) {
// Half their HP, assign half to self and for next tank // Half their HP, assign half to self and for next tank
int hp = GetEntProp(tank, Prop_Send, "m_iHealth") / 2; int hp = GetEntProp(tank, Prop_Send, "m_iHealth") / 2;
@ -735,16 +727,16 @@ void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) {
PrintToConsoleAll("[EPI] Random chance for split tank failed"); PrintToConsoleAll("[EPI] Random chance for split tank failed");
} }
// Then, summon the next tank // Then, summon the next tank
} else if(finaleStage == Stage_TankSplit) { } else if(g_finaleStage == Stage_TankSplit) {
CreateTimer(0.2, Timer_SetHealth, user); CreateTimer(0.2, Timer_SetHealth, user);
} }
} }
} }
Action Timer_SpawnFinaleTank(Handle t, int user) { Action Timer_SpawnFinaleTank(Handle t, int user) {
if(finaleStage == Stage_FinaleTank2) { if(g_finaleStage == Stage_FinaleTank2) {
DirectorSpawn(Special_Tank); DirectorSpawn(Special_Tank);
// ServerCommand("sm_forcespecial tank"); // ServerCommand("sm_forcespecial tank");
finaleStage = Stage_Inactive; g_finaleStage = Stage_Inactive;
} }
return Plugin_Handled; return Plugin_Handled;
} }
@ -763,9 +755,9 @@ Action Timer_SetHealth(Handle h, int user) {
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 && g_finaleStage == Stage_FinaleDuplicatePending) {
SetEntProp(tank, Prop_Send, "m_iHealth", extraTankHP); SetEntProp(tank, Prop_Send, "m_iHealth", extraTankHP);
finaleStage = Stage_InactiveFinale; g_finaleStage = Stage_InactiveFinale;
} }
} }
@ -784,126 +776,106 @@ public void OnGetWeaponsInfo(int pThis, const char[] classname) {
//Called on the first spawn in a mission. //Called on the first spawn in a mission.
void Event_GameStart(Event event, const char[] name, bool dontBroadcast) { void Event_GameStart(Event event, const char[] name, bool dontBroadcast) {
firstGiven = false; g_startCampaignGiven = false;
extraKitsAmount = 0; g_extraKitsAmount = 0;
extraKitsStarted = 0; g_extraKitsStart = 0;
abmExtraCount = 0; abmExtraCount = 0;
g_realSurvivorCount = 0;
g_survivorCount = 0;
hMinPlayers.IntValue = 4; hMinPlayers.IntValue = 4;
currentChapter = 0; g_currentChapter = 0;
pInv.Clear(); pInv.Clear();
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
playerData[i].state = State_Empty; playerData[i].state = State_Empty;
} }
} }
// This is only called when a player joins for the first time during an entire campaign, unless they fully disconnect.
// Idle bots also call this as they are created and destroyed on idle/resume
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;
if(IsFakeClient(client)) { if(IsFakeClient(client)) {
// Make the real player's bot invincible, ONLY for the first time it appears // Ignore any 'BOT' bots (ABMBot, etc), they are temporarily
char classname[32];
GetEntityClassname(client, classname, sizeof(classname));
if(StrContains(classname, "bot", false) > -1) return;
// Make new bots invincible for a few seconds, as they spawn on other players. Only applies to a player's bot
int player = L4D_GetIdlePlayerOfBot(client); int player = L4D_GetIdlePlayerOfBot(client);
// TODO: check instead hasJoined but state == state_empty
if(player > 0 && !playerData[client].hasJoined) { if(player > 0 && !playerData[client].hasJoined) {
playerData[client].hasJoined = true; playerData[client].hasJoined = true;
// TODO: Confirm this fix works playerData[client].state = State_Pending;
CreateTimer(1.5, Timer_RemoveInvincibility, userid); CreateTimer(1.5, Timer_RemoveInvincibility, userid);
SDKHook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken); SDKHook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken);
} }
} else { } else {
// Make the (real) player invincible: // Is a player
// Make the (real) player invincible as well:
CreateTimer(1.5, Timer_RemoveInvincibility, userid); CreateTimer(1.5, Timer_RemoveInvincibility, userid);
SDKHook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken); SDKHook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken);
playerData[client].state = State_Active; playerData[client].state = State_Active;
if(L4D_IsFirstMapInScenario() && !firstGiven) { if(L4D_IsFirstMapInScenario() && !g_startCampaignGiven) {
//Check if all clients are ready, and survivor count is > 4. // Players are joining the campaign, but not all clients are ready yet. Once a client is ready, we will give the extra players their items
if(AreAllClientsReady()) { if(AreAllClientsReady()) {
abmExtraCount = GetRealSurvivorsCount(); UpdateSurvivorCount();
if(abmExtraCount > 4) { if(g_realSurvivorCount > 4) {
PrintToServer("[EPI] First chapter kits given"); PrintToServer("[EPI] First chapter kits given");
firstGiven = true; g_startCampaignGiven = true;
//Set the initial value ofhMinPlayers //Set the initial value ofhMinPlayers
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = abmExtraCount;
}
PopulateItems(); PopulateItems();
CreateTimer(1.0, Timer_GiveKits); CreateTimer(1.0, Timer_GiveKits);
} }
UnlockDoor(2); UnlockDoor(2);
} }
} else { } else {
// New client has connected, not on first map. // New client has connected, late on the first chapter or on any other chapter
// TODO: Check if Timer_UpdateMinPlayers is needed, or if this works: UpdateSurvivorCount();
// Never decrease abmExtraCount
int newCount = GetRealSurvivorsCount();
if(newCount > abmExtraCount && abmExtraCount > 4) {
abmExtraCount = newCount;
PrintDebug(DEBUG_GENERIC, "New client, setting abmExtraCount to %d", newCount);
hMinPlayers.IntValue = abmExtraCount;
ConVar friendlyFireFactor = GetActiveFriendlyFireFactor();
// TODO: Get previous default
friendlyFireFactor.FloatValue = friendlyFireFactor.FloatValue - ((newCount - 4) * cvFFDecreaseRate.FloatValue);
if(friendlyFireFactor.FloatValue < 0.0) {
friendlyFireFactor.FloatValue = 0.01;
}
}
// If 5 survivors, then set them up, TP them. // If 5 survivors, then set them up, TP them.
if(newCount > 4) { if(g_realSurvivorCount > 4) {
CreateTimer(0.1, Timer_SetupNewClient, userid); CreateTimer(0.1, Timer_SetupNewClient, userid);
} }
} }
} }
} }
// This is called everytime a player joins, such as map transitions
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(!g_isGamemodeAllowed) return;
int user = event.GetInt("userid"); int userid = event.GetInt("userid");
int client = GetClientOfUserId(user); int client = GetClientOfUserId(user);
if(GetClientTeam(client) == 2) { UpdateSurvivorCount();
if(!IsFakeClient(client)) { if(GetClientTeam(client) == 2 && !IsFakeClient(client) && !L4D_IsFirstMapInScenario()) {
if(!L4D_IsFirstMapInScenario()) { // Start door timeout:
playersLoadedIn++; CreateTimer(hSaferoomDoorWaitSeconds.FloatValue, Timer_OpenSaferoomDoor, _, TIMER_FLAG_NO_MAPCHANGE);
if(playersLoadedIn == 1) {
CreateTimer(hSaferoomDoorWaitSeconds.FloatValue, Timer_OpenSaferoomDoor, _, TIMER_FLAG_NO_MAPCHANGE); if(g_prevPlayerCount > 0) {
} // Open the door if we hit % percent
if(playerstoWaitFor > 0) { float percentIn = float(g_realSurvivorCount) / float(g_prevPlayerCount);
float percentIn = float(playersLoadedIn) / float(playerstoWaitFor); if(percentIn > hMinPlayersSaferoomDoor.FloatValue)
if(percentIn > hMinPlayersSaferoomDoor.FloatValue) UnlockDoor(2);
UnlockDoor(2); } else{
}else{ UnlockDoor(2);
UnlockDoor(2);
}
}
} }
CreateTimer(0.5, Timer_GiveClientKit, user); CreateTimer(0.5, Timer_GiveClientKit, userid);
SDKHook(client, SDKHook_WeaponEquip, Event_Pickup); SDKHook(client, SDKHook_WeaponEquip, Event_Pickup);
} }
int count = GetRealSurvivorsCount(); TryStartHud();
int threshold = 0;
if(hEPIHudState.IntValue == 1) {
threshold = L4D2_GetSurvivorSetMap() == 2 ? 4 : 5;
}
if(hEPIHudState.IntValue > 0 && count > threshold && updateHudTimer == null) {
PrintToServer("[EPI] Creating new hud timer (player spawn)");
updateHudTimer = CreateTimer(EXTRA_PLAYER_HUD_UPDATE_INTERVAL, Timer_UpdateHud, _, TIMER_REPEAT);
}
UpdatePlayerInventory(client); UpdatePlayerInventory(client);
} }
void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) { 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 && GetClientTeam(client) == 2) {
playerData[client].hasJoined = false; playerData[client].hasJoined = false;
PrintToServer("debug: Player %N (index %d, uid %d) now pending empty", client, client, userid);
playerData[client].state = State_PendingEmpty; playerData[client].state = State_PendingEmpty;
playerData[client].nameCache[0] = '\0'; playerData[client].nameCache[0] = '\0';
/*DataPack pack; PrintToServer("debug: Player (index %d, uid %d) now pending empty", client, client, userid);
CreateDataTimer(cvDropDisconnectTime.FloatValue, Timer_DropSurvivor, pack);
pack.WriteCell(userid);
pack.WriteCell(client);*/
CreateTimer(cvDropDisconnectTime.FloatValue, Timer_DropSurvivor, client); CreateTimer(cvDropDisconnectTime.FloatValue, Timer_DropSurvivor, client);
} }
} }
@ -921,7 +893,7 @@ Action Timer_DropSurvivor(Handle h, int client) {
if(hMinPlayers != null) { if(hMinPlayers != null) {
PrintToServer("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount); PrintToServer("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount);
PrintToConsoleAll("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount); PrintToConsoleAll("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount);
hMinPlayers.IntValue = --abmExtraCount; hMinPlayers.IntValue = g_realSurvivorCount;
if(hMinPlayers.IntValue < 4) { if(hMinPlayers.IntValue < 4) {
hMinPlayers.IntValue = 4; hMinPlayers.IntValue = 4;
} }
@ -946,7 +918,7 @@ 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;
return Plugin_Continue; return Plugin_Changed;
} }
return Plugin_Continue; return Plugin_Continue;
} }
@ -967,11 +939,15 @@ char TIER2_WEAPONS[9][] = {
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;
// Incase their bot snagged a kit before we could give them one:
if(!DoesClientHaveKit(client)) { if(!DoesClientHaveKit(client)) {
int item = GivePlayerItem(client, "weapon_first_aid_kit"); int item = GivePlayerItem(client, "weapon_first_aid_kit");
EquipPlayerWeapon(client, item); EquipPlayerWeapon(client, item);
} }
// Iterate all clients and get:
// a) the client with the lowest intensity
// b) every survivor's tier2 / tier1 / secondary weapons
int lowestClient = -1; int lowestClient = -1;
float lowestIntensity; float lowestIntensity;
char weaponName[64]; char weaponName[64];
@ -985,6 +961,7 @@ Action Timer_SetupNewClient(Handle h, int userid) {
int wpn = GetPlayerWeaponSlot(i, 0); int wpn = GetPlayerWeaponSlot(i, 0);
if(wpn > 0) { if(wpn > 0) {
GetEdictClassname(wpn, weaponName, sizeof(weaponName)); GetEdictClassname(wpn, weaponName, sizeof(weaponName));
// Ignore grenade launcher / m60, not a normal weapon to give
if(!StrEqual(weaponName, "weapon_grenade_launcher") && !StrEqual(weaponName, "weapon_rifle_m60")) { if(!StrEqual(weaponName, "weapon_grenade_launcher") && !StrEqual(weaponName, "weapon_rifle_m60")) {
for(int j = 0; j < TIER2_WEAPON_COUNT; j++) { for(int j = 0; j < TIER2_WEAPON_COUNT; j++) {
if(StrEqual(TIER2_WEAPONS[j], weaponName)) { if(StrEqual(TIER2_WEAPONS[j], weaponName)) {
@ -1014,29 +991,32 @@ Action Timer_SetupNewClient(Handle h, int userid) {
} }
} }
// Give player any random t2 weapon, if no one has one, fallback to t1, if no one has one, give them a magnum // Give player any random t2 weapon, if no one has one, fallback to t1.
if(tier2Weapons.Length > 0) { if(tier2Weapons.Length > 0) {
tier2Weapons.GetString(GetRandomInt(0, tier2Weapons.Length - 1), weaponName, sizeof(weaponName)); tier2Weapons.GetString(GetRandomInt(0, tier2Weapons.Length - 1), weaponName, sizeof(weaponName));
// Format(weaponName, sizeof(weaponName), "weapon_%s", weaponName); // Format(weaponName, sizeof(weaponName), "weapon_%s", weaponName);
PrintToServer("[EPI/debug] Giving new client (%N) tier 2: %s", client, weaponName); PrintDebug(DEBUG_SPAWNLOGIC, "Giving new client (%N) tier 2: %s", client, weaponName);
GiveWeapon(client, weaponName, 0.3, 0); GiveWeapon(client, weaponName, 0.3, 0);
} else if(tier1Weapons.Length > 0) { } else if(tier1Weapons.Length > 0) {
// Format(weaponName, sizeof(weaponName), "weapon_%s", TIER1_WEAPONS[GetRandomInt(0, TIER1_WEAPON_COUNT - 1)]); // Format(weaponName, sizeof(weaponName), "weapon_%s", TIER1_WEAPONS[GetRandomInt(0, TIER1_WEAPON_COUNT - 1)]);
tier1Weapons.GetString(GetRandomInt(0, tier1Weapons.Length - 1), weaponName, sizeof(weaponName)); tier1Weapons.GetString(GetRandomInt(0, tier1Weapons.Length - 1), weaponName, sizeof(weaponName));
PrintToServer("[EPI/debug] Giving new client (%N) tier 1: %s", client, weaponName); PrintDebug(DEBUG_SPAWNLOGIC, "Giving new client (%N) tier 1: %s", client, weaponName);
GiveWeapon(client, weaponName, 0.6, 0); GiveWeapon(client, weaponName, 0.6, 0);
} }
PrintToServer("%N: Giving random secondary / %d", secondaryWeapons.Length, client); PrintDebug(DEBUG_SPAWNLOGIC, "%N: Giving random secondary / %d", secondaryWeapons.Length, client);
PrintToConsoleAll("%N: Giving random secondary / %d", secondaryWeapons.Length, client);
if(secondaryWeapons.Length > 0) { if(secondaryWeapons.Length > 0) {
secondaryWeapons.GetString(GetRandomInt(0, secondaryWeapons.Length - 1), weaponName, sizeof(weaponName)); secondaryWeapons.GetString(GetRandomInt(0, secondaryWeapons.Length - 1), weaponName, sizeof(weaponName));
GiveWeapon(client, weaponName, 0.6, 1); GiveWeapon(client, weaponName, 0.6, 1);
} }
if(lowestClient > 0) { if(lowestClient > 0) {
// Get a position behind the player, but not too far to put them in a wall.
// Hopefully reducing the chance they shot for "appearing" infront of a player
float pos[3]; float pos[3];
GetClientAbsOrigin(lowestClient, pos); GetHorizontalPositionFromClient(lowestClient, -20.0, pos);
TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR);
// Just incase they _are_ in a wall, let the game check:
L4D_WarpToValidPositionIfStuck(client);
} }
delete tier2Weapons; delete tier2Weapons;
@ -1046,6 +1026,7 @@ Action Timer_SetupNewClient(Handle h, int userid) {
return Plugin_Handled; return Plugin_Handled;
} }
// Gives a player a weapon, clearing their previous and with a configurable delay to prevent issues
void GiveWeapon(int client, const char[] weaponName, float delay = 0.3, int clearSlot = -1) { void GiveWeapon(int client, const char[] weaponName, float delay = 0.3, int clearSlot = -1) {
if(clearSlot > 0) { if(clearSlot > 0) {
int oldWpn = GetPlayerWeaponSlot(client, clearSlot); int oldWpn = GetPlayerWeaponSlot(client, clearSlot);
@ -1075,6 +1056,7 @@ Action Timer_GiveWeapon(Handle h, DataPack pack) {
} }
return Plugin_Handled; return Plugin_Handled;
} }
// First spawn invincibility:
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) {
@ -1087,6 +1069,7 @@ Action OnInvincibleDamageTaken(int victim, int& attacker, int& inflictor, float
damage = 0.0; damage = 0.0;
return Plugin_Stop; return Plugin_Stop;
} }
// Gives 5+ kit after saferoom loaded:
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)) {
@ -1095,66 +1078,47 @@ Action Timer_GiveClientKit(Handle hdl, int user) {
return Plugin_Continue; return Plugin_Continue;
} }
Action Timer_UpdateMinPlayers(Handle hdl) {
//Set abm's min players to the amount of real survivors. Ran AFTER spawned incase they are pending joining
int newPlayerCount = GetRealSurvivorsCount();
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
if(newPlayerCount > 4 && hMinPlayers.IntValue < newPlayerCount && newPlayerCount < 18) {
abmExtraCount = newPlayerCount;
PrintDebug(DEBUG_GENERIC, "update abm_minplayers -> %d", abmExtraCount);
//Create the extra player hud
hMinPlayers.IntValue = abmExtraCount;
}
}
return Plugin_Continue;
}
// Gives start of campaign kits
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"); g_isCheckpointReached = false;
isCheckpointReached = false;
//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(g_isFailureRound) {
extraKitsAmount = extraKitsStarted; g_extraKitsAmount = g_extraKitsStart;
//give kits if first //give kits if first
if(L4D_IsFirstMapInScenario()) { if(L4D_IsFirstMapInScenario()) {
GiveStartingKits(); GiveStartingKits();
} }
isFailureRound = false; g_isFailureRound = false;
} else if(!L4D_IsFirstMapInScenario()) { } else if(!L4D_IsFirstMapInScenario()) {
//Re-set value incase it reset. g_currentChapter++;
//hMinPlayers.IntValue = abmExtraCount;
currentChapter++;
} else if(L4D_IsMissionFinalMap()) { } else if(L4D_IsMissionFinalMap()) {
//Add extra kits for finales //Add extra kits for finales
char curMap[64]; char curMap[64];
GetCurrentMap(curMap, sizeof(curMap)); GetCurrentMap(curMap, sizeof(curMap));
// Disable tank split on hard rain finale
if(StrEqual(curMap, "c4m5_milltown_escape")) { if(StrEqual(curMap, "c4m5_milltown_escape")) {
allowTankSplit = false; g_tankSplitEnabled = false;
} else { } else {
allowTankSplit = true; g_tankSplitEnabled = true;
} }
int extraKits = GetSurvivorsCount() - 4; int extraKits = GetSurvivorsCount() - 4;
if(extraKits > 0) { if(extraKits > 0) {
extraKitsAmount += extraKits; // Keep how many extra kits were left after we loaded in, for resetting on failure rounds
extraKitsStarted = extraKitsAmount; g_extraKitsAmount += extraKits;
g_extraKitsStart = g_extraKitsAmount;
} }
currentChapter++; g_currentChapter++;
} else { } else {
currentChapter++; g_currentChapter++;
} }
if(!isLateLoaded) { if(!isLateLoaded) {
isLateLoaded = false; isLateLoaded = false;
@ -1166,7 +1130,8 @@ public void OnMapStart() {
while ((entity = FindEntityByClassname(entity, "prop_door_rotating_checkpoint")) != -1 && entity > MaxClients) { while ((entity = FindEntityByClassname(entity, "prop_door_rotating_checkpoint")) != -1 && entity > MaxClients) {
bool isLocked = GetEntProp(entity, Prop_Send, "m_bLocked") == 1; bool isLocked = GetEntProp(entity, Prop_Send, "m_bLocked") == 1;
if(isLocked) { if(isLocked) {
firstSaferoomDoorEntity = EntIndexToEntRef(entity); g_saferoomDoorEnt = EntIndexToEntRef(entity);
AcceptEntityInput(entity, "Open");
AcceptEntityInput(entity, "Close"); AcceptEntityInput(entity, "Close");
AcceptEntityInput(entity, "Lock"); AcceptEntityInput(entity, "Lock");
AcceptEntityInput(entity, "ForceClosed"); AcceptEntityInput(entity, "ForceClosed");
@ -1183,8 +1148,7 @@ public void OnMapStart() {
HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
playersLoadedIn = 0; g_finaleStage = Stage_Inactive;
finaleStage = Stage_Inactive;
L4D2_RunScript(HUD_SCRIPT_CLEAR); L4D2_RunScript(HUD_SCRIPT_CLEAR);
Director_OnMapStart(); Director_OnMapStart();
@ -1209,19 +1173,19 @@ public void OnConfigsExecuted() {
public void OnMapEnd() { public void OnMapEnd() {
for(int i = 0; i < ammoPacks.Length; i++) { // Reset the ammo packs, deleting the internal arraylist
ArrayList clients = ammoPacks.Get(i, AMMOPACK_USERS); for(int i = 0; i < g_ammoPacks.Length; i++) {
ArrayList clients = g_ammoPacks.Get(i, AMMOPACK_USERS);
delete clients; delete clients;
} }
g_ammoPacks.Clear();
// Reset cabinets:
for(int i = 0; i < sizeof(cabinets); i++) { for(int i = 0; i < sizeof(cabinets); i++) {
cabinets[i].id = 0; cabinets[i].id = 0;
for(int b = 0; b < CABINET_ITEM_BLOCKS; b++) { for(int b = 0; b < CABINET_ITEM_BLOCKS; b++) {
cabinets[i].items[b] = 0; cabinets[i].items[b] = 0;
} }
} }
ammoPacks.Clear();
playersLoadedIn = 0;
// abmExtraCount = 0;
delete updateHudTimer; delete updateHudTimer;
Director_OnMapEnd(); Director_OnMapEnd();
} }
@ -1236,8 +1200,8 @@ public Action Timer_Populate(Handle h) {
} }
public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, int client, float time) { public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, int client, float time) {
if(!isCheckpointReached && client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { if(!g_isCheckpointReached && client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) {
isCheckpointReached = true; g_isCheckpointReached = true;
abmExtraCount = GetSurvivorsCount(); abmExtraCount = GetSurvivorsCount();
if(abmExtraCount > 4) { if(abmExtraCount > 4) {
int extraPlayers = abmExtraCount - 4; int extraPlayers = abmExtraCount - 4;
@ -1250,33 +1214,33 @@ public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, i
//If hAddExtraKits TRUE: Append to previous, FALSE: Overwrite //If hAddExtraKits TRUE: Append to previous, FALSE: Overwrite
if(hAddExtraKits.BoolValue) if(hAddExtraKits.BoolValue)
extraKitsAmount += extraPlayers; g_extraKitsAmount += extraPlayers;
else else
extraKitsAmount = extraPlayers; g_extraKitsAmount = extraPlayers;
extraKitsStarted = extraKitsAmount; g_extraKitsStart = g_extraKitsAmount;
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = abmExtraCount;
PrintToConsoleAll("CHECKPOINT REACHED BY %N | EXTRA KITS: %d", client, extraPlayers); PrintToConsoleAll("CHECKPOINT REACHED BY %N | EXTRA KITS: %d", client, extraPlayers);
PrintToServer("Player entered saferoom. Providing %d extra kits", extraKitsAmount); PrintToServer("Player entered saferoom. Providing %d extra kits", g_extraKitsAmount);
} }
} }
} }
public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) {
if(!isFailureRound) isFailureRound = true; if(!g_isFailureRound) g_isFailureRound = true;
areItemsPopulated = false; g_areItemsPopulated = false;
return Plugin_Continue; return Plugin_Continue;
} }
public Action Event_MapTransition(Event event, const char[] name, bool dontBroadcast) { public Action Event_MapTransition(Event event, const char[] name, bool dontBroadcast) {
#if defined DEBUG #if defined DEBUG
PrintToServer("Map transition | %d Extra Kits", extraKitsAmount); PrintToServer("Map transition | %d Extra Kits", g_g_extraKitsAmount);
#endif #endif
isLateLoaded = false; isLateLoaded = false;
extraKitsStarted = extraKitsAmount; g_extraKitsStart = g_extraKitsAmount;
abmExtraCount = GetRealSurvivorsCount(); abmExtraCount = GetRealSurvivorsCount();
playerstoWaitFor = GetRealSurvivorsCount(); g_prevPlayerCount = GetRealSurvivorsCount();
return Plugin_Continue; return Plugin_Continue;
} }
//TODO: Possibly hacky logic of on third different ent id picked up, in short timespan, detect as set of 4 (pills, kits) & give extra //TODO: Possibly hacky logic of on third different ent id picked up, in short timespan, detect as set of 4 (pills, kits) & give extra
@ -1298,8 +1262,8 @@ public void OnEntityCreated(int entity, const char[] classname) {
}else if(StrEqual(classname, "prop_health_cabinet", true)) { }else if(StrEqual(classname, "prop_health_cabinet", true)) {
SDKHook(entity, SDKHook_SpawnPost, Hook_CabinetSpawn); SDKHook(entity, SDKHook_SpawnPost, Hook_CabinetSpawn);
}else if (StrEqual(classname, "upgrade_ammo_explosive") || StrEqual(classname, "upgrade_ammo_incendiary")) { }else if (StrEqual(classname, "upgrade_ammo_explosive") || StrEqual(classname, "upgrade_ammo_incendiary")) {
int index = ammoPacks.Push(entity); int index = g_ammoPacks.Push(entity);
ammoPacks.Set(index, new ArrayList(1), AMMOPACK_USERS); g_ammoPacks.Set(index, new ArrayList(1), AMMOPACK_USERS);
SDKHook(entity, SDKHook_Use, OnUpgradePackUse); SDKHook(entity, SDKHook_Use, OnUpgradePackUse);
} }
} }
@ -1347,10 +1311,10 @@ public Action OnUpgradePackUse(int entity, int activator, int caller, UseType ty
int primaryWeapon = GetPlayerWeaponSlot(activator, 0); int primaryWeapon = GetPlayerWeaponSlot(activator, 0);
if(IsValidEdict(primaryWeapon) && HasEntProp(primaryWeapon, Prop_Send, "m_upgradeBitVec")) { if(IsValidEdict(primaryWeapon) && HasEntProp(primaryWeapon, Prop_Send, "m_upgradeBitVec")) {
int index = ammoPacks.FindValue(entity, AMMOPACK_ENTID); int index = g_ammoPacks.FindValue(entity, AMMOPACK_ENTID);
if(index == -1) return Plugin_Continue; if(index == -1) return Plugin_Continue;
ArrayList clients = ammoPacks.Get(index, AMMOPACK_USERS); ArrayList clients = g_ammoPacks.Get(index, AMMOPACK_USERS);
if(clients.FindValue(activator) > -1) { if(clients.FindValue(activator) > -1) {
ClientCommand(activator, "play ui/menu_invalid.wav"); ClientCommand(activator, "play ui/menu_invalid.wav");
return Plugin_Handled; return Plugin_Handled;
@ -1386,7 +1350,7 @@ public Action OnUpgradePackUse(int entity, int activator, int caller, UseType ty
if(clients.Length >= GetSurvivorsCount()) { if(clients.Length >= GetSurvivorsCount()) {
AcceptEntityInput(entity, "kill"); AcceptEntityInput(entity, "kill");
delete clients; delete clients;
ammoPacks.Erase(index); g_ammoPacks.Erase(index);
} }
return Plugin_Handled; return Plugin_Handled;
} }
@ -1416,10 +1380,10 @@ public Action Hook_Use(int entity, int activator, int caller, UseType type, floa
public Action Timer_ResetAmmoPack(Handle h, int entity) { public Action Timer_ResetAmmoPack(Handle h, int entity) {
if(IsValidEntity(entity)) { if(IsValidEntity(entity)) {
int index = ammoPacks.FindValue(entity, AMMOPACK_ENTID); int index = g_ammoPacks.FindValue(entity, AMMOPACK_ENTID);
if(index == -1) return Plugin_Continue; if(index == -1) return Plugin_Continue;
ArrayList clients = ammoPacks.Get(index, AMMOPACK_USERS); ArrayList clients = g_ammoPacks.Get(index, AMMOPACK_USERS);
clients.Clear(); clients.Clear();
} }
return Plugin_Continue; return Plugin_Continue;
@ -1431,7 +1395,7 @@ public Action Timer_OpenSaferoomDoor(Handle h) {
} }
void UnlockDoor(int flag) { void UnlockDoor(int flag) {
int entity = EntRefToEntIndex(firstSaferoomDoorEntity); int entity = EntRefToEntIndex(g_saferoomDoorEnt);
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");
@ -1439,10 +1403,11 @@ void UnlockDoor(int flag) {
SDKUnhook(entity, SDKHook_Use, Hook_Use); SDKUnhook(entity, SDKHook_Use, Hook_Use);
if(hSaferoomDoorAutoOpen.IntValue & flag) { if(hSaferoomDoorAutoOpen.IntValue & flag) {
AcceptEntityInput(entity, "Open"); AcceptEntityInput(entity, "Open");
g_saferoomDoorEnt = INVALID_ENT_REFERENCE;
if(!g_areItemsPopulated)
PopulateItems();
} }
firstSaferoomDoorEntity = INVALID_ENT_REFERENCE;
if(!areItemsPopulated)
PopulateItems();
} }
} }
@ -1535,10 +1500,11 @@ Action Timer_UpdateHud(Handle h) {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
void PopulateItems() { void PopulateItems() {
int survivors = GetRealSurvivorsCount(); if(g_areItemsPopulated) return;
if(survivors <= 4) return; UpdateSurvivorCount();
if(g_realSurvivorCount <= 4) return;
areItemsPopulated = true; g_areItemsPopulated = true;
//Generic Logic //Generic Logic
float percentage = hExtraItemBasePercentage.FloatValue * survivors; float percentage = hExtraItemBasePercentage.FloatValue * survivors;
@ -1556,7 +1522,7 @@ void PopulateItems() {
&& HasEntProp(i, Prop_Data, "m_itemCount") && HasEntProp(i, Prop_Data, "m_itemCount")
) { ) {
int count = GetEntProp(i, Prop_Data, "m_itemCount"); int count = GetEntProp(i, Prop_Data, "m_itemCount");
if(count > 0 && GetRandomFloat() < percentage) { if(count > 0 && GetURandomFloat() < percentage) {
SetEntProp(i, Prop_Data, "m_itemCount", ++count); SetEntProp(i, Prop_Data, "m_itemCount", ++count);
++affected; ++affected;
} }
@ -1579,21 +1545,21 @@ void PopulateItems() {
} }
int spawnCount = GetEntProp(cabinets[i].id, Prop_Data, "m_pillCount"); int spawnCount = GetEntProp(cabinets[i].id, Prop_Data, "m_pillCount");
int extraAmount = RoundToCeil(float(abmExtraCount) * (float(spawnCount)/4.0) - spawnCount); int extraAmount = RoundToCeil(float(abmExtraCount) * (float(spawnCount)/4.0) - spawnCount);
bool hasASpawner; bool hasSpawner;
while(extraAmount > 0) { while(extraAmount > 0) {
//FIXME: spawner is sometimes invalid entity. Ref needed? //FIXME: spawner is sometimes invalid entity. Ref needed?
for(int block = 0; block < CABINET_ITEM_BLOCKS; block++) { for(int block = 0; block < CABINET_ITEM_BLOCKS; block++) {
spawner = cabinets[i].items[block]; spawner = cabinets[i].items[block];
if(spawner > 0) { if(spawner > 0) {
if(!HasEntProp(spawner, Prop_Data, "m_itemCount")) continue; if(!HasEntProp(spawner, Prop_Data, "m_itemCount")) continue;
hasASpawner = true; hasSpawner = true;
count = GetEntProp(spawner, Prop_Data, "m_itemCount") + 1; count = GetEntProp(spawner, Prop_Data, "m_itemCount") + 1;
SetEntProp(spawner, Prop_Data, "m_itemCount", count); SetEntProp(spawner, Prop_Data, "m_itemCount", count);
if(--extraAmount == 0) break; if(--extraAmount == 0) break;
} }
} }
//Incase cabinet is empty //Incase cabinet is empty
if(!hasASpawner) break; if(!hasSpawner) break;
} }
} }
} }
@ -1601,21 +1567,11 @@ void PopulateItems() {
///////////////////////////////////// /////////////////////////////////////
/// Stocks /// Stocks
//////////////////////////////////// ////////////////////////////////////
// enum struct PlayerData { bool IsGamemodeAllowed() {
// bool itemGiven; //Is player being given an item (such that the next pickup event is ignored) char buffer[64], curGamemode;
// bool isUnderAttack; //Is the player under attack (by any special) cvEPIGamemodes.GetString(buffer, sizeof(buffer));
// bool active; return StrContains(buffer, g_currentGamemode, false) > -1;
}
// WeaponId itemID[6]; //int -> char?
// bool lasers;
// char meleeID[32];
// int primaryHealth;
// int tempHealth;
// char model[32];
// }
void DropDroppedInventories() { void DropDroppedInventories() {
StringMapSnapshot snapshot = pInv.Snapshot(); StringMapSnapshot snapshot = pInv.Snapshot();
@ -1709,6 +1665,45 @@ bool DoesInventoryDiffer(int client) {
return currentPrimary != storedPrimary || currentSecondary != storedSecondary; return currentPrimary != storedPrimary || currentSecondary != storedSecondary;
} }
void UpdateSurvivorCount() {
return DEBUG_FORCE_PLAYERS;
#endif
int countTotal = 0, countReal = 0, countActive = 0;
#if !defined DEBUG_FORCE_PLAYERS
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
if(!IsFakeClient(i)) {
countReal++;
}
countTotal++;
if(pdata[i].state == State_Active) {
countActive++;
}
}
}
g_survivorCount = countTotal;
g_realSurvivorCount = countReal;
PrintDebug(DEBUG_GENERIC, "UpdateSurvivorCount: total=%d real=%d active=%d", countTotal, countReal, countActive);
#endif
#if defined DEBUG_FORCE_PLAYERS
g_survivorCount = DEBUG_FORCE_PLAYERS;
g_realSurvivorCount = DEBUG_FORCE_PLAYERS;
#endif
if(g_realSurvivorCount > 4) {
// Update friendly fire values to reduce accidental FF in crowded corridors
ConVar friendlyFireFactor = GetActiveFriendlyFireFactor();
// TODO: Get previous default
friendlyFireFactor.FloatValue = friendlyFireFactor.FloatValue - ((newCount - 4) * cvFFDecreaseRate.FloatValue);
if(friendlyFireFactor.FloatValue < 0.0) {
friendlyFireFactor.FloatValue = 0.01;
}
}
// TODO: update hMinPlayers
}
stock int FindFirstSurvivor() { stock int FindFirstSurvivor() {
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) {
@ -1727,7 +1722,7 @@ stock void GiveStartingKits() {
if(skipLeft > 0 || DoesClientHaveKit(i)) { if(skipLeft > 0 || DoesClientHaveKit(i)) {
--skipLeft; --skipLeft;
continue; continue;
}else{ } else {
int item = GivePlayerItem(i, "weapon_first_aid_kit"); int item = GivePlayerItem(i, "weapon_first_aid_kit");
EquipPlayerWeapon(i, item); EquipPlayerWeapon(i, item);
} }
@ -1748,16 +1743,6 @@ stock int GetSurvivorsCount() {
return count; return count;
} }
stock int GetActiveCount() {
int count;
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && playerData[i].state == State_Active) {
count++;
}
}
return count;
}
stock int GetRealSurvivorsCount() { stock int GetRealSurvivorsCount() {
#if defined DEBUG_FORCE_PLAYERS #if defined DEBUG_FORCE_PLAYERS
return DEBUG_FORCE_PLAYERS; return DEBUG_FORCE_PLAYERS;
@ -1784,21 +1769,19 @@ stock bool AreAllClientsReady() {
stock bool DoesClientHaveKit(int client) { stock bool DoesClientHaveKit(int client) {
char wpn[32]; char wpn[32];
if(IsClientConnected(client) && IsClientInGame(client) && GetClientWeaponName(client, 3, wpn, sizeof(wpn))) { if(IsClientConnected(client) && IsClientInGame(client) && GetClientWeaponName(client, 3, wpn, sizeof(wpn))) {
if(StrEqual(wpn, "weapon_first_aid_kit")) { return StrEqual(wpn, "weapon_first_aid_kit");
return true;
}
} }
return false; return false;
} }
stock bool UseExtraKit(int client) { stock bool UseExtraKit(int client) {
if(extraKitsAmount > 0) { if(g_extraKitsAmount > 0) {
playerData[client].itemGiven = true; playerData[client].itemGiven = true;
int ent = GivePlayerItem(client, "weapon_first_aid_kit"); int ent = GivePlayerItem(client, "weapon_first_aid_kit");
EquipPlayerWeapon(client, ent); EquipPlayerWeapon(client, ent);
playerData[client].itemGiven = false; playerData[client].itemGiven = false;
if(--extraKitsAmount <= 0) { if(--g_extraKitsAmount <= 0) {
extraKitsAmount = 0; g_extraKitsAmount = 0;
} }
return true; return true;
} }