This commit is contained in:
Jackzie 2024-02-15 09:02:46 -06:00
parent 5c37d46bcc
commit 344d2ccd69
2 changed files with 162 additions and 35 deletions

View file

@ -6,7 +6,7 @@
#define DIRECTOR_WITCH_MAX_WITCHES 5 // The maximum amount of extra witches to spawn
#define DIRECTOR_WITCH_ROLLS 3 // The number of dice rolls, increase if you want to increase freq
#define DIRECTOR_MIN_SPAWN_TIME 13.0 // Possibly randomized, per-special
#define DIRECTOR_SPAWN_CHANCE 0.04 // The raw chance of a spawn
#define DIRECTOR_SPAWN_CHANCE 0.038 // The raw chance of a spawn
#define DIRECTOR_CHANGE_LIMIT_CHANCE 0.05 // The chance that the maximum amount per-special is changed
#define DIRECTOR_SPECIAL_TANK_CHANCE 0.05 // The chance that specials can spawn when a tank is active
#define DIRECTOR_STRESS_CUTOFF 0.75 // The minimum chance a random cut off stress value is chosen [this, 1.0]

View file

@ -34,6 +34,7 @@
#define EXTRA_TANK_MIN_SEC 2.0
#define EXTRA_TANK_MAX_SEC 16.0
#define MAX_RANDOM_SPAWNS 12
#define DATE_FORMAT "%F at %I:%M %p"
@ -83,7 +84,7 @@ public Plugin myinfo =
url = "https://github.com/Jackzmc/sourcemod-plugins"
};
ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning, cvEPIGamemodes, hGamemode, cvEPITankHealth, cvEPIEnabledMode;
ConVar hExtraItemBasePercentage, hExtraSpawnBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning, cvEPIGamemodes, hGamemode, cvEPITankHealth, cvEPIEnabledMode;
ConVar g_ffFactorCvar, hExtraTankThreshold;
int g_extraKitsAmount, g_extraKitsStart, g_saferoomDoorEnt, g_prevPlayerCount;
bool g_forcedSurvivorCount;
@ -296,7 +297,8 @@ public void OnPluginStart() {
hExtraItemBasePercentage = CreateConVar("epi_item_chance", "0.056", "The base chance (multiplied by player count) of an extra item being spawned.", FCVAR_NONE, true, 0.0, true, 1.0);
hExtraItemBasePercentage = CreateConVar("epi_item_chance", "0.034", "The base chance (multiplied by player count) of an extra item being spawned.", FCVAR_NONE, true, 0.0, true, 1.0);
hExtraSpawnBasePercentage = CreateConVar("epi_spawn_chance", "0.01", "The base chance (multiplied by player count) of an extra item spawner being created.", FCVAR_NONE, true, 0.0, true, 1.0);
hAddExtraKits = CreateConVar("epi_kitmode", "0", "Decides how extra kits should be added.\n0 -> Overwrites previous extra kits\n1 -> Adds onto previous extra kits", FCVAR_NONE, true, 0.0, true, 1.0);
hUpdateMinPlayers = CreateConVar("epi_updateminplayers", "1", "Should the plugin update abm\'s cvar min_players convar to the player count?\n 0 -> NO\n1 -> YES", FCVAR_NONE, true, 0.0, true, 1.0);
hMinPlayersSaferoomDoor = CreateConVar("epi_doorunlock_percent", "0.75", "The percent of players that need to be loaded in before saferoom door is opened.\n 0 to disable", FCVAR_NONE, true, 0.0, true, 1.0);
@ -338,12 +340,13 @@ public void OnPluginStart() {
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) {
if(GetClientTeam(i) == 2) {
SaveInventory(i, true);
SaveInventory(i);
}
playerData[i].Setup(i);
SDKHook(i, SDKHook_WeaponEquip, Event_Pickup);
}
}
g_currentChapter = L4D_GetCurrentChapter();
TryStartHud();
}
@ -404,8 +407,9 @@ public void OnClientDisconnect(int client) {
playerData[client].state = State_Empty;
if(!IsFakeClient(client) && IsClientInGame(client) && GetClientTeam(client) == 2)
SaveInventory(client, true);
SaveInventory(client);
g_isSpeaking[client] = false;
g_saveTimer[client] = null;
}
public void OnPluginEnd() {
@ -599,6 +603,10 @@ Action Command_Trigger(int client, int args) {
g_areItemsPopulated = false;
PopulateItems();
ReplyToCommand(client, "Items populated.");
} else if(StrEqual(arg, "kits")) {
g_extraKitsAmount = 4;
IncreaseKits(L4D_IsMissionFinalMap());
ReplyToCommand(client, "Kits spawned. Finale: %b", L4D_IsMissionFinalMap());
} else if(StrEqual(arg, "addbot")) {
if(GetFeatureStatus(FeatureType_Native, "NextBotCreatePlayerBotSurvivorBot") != FeatureStatus_Available){
ReplyToCommand(client, "Unsupported.");
@ -625,7 +633,7 @@ Action Command_SaveInventory(int client, int args) {
ReplyToCommand(client, "No player found");
return Plugin_Handled;
}
SaveInventory(player, true);
SaveInventory(player);
ReplyToCommand(client, "Saved inventory for %N", player);
return Plugin_Handled;
}
@ -789,8 +797,8 @@ Action Command_DebugStats(int client, int args) {
/// EVENTS
////////////////////////////////////
void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float damage, int damagetype) {
if(GetClientTeam(victim) == 2 && !IsFakeClient(victim))
SaveInventory(victim);
if(victim <= MaxClients && attacker <= MaxClients && attacker > 0 && GetClientTeam(victim) == 2 && !IsFakeClient(victim))
QueueSaveInventory(attacker);
}
void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) {
int bot = GetClientOfUserId(event.GetInt("bot"));
@ -987,7 +995,7 @@ void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0 && GetClientTeam(client) == 2 && !IsFakeClient(client)) {
UpdatePlayerInventory(client);
SaveInventory(client);
QueueSaveInventory(client);
}
}
@ -1191,38 +1199,39 @@ void IncreaseKits(bool inFinale) {
int count = 0;
while(g_extraKitsAmount > 0) {
GetEntPropVector(entity, Prop_Data, "m_vecOrigin", pos);
bool result = false;
bool isValidKit = false;
if(inFinale) {
// Finale
Address address = L4D_GetNearestNavArea(pos);
if(address != Address_Null) {
int attributes = L4D_GetNavArea_SpawnAttributes(address);
if(attributes & NAV_SPAWN_FINALE) {
result = true;
isValidKit = true;
}
}
} else {
// Checkpoint
result = L4D_IsPositionInLastCheckpoint(pos);
isValidKit = L4D_IsPositionInLastCheckpoint(pos);
}
if(result) {
if(isValidKit) {
count++;
// Give it a little chance to nudge itself
pos[0] += GetRandomFloat(-8.0, 8.0);
pos[1] += GetRandomFloat(-8.0, 8.0);
pos[2] += 0.4;
pos[2] += 0.8;
SpawnItem("first_aid_kit", pos);
g_extraKitsAmount--;
}
entity = FindEntityByClassname(entity, "weapon_first_aid_kit_spawn");
// Loop around
if(entity == INVALID_ENT_REFERENCE) {
entity = -1;
// If we did not find any suitable kits, stop here.
if(count == 0) {
PrintToServer("[EPI] Warn: No valid kit spawns (weapon_first_aid_kit_spawn) found (inFinale=%b)", inFinale);
break;
}
entity = FindEntityByClassname(-1, "weapon_first_aid_kit_spawn");
}
}
}
@ -1291,7 +1300,6 @@ public void OnMapStart() {
}
if(L4D_IsMissionFinalMap()) {
IncreaseKits(true);
// Disable tank split on hard rain finale
g_extraFinaleTankEnabled = true;
if(StrEqual(map, "c4m5_milltown_escape")) {
@ -1336,6 +1344,120 @@ public void OnMapStart() {
}
}
enum WallCheck {
Wall_North,
Wall_East,
Wall_South,
Wall_West
}
/// TODO: confirm this is correct
float WALL_ANG[4][3] = {
{0.0,0.0,0.0},
{0.0,90.0,0.0},
{0.0,180.0,0.0},
{0.0,270.0,0.0},
};
float WALL_CHECK_SIZE_MIN[3] = { -20.0, -5.0, -10.0 };
float WALL_CHECK_SIZE_MAX[3] = { 20.0, 5.0, 10.0 };
bool IsWallNearby(const float pos[3], WallCheck wall, float maxDistance = 80.0) {
float endPos[3];
GetHorizontalPositionFromOrigin(pos, WALL_ANG[wall], maxDistance, endPos);
TR_TraceHull(pos, endPos, WALL_CHECK_SIZE_MIN, WALL_CHECK_SIZE_MAX, MASK_SOLID_BRUSHONLY);
return TR_DidHit();
}
void PopulateItemSpawns() {
ArrayList navs = new ArrayList();
L4D_GetAllNavAreas(navs);
navs.Sort(Sort_Random, Sort_Integer);
float pos[3];
float percentage = hExtraSpawnBasePercentage.FloatValue * (g_survivorCount - 4);
PrintToServer("[EPI] Populating extra item spawns based on player count (%d-4) | Percentage %.2f%%", g_survivorCount, percentage * 100);
int tier;
// On first chapter, 10% chance to give tier 2
if(g_currentChapter == 1) tier = GetRandomFloat() < 0.15 ? 1 : 0;
else tier = DiceRoll(0, 3, 2, BIAS_LEFT);
int count;
for(int i = 0; i < navs.Length; i++) {
Address nav = navs.Get(i);
int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav);
int baseFlags = L4D_GetNavArea_AttributeFlags(nav);
if(!(baseFlags & (NAV_BASE_FLOW_BLOCKED)) &&
!(spawnFlags & NAV_SPAWN_ESCAPE_ROUTE|NAV_SPAWN_DESTROYED_DOOR|NAV_SPAWN_CHECKPOINT|NAV_SPAWN_NO_MOBS|NAV_SPAWN_STOP_SCAN))
{
L4D_FindRandomSpot(view_as<int>(nav), pos);
bool north = IsWallNearby(pos, Wall_North);
bool east = IsWallNearby(pos, Wall_East);
bool south = IsWallNearby(pos, Wall_South);
bool west = IsWallNearby(pos, Wall_West);
// TODO: collision check (windows like c1m1)
int wallCount = 0;
if(north) wallCount++;
if(east) wallCount++;
if(south) wallCount++;
if(west) wallCount++;
if(wallCount >= 2) {
if(GetURandomFloat() < percentage) {
int wpn;
pos[2] += 7.0;
if(GetURandomFloat() > 0.30) {
wpn = CreateWeaponSpawn(pos, "", tier);
} else {
wpn = CreateRandomMeleeSpawn(pos);
}
if(wpn == -1) continue;
if(++count > MAX_RANDOM_SPAWNS) break;
}
}
}
}
PrintToServer("[EPI] Spawned %d/%d new item spawns (tier=%d)", count, MAX_RANDOM_SPAWNS, tier);
delete navs;
}
char WEAPON_SPAWN_CLASSNAMES[32][] = {
"weapon_pistol_magnum_spawn","weapon_smg_spawn","weapon_smg_silenced_spawn","weapon_pumpshotgun_spawn","weapon_shotgun_chrome_spawn","weapon_pipe_bomb_spawn","weapon_upgradepack_incendiary_spawn","weapon_upgradepack_explosive_spawn","weapon_adrenaline_spawn","weapon_smg_mp5_spawn","weapon_defibrillator_spawn","weapon_propanetank_spawn","weapon_oxygentank_spawn","weapon_chainsaw_spawn","weapon_gascan_spawn","weapon_ammo_spawn","weapon_sniper_scout_spawn","weapon_hunting_rifle_spawn","weapon_pain_pills_spawn","weapon_rifle_spawn","weapon_rifle_desert_spawn","weapon_sniper_military_spawn","weapon_autoshotgun_spawn","weapon_shotgun_spas_spawn","weapon_first_aid_kit_spawn","weapon_molotov_spawn","weapon_vomitjar_spawn","weapon_rifle_ak47_spawn","weapon_rifle_sg552_spawn","weapon_grenade_launcher_spawn","weapon_sniper_awp_spawn","weapon_rifle_m60_spawn"
};
int TIER_MAXES[] = { 10, 18, 28, 31 };
/**
* Creates a weapon_*_spawn at position, with a random orientation. If classname not provided, it will be randomly selected
* @param classname the full classname of weapon to spawn
* @return returns -1 on error, or entity index
*/
int CreateWeaponSpawn(const float pos[3], const char[] classname = "", int tier = 0) {
int entity;
if(classname[0] == '\0') {
int index = GetRandomInt(0, TIER_MAXES[tier]);
entity = CreateEntityByName(WEAPON_SPAWN_CLASSNAMES[index]);
} else {
entity = CreateEntityByName(classname);
}
if(entity == -1) return -1;
DispatchKeyValueInt(entity, "spawn_without_director", 0);
DispatchKeyValueInt(entity, "spawnflags", 1);
DispatchKeyValueInt(entity, "count", 1);
float ang[3];
ang[1] = GetRandomFloat(0.0, 360.0);
TeleportEntity(entity, pos, ang, NULL_VECTOR);
if(!DispatchSpawn(entity)) return -1;
return entity;
}
int CreateRandomMeleeSpawn(const float pos[3], const char[] choices = "any") {
int entity = CreateEntityByName("weapon_melee_spawn");
if(entity == -1) return -1;
DispatchKeyValue(entity, "melee_weapon", choices);
DispatchKeyValueInt(entity, "spawnflags", 1);
float ang[3];
ang[1] = GetRandomFloat(0.0, 360.0);
TeleportEntity(entity, pos, ang, NULL_VECTOR);
if(!DispatchSpawn(entity)) return -1;
return entity;
}
public void OnConfigsExecuted() {
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = g_realSurvivorCount;
@ -1654,6 +1776,10 @@ void PopulateItems() {
g_areItemsPopulated = true;
if(L4D_IsMissionFinalMap(true)) {
IncreaseKits(true);
}
//Generic Logic
float percentage = hExtraItemBasePercentage.FloatValue * (g_survivorCount - 4);
PrintToServer("[EPI] Populating extra items based on player count (%d-4) | Percentage %.2f%%", g_survivorCount, percentage * 100);
@ -1665,12 +1791,15 @@ void PopulateItems() {
if(IsValidEntity(i)) {
GetEntityClassname(i, classname, sizeof(classname));
if(StrContains(classname, "_spawn", true) > -1
&& StrContains(classname, "zombie", true) == -1
&& StrContains(classname, "zombie", true) == -1 // not zombie or scavenge
&& StrContains(classname, "scavenge", true) == -1
&& HasEntProp(i, Prop_Data, "m_itemCount")
) {
int count = GetEntProp(i, Prop_Data, "m_itemCount");
if(count > 0 && GetURandomFloat() < percentage) {
if(count == 4) {
// Some item spawns are only for 4 players, so here we set to # of players:
SetEntProp(i, Prop_Data, "m_itemCount", g_survivorCount);
} else if(count > 0 && GetURandomFloat() < percentage) {
SetEntProp(i, Prop_Data, "m_itemCount", ++count);
++affected;
}
@ -1679,6 +1808,7 @@ void PopulateItems() {
}
PrintDebug(DEBUG_SPAWNLOGIC, "Incremented counts for %d items", affected);
PopulateItemSpawns();
PopulateCabinets();
}
@ -1771,28 +1901,25 @@ void UpdatePlayerInventory(int client) {
}
}
Action Timer_SaveInventory(Handle h, int userid) {
int client = GetClientOfUserId(userid);
if(client > 0) {
Action Timer_SaveInventory(Handle h, int client) {
if(IsValidClient(client)) {
// Force save to bypass our timeout
SaveInventory(client, true);
SaveInventory(client);
}
g_saveTimer[client] = null;
return Plugin_Stop;
}
void SaveInventory(int client, bool force = false) {
void QueueSaveInventory(int client) {
int time = GetTime();
if(!force) {
if(time - playerData[client].joinTime < MIN_JOIN_TIME) return;
// Queue their inventory to be saved after a timeout.
// Any time a save happens between prev save and timeout will delay the timeout.
// This should ensure that the saved inventory is most of the time up-to-date
if(g_saveTimer[client] != null)
delete g_saveTimer[client];
g_saveTimer[client] = CreateTimer(INV_SAVE_TIME, Timer_SaveInventory, GetClientUserId(client));
} else {
g_saveTimer[client] = null;
if(time - playerData[client].joinTime < MIN_JOIN_TIME) return;
if(g_saveTimer[client] != null) {
delete g_saveTimer[client];
}
g_saveTimer[client] = CreateTimer(INV_SAVE_TIME, Timer_SaveInventory, client);
}
void SaveInventory(int client) {
if(!IsClientInGame(client) || GetClientTeam(client) != 2) return;
int time = GetTime();
PlayerInventory inventory;
inventory.timestamp = time;
inventory.isAlive = IsPlayerAlive(client);