Skip to content

Instantly share code, notes, and snippets.

@hjhee
Created June 14, 2017 03:24
Show Gist options
  • Save hjhee/054b13387b14820d05b519d76c3812b2 to your computer and use it in GitHub Desktop.
Save hjhee/054b13387b14820d05b519d76c3812b2 to your computer and use it in GitHub Desktop.
improved ASDC for l4d2
#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