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