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