Created
June 14, 2017 03:24
-
-
Save hjhee/054b13387b14820d05b519d76c3812b2 to your computer and use it in GitHub Desktop.
improved ASDC for l4d2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma semicolon 1 | |
#include <sourcemod> | |
#define PLUGIN_VERSION "1.3" | |
public Plugin myinfo= | |
{ | |
name="L4D2 Automatic Scaling Difficulty Controller(ASDC)", | |
author="Fwoosh/harryhecanada/hjhee", | |
description="Difficulty Controller for the L4D2 AI director, automatically spawns extra zombies to increase difficulty.", | |
version=PLUGIN_VERSION, | |
url="www.AlliedMods.net" | |
} | |
//Default limits for maximum tanks and witches, with seperate counters for each | |
#define MAX_WITCHES 2 | |
#define MAX_TANKS 1 | |
//Counting Variables | |
int witchcount=0; | |
int tankcount=0; | |
// Handle ASDCunload=INVALID_HANDLE; | |
//Handles for type selection | |
Handle ASDCtypeMOB=INVALID_HANDLE; | |
Handle ASDCtypetank=INVALID_HANDLE; | |
Handle ASDCtypewitch=INVALID_HANDLE; | |
//Tick per Time base and multiplier | |
Handle ASDCbase=INVALID_HANDLE; | |
Handle ASDCmult=INVALID_HANDLE; | |
Handle ASDCCImult=INVALID_HANDLE; | |
Handle ASDCFINmult=INVALID_HANDLE; | |
//Common infected amount handlers | |
Handle ASDCcommons=INVALID_HANDLE; | |
Handle ASDCcommonsbackground=INVALID_HANDLE; | |
Handle ASDCmob=INVALID_HANDLE; | |
//Intervals | |
Handle ASDCMOBinterval=INVALID_HANDLE; | |
Handle ASDCTankinterval=INVALID_HANDLE; | |
Handle ASDCWitchinterval=INVALID_HANDLE; | |
//base Tick timer | |
Handle mTimer=INVALID_HANDLE; | |
//Ticks for each type of spawn | |
float MOBtick=0.0; | |
float Tanktick=0.0; | |
float Witchtick=0.0; | |
// int l4d2flag=-1; | |
int finale_enable=0; | |
int finaleflag=1; | |
int g_reserved_common; | |
bool flag_reserved_common; | |
Handle cv_reserved_common=INVALID_HANDLE; | |
Handle cv_pain_pills_decay_rate=INVALID_HANDLE; | |
Handle cv_z_common_limit=INVALID_HANDLE; | |
Handle cv_z_background_limit=INVALID_HANDLE; | |
Handle cv_z_wandering_density=INVALID_HANDLE; | |
Handle cv_z_mob_population_density=INVALID_HANDLE; | |
Handle cv_z_mega_mob_size=INVALID_HANDLE; | |
Handle cv_z_mob_spawn_max_size=INVALID_HANDLE; | |
Handle cv_z_mob_spawn_finale_size=INVALID_HANDLE; | |
bool type_mob; | |
bool type_tank; | |
bool type_witch; | |
float interval_mob; | |
float interval_tank; | |
float interval_witch; | |
float asdc_base; | |
float asdc_mult; | |
float asdc_cimult; | |
float asdc_finmult; | |
float asdc_commons; | |
float asdc_commonsbackground; | |
float asdc_mob; | |
int g_alive=4; | |
public OnPluginStart(){ | |
cv_reserved_common=FindConVar("z_reserved_wanderers"); | |
cv_pain_pills_decay_rate=FindConVar("pain_pills_decay_rate"); | |
cv_z_common_limit=FindConVar("z_common_limit"); | |
cv_z_background_limit=FindConVar("z_background_limit"); | |
cv_z_wandering_density=FindConVar("z_wandering_density"); | |
cv_z_mob_population_density=FindConVar("z_mob_population_density"); | |
cv_z_mega_mob_size=FindConVar("z_mega_mob_size"); | |
cv_z_mob_spawn_max_size=FindConVar("z_mob_spawn_max_size"); | |
cv_z_mob_spawn_finale_size=FindConVar("z_mob_spawn_finale_size"); | |
CreateConVar("ASDCversion", PLUGIN_VERSION, "L4D2 Monster Bots Version", FCVAR_DONTRECORD); | |
//Type Enables | |
ASDCtypeMOB=CreateConVar("ASDCtypeMOB", "1", "Is Mobs of Common Infected wave spawn on (1/0). Set to 0 to disable.", 0, true, 0.0); | |
ASDCtypetank=CreateConVar("ASDCtypetank", "0", "Is tank spawn on (1/0). Set to 0 to disable.", 0, true, 0.0); | |
ASDCtypewitch=CreateConVar("ASDCtypewitch", "1", "Is witch spawn on (1/0). Set to 0 to disable.", 0, true, 0.0); | |
//Math | |
ASDCbase=CreateConVar("ASDCbase", "2", "Base time scale for difficulty controller. Set base and mult to 0 to turn off ASDC.", 0, true, 0.0); | |
ASDCmult=CreateConVar("ASDCmult", "2", "Multiplication tuning for difficulty controller. Set base and mult to 0 to turn off ASDC.", 0, true, 0.0); | |
ASDCCImult=CreateConVar("ASDCCImult", "16.5", "Multiplication tuning for CI difficulty controller. Set base and mult to 0 to turn off CI part of ASDC.", 0, true, 0.0); | |
ASDCFINmult=CreateConVar("ASDCFINmult", "1.51", "Multiplication tuning for CI finale difficulty controller. Set base and mult to 0 to turn off CI finale part of ASDC.", 0, true, 0.0); | |
//Base Intervals | |
ASDCMOBinterval=CreateConVar("ASDCMOBinterval", "200", "How many ticks(unmodified seconds) till another Mob of CI spawns", 0, true, 0.0); | |
ASDCTankinterval=CreateConVar("ASDCtankinterval", "180", "How many ticks(unmodified seconds) till another tank spawns", 0, true, 0.0); | |
ASDCWitchinterval=CreateConVar("ASDCwitchinterval", "120", "How many ticks(unmodified seconds) till another witch spawns", 0, true, 0.0); | |
//CI Controls | |
ASDCcommons=CreateConVar("ASDCcommons", "10", "Number of CI per person in a CI zombie wave.", 0, true, 0.0); | |
ASDCcommonsbackground=CreateConVar("ASDCcommonsbackground", "31", "Number of CI per person in a CI zombie wave.", 0, true, 0.0); | |
ASDCmob=CreateConVar("ASDCmob", "8.9", "Number of CI per person in a Mega CI zombie wave.", 0, true, 0.0); | |
HookEvent("round_start", Event_RoundStart); | |
HookEvent("round_end", Event_RoundEnd); | |
HookEvent("map_transition", Event_RoundEnd); | |
HookEvent("round_start_pre_entity", Event_RoundEnd); | |
HookEvent("round_start_post_nav", Event_RoundEnd); | |
//HookEvent("infected_death", Game_Start); | |
flag_reserved_common=true; | |
init_convar(); | |
HookConVarChange(cv_reserved_common, reserved_common_changed); | |
HookConVarChange(ASDCbase, convar_changed); | |
HookConVarChange(ASDCmult, convar_changed); | |
HookConVarChange(ASDCCImult, convar_changed); | |
HookConVarChange(ASDCFINmult, convar_changed); | |
HookConVarChange(ASDCcommons, convar_changed); | |
HookConVarChange(ASDCcommonsbackground, convar_changed); | |
HookConVarChange(ASDCmob, convar_changed); | |
HookConVarChange(ASDCtypeMOB, convar_changed); | |
HookConVarChange(ASDCtypetank, convar_changed); | |
HookConVarChange(ASDCtypewitch, convar_changed); | |
HookConVarChange(ASDCMOBinterval, convar_changed); | |
HookConVarChange(ASDCTankinterval, convar_changed); | |
HookConVarChange(ASDCWitchinterval, convar_changed); | |
// AutoExecConfig(false, "l4d2_ASDCconfig"); | |
} | |
void init_convar(){ | |
if(cv_reserved_common!=null) | |
g_reserved_common=GetConVarInt(cv_reserved_common); | |
if(g_reserved_common<0) | |
g_reserved_common=0; | |
asdc_base=GetConVarFloat(ASDCbase); | |
asdc_mult=GetConVarFloat(ASDCmult); | |
asdc_cimult=GetConVarFloat(ASDCCImult); | |
asdc_finmult=GetConVarFloat(ASDCFINmult); | |
asdc_commons=GetConVarFloat(ASDCcommons); | |
asdc_commonsbackground=GetConVarFloat(ASDCcommonsbackground); | |
asdc_mob=GetConVarFloat(ASDCmob); | |
type_mob=GetConVarBool(ASDCtypeMOB); | |
type_tank=GetConVarBool(ASDCtypetank); | |
type_witch=GetConVarBool(ASDCtypewitch); | |
interval_mob=GetConVarFloat(ASDCMOBinterval); | |
interval_tank=GetConVarFloat(ASDCTankinterval); | |
interval_witch=GetConVarFloat(ASDCWitchinterval); | |
} | |
public convar_changed(ConVar convar, const char[] oldValue, const char[] newValue){ | |
asdc_base=GetConVarFloat(ASDCbase); | |
asdc_mult=GetConVarFloat(ASDCmult); | |
asdc_cimult=GetConVarFloat(ASDCCImult); | |
asdc_finmult=GetConVarFloat(ASDCFINmult); | |
asdc_commons=GetConVarFloat(ASDCcommons); | |
asdc_commonsbackground=GetConVarFloat(ASDCcommonsbackground); | |
asdc_mob=GetConVarFloat(ASDCmob); | |
type_mob=GetConVarBool(ASDCtypeMOB); | |
type_tank=GetConVarBool(ASDCtypetank); | |
type_witch=GetConVarBool(ASDCtypewitch); | |
interval_mob=GetConVarFloat(ASDCMOBinterval); | |
interval_tank=GetConVarFloat(ASDCTankinterval); | |
interval_witch=GetConVarFloat(ASDCWitchinterval); | |
} | |
public reserved_common_changed(ConVar convar, const char[] oldValue, const char[] newValue){ | |
if(flag_reserved_common){ | |
if(cv_reserved_common!=null) | |
g_reserved_common=GetConVarInt(cv_reserved_common); | |
if(g_reserved_common<0) | |
g_reserved_common=0; | |
} | |
} | |
public Action Event_RoundStart(Handle event, const char[] name, bool dontBroadcast){ | |
char mapname[30]; | |
//Get map name | |
GetCurrentMap(mapname, sizeof(mapname)); | |
//Increase default limits to match spawns | |
SetConVarInt(FindConVar("z_mob_min_notify_count"), RoundToCeil(asdc_commons)); | |
flag_reserved_common=true; | |
finaleflag=1; | |
if (StrEqual(mapname,"c8m5_rooftop")|| | |
StrEqual(mapname,"c9m2_lots")|| | |
StrEqual(mapname,"c10m5_houseboat")|| | |
StrEqual(mapname,"c11m5_runway")|| | |
StrEqual(mapname,"c12m5_cornfield")|| | |
StrEqual(mapname,"c7m3_port")|| | |
StrEqual(mapname,"c1m4_atrium")|| | |
StrEqual(mapname,"c6m3_port")|| | |
StrEqual(mapname,"c2m5_concert")|| | |
StrEqual(mapname,"c3m4_plantation")|| | |
StrEqual(mapname,"c4m5_milltown_escape")|| | |
StrEqual(mapname,"c5m5_bridge")|| | |
StrEqual(mapname,"c13m4_cutthroatcreek")){ | |
finaleflag=0; | |
} | |
finale_enable=0; | |
if (StrEqual(mapname,"c8m5_rooftop")|| | |
StrEqual(mapname,"c9m2_lots")|| | |
StrEqual(mapname,"c10m5_houseboat")|| | |
StrEqual(mapname,"c11m5_runway")|| | |
StrEqual(mapname,"c12m5_cornfield")|| | |
StrEqual(mapname,"c7m3_port")|| | |
// StrEqual(mapname,"c1m4_atrium")|| | |
// StrEqual(mapname,"c6m3_port")|| | |
StrEqual(mapname,"c2m5_concert")|| | |
// StrEqual(mapname,"c3m4_plantation")|| | |
StrEqual(mapname,"c4m5_milltown_escape")|| | |
StrEqual(mapname,"c5m5_bridge") | |
//StrEqual(mapname,"c13m4_cutthroatcreek") | |
){ | |
finale_enable=1; | |
} | |
SetConVarInt(FindConVar("z_mob_spawn_min_size"), RoundToCeil(asdc_commons)); | |
SetConVarInt(cv_z_mob_spawn_max_size, RoundToCeil(asdc_mob*5)); | |
SetConVarInt(cv_z_mob_spawn_finale_size, RoundToCeil(asdc_mob*2)); | |
//Not sure about ASDCing background limit, seemed to crash client game in certain situations. | |
SetConVarInt(cv_z_background_limit, RoundToCeil(asdc_commonsbackground*8)); | |
} | |
public Action L4D_OnGetScriptValueInt(const String:key[], &retVal){ | |
if(finaleflag==0 && finale_enable){ | |
int val=retVal; | |
if(StrEqual(key, "CommonLimit")) | |
val=RoundToCeil(7.5*g_alive); | |
if(val!=retVal){ | |
retVal=val; | |
return Plugin_Handled; | |
} | |
} | |
return Plugin_Continue; | |
} | |
public Action Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast){ | |
//Kill timer to prevent spawn during load screen | |
if(mTimer){ | |
KillTimer(mTimer); | |
mTimer=INVALID_HANDLE; | |
} | |
if(asdc_mult>0&&RoundToCeil(asdc_base)>0){ | |
for(new i=1; i<=MaxClients; i++){ | |
if(IsClientInGame(i)&&IsFakeClient(i)&&GetClientTeam(i)==3&&!IsTank(i)){ | |
KickClient(i); | |
} | |
} | |
} | |
} | |
// public Action Game_Start(Handle:event, const String:name[], bool:dontBroadcast){ | |
public OnClientPostAdminCheck(int client){ | |
//create tick timer when a zombie dies, had to be this way because other event hooks not reliable. | |
if(!mTimer){ | |
mTimer=CreateTimer(3.0, TimerUpdate, _, TIMER_REPEAT); | |
if(finaleflag==0){ | |
flag_reserved_common=false; | |
SetConVarInt(cv_reserved_common, 0); | |
flag_reserved_common=true; | |
}else{ | |
flag_reserved_common=false; | |
SetConVarInt(cv_reserved_common, g_reserved_common); | |
flag_reserved_common=true; | |
} | |
} | |
} | |
public Action TimerUpdate(Handle:timer){ | |
if(!IsServerProcessing()) return; | |
//Calculate tick/sec based on player health | |
float temp=3*(asdc_base+ASDC(2)*asdc_mult); | |
//Increment ticks | |
if(type_tank){ | |
Tanktick+=temp; | |
} | |
if(type_witch){ | |
Witchtick+=temp; | |
} | |
if(type_mob){ | |
MOBtick+=temp; | |
} | |
CountMonsters(); | |
//Spawn SI | |
if(Tanktick>=interval_tank){ | |
if(tankcount<MAX_TANKS){ | |
new tankbot=CreateFakeClient("Tank"); | |
if(tankbot>0){ | |
//PrintToServer("Spawning Tank."); | |
SpawnCommand(tankbot, "z_spawn_old", "tank auto"); | |
tankcount++; | |
} | |
} | |
Tanktick=0.0; | |
} | |
if(Witchtick>=interval_witch){ | |
if(witchcount<MAX_WITCHES){ | |
new witchbot=CreateFakeClient("Witch"); | |
if(witchbot>0){ | |
//PrintToServer("Spawning Witch."); | |
SpawnCommand(witchbot, "z_spawn_old", "witch auto"); | |
witchcount++; | |
} | |
} | |
Witchtick=0.0; | |
} | |
if(MOBtick>=interval_mob){ | |
new spawnbot=CreateFakeClient("Mob"); | |
SpawnCommand(spawnbot, "z_spawn_old", "mob auto"); | |
MOBtick=0.0; | |
} | |
} | |
public Action Kickbot(Handle:timer, any:client){ | |
if(IsFakeClient(client)) | |
KickClientEx(client); | |
} | |
CountMonsters(){ | |
witchcount=0; | |
tankcount=0; | |
decl String:classname[32]; | |
for(new i=1; i<=MaxClients; i++){ | |
if(IsClientInGame(i)&&IsFakeClient(i)&&GetClientTeam(i)==3){ | |
GetClientModel(i, classname, sizeof(classname)); | |
//Special cases for counting tank and witch | |
if(StrContains(classname, "witch")&&ASDCtypetank){ | |
witchcount++; | |
} | |
if(StrContains(classname, "hulk")&&ASDCtypewitch){ | |
tankcount++; | |
} | |
} | |
} | |
} | |
stock bool IsPlayerIncapped(Client){ | |
if(GetEntProp(Client, Prop_Send, "m_isIncapacitated")==1) | |
return true; | |
else | |
return false; | |
} | |
stock GetClientRealHealth(client){ | |
//First, we get the amount of temporal health the client has | |
float buffer=GetEntPropFloat(client, Prop_Send, "m_healthBuffer"); | |
//We declare the permanent and temporal health variables | |
float TempHealth; | |
new PermHealth=GetClientHealth(client); | |
//In case the buffer is 0 or less, we set the temporal health as 0, because the client has not used any pills or adrenaline yet | |
if(buffer<=0.0){ | |
TempHealth=0.0; | |
} | |
//In case it is higher than 0, we proceed to calculate the temporl health | |
else{ | |
//This is the difference between the time we used the temporal item, and the current time | |
float difference=GetGameTime()-GetEntPropFloat(client, Prop_Send, "m_healthBufferTime"); | |
//We get the decay rate from this convar (Note: Adrenaline uses this value) | |
float decay=GetConVarFloat(cv_pain_pills_decay_rate); | |
//This is a constant we create to determine the amount of health. This is the amount of time it has to pass | |
//before 1 Temporal HP is consumed. | |
float constant=1.0/decay; | |
//Then we do the calcs | |
TempHealth=buffer-(difference/constant); | |
} | |
//If the temporal health resulted less than 0, then it is just 0. | |
if(TempHealth<0.0){ | |
TempHealth=0.0; | |
} | |
//Return the value | |
return RoundToFloor(PermHealth+TempHealth); | |
} | |
Float:ASDC(TeamValue){ | |
float health=0.0; | |
new temp=0; | |
new alive=0, cnt=0; | |
float ASDCout=0.0; | |
for(new i=1; i<=MaxClients; i++){ | |
if(IsClientInGame(i)&&GetClientTeam(i)==TeamValue){ | |
if(IsPlayerAlive(i)&&!IsPlayerIncapped(i)){ | |
health+=GetClientRealHealth(i); | |
alive++; | |
} | |
cnt++; | |
} | |
} | |
if(alive==0) | |
alive=4; | |
g_alive=alive; | |
ASDCout=health/(100.0*float(cnt)); | |
//if(l4d2flag>0){ | |
SetConVarInt(cv_z_common_limit, RoundToCeil(7.5*alive)+finaleflag*g_reserved_common); // z_common_limit=8~60 | |
//Set Amount of zombies to spawn in a waves | |
SetConVarInt(cv_z_background_limit, RoundToCeil(asdc_commonsbackground*alive)); | |
//Sets CI density to 1% of ASDC output. | |
health=(asdc_commons*ASDCout*asdc_cimult+asdc_commons)*0.1; | |
SetConVarFloat(cv_z_wandering_density, health); | |
SetConVarFloat(cv_z_mob_population_density, health); | |
temp=RoundToCeil(asdc_mob*alive*ASDCout*asdc_cimult+asdc_mob); | |
SetConVarInt(cv_z_mega_mob_size, temp); | |
temp=RoundToCeil(asdc_commons*1*alive*ASDCout+asdc_commons); | |
SetConVarInt(cv_z_mob_spawn_max_size, temp); | |
temp=RoundToCeil((asdc_mob*alive*alive*ASDCout/cnt*asdc_finmult+asdc_mob)); | |
SetConVarInt(cv_z_mob_spawn_finale_size, temp); | |
//} | |
return ASDCout; | |
} | |
bool IsTank(i){ | |
decl String:classname[32]; | |
GetClientModel(i, classname, sizeof(classname)); | |
if(StrContains(classname, "hulk", false)!=-1) | |
return true; | |
return false; | |
} | |
stock SpawnCommand(client, String:command[], String:arguments[]=""){ | |
if(client){ | |
ChangeClientTeam(client, 3); | |
new flags=GetCommandFlags(command); | |
SetCommandFlags(command, flags & ~FCVAR_CHEAT); | |
FakeClientCommand(client, "%s %s", command, arguments); | |
SetCommandFlags(command, flags|FCVAR_CHEAT); | |
CreateTimer(0.1, Kickbot, client); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment