Merge remote-tracking branch 'origin' into origin/rewrite

This commit is contained in:
Jackz 2023-10-09 21:25:48 -05:00
commit 0c6cdabe4d
No known key found for this signature in database
GPG key ID: E0BBD94CF657F603
21 changed files with 842 additions and 489 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -36,7 +36,7 @@ int g_BeamSprite;
int g_HaloSprite; int g_HaloSprite;
int g_iLaserIndex; int g_iLaserIndex;
#define MAX_FORBIDDEN_CLASSNAMES 7 #define MAX_FORBIDDEN_CLASSNAMES 9
static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
// "env_physics_blocker", // "env_physics_blocker",
// "env_player_blocker", // "env_player_blocker",
@ -45,7 +45,8 @@ static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
"func_button", "func_button",
"func_elevator", "func_elevator",
"func_button_timed", "func_button_timed",
// "func_movelinear", "func_movelinear",
"func_tracktrain",
// "infected", // "infected",
"func_lod", "func_lod",
"prop_ragdoll" "prop_ragdoll"
@ -149,6 +150,9 @@ public Action Cmd_Grab(client, args) {
if(parent > 0) { if(parent > 0) {
ent = parent; ent = parent;
} }
if(!CheckBlacklist(ent)) {
return Plugin_Handled;
}
float entOrigin[3], playerGrabOrigin[3]; float entOrigin[3], playerGrabOrigin[3];
GetEntPropVector(ent, Prop_Send, "m_vecOrigin", entOrigin); GetEntPropVector(ent, Prop_Send, "m_vecOrigin", entOrigin);
@ -569,6 +573,7 @@ bool Filter_IgnoreForbidden(int entity, int mask, int data) {
bool CheckBlacklist(int entity) { bool CheckBlacklist(int entity) {
static char buffer[64]; static char buffer[64];
GetEntityClassname(entity, buffer, sizeof(buffer)); GetEntityClassname(entity, buffer, sizeof(buffer));
PrintToServer("GrabEnt:CheckBlacklist | classname=\"%s\"", buffer);
for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) {
if(StrEqual(FORBIDDEN_CLASSNAMES[i], buffer)) { if(StrEqual(FORBIDDEN_CLASSNAMES[i], buffer)) {
return false; return false;
@ -577,6 +582,7 @@ bool CheckBlacklist(int entity) {
if(StrContains(buffer, "prop_") > -1) { if(StrContains(buffer, "prop_") > -1) {
GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { 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)) { if(StrEqual(FORBIDDEN_MODELS[i], buffer)) {
return false; return false;
} }

View file

@ -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;
}

View 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+1][] = {
"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 && IsEPIActive()) {
InitExtraWitches();
}
float time = GetGameTime();
for(int i = 1; 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 && IsEPIActive()) {
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 == view_as<int>(Special_Tank) && IsEPIActive() && 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");
float additionalHealth = float(g_survivorCount - 4) * cvEPITankHealth.FloatValue;
health += RoundFloat(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 = g_survivorCount;
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 = 1; 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, g_survivorCount - 4);
for(int i = 1; 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(g_survivorCount - 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(!IsEPIActive()) return;
for(int i = 1; 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(!IsEPIActive()) 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(g_survivorCount - 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;
}

View file

@ -4,7 +4,7 @@
//Allow MAX_TROLLS to be defined elsewhere //Allow MAX_TROLLS to be defined elsewhere
#if defined MAX_TROLLS #if defined MAX_TROLLS
#else #else
#define MAX_TROLLS 54 #define MAX_TROLLS 56
#endif #endif
enum trollModifier { enum trollModifier {
@ -14,25 +14,6 @@ enum trollModifier {
TrollMod_PlayerOnly = 1 << 2, // Does the troll only work on players, not bots? If set, troll only applied on real user. If not, troll applied to both bot and idler TrollMod_PlayerOnly = 1 << 2, // Does the troll only work on players, not bots? If set, troll only applied on real user. If not, troll applied to both bot and idler
} }
//up to 30 flags technically possiible
enum trollFlag {
Flag_1 = 1 << 0,
Flag_2 = 1 << 1,
Flag_3 = 1 << 2,
Flag_4 = 1 << 3,
Flag_5 = 1 << 4,
Flag_6 = 1 << 5,
Flag_7 = 1 << 6,
Flag_8 = 1 << 7,
}
enum valueType {
Value_None,
Value_Float,
Value_String,
Value_Integer
}
StringMap trollKV; StringMap trollKV;
char trollIds[MAX_TROLLS+1][MAX_TROLL_NAME_LENGTH]; char trollIds[MAX_TROLLS+1][MAX_TROLL_NAME_LENGTH];
char DEFAULT_FLAG_PROMPT_MULTIPLE[] = "Enable options (Multiple)"; char DEFAULT_FLAG_PROMPT_MULTIPLE[] = "Enable options (Multiple)";
@ -197,19 +178,6 @@ enum struct Troll {
return this.flagNames != null && this.flagNames.Length > 0 && this.flagPrompts.Length > 0; return this.flagNames != null && this.flagNames.Length > 0 && this.flagPrompts.Length > 0;
} }
bool IsFlagActive(int client, trollFlag flag) {
return this.activeFlagClients[client] & view_as<int>(flag) != 0;
}
bool IsFlagNameActive(int client, const char[] flagName) {
static char buffer[MAX_TROLL_FLAG_LENGTH];
for(int i = 0; i < this.flagNames.Length; i++) {
this.flagNames.GetString(i, buffer, sizeof(buffer));
if(StrEqual(buffer, flagName, false)) return this.IsFlagActive(client, view_as<trollFlag>(i));
}
return false;
}
int GetFlagCount() { int GetFlagCount() {
return this.flagNames != null ? this.flagNames.Length : 0; return this.flagNames != null ? this.flagNames.Length : 0;
} }
@ -291,6 +259,7 @@ void ResetClient(int victim, bool wipe = true) {
BaseComm_SetClientMute(victim, false); BaseComm_SetClientMute(victim, false);
SetEntityGravity(victim, 1.0); SetEntityGravity(victim, 1.0);
SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0);
SetEntProp(victim, Prop_Send, "m_iHideHUD", 0)
SDKUnhook(victim, SDKHook_WeaponCanUse, Event_ItemPickup); SDKUnhook(victim, SDKHook_WeaponCanUse, Event_ItemPickup);
int wpn = GetClientWeaponEntIndex(victim, 0); int wpn = GetClientWeaponEntIndex(victim, 0);
if(wpn > -1) if(wpn > -1)
@ -372,9 +341,8 @@ void ApplyTroll(int victim, const char[] name, int activator, trollModifier modi
// Clear troll specific timer: // Clear troll specific timer:
if(Trolls[trollIndex].timerInterval > 0.0) { if(Trolls[trollIndex].timerInterval > 0.0) {
PrintToServer("epi: there is timer for \"%s\", flags=%d", name, flags);
if(!isActive) { if(!isActive) {
if(Trolls[trollIndex].timerRequiredFlags == 0 || Trolls[trollIndex].timerRequiredFlags & flags) { if(modifier & TrollMod_Constant && (Trolls[trollIndex].timerRequiredFlags == 0 || Trolls[trollIndex].timerRequiredFlags & flags)) {
Trolls[trollIndex].timerHandles[victim] = CreateTimer(Trolls[trollIndex].timerInterval, Trolls[trollIndex].timerFunction, victim, TIMER_REPEAT); Trolls[trollIndex].timerHandles[victim] = CreateTimer(Trolls[trollIndex].timerInterval, Trolls[trollIndex].timerFunction, victim, TIMER_REPEAT);
} }
} else if(Trolls[trollIndex].timerHandles[victim] != null) { } else if(Trolls[trollIndex].timerHandles[victim] != null) {
@ -392,7 +360,7 @@ void ApplyTroll(int victim, const char[] name, int activator, trollModifier modi
float max = 1.0; float max = 1.0;
if(Trolls[MetaInverseTrollID].activeFlagClients[activator] & 2) max = 0.5; if(Trolls[MetaInverseTrollID].activeFlagClients[activator] & 2) max = 0.5;
else if(Trolls[MetaInverseTrollID].activeFlagClients[activator] & 4) max = 0.1; else if(Trolls[MetaInverseTrollID].activeFlagClients[activator] & 4) max = 0.1;
if(GetRandomFloat() <= max) { if(GetURandomFloat() <= max) {
victim = activator; victim = activator;
} }
} }
@ -427,7 +395,7 @@ void ApplyTroll(int victim, const char[] name, int activator, trollModifier modi
CShowActivityEx(activator, "[FTT] ", "deactivated {yellow}%s{default} on %N. ", troll.name, victim); CShowActivityEx(activator, "[FTT] ", "deactivated {yellow}%s{default} on %N. ", troll.name, victim);
LogAction(activator, victim, "\"%L\" deactivated \"%s\" on \"%L\"", activator, troll.name, victim); LogAction(activator, victim, "\"%L\" deactivated \"%s\" on \"%L\"", activator, troll.name, victim);
} else { } else {
static char flagName[MAX_TROLL_FLAG_LENGTH]; char flagName[MAX_TROLL_FLAG_LENGTH];
// strcopy(flagName, sizeof(flagName), troll.name) // strcopy(flagName, sizeof(flagName), troll.name)
// Call_StartForward(g_TrollAppliedForward); // Call_StartForward(g_TrollAppliedForward);
// Call_PushCell(victim); // Call_PushCell(victim);
@ -435,7 +403,6 @@ void ApplyTroll(int victim, const char[] name, int activator, trollModifier modi
// Call_PushCell(flags); // Call_PushCell(flags);
// Call_PushCell(activator); // Call_PushCell(activator);
// Call_Finish(); // Call_Finish();
flagName[0] = '\0';
for(int i = 0; i < 32; i++) { for(int i = 0; i < 32; i++) {
if(flags & (1 << i)) { if(flags & (1 << i)) {
// If at least one flag already, reset to none: // If at least one flag already, reset to none:
@ -446,32 +413,31 @@ void ApplyTroll(int victim, const char[] name, int activator, trollModifier modi
troll.GetFlagName(i, flagName, sizeof(flagName)); troll.GetFlagName(i, flagName, sizeof(flagName));
} }
} }
if(flags > 0 && flags & flags - 1 == 0 && flags & flags + 1 == 0) {
// Get the flag name if there is only one flag set if(flags > 0) {
troll.GetFlagName(GetIndexFromPower(flags), flagName, sizeof(flagName)); // Checks if there is not more than one flag set on the bitfield
if(flags & flags - 1 == 0 && flags & flags + 1 == 0) {
// Get the flag name if there is only one flag set
troll.GetFlagName(GetIndexFromPower(flags), flagName, sizeof(flagName));
} else {
Format(flagName, sizeof(flagName), "%d", flags);
}
} }
if(modifier & TrollMod_Constant) { if(modifier & TrollMod_Constant) {
if(flags > 0) { if(flags > 0) {
if(flagName[0] != '\0') { CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default} ({yellow}%s{default}) for %N. ", troll.name, flagName, victim);
CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default} ({yellow}%s{default}) for %N. ", troll.name, flagName, victim);
} else {
CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default} ({yellow}%d{default}) for %N. ", troll.name, flags, victim);
}
} else } else
CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default} for %N. ", troll.name, victim); CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default} for %N. ", troll.name, victim);
} else if(flags > 0) { } else if(flags > 0) {
if(flagName[0] != '\0') { CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default} ({yellow}%s{default}) for %N. ", troll.name, flagName, victim);
CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default} ({yellow}%s{default}) for %N. ", troll.name, flagName, victim); } else {
} else {
CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default} ({yellow}%d{default}) for %N. ", troll.name, flags, victim);
}
} else
CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default} for %N. ", troll.name, victim); CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default} for %N. ", troll.name, victim);
}
LogAction(activator, victim, "\"%L\" activated \"%s\" (%d) for \"%L\"", activator, troll.name, flags, victim); LogAction(activator, victim, "\"%L\" activated \"%s\" (%d) for \"%L\"", activator, troll.name, flags, victim);
} }
} else { } else {
CReplyToCommand(activator, "ftt: Applied {yellow}\"%s\"{default} on %N with flags=%d", troll.name, victim, flags); CReplyToCommand(activator, "[FTT] Applied silently {yellow}\"%s\"{default} on %N with flags=%d", troll.name, victim, flags);
} }
} }

View file

@ -65,8 +65,8 @@ void SetupsTrollCombos() {
SetupCombo(combo, "Nuclear"); SetupCombo(combo, "Nuclear");
combo.AddTroll("Slow Speed"); combo.AddTroll("Slow Speed");
combo.AddTroll("Special Magnet"); combo.AddTroll("Special Magnet", .flags=1);
combo.AddTroll("Tank Magnet"); combo.AddTroll("Tank Magnet", .flags=1);
#if defined _behavior_included #if defined _behavior_included
combo.AddTroll("Witch Magnet"); combo.AddTroll("Witch Magnet");
#endif #endif

View file

@ -732,6 +732,7 @@ Action Command_SetReverseFF(int client, int args) {
ApplyTroll(target, "Reverse FF", client, TrollMod_Constant, flag); ApplyTroll(target, "Reverse FF", client, TrollMod_Constant, flag);
return Plugin_Handled; return Plugin_Handled;
} }
Action Command_SetMagnetShortcut(int client, int args) { Action Command_SetMagnetShortcut(int client, int args) {
if(args < 1) { if(args < 1) {
ReplyToCommand(client, "Usage: sm_magnet <target>"); ReplyToCommand(client, "Usage: sm_magnet <target>");
@ -745,4 +746,42 @@ Action Command_SetMagnetShortcut(int client, int args) {
} }
ShowTrollsForCategory(client, GetClientUserId(target), 0); ShowTrollsForCategory(client, GetClientUserId(target), 0);
return Plugin_Handled; return Plugin_Handled;
}
Action Command_CarSplat(int client, int args) {
if(args < 1) {
ReplyToCommand(client, "Usage: sm_magnet <target> [top/front/back]");
return Plugin_Handled;
}
char arg[32];
GetCmdArg(1, arg, sizeof(arg));
int target = GetSinglePlayer(client, arg, COMMAND_FILTER_ALIVE);
if(target <= 0) {
return Plugin_Handled;
}
if(args == 2) {
GetCmdArg(2, arg, sizeof(arg));
float speed = 450.0;
if(args == 3) {
GetCmdArg(3, arg, sizeof(arg));
speed = StringToFloat(arg);
if(speed <= 0.0) speed = 450.0;
}
if(StrEqual(arg, "top")) {
SpawnCarOnPlayer(target);
} else if(StrEqual(arg, "front")) {
SpawnCarToPlayer(target, speed);
} else if(StrEqual(arg, "back")) {
SpawnCarToPlayer(target, -speed);
} else {
ReplyToCommand(client, "Invalid direction: top/front/back or blank for menu");
return Plugin_Handled;
}
LogAction(client, target, "spawned car on/in %s of \"%L\"", arg, target);
ShowActivity(client, "spawned car (%s) of %N", arg, target);
} else {
Troll troll;
GetTroll("Car Splat", troll);
ShowSelectFlagMenu(client, GetClientUserId(target), view_as<int>(TrollMod_Instant), troll);
}
return Plugin_Handled;
} }

View file

@ -75,21 +75,21 @@ void EntityCreateCallback(int entity) {
if(Trolls[badThrowID].IsActive(entOwner)) { if(Trolls[badThrowID].IsActive(entOwner)) {
static float pos[3]; static float pos[3];
GetClientEyePosition(entOwner, pos); GetClientEyePosition(entOwner, pos);
if(Trolls[badThrowID].IsFlagActive(entOwner, Flag_1) && StrEqual(class, "vomitjar_projectile", true)) { if(Trolls[badThrowID].activeFlagClients[entOwner] & 1 && StrEqual(class, "vomitjar_projectile", true)) {
AcceptEntityInput(entity, "Kill"); RemoveEntity(entity);
if(hBadThrowHitSelf.FloatValue > 0.0 && GetRandomFloat() <= hBadThrowHitSelf.FloatValue) { if(hBadThrowHitSelf.FloatValue > 0.0 && GetRandomFloat() <= hBadThrowHitSelf.FloatValue) {
L4D_CTerrorPlayer_OnVomitedUpon(entOwner, entOwner); L4D_CTerrorPlayer_OnVomitedUpon(entOwner, entOwner);
EmitSoundToAll("weapons/ceda_jar/ceda_jar_explode.wav", entOwner); EmitSoundToAll("weapons/ceda_jar/ceda_jar_explode.wav", entOwner);
FindClosestClient(entOwner, false, pos); FindClosestClient(entOwner, false, pos);
} }
SpawnItem("vomitjar", pos); SpawnItem("vomitjar", pos);
} else if(Trolls[badThrowID].IsFlagActive(entOwner, Flag_2) && StrEqual(class, "molotov_projectile", true)) { } else if(Trolls[badThrowID].activeFlagClients[entOwner] & 2 && StrEqual(class, "molotov_projectile", true)) {
// Burn them if no one near :) // Burn them if no one near :)
if(hBadThrowHitSelf.FloatValue > 0.0 && GetRandomFloat() <= hBadThrowHitSelf.FloatValue) { if(hBadThrowHitSelf.FloatValue > 0.0 && GetRandomFloat() <= hBadThrowHitSelf.FloatValue) {
GetClientAbsOrigin(entOwner, pos); GetClientAbsOrigin(entOwner, pos);
// Kill molotov if too close to a player, else teleport to feet // Kill molotov if too close to a player, else teleport to feet
if(IsAnyPlayerNear(entOwner, 500.0)) { if(IsAnyPlayerNear(entOwner, 500.0)) {
AcceptEntityInput(entity, "Kill"); RemoveEntity(entity);
EmitSoundToAll("weapons/molotov/molotov_detonate_1.wav", entOwner); EmitSoundToAll("weapons/molotov/molotov_detonate_1.wav", entOwner);
} else { } else {
float vel[3]; float vel[3];
@ -101,7 +101,7 @@ void EntityCreateCallback(int entity) {
SpawnItem("molotov", pos); SpawnItem("molotov", pos);
AcceptEntityInput(entity, "Kill"); AcceptEntityInput(entity, "Kill");
} }
} else if(Trolls[badThrowID].IsFlagActive(entOwner, Flag_3) && StrEqual(class, "pipe_bomb_projectile", true)) { } else if(Trolls[badThrowID].activeFlagClients[entOwner] & 3 && StrEqual(class, "pipe_bomb_projectile", true)) {
if(hBadThrowHitSelf.FloatValue > 0.0 && GetRandomFloat() <= hBadThrowHitSelf.FloatValue) { if(hBadThrowHitSelf.FloatValue > 0.0 && GetRandomFloat() <= hBadThrowHitSelf.FloatValue) {
TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR); TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR);
ExplodeProjectile(entity); ExplodeProjectile(entity);
@ -386,7 +386,9 @@ public Action L4D2_OnChooseVictim(int attacker, int &curTarget) {
} }
bool WillMagnetRun(const Troll troll, int i) { bool WillMagnetRun(const Troll troll, int i) {
if(troll.activeFlagClients[i] == 0) return false; // In the case none of the flags are set, return true (100% chance)
// Some systems may give magnet w/ no flags
if(troll.activeFlagClients[i] == 0) return true;
float cChance = 1.0; float cChance = 1.0;
//Skip first bit as it is ('Always') //Skip first bit as it is ('Always')
@ -424,11 +426,13 @@ public Action OnClientSayCommand(int client, const char[] command, const char[]
// Honk Processing // Honk Processing
static char strings[32][8]; static char strings[32][8];
int words = ExplodeString(sArgs, " ", strings, sizeof(strings), 5); int words = ExplodeString(sArgs, " ", strings, sizeof(strings), 5);
for(int i = 0; i < words; i++) { for(int i = 0; i < words; i++) {
// Strings should be padded by 7 characters (+ null term) to fill up 8 bytes
if(GetRandomFloat() <= 0.8) strings[i] = "honk "; if(GetRandomFloat() <= 0.8) strings[i] = "honk ";
else strings[i] = "squeak"; else strings[i] = "squeak ";
} }
int length = 7 * words; int length = 8 * words;
char[] message = new char[length]; char[] message = new char[length];
ImplodeStrings(strings, 32, " ", message, length); ImplodeStrings(strings, 32, " ", message, length);
if(Trolls[honkID].activeFlagClients[client] & 1) if(Trolls[honkID].activeFlagClients[client] & 1)
@ -761,71 +765,85 @@ public Action NerfGun_OnTakeDamage(int victim, int& attacker, int& inflictor, fl
return Plugin_Continue; return Plugin_Continue;
} }
public Action Event_TakeDamage(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { // Only triggered for players, victim is 0 < victim <= MaxClients
//Stop FF from marked: Action Event_TakeDamage(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) {
static int reverseFF; // Ignore passing bots from FF
if(reverseFF == 0) reverseFF = GetTrollID("Reverse FF");
if(attacker > 0 && attacker <= MaxClients) { if(attacker > 0 && attacker <= MaxClients) {
if(GetClientTeam(attacker) == 4 && IsFakeClient(attacker)) return Plugin_Continue; if(GetClientTeam(attacker) == 4 && IsFakeClient(attacker)) return Plugin_Continue;
} }
// Boost all damage no matter what
if(attacker > 0 && victim <= MaxClients && attacker <= MaxClients && IsClientInGame(attacker) && IsPlayerAlive(attacker)) { if(Trolls[t_damageBoostIndex].IsActive(victim)) {
damage * 2;
return Plugin_Changed;
}
// Only apply for when attackers are players
if(attacker > 0 && attacker <= MaxClients && IsClientInGame(attacker) && IsPlayerAlive(attacker)) {
if(pdata[attacker].shootAtTarget == victim) return Plugin_Continue; if(pdata[attacker].shootAtTarget == victim) return Plugin_Continue;
if(pdata[attacker].pendingTrollBan > 0 && GetClientTeam(attacker) == 2 && GetClientTeam(victim) == 2) { bool isSameTeam = GetClientTeam(attacker) == GetClientTeam(victim);
if(pdata[attacker].pendingTrollBan > 0 && isSameTeam) {
return Plugin_Stop; return Plugin_Stop;
} }
if(damage > 0.0 && victim != attacker && Trolls[slipperyShoesIndex].IsActive(victim) && Trolls[slipperyShoesIndex].activeFlagClients[victim] & 16) {
L4D_StaggerPlayer(victim, victim, NULL_VECTOR);
}
if(Trolls[t_slotRouletteIndex].IsActive(victim)) { if(Trolls[t_slotRouletteIndex].IsActive(victim)) {
SetSlot(victim, -1); SetSlot(victim, -1);
} }
if(victim != attacker) {
if(IsTrollActive(victim, "Damage Boost")) { if(damage > 0.0 && Trolls[slipperyShoesIndex].IsActive(victim) && Trolls[slipperyShoesIndex].activeFlagClients[victim] & 16) {
damage * 2; L4D_StaggerPlayer(victim, victim, NULL_VECTOR);
return Plugin_Changed;
} else if(Trolls[reverseFF].IsActive(attacker) && attacker != victim && GetClientTeam(attacker) == GetClientTeam(victim)
&& (damagetype == DMG_BURN && Trolls[reverseFF].activeFlagClients[attacker] & 32)
&& (damagetype == DMG_BLAST && Trolls[reverseFF].activeFlagClients[attacker] & 64)
) {
float returnDmg = damage; //default is 1:1
if(Trolls[reverseFF].activeFlagClients[attacker] & 2) {
returnDmg *= 2.0;
} else if(Trolls[reverseFF].activeFlagClients[attacker] & 4) {
returnDmg /= 2.0;
} else if(Trolls[reverseFF].activeFlagClients[attacker] & 8) {
returnDmg = 0.0;
} else if(Trolls[reverseFF].activeFlagClients[attacker] & 16) {
returnDmg *= 3.0;
} }
SDKHooks_TakeDamage(attacker, attacker, attacker, returnDmg, damagetype, -1); if(isSameTeam && Trolls[t_reverseFFIndex].IsActive(attacker)) {
damage = 0.0; // Should this be applied? (as in no FF granted)
return Plugin_Changed; bool disableFF = false;
} if(damagetype == DMG_BURN) {
disableFF = Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 32 != 0;
} else if(damagetype == DMG_BLAST) {
disableFF = Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 64 != 0;
} else {
// Does not run if DMG_BURN or DMG_BLAST
disableFF = true;
}
if(disableFF) {
float returnDmg = damage; //default is 1:1
if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 2) {
returnDmg *= 2.0;
} else if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 4) {
returnDmg /= 2.0;
} else if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 8) {
returnDmg = 0.0;
} else if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 16) {
returnDmg *= 3.0;
}
SDKHooks_TakeDamage(attacker, attacker, attacker, returnDmg, damagetype, -1);
damage = 0.0;
return Plugin_Changed;
}
}
// Only retailate if 2 (anyone) or 1 and they aren't an admin. If we are also a bot.
if(IsFakeClient(victim) && (hBotReverseFFDefend.IntValue == 2 || GetUserAdmin(attacker) == INVALID_ADMIN_ID)) {
// If the attacker is not a bot and on our team, then retaliate
if(!IsFakeClient(attacker) && GetClientTeam(attacker) == 2 && GetClientTeam(victim) == 2) {
if(hBotDefendChance.IntValue >= GetURandomFloat()) {
if(pdata[victim].shootAtTarget == attacker) {
pdata[attacker].shootAtTargetHealth -= RoundToCeil(damage);
pdata[victim].shootAtLoops += 4;
return Plugin_Continue;
} else if(pdata[victim].shootAtTarget > 0) {
// Don't switch, wait for timer to stop
return Plugin_Continue;
}
SetBotTarget(attacker, victim, GetClientRealHealth(attacker) - RoundFloat(damage));
}
}
}
}
// If FF is fire / blast, allow through unless its from a bot, then stop.
// Prevents players with no FF from lighting team on fire and disconnecting, making their new bot do the FF
if(damagetype & DMG_BURN || damagetype & DMG_BLAST) { if(damagetype & DMG_BURN || damagetype & DMG_BLAST) {
if(IsFakeClient(attacker)) return Plugin_Handled; if(IsFakeClient(attacker)) return Plugin_Handled;
else return Plugin_Continue; else return Plugin_Continue;
} }
// Don't let bots do damage to the wrong targets if they are "defending" themselves
if(hBotReverseFFDefend.IntValue > 0 && IsFakeClient(attacker) && pdata[attacker].shootAtTarget == 0 && GetClientTeam(attacker) == 2 && GetClientTeam(victim) == 2) return Plugin_Stop; if(hBotReverseFFDefend.IntValue > 0 && IsFakeClient(attacker) && pdata[attacker].shootAtTarget == 0 && GetClientTeam(attacker) == 2 && GetClientTeam(victim) == 2) return Plugin_Stop;
if(attacker != victim && hBotReverseFFDefend.IntValue > 0 && hBotReverseFFDefend.IntValue == 2 || GetUserAdmin(attacker) == INVALID_ADMIN_ID) {
if(IsFakeClient(victim) && !IsFakeClient(attacker) && GetClientTeam(attacker) == 2 && GetClientTeam(victim) == 2) {
if(hBotDefendChance.IntValue >= GetRandomFloat()) {
if(pdata[victim].shootAtTarget == attacker) {
pdata[attacker].shootAtTargetHealth -= RoundFloat(damage);
pdata[victim].shootAtLoops += 4;
return Plugin_Continue;
} else if(pdata[victim].shootAtTarget > 0) {
// Don't switch, wait for timer to stop
return Plugin_Continue;
}
SetBotTarget(attacker, victim, GetClientRealHealth(attacker) - RoundFloat(damage));
}
}
}
} }
return Plugin_Continue; return Plugin_Continue;
} }

View file

@ -176,7 +176,7 @@ ArrayList GetPhrasesArray(const char[] key) {
stock int FindClosestClientAdminPriority(int source, float pos[3]) { stock int FindClosestClientAdminPriority(int source, float pos[3]) {
int c = FindClosestAdmin(source, pos); int c = FindClosestAdmin(source, pos);
if(c == -1) return FindClosestClient(source, ignoreBots, pos); if(c == -1) return FindClosestClient(source, true,pos);
else return c; else return c;
} }
@ -348,22 +348,6 @@ stock void ExplodeProjectile(int entity, bool smoke = true) {
SetEntProp(entity, Prop_Data, "m_nNextThinkTick", 1); //for smoke SetEntProp(entity, Prop_Data, "m_nNextThinkTick", 1); //for smoke
} }
bool SpawnCarOnPlayer(int target) {
float min[3] = { -30.0, -30.0, -2.0};
float max[3] = { 30.0, 30.0, 50.0 };
float pos[3];
float ang[3];
GetClientEyePosition(target, pos);
GetClientEyeAngles(target, ang);
if(IsAreaClear(pos, ang, min, max)) {
pos[2] += 40.0;
int id = CreateProp("prop_physics", MODEL_CAR, pos, ang);
CreateTimer(4.0, Timer_Delete, id);
return true;
}
return false;
}
stock int CreateProp(const char[] entClass, const char[] model, const float pos[3], const float ang[3] = { 0.0, 0.0, 0.0 }, const float vel[3] = {0.0, 0.0, 0.0}) { stock int CreateProp(const char[] entClass, const char[] model, const float pos[3], const float ang[3] = { 0.0, 0.0, 0.0 }, const float vel[3] = {0.0, 0.0, 0.0}) {
int entity = CreateEntityByName(entClass); int entity = CreateEntityByName(entClass);
DispatchKeyValue(entity, "model", model); DispatchKeyValue(entity, "model", model);
@ -412,6 +396,22 @@ bool SpawnCarToPlayer(int target, float distance) {
return true; return true;
} }
bool SpawnCarOnPlayer(int target) {
float min[3] = { -30.0, -30.0, -2.0};
float max[3] = { 30.0, 30.0, 50.0 };
float pos[3];
float ang[3];
GetClientEyePosition(target, pos);
GetClientEyeAngles(target, ang);
if(IsAreaClear(pos, ang, min, max)) {
pos[2] += 40.0;
int id = CreateProp("prop_physics", MODEL_CAR, pos, ang);
CreateTimer(4.0, Timer_Delete, id);
return true;
}
return false;
}
bool g_iPendingSurvivorAdd; bool g_iPendingSurvivorAdd;
int isCustomSurvivor[MAXPLAYERS+1]; int isCustomSurvivor[MAXPLAYERS+1];
@ -446,7 +446,7 @@ void ClearInventory(int client) {
} }
} }
void StopHealingBots(bool dontKill = false) { void StopHealingBots() {
healTargetPlayer = 0; healTargetPlayer = 0;
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
pdata[i].flags &= ~view_as<int>(Flag_IsTargettingHealer); pdata[i].flags &= ~view_as<int>(Flag_IsTargettingHealer);

View file

@ -1,18 +1,16 @@
Action Timer_ThrowTimer(Handle timer) { Action Timer_ThrowTimer(Handle timer, int client) {
int count = 0; if(!IsClientInGame(client)) {
for(int i = 1; i < MaxClients; i++) { Trolls[t_throwItAllIndex].timerHandles[client] = null;
if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && IsTrollActive(i, "Throw It All")) { return Plugin_Stop;
ThrowAllItems(i);
count++;
}
} }
return count > 0 ? Plugin_Continue : Plugin_Stop; ThrowAllItems(client);
return Plugin_Continue;
} }
int instantCommonRef[MAXPLAYERS+1]; int instantCommonRef[MAXPLAYERS+1];
Action Timer_RandomVelocity(Handle h, int client) { Action Timer_RandomVelocity(Handle h, int client) {
if(!IsClientConnected(client)) { if(!IsClientInGame(client)) {
Trolls[t_randomizeVelocityIndex].timerHandles[client] = null; Trolls[t_randomizeVelocityIndex].timerHandles[client] = null;
return Plugin_Stop; return Plugin_Stop;
} }
@ -139,7 +137,7 @@ Action Timer_Main(Handle timer) {
amplitude = 100.0; amplitude = 100.0;
freq = 200.0; freq = 200.0;
} }
ShakePlayer(i, amplitude, freq, MAIN_TIMER_INTERVAL_S + 1.0); ShakePlayer(i, amplitude, freq, MAIN_TIMER_INTERVAL_S + 2.0);
} }
if(Trolls[t_slotRouletteIndex].IsActive(i) && Trolls[t_slotRouletteIndex].activeFlagClients[i] & 8) { if(Trolls[t_slotRouletteIndex].IsActive(i) && Trolls[t_slotRouletteIndex].activeFlagClients[i] & 8) {
float chance = 1.0; float chance = 1.0;
@ -155,6 +153,9 @@ Action Timer_Main(Handle timer) {
SetSlot(i, -1); SetSlot(i, -1);
} }
} }
if(Trolls[t_hideHUDIndex].IsActive(i)) {
HideHUDRandom(i);
}
} }
} }
if(++loopTick >= 60) { if(++loopTick >= 60) {
@ -163,7 +164,7 @@ Action Timer_Main(Handle timer) {
return Plugin_Continue; return Plugin_Continue;
} }
Action Timer_SlotRoulette(Handle h, int client) { Action Timer_SlotRoulette(Handle h, int client) {
if(!IsClientConnected(client)) { if(!IsClientInGame(client)) {
Trolls[t_slotRouletteIndex].timerHandles[client] = null; Trolls[t_slotRouletteIndex].timerHandles[client] = null;
return Plugin_Stop; return Plugin_Stop;
} }
@ -411,8 +412,7 @@ Action Timer_StopHealBots(Handle h, DataPack pack) {
if(victim) { if(victim) {
DisableTroll(victim, "Dep Bots"); DisableTroll(victim, "Dep Bots");
} }
// TODO: stop right one StopHealingBots();
StopHealingBots(true);
return Plugin_Stop; return Plugin_Stop;
} }
@ -474,4 +474,27 @@ Action Timer_ResetGravity(Handle h, int entref) {
SetEntityGravity(entity, 800.0); // could pull from sv_gravity but no ones gonna notice SetEntityGravity(entity, 800.0); // could pull from sv_gravity but no ones gonna notice
} }
return Plugin_Handled; return Plugin_Handled;
}
Action Timer_RestoreHud(Handle h, int userid) {
int client = GetClientOfUserId(userid);
if(client > 0) {
SetEntProp(client, Prop_Send, "m_iHideHUD", 0);
}
return Plugin_Handled;
}
Action Timer_TurnCamera(Handle h, int client) {
if(!IsClientInGame(client)) {
Trolls[t_cameraTurnIndex].timerHandles[client] = null;
return Plugin_Stop;
}
static float ang[3];
GetClientEyeAngles(client, ang);
if(Trolls[t_cameraTurnIndex].activeFlagClients[client] & 1) {
ang[1] += 2.0;
} else {
ang[0] += 2.0;
}
SetAbsAngles(client, ang);
return Plugin_Continue;
} }

View file

@ -8,7 +8,11 @@ int t_randomizeVelocityIndex;
int t_vomitPlayerIndex; int t_vomitPlayerIndex;
int t_shakeyCameraIndex; int t_shakeyCameraIndex;
int t_slotRouletteIndex; int t_slotRouletteIndex;
// int fireSpitMagnetTrollIndex; int t_damageBoostIndex;
int t_reverseFFIndex;
int t_throwItAllIndex;
int t_hideHUDIndex;
int t_cameraTurnIndex;
void SetupTrolls() { void SetupTrolls() {
trollKV = new StringMap(); trollKV = new StringMap();
@ -87,7 +91,8 @@ void SetupTrolls() {
// CATEGORY: Items // CATEGORY: Items
SetCategory("Items"); SetCategory("Items");
SetupTroll("Throw It All", "Player throws their item(s) periodically to a nearby player", TrollMod_Instant); index = SetupTroll("Throw It All", "Player throws their item(s) periodically to a nearby player", TrollMod_Instant);
Trolls[index].SetTimer(THROWITALL_INTERVAL, Timer_ThrowTimer);
index = SetupTroll("Spicy Gas", "Gascans player picks up just ignite. Magic.", TrollMod_Constant); index = SetupTroll("Spicy Gas", "Gascans player picks up just ignite. Magic.", TrollMod_Constant);
Trolls[index].AddFlagPrompt(false); Trolls[index].AddFlagPrompt(false);
Trolls[index].AddFlag("Always (100%)", false); Trolls[index].AddFlag("Always (100%)", false);
@ -159,7 +164,7 @@ void SetupTrolls() {
/// CATEGORY: Health /// CATEGORY: Health
SetCategory("Health"); SetCategory("Health");
SetupTroll("Damage Boost", "Makes a player take more damage than normal", TrollMod_Constant); t_damageBoostIndex = SetupTroll("Damage Boost", "Makes a player take more damage than normal", TrollMod_Constant);
SetupTroll("Temp Health Quick Drain", "Makes a player's temporarily health drain very quickly", TrollMod_Constant); SetupTroll("Temp Health Quick Drain", "Makes a player's temporarily health drain very quickly", TrollMod_Constant);
SetupTroll("Slow Drain", "Will make the player slowly lose health over time", TrollMod_Constant); SetupTroll("Slow Drain", "Will make the player slowly lose health over time", TrollMod_Constant);
SetupTroll("KillMeSoftly", "Make player eat or waste pills whenever possible", TrollMod_Instant | TrollMod_Constant); SetupTroll("KillMeSoftly", "Make player eat or waste pills whenever possible", TrollMod_Instant | TrollMod_Constant);
@ -173,6 +178,7 @@ void SetupTrolls() {
Trolls[index].AddCustomFlagPrompt("Modes", true); Trolls[index].AddCustomFlagPrompt("Modes", true);
Trolls[index].AddFlag("Reverse Fire Damage", false); //32 Trolls[index].AddFlag("Reverse Fire Damage", false); //32
Trolls[index].AddFlag("Reverse Explosions", false); //64 Trolls[index].AddFlag("Reverse Explosions", false); //64
t_reverseFFIndex = index;
index = SetupTroll("Dep Bots", "Makes bots heal a player. At any cost", TrollMod_Constant); index = SetupTroll("Dep Bots", "Makes bots heal a player. At any cost", TrollMod_Constant);
@ -230,6 +236,12 @@ void SetupTrolls() {
Trolls[index].AddFlag("Severe Earthquake", false); //8 Trolls[index].AddFlag("Severe Earthquake", false); //8
Trolls[index].AddFlag("Bouncy Castle", false); //16 Trolls[index].AddFlag("Bouncy Castle", false); //16
t_randomizeVelocityIndex = index; t_randomizeVelocityIndex = index;
index = SetupTroll("Camera Turn", "Constantly turn their camera", TrollMod_Constant);
Trolls[index].SetTimer(0.1, Timer_TurnCamera);
Trolls[index].AddCustomFlagPrompt("Direction:", false);
Trolls[index].AddFlag("Left/Right", true); //1
Trolls[index].AddFlag("Up/Down", false); //2
t_cameraTurnIndex = index;
/// CATEGORY: MISC /// CATEGORY: MISC
@ -253,13 +265,20 @@ void SetupTrolls() {
Trolls[index].AddFlag("Violent", false); Trolls[index].AddFlag("Violent", false);
Trolls[index].AddFlag("Violent XX", false); Trolls[index].AddFlag("Violent XX", false);
t_shakeyCameraIndex = index; t_shakeyCameraIndex = index;
index = SetupTroll("Hide HUD", "Horrible", TrollMod_Constant);
Trolls[index].AddFlagPrompt(false);
Trolls[index].AddFlag("Rare & Short", false);
Trolls[index].AddFlag("Sometimes & Medium", false);
Trolls[index].AddFlag("Constantly", true);
t_hideHUDIndex = index;
index = SetupTroll("Meta: Random", "Picks a random troll", TrollMod_Instant);
index = SetupTroll("Meta: Inverse", "Uhm you are not supposed to see this...", TrollMod_Instant); index = SetupTroll("Meta: Inverse", "Uhm you are not supposed to see this...", TrollMod_Instant);
Trolls[index].hidden = true; Trolls[index].hidden = true;
Trolls[index].AddFlagPrompt(false); Trolls[index].AddFlagPrompt(false);
Trolls[index].AddFlag("100%", true); Trolls[index].AddFlag("100%", true);
Trolls[index].AddFlag("50%", false); Trolls[index].AddFlag("50%", false);
Trolls[index].AddFlag("10%", false); Trolls[index].AddFlag("10%", false);
index = SetupTroll("Meta: Random", "Picks a random troll", TrollMod_Instant);
@ -337,9 +356,6 @@ bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier mod
ThrowItemToPlayer(victim, activator, 3); ThrowItemToPlayer(victim, activator, 3);
} else ThrowAllItems(victim); } else ThrowAllItems(victim);
} }
if(hThrowTimer == INVALID_HANDLE && modifier & TrollMod_Constant) {
hThrowTimer = CreateTimer(hThrowItemInterval.FloatValue, Timer_ThrowTimer, _, TIMER_REPEAT);
}
} else if(StrEqual(troll.name, "Swarm")) { } else if(StrEqual(troll.name, "Swarm")) {
if(modifier & TrollMod_Instant) { if(modifier & TrollMod_Instant) {
L4D2_RunScript("RushVictim(GetPlayerFromUserID(%d), %d)", victim, 15000); L4D2_RunScript("RushVictim(GetPlayerFromUserID(%d), %d)", victim, 15000);
@ -519,6 +535,11 @@ bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier mod
CreateTimer(1.0, Timer_CheckForChargerOpportunity, GetClientUserId(victim), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); CreateTimer(1.0, Timer_CheckForChargerOpportunity, GetClientUserId(victim), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
} else if(StrEqual(troll.name, "No Rushing Us")) { } else if(StrEqual(troll.name, "No Rushing Us")) {
SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0);
} else if(StrEqual(troll.name, "Hide HUD")) {
if(toActive)
HideHUDRandom(victim);
else
SetEntProp(victim, Prop_Send, "m_iHideHUD", 0);
} else if(StrEqual(troll.name, "Meta: Random")) { } else if(StrEqual(troll.name, "Meta: Random")) {
int rndTroll = GetRandomInt(0, MAX_TROLLS); int rndTroll = GetRandomInt(0, MAX_TROLLS);
int rndFlags = 0; int rndFlags = 0;
@ -527,15 +548,14 @@ bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier mod
while(numFlags > 0) { while(numFlags > 0) {
// Apply a random flag // Apply a random flag
rndFlags |= GetRandomInt(0, maxFlags) rndFlags |= GetRandomInt(0, maxFlags)
maxFlags--; numFlags--;
} }
trollModifier rndMod = Trolls[rndTroll].GetDefaultMod(); trollModifier rndMod = Trolls[rndTroll].GetDefaultMod();
if(Trolls[rndTroll].HasMod(TrollMod_Constant) && GetURandomFloat() > 0.5) { if(Trolls[rndTroll].HasMod(TrollMod_Constant) && GetURandomFloat() > 0.5) {
rndMod = TrollMod_Instant;
} else if(Trolls[rndTroll].HasMod(TrollMod_Instant) && GetURandomFloat() > 0.5) {
rndMod = TrollMod_Constant; rndMod = TrollMod_Constant;
} }
if(Trolls[rndTroll].HasMod(TrollMod_Instant) && GetURandomFloat() > 0.5) {
rndMod |= TrollMod_Instant;
}
Trolls[rndTroll].Activate(victim, activator, rndMod, rndFlags); Trolls[rndTroll].Activate(victim, activator, rndMod, rndFlags);
} else if(~modifier & TrollMod_Constant) { } else if(~modifier & TrollMod_Constant) {
PrintToServer("[FTT] Warn: Possibly invalid troll, no apply action defined for \"%s\"", troll.name); PrintToServer("[FTT] Warn: Possibly invalid troll, no apply action defined for \"%s\"", troll.name);

View file

@ -15,10 +15,8 @@ GlobalForward g_PlayerMarkedForward;
GlobalForward g_TrollAppliedForward; GlobalForward g_TrollAppliedForward;
Handle g_hWitchAttack; Handle g_hWitchAttack;
int g_iWitchAttackVictim; int g_iWitchAttackVictim;
Handle hThrowTimer;
ConVar hAllowEnemyTeam; ConVar hAllowEnemyTeam;
ConVar hThrowItemInterval;
ConVar hAutoPunish; ConVar hAutoPunish;
ConVar hShoveFailChance; ConVar hShoveFailChance;
ConVar hAutoPunishExpire; ConVar hAutoPunishExpire;

View file

@ -44,7 +44,7 @@ char ActivePreset[MAXPLAYERS+1][32];
StringMap g_HatPresets; StringMap g_HatPresets;
int lastHatRequestTime[MAXPLAYERS+1]; int lastHatRequestTime[MAXPLAYERS+1];
#define MAX_FORBIDDEN_CLASSNAMES 13 #define MAX_FORBIDDEN_CLASSNAMES 14
char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
"prop_door_rotating_checkpoint", "prop_door_rotating_checkpoint",
"env_physics_blocker", "env_physics_blocker",
@ -56,7 +56,7 @@ char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
"func_elevator", "func_elevator",
"func_button_timed", "func_button_timed",
"func_tracktrain", "func_tracktrain",
// "func_movelinear", "func_movelinear",
// "infected", // "infected",
"func_lod", "func_lod",
"func_door", "func_door",
@ -125,6 +125,9 @@ Action Command_DoAHat(int client, int args) {
ReplyToCommand(client, "Flags: %d", hatData[client].flags); ReplyToCommand(client, "Flags: %d", hatData[client].flags);
// ReplyToCommand(client, "CurOffset: %f %f %f", ); // ReplyToCommand(client, "CurOffset: %f %f %f", );
return Plugin_Handled; return Plugin_Handled;
} else if(arg[0] == 'a') {
ShowAttachPointMenu(client);
return Plugin_Handled;
} }
// int orgEntity = entity; // int orgEntity = entity;
if(HasFlag(client, HAT_REVERSED)) { if(HasFlag(client, HAT_REVERSED)) {
@ -455,13 +458,63 @@ Action Command_DoAHat(int client, int args) {
} }
#define MAX_ATTACHMENT_POINTS 20
char ATTACHMENT_POINTS[MAX_ATTACHMENT_POINTS][] = {
"eyes",
"molotov",
"pills",
"grenade",
"primary",
"medkit",
"melee",
"survivor_light",
"bleedout",
"forward",
"survivor_neck",
"muzzle_flash",
"spine",
"legL",
"legR",
"thighL",
"thighR",
"lfoot",
"rfoot",
"mouth",
};
void ShowAttachPointMenu(int client) {
Menu menu = new Menu(AttachPointHandler);
menu.SetTitle("Choose an attach point");
for(int i = 0; i < MAX_ATTACHMENT_POINTS; i++) {
menu.AddItem(ATTACHMENT_POINTS[i], ATTACHMENT_POINTS[i]);
}
menu.Display(client, 0);
}
int AttachPointHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char attachPoint[32];
menu.GetItem(param2, attachPoint, sizeof(attachPoint));
if(!HasHat(client)) {
ReplyToCommand(client, "No hat is equipped");
} else {
int hat = GetHat(client);
char classname[32];
GetEntityClassname(hat, classname, sizeof(classname));
EquipHat(client, hat, classname, hatData[client].flags, attachPoint);
CReplyToCommand(client, "Attachment point set to {olive}%s", attachPoint);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
// Handles consent that a person to be hatted by another player // Handles consent that a person to be hatted by another player
public int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) {
if (action == MenuAction_Select) { if (action == MenuAction_Select) {
static char info[8]; char info[8];
menu.GetItem(param2, info, sizeof(info)); menu.GetItem(param2, info, sizeof(info));
static char str[2][8]; char str[2][8];
ExplodeString(info, "|", str, 2, 8, false); ExplodeString(info, "|", str, 2, 8, false);
int activator = GetClientOfUserId(StringToInt(str[0])); int activator = GetClientOfUserId(StringToInt(str[0]));
int hatAction = StringToInt(str[1]); int hatAction = StringToInt(str[1]);
@ -710,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); PrintToChat(entity, "[Hats] %N has set themselves as your hat", client);
} }
} else { } 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; int eflags = GetEntityFlags(entity) | FL_FROZEN;
SetEntityFlags(entity, eflags); SetEntityFlags(entity, eflags);
hatData[client].offset[2] = 36.0; hatData[client].offset[2] = 36.0;

View file

@ -225,6 +225,7 @@ enum struct WallBuilderData {
DispatchSpawn(entity); DispatchSpawn(entity);
TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR);
this.entity = entity; this.entity = entity;
SetEntProp(entity, Prop_Send, "m_nSolidType", 2);
return entity; return entity;
} }
@ -247,7 +248,7 @@ WallBuilderData WallBuilder[MAXPLAYERS+1];
// TODO: Stacker, copy tool, new command? // TODO: Stacker, copy tool, new command?
public Action Command_MakeWall(int client, int args) { public Action Command_MakeWall(int client, int args) {
if(WallBuilder[client].IsActive()) { 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 { } else {
WallBuilder[client].Reset(); WallBuilder[client].Reset();
if(args > 0) { if(args > 0) {

View file

@ -4,6 +4,7 @@
//#define DEBUG //#define DEBUG
#define ALLOW_HEALING_MIN_IDLE_TIME 180 #define ALLOW_HEALING_MIN_IDLE_TIME 180
#define MIN_IGNORE_IDLE_TIME 5
#define PLUGIN_VERSION "1.0" #define PLUGIN_VERSION "1.0"
#include <sourcemod> #include <sourcemod>
@ -11,10 +12,10 @@
#include <actions> #include <actions>
//#include <sdkhooks> //#include <sdkhooks>
int lastIdleTimeStart[MAXPLAYERS+1];
int idleTimeStart[MAXPLAYERS+1]; int idleTimeStart[MAXPLAYERS+1];
public Plugin myinfo = public Plugin myinfo = {
{
name = "L4D2 AI Tweaks", name = "L4D2 AI Tweaks",
author = "jackzmc", author = "jackzmc",
description = "", description = "",
@ -27,16 +28,26 @@ public void OnPluginStart() {
if(g_Game != Engine_Left4Dead2) { if(g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D2 only."); SetFailState("This plugin is for L4D2 only.");
} }
// HookEvent("player_bot_replace", Event_PlayerOutOfIdle ); HookEvent("player_bot_replace", Event_PlayerOutOfIdle );
HookEvent("bot_player_replace", Event_PlayerToIdle); HookEvent("bot_player_replace", Event_PlayerToIdle);
} }
void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid")); int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0) { if(client > 0) {
lastIdleTimeStart[client] = idleTimeStart[client];
idleTimeStart[client] = GetTime(); idleTimeStart[client] = GetTime();
} }
} }
void Event_PlayerOutOfIdle(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0) {
// After saferooms, idle players get resumed then immediately idle - so ignore that
if(GetTime() - idleTimeStart[client] < MIN_IGNORE_IDLE_TIME) {
idleTimeStart[client] = lastIdleTimeStart[client];
}
}
}
public void OnActionCreated( BehaviorAction action, int actor, const char[] name ) { public void OnActionCreated( BehaviorAction action, int actor, const char[] name ) {
/* Hooking friend healing action (when bot wants to heal someone) */ /* Hooking friend healing action (when bot wants to heal someone) */

View file

@ -29,7 +29,7 @@
//Sets abmExtraCount to this value if set //Sets abmExtraCount to this value if set
// #define DEBUG_FORCE_PLAYERS 7 // #define DEBUG_FORCE_PLAYERS 7
#define 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_MIN_SEC 2.0
#define EXTRA_TANK_MAX_SEC 20.0 #define EXTRA_TANK_MAX_SEC 20.0
@ -79,8 +79,8 @@ 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, cvEPIGamemodes, hGamemode; ConVar hExtraItemBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning, cvEPIGamemodes, hGamemode, cvEPITankHealth;
int g_extraKitsAmount, g_extraKitsStart, abmExtraCount, g_saferoomDoorEnt, playersLoadedIn, g_prevPlayerCount; int g_extraKitsAmount, g_extraKitsStart, g_saferoomDoorEnt, g_prevPlayerCount;
static int g_currentChapter; static int g_currentChapter;
static bool g_isCheckpointReached, isLateLoaded, g_startCampaignGiven, g_isFailureRound, g_areItemsPopulated; static bool g_isCheckpointReached, isLateLoaded, g_startCampaignGiven, g_isFailureRound, g_areItemsPopulated;
static ArrayList g_ammoPacks; static ArrayList g_ammoPacks;
@ -89,10 +89,10 @@ static bool showHudPingMode;
static int hudModeTicks; static int hudModeTicks;
static char g_currentGamemode[32]; static char g_currentGamemode[32];
static bool g_isGamemodeAllowed; static bool g_isGamemodeAllowed;
static int g_survivorCount; int g_survivorCount, g_realSurvivorCount;
static ArrayList g_allowedGamemodes; bool g_isFinaleEnding;
bool g_isSpeaking[MAXPLAYERS+1];
bool isCoop; bool isCoop;
enum Difficulty { enum Difficulty {
@ -231,6 +231,24 @@ enum struct Cabinet {
} }
static Cabinet cabinets[10]; //Store 10 cabinets static Cabinet cabinets[10]; //Store 10 cabinets
#define FINALE_TANK 8
#define FINALE_STARTED 1
#define FINALE_RESCUE_READY 6
#define FINALE_HORDE 7
#define FINALE_WAIT 10
enum FinaleStage {
Stage_Inactive = 0,
Stage_FinaleActive = 1,
Stage_FinaleTank1 = 2,
Stage_FinaleTank2 = 3,
Stage_FinaleDuplicatePending = 4,
Stage_TankSplit = 5,
Stage_InactiveFinale = -1
}
int extraTankHP;
FinaleStage g_finaleStage;
//// Definitions completSe //// Definitions completSe
#include <epi/director.sp> #include <epi/director.sp>
@ -267,6 +285,7 @@ public void OnPluginStart() {
HookEvent("player_info", Event_PlayerInfo); HookEvent("player_info", Event_PlayerInfo);
HookEvent("player_disconnect", Event_PlayerDisconnect); HookEvent("player_disconnect", Event_PlayerDisconnect);
HookEvent("player_death", Event_PlayerDeath); HookEvent("player_death", Event_PlayerDeath);
HookEvent("player_incapacitated", Event_PlayerIncapped);
HookEvent("charger_carry_start", Event_ChargerCarry); HookEvent("charger_carry_start", Event_ChargerCarry);
HookEvent("charger_carry_end", Event_ChargerCarry); HookEvent("charger_carry_end", Event_ChargerCarry);
@ -283,22 +302,25 @@ public void OnPluginStart() {
HookEvent("jockey_ride_end", Event_JockeyRide); HookEvent("jockey_ride_end", Event_JockeyRide);
HookEvent("witch_spawn", Event_WitchSpawn); HookEvent("witch_spawn", Event_WitchSpawn);
HookEvent("finale_vehicle_incoming", Event_FinaleVehicleIncoming);
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);
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); 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);
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); 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);
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); 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);
hSaferoomDoorWaitSeconds = CreateConVar("l4d2_extraitems_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", FCVAR_NONE, true, 0.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);
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); hSaferoomDoorWaitSeconds = CreateConVar("epi_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", 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); 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);
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); 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);
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); 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);
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); 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);
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); 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);
cvEPIHudFlags = CreateConVar("l4d2_extraitems_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", 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);
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); cvEPIHudFlags = CreateConVar("epi_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", FCVAR_NONE, true, 0.0);
cvEPIGamemodes = CreateConVar("l4d2_epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE); 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);
cvEPIGamemodes = CreateConVar("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);
@ -349,11 +371,14 @@ public void OnPluginStart() {
#endif #endif
RegAdminCmd("sm_epi_restore", Command_RestoreInventory, ADMFLAG_KICK); RegAdminCmd("sm_epi_restore", Command_RestoreInventory, ADMFLAG_KICK);
RegAdminCmd("sm_epi_save", Command_SaveInventory, ADMFLAG_KICK); RegAdminCmd("sm_epi_save", Command_SaveInventory, ADMFLAG_KICK);
CreateTimer(2.0, Timer_Director, _, TIMER_REPEAT); CreateTimer(DIRECTOR_TIMER_INTERVAL, Timer_Director, _, TIMER_REPEAT);
CreateTimer(30.0, Timer_ForceUpdateInventories, _, 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) { Action Timer_ForceUpdateInventories(Handle h) {
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
@ -361,6 +386,7 @@ Action Timer_ForceUpdateInventories(Handle h) {
// SaveInventory(i); // SaveInventory(i);
} }
} }
Director_CheckSpawnCounts();
return Plugin_Continue; return Plugin_Continue;
} }
@ -383,6 +409,7 @@ public void OnClientPutInServer(int client) {
public void OnClientDisconnect(int client) { public void OnClientDisconnect(int client) {
if(!IsFakeClient(client) && IsClientInGame(client)) if(!IsFakeClient(client) && IsClientInGame(client))
SaveInventory(client); SaveInventory(client);
g_isSpeaking[client] = false;
} }
public void OnPluginEnd() { public void OnPluginEnd() {
@ -549,6 +576,7 @@ Action Command_RestoreInventory(int client, int args) {
} }
return Plugin_Handled; return Plugin_Handled;
} }
// TODO: allow sc <players> <bots> for new sys
public Action Command_SetSurvivorCount(int client, int args) { public Action Command_SetSurvivorCount(int client, int args) {
int oldCount = g_realSurvivorCount; int oldCount = g_realSurvivorCount;
if(args > 0) { if(args > 0) {
@ -561,7 +589,7 @@ public Action Command_SetSurvivorCount(int client, int args) {
return Plugin_Handled; return Plugin_Handled;
} else { } else {
g_realSurvivorCount = g_survivorCount = newCount; g_realSurvivorCount = g_survivorCount = newCount;
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = g_realSurvivorCount;
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;
if(add) if(add)
@ -613,7 +641,6 @@ Action Command_RunExtraItems(int client, int args) {
return Plugin_Handled; return Plugin_Handled;
} }
Action Command_Debug(int client, int args) { Action Command_Debug(int client, int args) {
PrintToConsole(client, "abmExtraCount = %d", abmExtraCount);
PrintToConsole(client, "g_survivorCount = %d | g_realSurvivorCount = %d", g_survivorCount, g_realSurvivorCount); PrintToConsole(client, "g_survivorCount = %d | g_realSurvivorCount = %d", g_survivorCount, g_realSurvivorCount);
Director_PrintDebug(client); Director_PrintDebug(client);
return Plugin_Handled; return Plugin_Handled;
@ -667,24 +694,6 @@ Action Command_DebugStats(int client, int args) {
/// EVENTS /// EVENTS
//////////////////////////////////// ////////////////////////////////////
#define FINALE_TANK 8
#define FINALE_STARTED 1
#define FINALE_RESCUE_READY 6
#define FINALE_HORDE 7
#define FINALE_WAIT 10
enum FinaleStage {
Stage_Inactive = 0,
Stage_FinaleActive = 1,
Stage_FinaleTank1 = 2,
Stage_FinaleTank2 = 3,
Stage_FinaleDuplicatePending = 4,
Stage_TankSplit = 5,
Stage_InactiveFinale = -1
}
int extraTankHP;
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 && g_realSurvivorCount > 4) { if(finaleType == FINALE_STARTED && g_realSurvivorCount > 4) {
g_finaleStage = Stage_FinaleActive; g_finaleStage = Stage_FinaleActive;
@ -702,6 +711,8 @@ 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) {
// Only run when we aren't touching tanks (ABM in control)
if(cvEPISpecialSpawning.IntValue & 4) return;
int user = event.GetInt("userid"); int user = event.GetInt("userid");
int tank = GetClientOfUserId(user); int tank = GetClientOfUserId(user);
if(tank > 0 && IsFakeClient(tank) && g_realSurvivorCount > 4 && hExtraFinaleTank.IntValue > 0) { if(tank > 0 && IsFakeClient(tank) && g_realSurvivorCount > 4 && hExtraFinaleTank.IntValue > 0) {
@ -779,7 +790,6 @@ void Event_GameStart(Event event, const char[] name, bool dontBroadcast) {
g_startCampaignGiven = false; g_startCampaignGiven = false;
g_extraKitsAmount = 0; g_extraKitsAmount = 0;
g_extraKitsStart = 0; g_extraKitsStart = 0;
abmExtraCount = 0;
g_realSurvivorCount = 0; g_realSurvivorCount = 0;
g_survivorCount = 0; g_survivorCount = 0;
hMinPlayers.IntValue = 4; hMinPlayers.IntValue = 4;
@ -847,7 +857,7 @@ void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
if(!g_isGamemodeAllowed) return; if(!g_isGamemodeAllowed) return;
int userid = event.GetInt("userid"); int userid = event.GetInt("userid");
int client = GetClientOfUserId(user); int client = GetClientOfUserId(userid);
UpdateSurvivorCount(); UpdateSurvivorCount();
if(GetClientTeam(client) == 2 && !IsFakeClient(client) && !L4D_IsFirstMapInScenario()) { if(GetClientTeam(client) == 2 && !IsFakeClient(client) && !L4D_IsFirstMapInScenario()) {
// Start door timeout: // Start door timeout:
@ -891,8 +901,8 @@ Action Timer_DropSurvivor(Handle h, int client) {
if(playerData[client].state == State_PendingEmpty) { if(playerData[client].state == State_PendingEmpty) {
playerData[client].state = State_Empty; playerData[client].state = State_Empty;
if(hMinPlayers != null) { if(hMinPlayers != null) {
PrintToServer("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount); PrintToServer("[EPI] Dropping survivor %d. hMinPlayers-pre:%d g_survivorCount=%d g_realSurvivorCount=%d", client, hMinPlayers.IntValue, g_survivorCount, g_realSurvivorCount);
PrintToConsoleAll("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount); PrintToConsoleAll("[EPI] Dropping survivor %d. hMinPlayers-pre:%d g_survivorCount=%d g_realSurvivorCount=%d", client, hMinPlayers.IntValue, g_survivorCount, g_realSurvivorCount);
hMinPlayers.IntValue = g_realSurvivorCount; hMinPlayers.IntValue = g_realSurvivorCount;
if(hMinPlayers.IntValue < 4) { if(hMinPlayers.IntValue < 4) {
hMinPlayers.IntValue = 4; hMinPlayers.IntValue = 4;
@ -1167,12 +1177,13 @@ Timer_Director then checks if highest flow achieved (never decreases) is >= each
public void OnConfigsExecuted() { public void OnConfigsExecuted() {
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) { if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = g_realSurvivorCount;
} }
} }
public void OnMapEnd() { public void OnMapEnd() {
g_isFinaleEnding = false;
// Reset the ammo packs, deleting the internal arraylist // Reset the ammo packs, deleting the internal arraylist
for(int i = 0; i < g_ammoPacks.Length; i++) { for(int i = 0; i < g_ammoPacks.Length; i++) {
ArrayList clients = g_ammoPacks.Get(i, AMMOPACK_USERS); ArrayList clients = g_ammoPacks.Get(i, AMMOPACK_USERS);
@ -1198,13 +1209,19 @@ public Action Timer_Populate(Handle h) {
return Plugin_Continue; 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) { public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, int client, float time) {
if(!g_isCheckpointReached && client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { if(!g_isCheckpointReached && client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) {
g_isCheckpointReached = true; g_isCheckpointReached = true;
abmExtraCount = GetSurvivorsCount(); UpdateSurvivorCount();
if(abmExtraCount > 4) { if(IsEPIActive()) {
int extraPlayers = abmExtraCount - 4; int extraPlayers = g_survivorCount - 4;
float averageTeamHP = GetAverageHP(); float averageTeamHP = GetAverageHP();
if(averageTeamHP <= 30.0) extraPlayers += (extraPlayers / 2); //if perm. health < 30, give an extra 4 on top of the extra if(averageTeamHP <= 30.0) extraPlayers += (extraPlayers / 2); //if perm. health < 30, give an extra 4 on top of the extra
else if(averageTeamHP <= 50.0) extraPlayers += (extraPlayers / 3); //if the team's average health is less than 50 (permament) then give another else if(averageTeamHP <= 50.0) extraPlayers += (extraPlayers / 3); //if the team's average health is less than 50 (permament) then give another
@ -1220,9 +1237,9 @@ public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, i
g_extraKitsStart = g_extraKitsAmount; g_extraKitsStart = g_extraKitsAmount;
hMinPlayers.IntValue = abmExtraCount; hMinPlayers.IntValue = g_survivorCount;
PrintToConsoleAll("CHECKPOINT REACHED BY %N | EXTRA KITS: %d", client, extraPlayers); PrintToConsoleAll("[EPI] CHECKPOINT REACHED BY %N | EXTRA KITS: %d", client, extraPlayers);
PrintToServer("Player entered saferoom. Providing %d extra kits", g_extraKitsAmount); PrintToServer("[EPI] Player entered saferoom. Providing %d extra kits", g_extraKitsAmount);
} }
} }
} }
@ -1235,12 +1252,13 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
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", g_g_extraKitsAmount); PrintToServer("Map transition | %d Extra Kits", g_extraKitsAmount);
#endif #endif
isLateLoaded = false; isLateLoaded = false;
g_extraKitsStart = g_extraKitsAmount; g_extraKitsStart = g_extraKitsAmount;
abmExtraCount = GetRealSurvivorsCount(); // Update g_survivorCount, people may have dipped right before transition
g_prevPlayerCount = GetRealSurvivorsCount(); UpdateSurvivorCount();
g_prevPlayerCount = g_survivorCount;
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
@ -1414,7 +1432,7 @@ void UnlockDoor(int flag) {
Action Timer_UpdateHud(Handle h) { Action Timer_UpdateHud(Handle h) {
if(hEPIHudState.IntValue == 1 && !isCoop) { if(hEPIHudState.IntValue == 1 && !isCoop) {
PrintToServer("[EPI] Gamemode no longer coop, stopping (hudState=%d, abmExtraCount=%d)", hEPIHudState.IntValue, abmExtraCount); PrintToServer("[EPI] Gamemode no longer coop, stopping (hudState=%d, g_survivorCount=%d, g_realSurvivorCount=%d)", hEPIHudState.IntValue, g_survivorCount, g_realSurvivorCount);
L4D2_RunScript(HUD_SCRIPT_CLEAR); L4D2_RunScript(HUD_SCRIPT_CLEAR);
updateHudTimer = null; updateHudTimer = null;
return Plugin_Stop; return Plugin_Stop;
@ -1455,6 +1473,8 @@ Action Timer_UpdateHud(Handle h) {
} else { } else {
Format(prefix, HUD_NAME_LENGTH, "%s", playerData[client].nameCache[playerData[client].scrollIndex]); 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(); playerData[client].AdvanceScroll();
@ -1478,7 +1498,6 @@ Action Timer_UpdateHud(Handle h) {
} }
if(players[0] == '\0') { if(players[0] == '\0') {
PrintToServer("[EPI] No players online", hEPIHudState.IntValue, abmExtraCount);
L4D2_RunScript(HUD_SCRIPT_CLEAR); L4D2_RunScript(HUD_SCRIPT_CLEAR);
updateHudTimer = null; updateHudTimer = null;
return Plugin_Stop; return Plugin_Stop;
@ -1507,9 +1526,9 @@ void PopulateItems() {
g_areItemsPopulated = true; g_areItemsPopulated = true;
//Generic Logic //Generic Logic
float percentage = hExtraItemBasePercentage.FloatValue * survivors; float percentage = hExtraItemBasePercentage.FloatValue * g_realSurvivorCount;
PrintToServer("[EPI] Populating extra items based on player count (%d) | Percentage %.2f%%", survivors, percentage * 100); PrintToServer("[EPI] Populating extra items based on player count (%d) | Percentage %.2f%%", g_realSurvivorCount, percentage * 100);
PrintToConsoleAll("[EPI] Populating extra items based on player count (%d) | Percentage %.2f%%", survivors, percentage * 100); PrintToConsoleAll("[EPI] Populating extra items based on player count (%d) | Percentage %.2f%%", g_realSurvivorCount, percentage * 100);
char classname[64]; char classname[64];
int affected = 0; int affected = 0;
@ -1544,7 +1563,7 @@ void PopulateItems() {
continue; continue;
} }
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(g_survivorCount) * (float(spawnCount)/4.0) - spawnCount);
bool hasSpawner; bool hasSpawner;
while(extraAmount > 0) { while(extraAmount > 0) {
//FIXME: spawner is sometimes invalid entity. Ref needed? //FIXME: spawner is sometimes invalid entity. Ref needed?
@ -1568,7 +1587,7 @@ void PopulateItems() {
/// Stocks /// Stocks
//////////////////////////////////// ////////////////////////////////////
bool IsGamemodeAllowed() { bool IsGamemodeAllowed() {
char buffer[64], curGamemode; char buffer[128];
cvEPIGamemodes.GetString(buffer, sizeof(buffer)); cvEPIGamemodes.GetString(buffer, sizeof(buffer));
return StrContains(buffer, g_currentGamemode, false) > -1; return StrContains(buffer, g_currentGamemode, false) > -1;
} }
@ -1665,9 +1684,11 @@ bool DoesInventoryDiffer(int client) {
return currentPrimary != storedPrimary || currentSecondary != storedSecondary; return currentPrimary != storedPrimary || currentSecondary != storedSecondary;
} }
bool IsEPIActive() {
return g_realSurvivorCount > 4 && IsGamemodeAllowed();
}
void UpdateSurvivorCount() { void UpdateSurvivorCount() {
return DEBUG_FORCE_PLAYERS;
#endif
int countTotal = 0, countReal = 0, countActive = 0; int countTotal = 0, countReal = 0, countActive = 0;
#if !defined DEBUG_FORCE_PLAYERS #if !defined DEBUG_FORCE_PLAYERS
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
@ -1676,7 +1697,7 @@ void UpdateSurvivorCount() {
countReal++; countReal++;
} }
countTotal++; countTotal++;
if(pdata[i].state == State_Active) { if(playerData[i].state == State_Active) {
countActive++; countActive++;
} }
} }
@ -1694,7 +1715,7 @@ void UpdateSurvivorCount() {
// Update friendly fire values to reduce accidental FF in crowded corridors // Update friendly fire values to reduce accidental FF in crowded corridors
ConVar friendlyFireFactor = GetActiveFriendlyFireFactor(); ConVar friendlyFireFactor = GetActiveFriendlyFireFactor();
// TODO: Get previous default // TODO: Get previous default
friendlyFireFactor.FloatValue = friendlyFireFactor.FloatValue - ((newCount - 4) * cvFFDecreaseRate.FloatValue); friendlyFireFactor.FloatValue = friendlyFireFactor.FloatValue - ((g_realSurvivorCount - 4) * cvFFDecreaseRate.FloatValue);
if(friendlyFireFactor.FloatValue < 0.0) { if(friendlyFireFactor.FloatValue < 0.0) {
friendlyFireFactor.FloatValue = 0.01; friendlyFireFactor.FloatValue = 0.01;
} }

View file

@ -3,10 +3,9 @@
//#define DEBUG //#define DEBUG
#define MAIN_TIMER_INTERVAL_S 4.0 #define MAIN_TIMER_INTERVAL_S 3.5
#define PLUGIN_VERSION "1.0" #define PLUGIN_VERSION "1.0"
#define ANTI_RUSH_DEFAULT_FREQUENCY 20.0 #define THROWITALL_INTERVAL 30.0
#define ANTI_RUSH_FREQ_INC 0.75
#include <sourcemod> #include <sourcemod>
#include <sdktools> #include <sdktools>
@ -67,8 +66,6 @@ public void OnPluginStart() {
delete data; delete data;
hAllowEnemyTeam = CreateConVar("sm_ftt_select_enemy", "0", "Allow applying trolls to enemy teams", FCVAR_NONE, true, 0.0, true, 1.0); hAllowEnemyTeam = CreateConVar("sm_ftt_select_enemy", "0", "Allow applying trolls to enemy teams", FCVAR_NONE, true, 0.0, true, 1.0);
hThrowItemInterval = CreateConVar("sm_ftt_throw_interval", "30", "The interval in seconds to throw items. 0 to disable", FCVAR_NONE, true, 0.0);
hThrowItemInterval.AddChangeHook(Change_ThrowInterval);
hAutoPunish = CreateConVar("sm_ftt_autopunish_action", "0", "Setup automatic punishment of players. Add bits together\n0=Disabled, 1=Tank magnet, 2=Special magnet, 4=Swarm, 8=InstantVomit", FCVAR_NONE, true, 0.0); hAutoPunish = CreateConVar("sm_ftt_autopunish_action", "0", "Setup automatic punishment of players. Add bits together\n0=Disabled, 1=Tank magnet, 2=Special magnet, 4=Swarm, 8=InstantVomit", FCVAR_NONE, true, 0.0);
hAutoPunishExpire = CreateConVar("sm_ftt_autopunish_expire", "0", "How many minutes of gametime until autopunish is turned off? 0 for never.", FCVAR_NONE, true, 0.0); hAutoPunishExpire = CreateConVar("sm_ftt_autopunish_expire", "0", "How many minutes of gametime until autopunish is turned off? 0 for never.", FCVAR_NONE, true, 0.0);
hMagnetTargetMode = CreateConVar("sm_ftt_magnet_targetting", "6", "How does the specials target players. Add bits together\n0=Incapped are ignored, 1=Specials targets incapped, 2=Tank targets incapped 4=Witch targets incapped"); hMagnetTargetMode = CreateConVar("sm_ftt_magnet_targetting", "6", "How does the specials target players. Add bits together\n0=Incapped are ignored, 1=Specials targets incapped, 2=Tank targets incapped 4=Witch targets incapped");
@ -104,6 +101,7 @@ public void OnPluginStart() {
RegAdminCmd("sm_healbots", Command_HealTarget, ADMFLAG_BAN, "Make bots heal a player"); RegAdminCmd("sm_healbots", Command_HealTarget, ADMFLAG_BAN, "Make bots heal a player");
RegAdminCmd("sm_rff", Command_SetReverseFF, ADMFLAG_KICK, "Set reverse FF on player"); RegAdminCmd("sm_rff", Command_SetReverseFF, ADMFLAG_KICK, "Set reverse FF on player");
RegAdminCmd("sm_magnet", Command_SetMagnetShortcut, ADMFLAG_KICK, ""); RegAdminCmd("sm_magnet", Command_SetMagnetShortcut, ADMFLAG_KICK, "");
RegAdminCmd("sm_csplat", Command_CarSplat, ADMFLAG_KICK, "");
HookEvent("player_spawn", Event_PlayerSpawn); HookEvent("player_spawn", Event_PlayerSpawn);
HookEvent("player_first_spawn", Event_PlayerFirstSpawn); HookEvent("player_first_spawn", Event_PlayerFirstSpawn);
@ -133,16 +131,8 @@ public void OnPluginStart() {
// CVAR CHANGES // CVAR CHANGES
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
public void Change_ThrowInterval(ConVar convar, const char[] oldValue, const char[] newValue) {
//If a throw timer exists (someone has mode 11), destroy & recreate w/ new interval
if(hThrowTimer != INVALID_HANDLE) {
delete hThrowTimer;
hThrowTimer = CreateTimer(convar.FloatValue, Timer_ThrowTimer, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
}
}
// Turn on bot FF if bot defend enabled // Turn on bot FF if bot defend enabled
public void Change_BotDefend(ConVar convar, const char[] oldValue, const char[] newValue) { void Change_BotDefend(ConVar convar, const char[] oldValue, const char[] newValue) {
hSbFriendlyFire.IntValue = convar.IntValue != 0; hSbFriendlyFire.IntValue = convar.IntValue != 0;
} }
@ -221,3 +211,24 @@ Action OnWitchActionUpdate(BehaviorAction action, int actor, float interval, Act
result.SetReason("FTT"); result.SetReason("FTT");
return Plugin_Handled; return Plugin_Handled;
} }
void HideHUD(int victim, float timeout = 0.0) {
SetEntProp(victim, Prop_Send, "m_iHideHUD", 64);
if(timeout > 0.0)
CreateTimer(timeout, Timer_RestoreHud, GetClientUserId(victim));
}
void HideHUDRandom(int victim) {
float timeout = 0.0;
if(~Trolls[t_hideHUDIndex].activeFlagClients[victim] & 3) {
if(Trolls[t_hideHUDIndex].activeFlagClients[victim] & 1) {
if(GetURandomFloat() > 0.2)
return;
timeout = GetRandomFloat(1.0, 5.0);
} else if(Trolls[t_hideHUDIndex].activeFlagClients[victim] & 2) {
if(GetURandomFloat() < 0.5)
return;
timeout = GetRandomFloat(5.0, 10.0);
}
}
HideHUD(victim, timeout);
}