Merge branch 'master' of github.com:Jackzmc/sourcemod-plugins

This commit is contained in:
Jackz 2023-09-30 21:09:29 -05:00
commit 9fbb89e162
No known key found for this signature in database
GPG key ID: E0BBD94CF657F603
3 changed files with 251 additions and 118 deletions

Binary file not shown.

240
scripting/epi/director.sp Normal file
View file

@ -0,0 +1,240 @@
// SETTINGS
#define DIRECTOR_WITCH_MIN_TIME 120 // The minimum amount of time to pass since last witch spawn for the next extra witch to spawn
#define DIRECTOR_WITCH_CHECK_TIME 30.0 // How often to check if a witch should be spawned
#define DIRECTOR_WITCH_MAX_WITCHES 6 // The maximum amount of extra witches to spawn
#define DIRECTOR_WITCH_ROLLS 2 // The number of dice rolls, increase if you want to increase freq
#define DIRECTOR_MIN_SPAWN_TIME 20.0 // Possibly randomized, per-special
#define DIRECTOR_SPAWN_CHANCE 30.0 // The raw chance of a spawn
#define DIRECTOR_CHANGE_LIMIT_CHANCE 0.10 // The chance that the maximum amount per-special is changed
#define DIRECTOR_SPECIAL_TANK_CHANCE 0.05 // The chance that specials can spawn when a tank is active
#define DIRECTOR_STRESS_CUTOFF 0.60 // The minimum chance a random cut off stress value is chosen [this, 1.0]
/// DEFINITIONS
#define NUM_SPECIALS 6
#define TOTAL_NUM_SPECIALS 8
char SPECIAL_IDS[TOTAL_NUM_SPECIALS][] = {
"smoker",
"boomer",
"hunter",
"spitter",
"jockey",
"charger",
"witch",
"tank"
};
enum specialType {
Special_Smoker,
Special_Boomer,
Special_Hunter,
Special_Spitter,
Special_Jockey,
Special_Charger,
Special_Witch,
Special_Tank,
};
static float highestFlowAchieved;
static float g_lastSpawnTime[TOTAL_NUM_SPECIALS];
static int g_spawnLimit[TOTAL_NUM_SPECIALS];
static int g_spawnCount[TOTAL_NUM_SPECIALS];
static float g_minFlowSpawn; // The minimum flow for specials to start spawning (waiting for players to leave saferom)
static float g_minStressIntensity; // The minimum stress that specials arent allowed to spawn
static int extraWitchCount;
static Handle witchSpawnTimer = null;
float g_extraWitchFlowPositions[DIRECTOR_WITCH_MAX_WITCHES] = {};
/// EVENTS
void Director_OnMapStart() {
if(cvEPISpecialSpawning.BoolValue && abmExtraCount > 4) {
InitExtraWitches();
}
float time = GetGameTime();
for(int i = 0; i < TOTAL_NUM_SPECIALS; i++) {
g_lastSpawnTime[i] = time;
g_spawnLimit[i] = 1;
g_spawnCount[i] = 0;
}
}
void Director_OnMapEnd() {
for(int i = 0; i <= DIRECTOR_WITCH_MAX_WITCHES; i++) {
g_extraWitchFlowPositions[i] = 0.0;
}
delete witchSpawnTimer;
}
void Cvar_SpecialSpawningChange(ConVar convar, const char[] oldValue, const char[] newValue) {
if(convar.IntValue & 2 && abmExtraCount > 4) {
if(witchSpawnTimer == null)
witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT);
} else {
delete witchSpawnTimer;
}
}
void Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) {
g_spawnCount[Special_Witch]++;
}
void Director_OnClientPutInServer(int client) {
if(client > 0 && GetClientTeam(client) == 3) {
int class = GetEntProp(client, Prop_Send, "m_zombieClass");
// Ignore a hacky temp bot spawn
// To bypass director limits many plugins spawn an infected "bot" that immediately gets kicked, which allows a window to spawn a special
static char buf[32];
GetClientName(special, buf, sizeof(buf));
if(StrContains(buf, "bot", false) == -1) {
g_spawnCount[class]++;
}
}
}
void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0 && GetClientTeam(client) == 3) {
int class = GetEntProp(client, Prop_Send, "m_zombieClass");
g_spawnCount[class]--;
}
}
/// METHODS
void InitExtraWitches() {
float flowMax = L4D2Direct_GetMapMaxFlowDistance() - FLOW_CUTOFF;
// Just in case we don't have max flow or the map is extremely tiny, don't run:
if(flowMax > 0.0) {
int count = abmExtraCount;
if(count < 4) count = 4;
// Calculate the number of witches we want to spawn.
// We bias the dice roll to the right. We slowly increase min based on player count to shift distribution to the right
int min = RoundToFloor(float(count - 4) / 4.0);
extraWitchCount = DiceRoll(min, DIRECTOR_WITCH_MAX_WITCHES, DIRECTOR_WITCH_ROLLS, BIAS_LEFT);
PrintDebug(DEBUG_SPAWNLOGIC, "InitExtraWitches: %d witches (min=%d, max=%d, rolls=%d) checkInterval=%f", extraWitchCount, min, DIRECTOR_WITCH_MAX_WITCHES, DIRECTOR_WITCH_ROLLS, DIRECTOR_WITCH_CHECK_TIME);
for(int i = 0; i <= extraWitchCount; i++) {
g_extraWitchFlowPositions[i] = GetURandomFloat() * (flowMax-FLOW_CUTOFF) + FLOW_CUTOFF;
PrintDebug(DEBUG_SPAWNLOGIC, "Witch position #%d: flow %.2f (%.0f%%)", i, g_extraWitchFlowPositions[i], g_extraWitchFlowPositions[i] / flowMax);
}
witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT);
}
}
void Director_PrintDebug(int client) {
PrintToConsole(client, "===Extra Witches===");
PrintToConsole(client, "Map Bounds: [%f, %f]", FLOW_CUTOFF, L4D2Direct_GetMapMaxFlowDistance() - (FLOW_CUTOFF*2.0));
PrintToConsole(client, "Total Witches Spawned: %d | Target: %d", g_spawnCount[Special_Witch], extraWitchCount);
for(int i = 0; i < extraWitchCount && i < DIRECTOR_WITCH_MAX_WITCHES; i++) {
PrintToConsole(client, "%d. %f", i, g_extraWitchFlowPositions[i]);
}
}
void Director_RandomizeLimits() {
// We add +1 to spice it up
int max = RoundToCeil(float(abmExtraCount - 4) / 4) + 1;
for(int i = 0; i < NUM_SPECIALS; i++) {
specialType special = view_as<specialType>(i);
g_spawnLimit[i] = GetRandomInt(0, max);
}
}
void Director_RandomizeThings() {
g_minStressIntensity = GetRandomFloat(DIRECTOR_STRESS_CUTOFF, 1.0);
g_minFlowSpawn = GetRandomFloat(FLOW_CUTOFF, FLOW_CUTOFF * 2);
}
/// TIMERS
Action Timer_Director(Handle h) {
if(abmExtraCount <= 4) return Plugin_Continue;
float time = GetGameTime();
// Calculate the new highest flow
int highestPlayer = L4D_GetHighestFlowSurvivor();
float flow = L4D2Direct_GetFlowDistance(highestPlayer);
if(flow > highestFlowAchieved) {
highestFlowAchieved = flow;
}
// Only start spawning once they get to g_minFlowSpawn - a little past the start saferoom
if(highestFlowAchieved < g_minFlowSpawn) return Plugin_Continue;
float curAvgStress = L4D_GetAvgSurvivorIntensity();
// Don't spawn specials when tanks active, but have a small chance (DIRECTOR_SPECIAL_TANK_CHANCE) to bypass
if(L4D2_IsTankInPlay() && GetURandomFloat() > DIRECTOR_SPECIAL_TANK_CHANCE) {
return Plugin_Continue;
} else {
// Stop spawning when players are stressed from a random value chosen by [DIRECTOR_STRESS_CUTOFF, 1.0]
if(curAvgStress >= g_minStressIntensity) return Plugin_Continue;
}
// TODO: Scale spawning chance based on intensity? 0.0 = more likely, < g_minStressIntensity = less likely
// Scale the chance where stress = 0.0, the chance is 50% more, and stress = 1.0, the chance is 50% less
float spawnChance = DIRECTOR_SPAWN_CHANCE + (0.5 - curAvgStress) / 10
for(int i = 0; i < NUM_SPECIALS; i++) {
specialType special = view_as<specialType>(i);
// Skip if we hit our limit, or too soon:
if(g_spawnCount[i] >= g_spawnLimit[i]) continue;
if(time - g_lastSpawnTime[i] < DIRECTOR_MIN_SPAWN_TIME) continue;
if(GetURandomFloat() < spawnChance) {
DirectorSpawn(special);
}
}
if(GetURandomFloat() < DIRECTOR_CHANGE_LIMIT_CHANCE) {
Director_RandomizeLimits();
}
return Plugin_Continue;
}
Action Timer_DirectorWitch(Handle h) {
if(g_spawnCount[Special_Witch] < extraWitchCount) { //&& time - g_lastSpawnTimes.witch > DIRECTOR_WITCH_MIN_TIME
for(int i = 0; i <= extraWitchCount; i++) {
if(g_extraWitchFlowPositions[i] > 0.0 && highestFlowAchieved >= g_extraWitchFlowPositions[i]) {
// Reset the flow so we don't spawn another
g_extraWitchFlowPositions[i] = 0.0;
DirectorSpawn(Special_Witch);
break;
}
}
}
return Plugin_Continue;
}
// UTIL functions
void DirectorSpawn(specialType special) {
PrintChatToAdmins("EPI: DirectorSpawn(%s) (dont worry about it)", SPECIAL_IDS[view_as<int>(special)]);
int player = GetSuitableVictim();
PrintDebug(DEBUG_SPAWNLOGIC, "Director: spawning %s from %N (cnt=%d,lim=%d)", SPECIAL_IDS[view_as<int>(special)], player, g_spawnCount[view_as<int>(special)], g_spawnLimit[view_as<int>(special)]);
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");
g_lastSpawnTime[view_as<int>(special)] = GetGameTime();
}
// TODO: make
void DirectSpawn(specialType special, const float pos[3]) {
}
// Finds a player that is suitable (lowest intensity)
int GetSuitableVictim() {
int victim = -1;
float lowestIntensity = 0.0;
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) {
float intensity = L4D_GetPlayerIntensity(i);
// TODO: possibly add perm health into calculations
if(intensity < lowestIntensity || victim == -1) {
lowestIntensity = intensity;
victim = i;
}
}
}
return victim;
}

View file

@ -28,10 +28,7 @@
#define EXTRA_PLAYER_HUD_UPDATE_INTERVAL 0.8
//Sets abmExtraCount to this value if set
// #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
@ -82,11 +79,10 @@ public Plugin myinfo =
url = "https://github.com/Jackzmc/sourcemod-plugins"
};
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;
ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning;
int extraKitsAmount, extraKitsStarted, abmExtraCount, firstSaferoomDoorEntity, playersLoadedIn, playerstoWaitFor;
static int currentChapter;
static bool isCheckpointReached, isLateLoaded, firstGiven, isFailureRound, areItemsPopulated;
static float highestFlowAchieved;
static ArrayList ammoPacks;
static Handle updateHudTimer;
static bool showHudPingMode;
@ -94,11 +90,6 @@ static int hudModeTicks;
static char gamemode[32];
static int witchSpawnCount, witchLastSpawnTime, extraWitchCount;
float ExtraWitchFlowPositions[DIRECTOR_WITCH_MAX_WITCHES] = {};
static Handle witchSpawnTimer = null;
bool isCoop;
enum Difficulty {
@ -236,7 +227,9 @@ enum struct Cabinet {
}
static Cabinet cabinets[10]; //Store 10 cabinets
//// Definitions complete
//// Definitions completSe
#include <epi/director.sp>
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
if(late) isLateLoaded = true;
@ -270,6 +263,7 @@ public void OnPluginStart() {
//Special Event Tracking
HookEvent("player_info", Event_PlayerInfo);
HookEvent("player_disconnect", Event_PlayerDisconnect);
HookEvent("player_death", Event_PlayerDeath);
HookEvent("charger_carry_start", Event_ChargerCarry);
HookEvent("charger_carry_end", Event_ChargerCarry);
@ -369,32 +363,7 @@ public void OnPluginStart() {
}
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++) {
@ -406,6 +375,7 @@ Action Timer_ForceUpdateInventories(Handle h) {
}
public void OnClientPutInServer(int client) {
Director_OnClientPutInServer(client);
if(!IsFakeClient(client)) {
playerData[client].Setup(client);
@ -494,14 +464,6 @@ void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char[] newV
}
}
}
void Cvar_SpecialSpawningChange(ConVar convar, const char[] oldValue, const char[] newValue) {
if(convar.IntValue & 2 && abmExtraCount > 4) {
if(witchSpawnTimer == null)
witchSpawnTimer = CreateTimer(DIRECTOR_WITCH_CHECK_TIME, Timer_DirectorWitch, _, TIMER_REPEAT);
} else {
delete witchSpawnTimer;
}
}
public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[] newValue) {
cvar.GetString(gamemode, sizeof(gamemode));
@ -660,12 +622,7 @@ Action Command_RunExtraItems(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]);
}
Director_PrintDebug(client);
return Plugin_Handled;
}
Action Command_DebugStats(int client, int args) {
@ -819,10 +776,6 @@ public void OnGetWeaponsInfo(int pThis, const char[] classname) {
if(maxClipSize > 0)
weaponMaxClipSizes.SetValue(classname, maxClipSize);
}
void Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) {
witchSpawnCount++;
witchLastSpawnTime = GetTime();
}
///////////////////////////////////////////////////////
//// PLAYER STATE MANAGEMENT
@ -1168,8 +1121,6 @@ void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) {
public void OnMapStart() {
PrintDebug(DEBUG_GENERIC, "OnMapStart");
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(isFailureRound) {
extraKitsAmount = extraKitsStarted;
@ -1235,28 +1186,7 @@ public void OnMapStart() {
finaleStage = Stage_Inactive;
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);
}
Director_OnMapStart();
}
/*
@ -1288,15 +1218,11 @@ public void OnMapEnd() {
cabinets[i].items[b] = 0;
}
}
for(int i = 0; i <= DIRECTOR_WITCH_MAX_WITCHES; i++) {
ExtraWitchFlowPositions[i] = 0.0;
}
ammoPacks.Clear();
playersLoadedIn = 0;
highestFlowAchieved = 0.0;
// abmExtraCount = 0;
delete updateHudTimer;
delete witchSpawnTimer;
Director_OnMapEnd();
}
public void Event_RoundFreezeEnd(Event event, const char[] name, bool dontBroadcast) {
@ -2074,40 +2000,7 @@ stock float GetSurvivorFlowDifference() {
client = GetLowestFlowSurvivor();
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;