Left 4 Dead 2/Scripting/Decrypted mutations: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(Improved formatting of mutation6's description)
m (Tank Run's ID is "TankRun", not "Tank Run". Source: "map c1m1_hotel tankrun")
 
(13 intermediate revisions by 2 users not shown)
Line 1: Line 1:
==Decrypted L4D2 Mutation Scripts==
{{LanguageBar}}
These are all official L4D2 mutation scripts and their included scripts. Most scripts have been decrypted on February 26th 2021 with [[L4D2_Vscripts#External_links|L4D2 VScript Editor Beta 0.5 by Cynick]] and may be updated at any time.
{{toc-right}}{{sq}}{{l4d2}} These are all official L4D2 mutation scripts and their included scripts. Most scripts have been decrypted on February 26th 2021 with [[L4D2_Vscripts#External_links|L4D2 VScript Editor Beta 0.5 by Cynick]] and may be updated at any time.


==Valve Mutations==
==Valve Mutations==
Line 33: Line 33:
{
{
weapon_shotgun_spas = "weapon_autoshotgun_spawn"
weapon_shotgun_spas = "weapon_autoshotgun_spawn"
weapon_defibrillator = "weapon_first_aid_kit_spawn"
weapon_defibrillator = "weapon_pain_pills_spawn"
weapon_ammo_pack = "weapon_first_aid_kit_spawn"
weapon_ammo_pack = "weapon_first_aid_kit_spawn"
weapon_sniper_awp = "weapon_hunting_rifle_spawn"
weapon_sniper_awp = "weapon_hunting_rifle_spawn"
Line 94: Line 94:
EntFire( "weapon_*", "AddOutput", "weaponskin -1" );
EntFire( "weapon_*", "AddOutput", "weaponskin -1" );
EntFire( "trigger_upgrade_laser_sight", "Kill" );
EntFire( "trigger_upgrade_laser_sight", "Kill" );
 
foreach( wep, val in DirectorOptions.weaponsToRemove )
foreach( wep, val in DirectorOptions.weaponsToRemove )
EntFire( wep + "_spawn", "Kill" );
EntFire( wep + "_spawn", "Kill" );
foreach( wep, val in DirectorOptions.weaponsToConvert )
foreach( wep, val in DirectorOptions.weaponsToConvert )
{
{
local wep_spawner = null;
for ( local wep_spawner; wep_spawner = Entities.FindByClassname( wep_spawner, wep + "_spawn" ); )
while ( wep_spawner = Entities.FindByClassname( wep_spawner, wep + "_spawn" ) )
{
local spawnTable =
{
origin = wep_spawner.GetOrigin(),
angles = wep_spawner.GetAngles().ToKVString(),
targetname = wep_spawner.GetName(),
count = NetProps.GetPropInt( wep_spawner, "m_itemCount" ),
spawnflags = NetProps.GetPropInt( wep_spawner, "m_spawnflags" )
}
wep_spawner.Kill();
SpawnEntityFromTable(val, spawnTable);
}
}
 
if ( Director.IsL4D1Campaign() )
{
DirectorOptions.WaterSlowsMovement <- false;
 
if ( SessionState.ModeName == "l4d1coop" || SessionState.ModeName == "l4d1vs" )
{
{
if ( wep_spawner.IsValid() )
if ( IsMissionFinalMap() )
{
if ( SessionState.MapName != "c7m3_port" )
{
local finale = Entities.FindByClassname( null, "trigger_finale" );
if ( finale )
NetProps.SetPropInt( finale, "m_type", 0 );
}
}
else
{
{
local spawnTable =
if ( SessionState.MapName == "c10m4_mainstreet" )
{
local relay = Entities.FindByName( null, "forklift_relay" );
if ( relay )
{
EntityOutputs.RemoveOutput( relay, "OnTrigger", "director", "BeginScript", "c10m4_onslaught" );
EntityOutputs.AddOutput( relay, "OnTrigger", "director", "ForcePanicEvent", "", 9.0, -1 );
}
EntFire( "onslaught1", "Kill" );
}
else if ( SessionState.MapName == "c11m4_terminal" )
{
local van = Entities.FindByName( null, "van_button" );
if ( van )
{
EntityOutputs.RemoveOutput( van, "OnPressed", "@director", "", "" );
EntityOutputs.AddOutput( van, "OnPressed", "@director", "ForcePanicEvent", "", 3.0, -1 );
}
local relay = Entities.FindByName( null, "alarm_on_relay" );
if ( relay )
{
EntityOutputs.RemoveOutput( relay, "OnTrigger", "@director", "", "" );
EntityOutputs.RemoveOutput( relay, "OnTrigger", "alarm_safety_relay", "", "" );
EntityOutputs.AddOutput( relay, "OnTrigger", "@director", "ForcePanicEvent", "", 0.0, -1 );
EntityOutputs.AddOutput( relay, "OnTrigger", "alarm_off_relay", "Trigger", "", 15.0, -1 );
}
EntFire( "van_follow_trigger", "Kill" );
EntFire( "van_endscript_relay", "Kill" );
EntFire( "onslaught_hint_trigger", "Kill" );
}
else if ( SessionState.MapName == "c12m3_bridge" )
{
{
origin = wep_spawner.GetOrigin(),
local relay = Entities.FindByName( null, "train_engine_relay" );
angles = wep_spawner.GetAngles().ToKVString(),
if ( relay )
targetname = wep_spawner.GetName(),
{
count = NetProps.GetPropInt( wep_spawner, "m_itemCount" ),
EntityOutputs.RemoveOutput( relay, "OnTrigger", "director", "BeginScript", "c12m3_onslaught" );
spawnflags = NetProps.GetPropInt( wep_spawner, "m_spawnflags" )
EntityOutputs.AddOutput( relay, "OnTrigger", "director", "ForcePanicEvent", "", 2.0, -1 );
}
EntFire( "zombie_spawn1", "Kill" );
EntFire( "onslaught_hint_template", "Kill" );
}
}
wep_spawner.Kill();
else if ( SessionState.MapName == "c12m4_barn" )
SpawnEntityFromTable(val, spawnTable);
EntFire( "window_trigger", "Kill" );
}
}
}
}
}
}
}
if ( SessionState.ModeName == "l4d1coop" || SessionState.ModeName == "l4d1vs" )
 
if ( HasPlayerControlledZombies() )
{
if ( !IsModelPrecached( "models/v_models/weapons/v_claw_smoker_l4d1.mdl" ) )
PrecacheModel( "models/v_models/weapons/v_claw_smoker_l4d1.mdl" );
if ( !IsModelPrecached( "models/v_models/weapons/v_claw_boomer_l4d1.mdl" ) )
PrecacheModel( "models/v_models/weapons/v_claw_boomer_l4d1.mdl" );
if ( !IsModelPrecached( "models/v_models/weapons/v_claw_hunter_l4d1.mdl" ) )
PrecacheModel( "models/v_models/weapons/v_claw_hunter_l4d1.mdl" );
if ( !IsModelPrecached( "models/v_models/weapons/v_claw_hulk_l4d1.mdl" ) )
PrecacheModel( "models/v_models/weapons/v_claw_hulk_l4d1.mdl" );
 
function OnGameEvent_item_pickup( params )
{
{
if ( SessionState.MapName == "c8m5_rooftop" || SessionState.MapName == "c9m2_lots" || SessionState.MapName == "c10m5_houseboat"
local player = GetPlayerFromUserID( params["userid"] );
|| SessionState.MapName == "c11m5_runway" || SessionState.MapName == "c12m5_cornfield" )
 
if ( ( !player ) || ( player.IsSurvivor() ) )
return;
 
local modelName = player.GetModelName();
if ( ( modelName.find( "l4d1" ) != null ) || ( modelName == "models/infected/hulk_dlc3.mdl" ) )
return;
 
local function SetClawModel( modelName )
{
{
local finale = Entities.FindByClassname( null, "trigger_finale" );
local claw = player.GetActiveWeapon();
if ( finale )
local viewmodel = NetProps.GetPropEntity( player, "m_hViewModel" );
NetProps.SetPropInt( finale, "m_type", 0 );
 
if ( ( !claw ) || ( !viewmodel ) )
return;
 
claw.SetModel( modelName );
NetProps.SetPropInt( viewmodel, "m_nModelIndex", NetProps.GetPropInt( claw, "m_nModelIndex" ) );
NetProps.SetPropString( viewmodel, "m_ModelName", modelName );
}
}
else if ( SessionState.MapName == "c10m4_mainstreet" )
 
switch( player.GetZombieType() )
{
{
local relay = Entities.FindByName( null, "forklift_relay" );
case 1:
if ( relay )
{
{
EntityOutputs.RemoveOutput( relay, "OnTrigger", "director", "BeginScript", "c10m4_onslaught" );
player.SetModel( "models/infected/smoker_l4d1.mdl" );
EntityOutputs.AddOutput( relay, "OnTrigger", "director", "ForcePanicEvent", "", 9.0, -1 );
SetClawModel( "models/v_models/weapons/v_claw_smoker_l4d1.mdl" );
break;
}
}
EntFire( "onslaught1", "Kill" );
case 2:
}
else if ( SessionState.MapName == "c11m4_terminal" )
{
local van = Entities.FindByName( null, "van_button" );
if ( van )
{
{
EntityOutputs.RemoveOutput( van, "OnPressed", "@director", "", "" );
player.SetModel( "models/infected/boomer_l4d1.mdl" );
EntityOutputs.AddOutput( van, "OnPressed", "@director", "ForcePanicEvent", "", 3.0, -1 );
SetClawModel( "models/v_models/weapons/v_claw_boomer_l4d1.mdl" );
break;
}
}
local relay = Entities.FindByName( null, "alarm_on_relay" );
case 3:
if ( relay )
{
{
EntityOutputs.RemoveOutput( relay, "OnTrigger", "@director", "", "" );
player.SetModel( "models/infected/hunter_l4d1.mdl" );
EntityOutputs.RemoveOutput( relay, "OnTrigger", "alarm_safety_relay", "", "" );
SetClawModel( "models/v_models/weapons/v_claw_hunter_l4d1.mdl" );
EntityOutputs.AddOutput( relay, "OnTrigger", "@director", "ForcePanicEvent", "", 0.0, -1 );
break;
EntityOutputs.AddOutput( relay, "OnTrigger", "alarm_off_relay", "Trigger", "", 15.0, -1 );
}
}
EntFire( "van_follow_trigger", "Kill" );
case 8:
EntFire( "van_endscript_relay", "Kill" );
}
else if ( SessionState.MapName == "c12m3_bridge" )
{
local relay = Entities.FindByName( null, "train_engine_relay" );
if ( relay )
{
{
EntityOutputs.RemoveOutput( relay, "OnTrigger", "director", "BeginScript", "c12m3_onslaught" );
player.SetModel( "models/infected/hulk_l4d1.mdl" );
EntityOutputs.AddOutput( relay, "OnTrigger", "director", "ForcePanicEvent", "", 2.0, -1 );
SetClawModel( "models/v_models/weapons/v_claw_hulk_l4d1.mdl" );
EntFire( "zombie_spawn1", "Kill" );
break;
}
}
default:
break;
}
}
else if ( SessionState.MapName == "c12m4_barn" )
EntFire( "window_trigger", "Kill" );
}
}
}
}
Line 174: Line 249:
{
{
local player = GetPlayerFromUserID( params["userid"] );
local player = GetPlayerFromUserID( params["userid"] );
 
if ( ( !player ) || ( player.IsSurvivor() ) )
if ( ( !player ) || ( player.IsSurvivor() ) )
return;
return;
 
local modelName = player.GetModelName();
local modelName = player.GetModelName();
if ( ( modelName.find( "l4d1" ) != null ) || ( modelName == "models/infected/hulk_dlc3.mdl" ) )
if ( ( modelName.find( "l4d1" ) != null ) || ( modelName == "models/infected/hulk_dlc3.mdl" ) )
return;
return;
 
switch( player.GetZombieType() )
switch( player.GetZombieType() )
{
{
Line 213: Line 288:
if ( !("userid" in params) )
if ( !("userid" in params) )
return;
return;
 
local victim = GetPlayerFromUserID( params["userid"] );
local victim = GetPlayerFromUserID( params["userid"] );
 
if ( ( !victim ) || ( !victim.IsSurvivor() ) )
if ( ( !victim ) || ( !victim.IsSurvivor() ) )
return;
return;
 
EntFire( "survivor_death_model", "BecomeRagdoll" );
local prevRagdoll = NetProps.GetPropEntity( victim, "m_hRagdoll" );
if ( prevRagdoll != null )
return;
 
local clOrigin = victim.GetOrigin();
 
local ragdoll = null;
// cs_ragdoll can crash if proper netprops aren't set, some future-proofing
// get rid of uninitialized ragdoll if something goes wrong here
try
{
ragdoll = SpawnEntityFromTable( "cs_ragdoll", {} )
NetProps.SetPropVector( ragdoll, "m_vecOrigin", clOrigin );
NetProps.SetPropVector( ragdoll, "m_vecRagdollOrigin", clOrigin );
NetProps.SetPropInt( ragdoll, "m_nModelIndex", NetProps.GetPropInt( victim, "m_nModelIndex" ) );
NetProps.SetPropInt( ragdoll, "m_iTeamNum", NetProps.GetPropInt( victim, "m_iTeamNum" ) );
NetProps.SetPropEntity( ragdoll, "m_hPlayer", victim );
NetProps.SetPropInt( ragdoll, "m_iDeathPose", NetProps.GetPropInt( victim, "m_nSequence" ) );
NetProps.SetPropInt( ragdoll, "m_iDeathFrame", NetProps.GetPropInt( victim, "m_flAnimTime" ) );
NetProps.SetPropInt( ragdoll, "m_bClientSideAnimation", 1 );
NetProps.SetPropInt( ragdoll, "m_iTeamNum", NetProps.GetPropInt( victim, "m_iTeamNum" ) );
NetProps.SetPropInt( ragdoll, "m_nForceBone", NetProps.GetPropInt( victim, "m_nForceBone" ) );
NetProps.SetPropInt( ragdoll, "m_ragdollType", 4 );
NetProps.SetPropInt( ragdoll, "m_survivorCharacter", NetProps.GetPropInt( victim, "m_survivorCharacter" ) );
NetProps.SetPropEntity( victim, "m_hRagdoll", ragdoll );
 
//EntFire( "survivor_death_model", "Kill" );
// EntFire is too slow and you can see one-frame image of death model
for ( local body; body = Entities.FindByClassname( body, "survivor_death_model" ); )
{
body.Kill();
}
}
catch (err)
{
if ( ragdoll != null && ragdoll.IsValid() )
ragdoll.Kill();
 
EntFire( "survivor_death_model", "BecomeRagdoll" );
}
}
}
</source>}}
</source>}}
Line 949: Line 1,063:
Msg("Activating Mutation 15\n");
Msg("Activating Mutation 15\n");


 
DirectorOptions <-
DirectorOptions <-  
{
{
ActiveChallenge = 1
ActiveChallenge = 1


cm_ProhibitBosses = 0
cm_ProhibitBosses = false
cm_CommonLimit = 25
cm_CommonLimit = 25
cm_TankLimit = 1
cm_TankLimit = 1
Line 978: Line 1,091:
function OnGameEvent_survival_round_start( params )
function OnGameEvent_survival_round_start( params )
{
{
local validStartNames = { func_button_timed = "OnTimeUp", func_button = "OnPressed", script_func_button = "OnPressed", trigger_finale = "UseStart", prop_door_rotating = "OnOpen" };
local function GetSurvivalStartEntity()
{
local validIO = { logic_relay = { input = "Trigger", output = "OnTrigger" }, logic_timer = { input = "Enable", output = "OnTimer" } };
local function CheckOutputs( entity, outputName )
{
local ent = entity;
local output = outputName;
while ( ent )
{
local target = null;
local targetOutput = "";
local nElements = EntityOutputs.GetNumElements( ent, output );
for ( local i = 0; i < nElements; i++ )
{
local outputs = {};
EntityOutputs.GetOutputTable( ent, output, outputs, i );
local outputTarget = Entities.FindByName( null, outputs.target );
if ( !outputTarget )
continue;
if ( outputTarget.GetClassname() == "info_director" && outputs.input.find( "PanicEvent" ) != null )
return outputTarget;
else
{
if ( (outputTarget.GetClassname() in validIO) && (outputs.input == validIO[outputTarget.GetClassname()].input) )
{
target = outputTarget;
targetOutput = validIO[outputTarget.GetClassname()].output;
}
}
}
ent = target;
output = targetOutput;
}
return null;
}
foreach( classname, output in validStartNames )
{
for ( local ent; ent = Entities.FindByClassname( ent, classname ); )
{
if ( !EntityOutputs.HasAction( ent, output ) )
continue;
local target = CheckOutputs( ent, output );
if ( (!target) || (target.GetClassname() != "info_director") )
continue;
return ent;
}
}
return null;
}
EntFire( "info_director", "PanicEvent" );
EntFire( "info_director", "PanicEvent" );
 
if ( SessionState.MapName == "c1m2_steets" )
local startEntity = GetSurvivalStartEntity();
EntFire( "store_doors", "Open" );
if ( startEntity )
else if ( SessionState.MapName == "c7m1_docks" )
{
{
EntFire( "tankdoorin", "Open" );
if ( startEntity.GetClassname() == "func_button_timed" || startEntity.GetClassname() == "trigger_finale" )
EntFire( "tankdoorin_button", "Kill" );
{
EntFire( "tank_sound_timer", "Disable" );
local nElements = EntityOutputs.GetNumElements( startEntity, validStartNames[startEntity.GetClassname()] );
EntFire( "doorsound", "PlaySound" );
for ( local i = 0; i < nElements; i++ )
EntFire( "tank_fog", "Enable" );
{
EntFire( "tank_fog", "Disable", "", 0.5 );
local outputs = {};
EntFire( "big_splash", "Start" );
EntityOutputs.GetOutputTable( startEntity, validStartNames[startEntity.GetClassname()], outputs, i );
EntFire( "big_splash", "Stop", "", 2 );
EntFire( outputs.target, outputs.input, outputs.parameter, outputs.delay );
EntFire( "coop_tank", "Trigger" );
}
EntFire( "radio_game_event", "Kill" );
}
EntFire( "tank_door_clip", "Kill" );
else
EntFire( "director", "EnableTankFrustration" );
{
EntFire( "battlefield_cleared", "UnblockNav", "", 60 );
EntFire( startEntity.GetName(), "Unlock" );
EntFire( "tank_car_camera_clip", "Kill" );
EntFire( startEntity.GetName(), "Press" );
EntFire( startEntity.GetName(), "Open" );
}
}
}
else if ( SessionState.MapName == "c11m5_runway" )
EntFire( "planecrash_trigger", "Trigger", "", 16 );
else if ( SessionState.MapName == "c12m2_traintunnel" )
EntFire( "emergency_door", "Open" );
}
}
</source>}}


===Mutation16 - Hunting Party===
function OnGameEvent_round_start_post_nav( params )
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
Msg("Activating Mutation 16\n");
 
 
DirectorOptions <-
{
{
ActiveChallenge = 1
local bRemoveClips = false;
switch( SessionState.MapName )
{
// For comments and elaboration regarding these map fixes, see:
// Collection of Versus Survival & Taaannnk! Mutation stuck spawn fixes
// https://gist.github.com/Tsuey/e8996ad10bb7fb72e7d6b2aaa7bcd5db
// Most of these fixes should eventually be replaced with a fuller clip brush re-work of all base maps for modders/PvP,
// such as we already did for TLS at least with regard to new player SI areas opened on c8m3 (seen with ShowUpdate tool).


cm_SpecialSlotCountdownTime = 15
case "c1m2_streets":
{
EntFire( g_UpdateName + "_yeswayturnpike_clipb", "Kill", null, 30 ); // Delay oddly required
bRemoveClips = true;
break;
}
case "c3m1_plankcountry":
{
EntFire( g_UpdateName + "_meticulous_funcinfclip01", "Kill", null, 30 ); // Delay oddly required
make_axiswarp( "_axiswarp_semitrailer", "y-", 96, "-550 -8 0", "740 8 256", "-9505 10720 155" );
bRemoveClips = true;
break;
}
case "c5m4_quarter":
{
bRemoveClips = true;
break;
}
case "c8m2_subway":
{
make_prop( "dynamic", "_cosmetic_oobstep", "models/props/cs_office/vending_machine.mdl", "7366 3801 270", "90 0 0", "shadow_no", "solid_no" );
break;
}
case "c9m2_lots":
{
kill_entity( Entities.FindByClassnameNearest( "prop_dynamic", Vector( 8521, 5815, 348 ), 1 ) );
break;
}
case "c10m2_drainage":
{
kill_entity( Entities.FindByClassnameNearest( "prop_dynamic", Vector( -8972, -7890, -320 ), 1 ) );
make_ladder( "_ladder_versussurvivalfence1_cloned_tunnelmid", "-9478.5 -7280 -384", "155 45 -410" );
make_ladder( "_ladder_versussurvivalfence2_cloned_tunnelmid", "-9478.5 -7280 -384", "155 0 -410" );
make_ladder( "_ladder_versussurvivalfence3_cloned_tunnelmid", "-9478.5 -7280 -384", "155 -45 -410" );
bRemoveClips = true;
break;
}
case "c10m4_mainstreet":
{
make_clip( "_tankstuck_endbarricade", "SI Players", 1, "-400 -900 0", "400 900 1700", "3822 -3970 0" );
make_clip( "_tankstuck_excesscorner", "SI Players", 1, "-200 -100 0", "200 100 1700", "-2520 -5048 -64" );
break;
}
case "c11m2_offices":
{
kill_funcinfclip( 1043.94 ); // Final street left barricade
break;
}
case "c12m3_bridge":
{
kill_funcinfclip( 311.003 ); // Wrongway at end of train tunnel
break;
}
default:
break;
}
 
if ( bRemoveClips )
EntFire( "func_playerinfected_clip", "Kill" );
}
</source>}}
 
===Mutation16 - Hunting Party===
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
Msg("Activating Mutation 16\n");
 
 
DirectorOptions <-
{
ActiveChallenge = 1
 
cm_SpecialSlotCountdownTime = 15
cm_SpecialRespawnInterval = 1
cm_SpecialRespawnInterval = 1
cm_MaxSpecials = 4
cm_MaxSpecials = 4
Line 1,161: Line 1,398:
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 19\n");
Msg("Activating Mutation 19\n");


DirectorOptions <-
DirectorOptions <-
Line 1,168: Line 1,404:


// cm_frustrationTimer = 0
// cm_frustrationTimer = 0
cm_TankRun = 1
cm_TankRun = true
cm_ShouldHurry = 1
cm_ShouldHurry = true
cm_AutoSpawnInfectedGhosts = 1
cm_AutoSpawnInfectedGhosts = true


PreferredSpecialDirection = SPAWN_BEHIND_SURVIVORS
PreferredSpecialDirection = SPAWN_BEHIND_SURVIVORS
BehindSurvivorsSpawnDistance = 2000
SpawnBehindSurvivorsDistance = 2000
TankRunSpawnDelay = 15
TankRunSpawnDelay = 15


Line 1,182: Line 1,418:
{
{
return 8;
return 8;
}
}


weaponsToConvert =
weaponsToConvert =
Line 1,196: Line 1,432:
}
}
return 0;
return 0;
}
}
 
}
}
</source>}}


===Mutation20 - Healing Gnome===
function OnGameEvent_round_start_post_nav( params )
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
Msg("Activating Mutation 20\n");
 
 
DirectorOptions <-
{
{
ActiveChallenge = 1
local function CheckOutputs( entity, outputName )
 
weaponsToRemove =
{
{
weapon_first_aid_kit = 0
local ent = entity;
weapon_pain_pills = 0
local output = outputName;
weapon_adrenaline = 0
while ( ent )
{
local target = null;
local targetOutput = "";
local nElements = EntityOutputs.GetNumElements( ent, output );
for ( local i = 0; i < nElements; i++ )
{
local outputs = {};
EntityOutputs.GetOutputTable( ent, output, outputs, i );
local outputTarget = Entities.FindByName( null, outputs.target );
if ( !outputTarget )
continue;
 
if ( outputs.input == "Unlock" || outputs.input == "Enable" || outputs.input == "SetValueTest" || outputs.input == "MoveToFloor" )
return outputTarget;
else
{
if ( outputTarget.GetClassname() == "logic_relay" && outputs.input == "Trigger" )
{
target = outputTarget;
targetOutput = "OnTrigger";
}
}
}
ent = target;
output = targetOutput;
}
return null;
}
}
for ( local trigger; trigger = Entities.FindByClassname( trigger, "trigger_multiple" ); )
{
if ( NetProps.GetPropInt( trigger, "m_bAllowIncapTouch" ) == 0 || !EntityOutputs.HasAction( trigger, "OnEntireTeamStartTouch" ) )
continue;
NetProps.SetPropInt( trigger, "m_bAllowIncapTouch", 0 );
local target = CheckOutputs( trigger, "OnEntireTeamStartTouch" );
if ( !target )
continue;


function AllowWeaponSpawn( classname )
if ( target.GetClassname() == "logic_relay" )
{
{
if ( classname in weaponsToRemove )
for ( local button; button = Entities.FindByClassname( button, "func_button" ); )
{
local nElements = EntityOutputs.GetNumElements( button, "OnPressed" );
for ( local i = 0; i < nElements; i++ )
{
local outputs = {};
EntityOutputs.GetOutputTable( button, "OnPressed", outputs, i );
if ( outputs.target == target.GetName() )
{
target = button;
break;
}
}
}
}
else if ( target.GetClassname() == "logic_branch" )
{
{
return false;
local foundListener = false;
for ( local listener; listener = Entities.FindByClassname( listener, "logic_branch_listener" ); )
{
for ( local i = 0; i < 16; i++ )
{
if ( NetProps.GetPropString( listener, "m_nLogicBranchNames[" + i + "]" ) == target.GetName() )
{
foundListener = true;
break;
}
}
 
if ( foundListener )
{
local nElements = EntityOutputs.GetNumElements( listener, "OnAllTrue" );
for ( local i = 0; i < nElements; i++ )
{
local outputs = {};
EntityOutputs.GetOutputTable( listener, "OnAllTrue", outputs, i );
if ( outputs.input == "Unlock" )
{
target = Entities.FindByName( null, outputs.target );
break;
}
}
break;
}
}
}
}
return true;
}


// Challenge vars
local validOutputs = { func_button_timed = "OnTimeUp", func_button = "OnPressed", script_func_button = "OnPressed", prop_door_rotating = "OnOpen" };
cm_TempHealthOnly = 1
if ( !target.GetClassname() in validOutputs )
cm_AllowPillConversion = 0
continue;
cm_HealingGnome = 1
 
target.ValidateScriptScope();
TempHealthDecayRate = 0.001
local targetScope = target.GetScriptScope();
function RecalculateHealthDecay()
targetScope.OverrideTrigger <- trigger;
{
targetScope.UseEnt <- function()
if ( Director.HasAnySurvivorLeftSafeArea() )
{
{
TempHealthDecayRate = 0.27 // pain_pills_decay_rate default
for ( local player; player = Entities.FindByClassname( player, "player" ); )
{
if ( player.IsSurvivor() && player.IsIncapacitated() )
{
if ( !OverrideTrigger.IsTouching( player ) )
player.TakeDamage( player.GetHealth(), 0, null );
}
}
self.DisconnectOutput( validOutputs[target.GetClassname()], "UseEnt" );
}
}
target.ConnectOutput( validOutputs[target.GetClassname()], "UseEnt" );
}
}
}


function Update()
local bRemoveClips = false;
{
switch( SessionState.MapName )
DirectorOptions.RecalculateHealthDecay();
{
}
// For comments and elaboration regarding these map fixes, see:
</source>}}
// Collection of Versus Survival & Taaannnk! Mutation stuck spawn fixes
 
// https://gist.github.com/Tsuey/e8996ad10bb7fb72e7d6b2aaa7bcd5db
===RocketDude===
// Most of these fixes should eventually be replaced with a fuller clip brush re-work of all base maps for modders/PvP,
Technically, RocketDude was made by community members for the "The Last Stand" update, but listed as official because it is an official mutation.
// such as we already did for TLS at least with regard to new player SI areas opened on c8m3 (seen with ShowUpdate tool).
{{ExpandBox|<source lang=js>
 
//****************************************************************************************
case "c1m2_streets":
// //
{
// rocketDude.nut (mainfile) //
EntFire( g_UpdateName + "_yeswayturnpike_clipb", "Kill", null, 30 ); // Delay oddly required
// //
bRemoveClips = true;
//****************************************************************************************
break;
 
}
Msg("Activating RocketDude By ReneTM \n")
case "c2m2_fairgrounds":
 
{
::rocketdude_version <- "v1.7.7 build: 17:07:07 Feb 07 2021"
bRemoveClips = true;
::mapName <- Director.GetMapName().tolower()
break;
::survivorSet <- Director.GetSurvivorSet()
}
local grenadeData = {}
case "c3m1_plankcountry":
 
{
 
EntFire( g_UpdateName + "_meticulous_funcinfclip01", "Kill", null, 30 ); // Delay oddly required
 
make_axiswarp( "_axiswarp_semitrailer", "y-", 96, "-550 -8 0", "740 8 256", "-9505 10720 155" );
 
bRemoveClips = true;
// Different scripts 2 include
break;
// ----------------------------------------------------------------------------------------------------------------------------
}
 
case "c5m2_park":
IncludeScript("rocketdude/rd_debug")
{
IncludeScript("rocketdude/rd_utils")
make_clip( "_tankstuck_startroof", "SI Players", 1, "-106 -240 0", "86 240 196", "-2936 -816 -58" );
IncludeScript("rocketdude/rd_melee_getter")
break;
IncludeScript("rocketdude/rd_director")
}
IncludeScript("rocketdude/rd_damage_controll")
case "c5m4_quarter":
IncludeScript("rocketdude/rd_detonation_analysis")
{
IncludeScript("rocketdude/rd_meds")
bRemoveClips = true;
IncludeScript("rocketdude/rd_events")
break;
IncludeScript("rocketdude/rd_last_chance")
}
IncludeScript("rocketdude/rd_decals")
case "c6m1_riverbank":
IncludeScript("rocketdude/rd_map_specifics")
{
IncludeScript("rocketdude/rd_custom_map_support")
make_clip( "_tankstuck_startfence", "SI Players", 1, "-251 -760 0", "173 216 1513", "-261 2872 87" );
IncludeScript("rocketdude/rd_saferoom_timer")
break;
IncludeScript("rocketdude/rd_speedrun_mode")
}
IncludeScript("rocketdude/rd_mode_description")
case "c8m1_apartment":
IncludeScript("rocketdude/rd_hud_controller")
{
make_clip( "_tankstuck_alleystart", "SI Players", 1, "-105 -711 0", "823 57 4132", "2473 455 16" );
make_clip( "_tankstuck_alleymiddle", "SI Players", 1, "-127 -257 0", "209 256 4468", "-129 3584 16" );
kill_funcinfclip( 1060.85 ); // Delete clip that blocks the rubble and a cool unique spawn
break;
}
case "c8m2_subway":
{
make_clip( "_tankstuck_rubblestart", "SI Players", 1, "-139 -107 0", "119 99 177", "1621 3617 -337", "0 42 0" );
make_clip( "_tankstuck_rubblemiddle", "SI Players", 1, "-189 -145 0", "300 179 240", "6766 5299 -336", "0 -68 0" );
make_clip( "_tankstuck_rubblefinal", "SI Players", 1, "-189 -253 0", "140 320 176", "8249 3222 -336", "0 -53 0" );
make_clip( "_tankstuck_endalley1", "SI Players", 1, "-480 -855 0", "65 293 1400", "10303 3479 16" );
make_clip( "_tankstuck_endalley2", "SI Players", 1, "-373 -96 0", "395 99 1400", "11637 5342 16" );
make_clip( "_tankstuck_widestreet", "SI Players", 1, "-842 0 0", "386 555 1400", "10110 6784 8" );
break;
}
case "c8m3_sewers":
{
make_clip( "_tankstuck_startrooftops", "SI Players", 1, "-1749 -380 0", "219 332 750", "12261 4212 464" );
kill_funcinfclip( 606.217 ); // Delete clip behind a fence and inaccessible ladder
kill_funcinfclip( 733.138 ); // Delete clip for end area backstreet wrongway sign left
kill_funcinfclip( 762.564 ); // Delete clip for end area backstreet wrongway sign right
break;
}
case "c9m2_lots":
{
make_clip( "_tankstuck_startback", "SI Players", 1, "-120 -900 0", "120 900 1370", "-835 -110 -223" );
break;
}
case "c10m1_caves":
{
kill_funcinfclip( 1145.18 ); // Spawn left 1st
kill_funcinfclip( 1127.68 ); // Spawn left 2nd
kill_funcinfclip( 1110.42 ); // Spawn left 3rd
kill_funcinfclip( 1059.74 ); // Spawn left 4th
kill_funcinfclip( 1054.83 ); // Spawn left 5th
kill_funcinfclip( 1216.59 ); // Tunnelside
kill_funcinfclip( 1000.2 ); // Cliffside
kill_funcinfclip( 597.979 ); // End cave connection to map 2 (easter egg spot)
make_clip( "_tankstuck_tunnelside", "SI Players", 1, "-1700 -200 -1337", "1700 1024 610", "-13902 -10396 656", "0 45 0" );
break;
}
case "c10m2_drainage":
{
bRemoveClips = true;
break;
}
case "c10m3_ranchhouse":
{
kill_funcinfclip( 668.433 ); // Wrongway in beginning area
kill_funcinfclip( 2248.58 ); // Treeline after bus/shed
make_clip( "_tankstuck_cornerpath", "SI Players", 1, "-700 -800 -145", "800 790 1360", "-13598 -9362 68" );
make_clip( "_tankstuck_mountainwedge", "SI Players", 1, "-100 -100 0", "100 100 1300", "-8388 -3525 300" );
break;
}
case "c10m4_mainstreet":
{
make_clip( "_tankstuck_houserow", "SI Players", 1, "-2260 -440 0", "3450 900 1850", "68 158 -100" );
make_clip( "_tankstuck_endbarricade", "SI Players", 1, "-400 -900 0", "400 900 1700", "3822 -3970 0" );
make_clip( "_tankstuck_excesscorner", "SI Players", 1, "-200 -100 0", "200 100 1700", "-2520 -5048 -64" );
break;
}
case "c11m1_greenhouse":
{
kill_funcinfclip( 1154.43 ); // End area fence barricade
break;
}
case "c11m2_offices":
{
make_clip( "_tankstuck_fencenavarea", "SI Players", 1, "-64 -64 0", "64 64 1400", "10314 3862 16" );
kill_funcinfclip( 1043.94 ); // Final street left barricade
kill_funcinfclip( 1493.36 ); // Final street right barricade
break;
}
case "c11m3_garage":
{
make_clip( "_tankstuck_startfence", "SI Players", 1, "-1200 -80 0", "720 58 1800", "-4947 -3786 16" );
break;
}
case "c12m1_hilltop":
{
make_clip( "_tankstuck_wrongwayone", "SI Players", 1, "-320 -800 0", "0 750 1740", "-11967 -10090 450" );
make_clip( "_tankstuck_wrongwaytwo", "SI Players", 1, "-610 0 0", "180 460 1740", "-11604 -9345 450" );
break;
}
case "c12m2_traintunnel":
{
make_clip( "_tankstuck_tunnelend", "SI Players", 1, "-320 -800 -120", "450 258 240", "-1551 -10785 50" );
break;
}
case "c12m3_bridge":
{
kill_funcinfclip( 311.003 ); // Wrongway at end of train tunnel
break;
}
case "c12m4_barn":
{
make_clip( "_tankstuck_safeback", "SI Players", 1, "-170 -640 -100", "170 225 1700", "7704 -11710 425" );
break;
}
default:
break;
}


if ( bRemoveClips )
EntFire( "func_playerinfected_clip", "Kill" );
}
</source>}}


===Mutation20 - Healing Gnome===
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
Msg("Activating Mutation 20\n");




// Precache models and sounds
DirectorOptions <-
// ----------------------------------------------------------------------------------------------------------------------------
{
ActiveChallenge = 1


precacheSurvivorModels()
weaponsToRemove =
{
weapon_first_aid_kit = 0
weapon_pain_pills = 0
weapon_adrenaline = 0
}


precacheRocketDudeModels()
function AllowWeaponSpawn( classname )
{
if ( classname in weaponsToRemove )
{
return false;
}
return true;
}


precacheSounds()
// Challenge vars
cm_TempHealthOnly = 1
cm_AllowPillConversion = 0
cm_HealingGnome = 1
TempHealthDecayRate = 0.001
function RecalculateHealthDecay()
{
if ( Director.HasAnySurvivorLeftSafeArea() )
{
TempHealthDecayRate = 0.27 // pain_pills_decay_rate default
}
}
}


function Update()
{
DirectorOptions.RecalculateHealthDecay();
}
</source>}}


===RocketDude===
Technically, RocketDude was made by community members for the "The Last Stand" update, but listed as official because it is an official mutation.
{{ExpandBox|<source lang=js>
//****************************************************************************************
// //
// rocketDude.nut (mainfile) //
// //
//****************************************************************************************


Msg("Activating RocketDude By ReneTM \n")


// Creates a bullet time when the previous one is atleast 32 seconds ago.
::rocketdude_version <- "v1.7.7 build: 17:07:07 Feb 07 2021"
// When this condition is met bullet time will occur with a probability of 3%
::mapName <- Director.GetMapName().tolower()
// ----------------------------------------------------------------------------------------------------------------------------
::survivorSet <- Director.GetSurvivorSet()
local grenadeData = {}


local lastBulletTime = Time()
local lastDiceTime = Time()


::GLOBALS <-
{
allowBulletTime = false
}


function bulletTime(){
if(GLOBALS.allowBulletTime){
if(Time() > lastBulletTime + 32){
if(Time() >= lastDiceTime + 2){
if(rollDice(3)){
lastBulletTime = Time()
DoEntFire( "!self", "Start", "", 0, timeScaler, timeScaler )
DoEntFire( "!self", "Stop", "", 1, timeScaler, timeScaler )
}
lastDiceTime = Time()
}
}
}
}


function saveGlobals(){
// Different scripts 2 include
SaveTable("GLOBAL_SAVINGS", GLOBALS)
// ----------------------------------------------------------------------------------------------------------------------------
}
 
IncludeScript("rocketdude/rd_debug")
IncludeScript("rocketdude/rd_utils")
IncludeScript("rocketdude/rd_melee_getter")
IncludeScript("rocketdude/rd_director")
IncludeScript("rocketdude/rd_damage_controll")
IncludeScript("rocketdude/rd_detonation_analysis")
IncludeScript("rocketdude/rd_meds")
IncludeScript("rocketdude/rd_events")
IncludeScript("rocketdude/rd_last_chance")
IncludeScript("rocketdude/rd_decals")
IncludeScript("rocketdude/rd_map_specifics")
IncludeScript("rocketdude/rd_custom_map_support")
IncludeScript("rocketdude/rd_saferoom_timer")
IncludeScript("rocketdude/rd_speedrun_mode")
IncludeScript("rocketdude/rd_mode_description")
IncludeScript("rocketdude/rd_hud_controller")
 
 
 
 
// Precache models and sounds
// ----------------------------------------------------------------------------------------------------------------------------
 
precacheSurvivorModels()
 
precacheRocketDudeModels()
 
precacheSounds()
 
 
 
 
// Creates a bullet time when the previous one is atleast 32 seconds ago.
// When this condition is met bullet time will occur with a probability of 3%
// ----------------------------------------------------------------------------------------------------------------------------
 
local lastBulletTime = Time()
local lastDiceTime = Time()
 
::GLOBALS <-
{
allowBulletTime = false
}
 
function bulletTime(){
if(GLOBALS.allowBulletTime){
if(Time() > lastBulletTime + 32){
if(Time() >= lastDiceTime + 2){
if(rollDice(3)){
lastBulletTime = Time()
DoEntFire( "!self", "Start", "", 0, timeScaler, timeScaler )
DoEntFire( "!self", "Stop", "", 1, timeScaler, timeScaler )
}
lastDiceTime = Time()
}
}
}
}
 
function saveGlobals(){
SaveTable("GLOBAL_SAVINGS", GLOBALS)
}


function restoreGlobals(){
function restoreGlobals(){
Line 3,389: Line 3,891:
}
}
}
}
local ent = SpawnEntityFromTable("trigger_finale", keyvalues)
local ent = SpawnEntityFromTable("trigger_finale", keyvalues)
NetProps.SetPropEntity(Entities.FindByName(null, "finale_start_button"), "m_sGlowEntity", ent)
NetProps.SetPropEntity(Entities.FindByName(null, "finale_start_button"), "m_sGlowEntity", ent)
Line 4,725: Line 5,228:
::cvars <-
::cvars <-
{
{
//Survivor settings
// Survivor settings
sv_infinite_ammo = 1
sv_infinite_ammo = 1
survivor_allow_crawling = 1
survivor_allow_crawling = 1
Line 4,734: Line 5,237:
z_grab_ledges_solo = 1
z_grab_ledges_solo = 1
z_tank_incapacitated_decay_rate = 5
z_tank_incapacitated_decay_rate = 5
//Grenadelauncher settings
// Grenadelauncher settings
grenadelauncher_velocity = 1100
grenadelauncher_velocity = 1100
grenadelauncher_startpos_right = 0
grenadelauncher_startpos_right = 0
Line 4,740: Line 5,243:
grenadelauncher_vel_variance = 0
grenadelauncher_vel_variance = 0
grenadelauncher_vel_up = 0
grenadelauncher_vel_up = 0
//Force settings
// Force settings
phys_explosion_force = 4096
phys_explosion_force = 4096
melee_force_scalar = 16
melee_force_scalar = 16
melee_force_scalar_combat_character = 512
melee_force_scalar_combat_character = 512
phys_pushscale = 512
phys_pushscale = 512
//Infected settings
// Infected settings
z_force_attack_from_sound_range = 512
z_force_attack_from_sound_range = 512
z_brawl_chance = 1
z_brawl_chance = 1
//Medicals
// Medicals
pain_pills_health_threshold = 199
pain_pills_health_threshold = 199
pain_pills_health_value = 100
pain_pills_health_value = 100
//Items
// Items
sv_infected_riot_control_tonfa_probability = 0
sv_infected_riot_control_tonfa_probability = 0
sv_infected_ceda_vomitjar_probability = 0
sv_infected_ceda_vomitjar_probability = 0
//Votes
// Votes
sv_vote_creation_timer = 8
sv_vote_creation_timer = 8
sv_vote_plr_map_limit = 128
sv_vote_plr_map_limit = 128
//Misc
// Misc
z_spawn_flow_limit = 99999
z_spawn_flow_limit = 99999
director_afk_timeout = 99999
director_afk_timeout = 99999
mp_allowspectators = 0
mp_allowspectators = 0
// Disable Placeholder bots
director_transition_timeout = 1
}
}


Line 6,337: Line 6,842:
Msg("Made by Rayman1103\n");
Msg("Made by Rayman1103\n");


MutationOptions <-
{
cm_ShouldHurry = true
cm_AllowPillConversion = false
cm_AllowSurvivorRescue = false
SurvivorMaxIncapacitatedCount = 0
TempHealthDecayRate = 0.0


DirectorOptions <-
function AllowFallenSurvivorItem( classname )
{
{
ActiveChallenge = 1
if ( classname == "weapon_first_aid_kit" )
return false;


cm_ShouldHurry = 1
return true;
cm_AllowPillConversion = 0
}
cm_AllowSurvivorRescue = 0
SurvivorMaxIncapacitatedCount = 0


weaponsToConvert =
weaponsToConvert =
{
{
weapon_first_aid_kit = "weapon_pain_pills_spawn"
weapon_first_aid_kit = "weapon_pain_pills_spawn"
weapon_adrenaline = "weapon_pain_pills_spawn"
weapon_adrenaline = "weapon_pain_pills_spawn"
}
}


Line 6,356: Line 6,867:
{
{
if ( classname in weaponsToConvert )
if ( classname in weaponsToConvert )
{
return weaponsToConvert[classname];
return weaponsToConvert[classname];
}
 
return 0;
return 0;
}
}
Line 6,371: Line 6,881:
{
{
if ( idx < DefaultItems.len() )
if ( idx < DefaultItems.len() )
{
return DefaultItems[idx];
return DefaultItems[idx];
}
 
return 0;
return 0;
}
}
}
function OnGameEvent_round_start( params )
{
Convars.SetValue( "pain_pills_decay_rate", 0.0 );


TempHealthDecayRate = 0.001
for ( local player; player = Entities.FindByClassname( player, "player" ); ) // only works in restarts, which is desired
function RecalculateHealthDecay()
{
{
if ( Director.HasAnySurvivorLeftSafeArea() )
if ( player.IsSurvivor() && !IsPlayerABot( player ) )
{
player.GetScriptScope().HeartbeatOn = false;
TempHealthDecayRate = 0.27 // pain_pills_decay_rate default
}
}
}
}
}


function Update()
function OnGameEvent_player_left_safe_area( params )
{
{
DirectorOptions.RecalculateHealthDecay();
SessionOptions.TempHealthDecayRate = 0.27;
}
}
</source>}}


===Community6 - Confogl===
function OnGameEvent_player_hurt( params )
{{ExpandBox|<source lang=js>
{
// vim: set ts=4
local player = GetPlayerFromUserID( params["userid"] );
// CompLite.nut (Confogl Mutation)
if ( !player || !player.IsSurvivor() || player.IsHangingFromLedge() )
// Copyright (C) 2012 ProdigySim
return;
// All rights reserved.
// =============================================================================


IncludeScript("globals", this);
if ( NetProps.GetPropInt( player, "m_bIsOnThirdStrike" ) == 0 )
{
local health = player.GetHealth();
if ( health > 0 )
{
if ( !IsPlayerABot( player ) )
{
local scope = player.GetScriptScope();
if ( !scope.HeartbeatOn && health < player.GetMaxHealth() / 4 )
{
EmitSoundOnClient( "Player.Heartbeat", player );
scope.HeartbeatOn = true;
}
}
if ( health == 1 )
{
NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 1 );
NetProps.SetPropInt( player, "m_isGoingToDie", 1 );
}
}
}
}


CompLite = InitializeCompLite();
function OnGameEvent_defibrillator_used( params )
{
local player = GetPlayerFromUserID( params["subject"] );
if ( !player )
return;


// Don't need to do anything else if we're not first load
player.SetHealth( 1 );
if(CompLite.Globals.GetCurrentRound() > 0)
player.SetHealthBuffer( 99 );
 
if ( !IsPlayerABot( player ) )
{
EmitSoundOnClient( "Player.Heartbeat", player );
player.GetScriptScope().HeartbeatOn = true;
}
NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 1 );
NetProps.SetPropInt( player, "m_isGoingToDie", 1 );
}
 
function OnGameEvent_heal_success( params )
{
{
Msg("CompLite Starting Round "+CompLite.Globals.GetCurrentRound()+" on ");
local player = GetPlayerFromUserID( params["subject"] );
local mi = CompLite.Globals.MapInfo;
if ( !player )
if(mi.isIntro) Msg("an intro map.\n");
return;
else Msg("a non-intro map.\n");


Msg("Found "+mi.saferoomPoints.len()+" saferoom points.\n");
if ( !IsPlayerABot( player ) )
Msg("Map has a scavenge event? " + mi.hasScavengeEvent + "\n");
{
Msg("MapName: "+mi.mapname+"\n");
local scope = player.GetScriptScope();
 
if ( scope.HeartbeatOn && player.GetHealth() >= player.GetMaxHealth() / 4 )
return;
{
StopSoundOn( "Player.Heartbeat", player );
scope.HeartbeatOn = false;
}
}
}
}


Msg("Activating Mutation CompLite v3.6\n");
function CheckHealthAfterLedgeHang( userid )
{
local player = GetPlayerFromUserID( userid );
if ( !player )
return;


DirectorOptions.ActiveChallenge <- 1
local health = player.GetHealth();
DirectorOptions.cm_ProhibitBosses <- 0
DirectorOptions.cm_AllowPillConversion <- 0


// Name shortening references
if ( !IsPlayerABot( player ) )
local g_Timer = CompLite.Globals.Timer;
{
local g_FrameTimer = CompLite.Globals.FrameTimer;
local scope = player.GetScriptScope();
local g_MapInfo = CompLite.Globals.MapInfo;
if ( !scope.HeartbeatOn && health < player.GetMaxHealth() / 4 )
local g_GSC = CompLite.Globals.GSC;
{
local g_GSM = CompLite.Globals.GSM;
EmitSoundOnClient( "Player.Heartbeat", player );
local g_MobResetti = CompLite.Globals.MobResetti;
scope.HeartbeatOn = true;
local Modules = CompLite.Modules;
}
}
if ( health == 1 )
{
NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 1 );
NetProps.SetPropInt( player, "m_isGoingToDie", 1 );
}
}


// Uncomment to add a debug event listener
function OnGameEvent_revive_success( params )
//g_GSC.AddListener(Modules.MsgGSL());
{
local player = GetPlayerFromUserID( params["subject"] );
if ( !params["ledge_hang"] || !player )
return;


g_GSC.AddListener(Modules.SpitterControl(Director, DirectorOptions, Entities));
if ( NetProps.GetPropInt( player, "m_bIsOnThirdStrike" ) == 0 )
g_GSC.AddListener(Modules.MobControl(g_MobResetti));
EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.CheckHealthAfterLedgeHang(" + params["subject"] + ")" );
}


function OnGameEvent_player_spawn( params )
{
local player = GetPlayerFromUserID( params["userid"] );
if ( !player || !player.IsSurvivor() )
return;


// Give out hunting rifles on non-intro maps.
if ( !IsPlayerABot( player ) )
// But limit them to 1 of each.
{
g_GSC.AddListener(Modules.HRControl(Entities, CompLite.Globals, Director));
player.ValidateScriptScope();
local scope = player.GetScriptScope();
if ( !("HeartbeatOn" in scope) )
scope.HeartbeatOn <- false;
}
}


function OnGameEvent_player_death( params )
{
if ( !("userid" in params) )
return;


g_GSC.AddListener(
local player = GetPlayerFromUserID( params["userid"] );
Modules.BasicItemSystems(
if ( !player || !player.IsSurvivor() )
// AllowWeaponSpawn Limits
return;
// 0: Always remove
 
// >0: Keep the first n instances, delete others
if ( !IsPlayerABot( player ) )
// <-1: Delete the first n instances, keep others.
{
local scope = player.GetScriptScope();
if ( scope.HeartbeatOn )
{
{
weapon_defibrillator = 0
StopSoundOn( "Player.Heartbeat", player );
weapon_grenade_launcher = 0
scope.HeartbeatOn = false;
weapon_upgradepack_incendiary = 0
}
weapon_upgradepack_explosive = 0
}
weapon_chainsaw = 0
}
//weapon_molotov = 1
 
//weapon_pipe_bomb = 2
function OnGameEvent_player_bot_replace( params )
//weapon_vomitjar = 1
{
weapon_propanetank = 0
local player = GetPlayerFromUserID( params["player"] );
weapon_oxygentank = 0
if ( !player || !player.IsSurvivor() ) // in case an infected player uses jointeam 1
weapon_rifle_m60 = 0
return;
//weapon_first_aid_kit = -5
 
upgrade_item = 0
local scope = player.GetScriptScope();
},
if ( scope.HeartbeatOn )
// Conversion Rules
{
{
StopSoundOn( "Player.Heartbeat", player );
weapon_autoshotgun   = "weapon_pumpshotgun_spawn"
scope.HeartbeatOn = false;
weapon_shotgun_spas = "weapon_shotgun_chrome_spawn"
}
weapon_rifle = "weapon_smg_spawn"
}
weapon_rifle_desert = "weapon_smg_spawn"
 
weapon_rifle_sg552   = "weapon_smg_mp5_spawn"
function OnGameEvent_bot_player_replace( params )
weapon_rifle_ak47   = "weapon_smg_silenced_spawn"
{
weapon_hunting_rifle = "weapon_smg_silenced_spawn"
local player = GetPlayerFromUserID( params["player"] );
weapon_sniper_military  = "weapon_shotgun_chrome_spawn"
if ( !player )
weapon_sniper_awp   = "weapon_shotgun_chrome_spawn"
return;
weapon_sniper_scout = "weapon_pumpshotgun_spawn"
weapon_first_aid_kit = "weapon_pain_pills_spawn"
weapon_molotov = "weapon_molotov_spawn"
weapon_pipe_bomb = "weapon_pipe_bomb_spawn"
weapon_vomitjar = "weapon_vomitjar_spawn"
},
// Default item list
[
"weapon_pain_pills",
"weapon_pistol",
"weapon_hunting_rifle"
]
)
);


// Enforce various item spawns to be single pickup.
if ( !player.IsDead() ) // in case jointeam 2 or sb_takecontrol was used on a dead bot
g_GSC.AddListener(
{
Modules.EntKVEnforcer(Entities,
if ( player.GetHealth() < player.GetMaxHealth() / 4 )
// classnames
player.GetScriptScope().HeartbeatOn = true; // unreliable if sb_takecontrol was used
[
else
"weapon_adrenaline_spawn",
StopSoundOn( "Player.Heartbeat", player );
"weapon_pain_pills_spawn",
}
"weapon_melee_spawn",
}
"weapon_molotov_spawn",
"weapon_vomitjar_spawn",
"weapon_pipebomb_spawn"
],
// models
[],
// key to enforce
"count",
// value to set it to
1
)
);


// Entity tracking/removal/limiting
function OnGameEvent_player_complete_sacrifice( params )
g_GSC.AddListener(
{
Modules.ItemControl(Entities,
local player = GetPlayerFromUserID( params["userid"] );
// Limit to value by classname
if ( !player )
{
return;
weapon_adrenaline_spawn = 1
weapon_pain_pills_spawn = 4
witch = 1
func_playerinfected_clip = 0
weapon_molotov_spawn = 1
weapon_pipe_bomb_spawn = 1
weapon_vomitjar_spawn = 1
},
// Limit to value by model name
{
["models/props_junk/propanecanister001a.mdl"] = 0,
["models/props_equipment/oxygentank01.mdl"] = 0,
["models/props_junk/explosive_box001.mdl"] = 1
},
// Remove these items from all saferooms
[
"weapon_adrenaline_spawn",
"weapon_pain_pills_spawn",
//"weapon_melee_spawn",
"weapon_molotov_spawn",
"weapon_pipe_bomb_spawn",
"weapon_vomitjar_spawn"
],
g_MapInfo
)
);


// Limit melee weapons to 4
NetProps.SetPropInt( player, "m_takedamage", 0 );
g_GSC.AddListener(Modules.MeleeWeaponControl(Entities, 4));
NetProps.SetPropInt( player, "m_isIncapacitated", 1 );
}


// Remove all gascans on non-scavenge maps
if ( !Director.IsSessionStartMap() )
g_GSC.AddListener(Modules.GasCanControl(Entities, g_MapInfo));
{
function PlayerSpawnDeadAfterTransition( userid )
{
local player = GetPlayerFromUserID( userid );
if ( !player )
return;


player.SetHealth( 24 );
player.SetHealthBuffer( 26 );


Msg("GSC/M/L Script run.\n");
if ( !IsPlayerABot( player ) )
</source>}}
{
EmitSoundOnClient( "Player.Heartbeat", player );
player.GetScriptScope().HeartbeatOn = true;
}
}


====Confogl Additional Scripts====
function PlayerSpawnAliveAfterTransition( userid )
These scripts are included in the main Confogl and <code>globals</code> scripts.
 
=====globals=====
{{ExpandBox|<source lang=js>
// Call this once on script start to initialize CompLite's libraries and hook them up to ChallengeScript events.
// parentTable: The parent table where the CompLite library namespace should be created. This
// should be a table which will not be wiped on roundstart. getroottable() is a good idea, defaults to getroottable();
// customNameSpace: The custom name to use for the CompLite Libraries' namespace. Should be passed as a string. Defaults to "CompLite"
// ChallengeScript: Pass a reference to the ChallengeScript table. Defaults to ::DirectorScript.MapScript.ChallengeScript
function InitializeCompLite(parentTable = getroottable() , customNameSpace = "CompLite", ChallengeScript = ::DirectorScript.MapScript.ChallengeScript )
{
if(customNameSpace in parentTable)
{
{
local CompLite = parentTable[customNameSpace];
local player = GetPlayerFromUserID( userid );
CompLite.Globals.IncrementRoundNumber();
if ( !player )
CompLite.Globals.GSM.Reset();
return;


if(CompLite.Globals.GetCurrentRound() == 1)
local oldHealth = player.GetHealth();
{
local maxHeal = player.GetMaxHealth() / 2;
CompLite.Globals.MapInfo.IdentifyMap(Entities);
local healAmount = 0;
}


ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
if ( oldHealth < maxHeal )
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
return CompLite;
}
local CompLite = parentTable[customNameSpace] <- {};
IncludeScript("gamestate_model", CompLite);
IncludeScript("globaltimers", CompLite);
IncludeScript("utils", CompLite);
IncludeScript("modules", CompLite);
CompLite.ChallengeScript <- {
CompLite = CompLite
DirectorOptions = {
CompLite = CompLite
function AllowWeaponSpawn( classname )
{
return CompLite.Globals.GSM.OnAllowWeaponSpawn(classname);
}
function ConvertWeaponSpawn( classname )
{
return CompLite.Globals.GSM.OnConvertWeaponSpawn(classname);
}
function GetDefaultItem( idx )
{
return CompLite.Globals.GSM.OnGetDefaultItem(idx);
}
function ConvertZombieClass( id )
{
return CompLite.Globals.GSM.OnConvertZombieClass(id);
}
}
 
function Update()
{
{
CompLite.Globals.Timer.Update();
healAmount = floor( (maxHeal - oldHealth) * 0.8 + 0.5 );
CompLite.Globals.FrameTimer.Update();
player.SetHealth( oldHealth + healAmount );
CompLite.Globals.GSM.DoFrameUpdate();
local bufferHealth = player.GetHealthBuffer() - healAmount;
if ( bufferHealth < 0.0 )
bufferHealth = 0.0;
player.SetHealthBuffer( bufferHealth );
}
}
NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 0 );
NetProps.SetPropInt( player, "m_isGoingToDie", 0 );
}
}


CompLite.Globals <- CompLiteGlobals(CompLite, Director, CompLite.ChallengeScript.DirectorOptions);
function OnGameEvent_player_transitioned( params )
{
local player = GetPlayerFromUserID( params["userid"] );
if ( !player || !player.IsSurvivor() )
return;


ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
if ( NetProps.GetPropInt( player, "m_lifeState" ) == 2 )
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.PlayerSpawnDeadAfterTransition(" + params["userid"] + ")", 0.03 );
 
else
CompLite.Globals.MapInfo.IdentifyMap(Entities);
EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.PlayerSpawnAliveAfterTransition(" + params["userid"] + ")" );
 
return CompLite;
}
 
class CompLiteGlobals {
constructor(NameSpace, director, dopts)
{
Timer = NameSpace.Timers.GlobalSecondsTimer();
FrameTimer = NameSpace.Timers.GlobalFrameTimer();
MapInfo = NameSpace.Utils.MapInfo();
GSC = NameSpace.GameState.GameStateController();
GSM = NameSpace.GameState.GameStateModel(GSC, director);
MobResetti = NameSpace.Utils.ZeroMobReset(director, dopts, FrameTimer);
}
}
function IncrementRoundNumber() { m_iRoundNumber++; }
function GetCurrentRound() { return m_iRoundNumber; }
//public
Timer = null;
FrameTimer = null;
MapInfo = null;
GSM = null;
GSC = null;
MobResetti = null;
// private
m_iRoundNumber = 0;
}
}
</source>}}
</source>}}


=====gamestate_model=====
===Community6 - Confogl===
{{ExpandBox|<source lang=js>
{{ExpandBox|<source lang=js>
// vim: set ts=4
// vim: set ts=4
// L4D2 GameState Model for Mutation VScripts
// CompLite.nut (Confogl Mutation)
// Copyright (C) 2012 ProdigySim
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// All rights reserved.
// =============================================================================
// =============================================================================


// double include protection
IncludeScript("globals", this);
if("GameState" in this) return;
 
GameState <- {
CompLite = InitializeCompLite();
ROUNDSTART_DELAY_INTERVAL = 2
};
IncludeScript("utils", this);


class GameState.GameStateModel
// Don't need to do anything else if we're not first load
if(CompLite.Globals.GetCurrentRound() > 0)
{
{
constructor(controller, director)
Msg("CompLite Starting Round "+CompLite.Globals.GetCurrentRound()+" on ");
{
local mi = CompLite.Globals.MapInfo;
m_controller = controller;
if(mi.isIntro) Msg("an intro map.\n");
m_pDirector = director;
else Msg("a non-intro map.\n");
}
 
Msg("Found "+mi.saferoomPoints.len()+" saferoom points.\n");
Msg("Map has a scavenge event? " + mi.hasScavengeEvent + "\n");
Msg("MapName: "+mi.mapname+"\n");


function DoFrameUpdate()
return;
{
}
if(m_bLastUpdateTankInPlay)
{
if(!m_pDirector.IsTankInPlay())
{
m_bLastUpdateTankInPlay = false;
m_controller.TriggerTankLeavesPlay();
}
}
else if(m_pDirector.IsTankInPlay())
{
m_bLastUpdateTankInPlay = true;
m_controller.TriggerTankEntersPlay();
}
if(!m_bLastUpdateSafeAreaOpened && m_pDirector.HasAnySurvivorLeftSafeArea())
{
m_bLastUpdateSafeAreaOpened = true;
m_controller.TriggerSafeAreaOpen();
}
if(!m_bRoundStarted && m_bHeardAWS && m_bHeardCWS && m_bHeardGDI
&& m_iRoundStartTime < Time()-m_roundstart_delay)
{
m_bRoundStarted = true;
m_controller.TriggerRoundStart(GetCurrentRound());
}
}
function OnAllowWeaponSpawn( classname )
{
m_bHeardAWS = true;
m_iRoundStartTime = Time();
return m_controller.TriggerAllowWeaponSpawn(classname);
}
function OnConvertWeaponSpawn( classname )
{
m_bHeardCWS = true;
m_iRoundStartTime = Time();
return m_controller.TriggerConvertWeaponSpawn(classname);
}
function OnGetDefaultItem( idx )
{
m_bHeardGDI = true;
m_iRoundStartTime = Time();
return m_controller.TriggerGetDefaultItem(idx);
}
function OnConvertZombieClass(id)
{
return m_controller.TriggerPCZSpawn(id);
}


function Reset()
Msg("Activating Mutation CompLite v3.6\n");
{
m_bRoundStarted = false;
m_bHeardAWS = false;
m_bHeardCWS = false;
m_bHeardGDI = false;
m_iRoundStartTime = 0;
m_bLastUpdateSafeAreaOpened = false;
}


// Check for various round-start even. ts before triggering OnRoundStart()
DirectorOptions.ActiveChallenge <- 1
m_bRoundStarted = false;
DirectorOptions.cm_ProhibitBosses <- 0
m_bHeardAWS = false;
DirectorOptions.cm_AllowPillConversion <- 0
m_bHeardCWS = false;
m_bHeardGDI = false;
m_iRoundStartTime = 0;


m_bLastUpdateTankInPlay = false;
// Name shortening references
m_bLastUpdateSafeAreaOpened = false;
local g_Timer = CompLite.Globals.Timer;
local g_FrameTimer = CompLite.Globals.FrameTimer;
m_controller = null;
local g_MapInfo = CompLite.Globals.MapInfo;
m_pDirector = null;
local g_GSC = CompLite.Globals.GSC;
m_roundstart_delay = GameState.ROUNDSTART_DELAY_INTERVAL;
local g_GSM = CompLite.Globals.GSM;
static GetCurrentRound = Utils.GetCurrentRound;
local g_MobResetti = CompLite.Globals.MobResetti;
};
local Modules = CompLite.Modules;
 
// Uncomment to add a debug event listener
//g_GSC.AddListener(Modules.MsgGSL());


class GameState.GameStateListener
g_GSC.AddListener(Modules.SpitterControl(Director, DirectorOptions, Entities));
{
g_GSC.AddListener(Modules.MobControl(g_MobResetti));
// Called on round start. These may be multiples of these triggered, unfortunately.
function OnRoundStart(roundNumber) {}
// Called when a player leaves saferoom or the saferoom timer counts down
function OnSafeAreaOpened() {}
// Called when tank spawns
function OnTankEntersPlay() {}
// Called when tank dies/leaves play
function OnTankLeavesPlay() {}
// Called when a player-controlled zombie is going to be spawned via ConvertZombieClass
// This event will be chained--called on all Listeners with the modified id passed into successive calls.
// id: SIClass id of the PCZ to be spawned
// return another SIClass value to convert the PCZ spawn.
function OnSpawnPCZ(id) {}
// Called when a player-controlled zombie is going to be spawned via ConvertZombieClass
// After conversions from OnSpawnPCZ have taken place
// id: actual SIClass id to be spawned
function OnSpawnedPCZ(id) {}
// Called when DirectorOptions.GetDefaultItem() is called.
// This event chain will notify all listeners, but only one return value will be used.
// TODO: further abstraction to just have lists returned...
// Should be at the beginning of the round normally.
function OnGetDefaultItem(idx) {}
// Called when DirectorOptions.AllowWeaponSpawn() is called.
// Should be at the beginning of the round normally, after conversions take place.
// This event will stop being called on Listeners when one listener returns false.
// classname: string classname of weapon to allow/disallow
// return true to allow, false to disallow.
function OnAllowWeaponSpawn(classname) {}
// Called when DirectorOptions.ConvertWeaponSpawn is called
// This event will be chained--called on all Listeners with the modified classname passed into successive calls.
// classname: Classname of the weapon that would spawned
// retun the classname that it should be converted to, or 0 for no conversion.
function OnConvertWeaponSpawn(classname) {}
};


class GameState.GameStateController
{
function AddListener(listener)
{
m_listeners.push(listener)
}


function TriggerRoundStart(roundNumber)
// Give out hunting rifles on non-intro maps.
{
// But limit them to 1 of each.
foreach(listener in m_listeners)
g_GSC.AddListener(Modules.HRControl(Entities, CompLite.Globals, Director));
listener.OnRoundStart(roundNumber);
 
}
function TriggerSafeAreaOpen()
{
foreach(listener in m_listeners)
listener.OnSafeAreaOpened();
}
function TriggerTankEntersPlay()
{
foreach(listener in m_listeners)
listener.OnTankEntersPlay();
}
function TriggerTankLeavesPlay()
{
foreach(listener in m_listeners)
listener.OnTankLeavesPlay();
}
function TriggerPCZSpawn(id)
{
local retval = id;
foreach(listener in m_listeners)
{
// Allow each listener to try to convert.
// Not pretty in the long run but I'm okay with it.
local ret = listener.OnSpawnPCZ(retval);
if(ret != null) retval = ret;
}


// Simply notify everyone of the final value
g_GSC.AddListener(
foreach(listener in m_listeners)
Modules.BasicItemSystems(
listener.OnSpawnedPCZ(retval)
// AllowWeaponSpawn Limits
return retval;
// 0: Always remove
}
// >0: Keep the first n instances, delete others
function TriggerAllowWeaponSpawn(classname)
// <-1: Delete the first n instances, keep others.
{
foreach(listener in m_listeners)
{
{
// Cancel call chain once one listener returns false (says not to spawn it).
weapon_defibrillator = 0
if(listener.OnAllowWeaponSpawn(classname) == false) return false;
weapon_grenade_launcher = 0
// Interesting ! semantics
weapon_upgradepack_incendiary = 0
//             null  false
weapon_upgradepack_explosive = 0
// !ret        true  true
weapon_chainsaw = 0
// ret==false  false true
//weapon_molotov = 1
// ret==null  true  false
//weapon_pipe_bomb = 2
}
//weapon_vomitjar = 1
return true;
weapon_propanetank = 0
}
weapon_oxygentank = 0
function TriggerConvertWeaponSpawn(classname)
weapon_rifle_m60 = 0
{
//weapon_first_aid_kit = -5
local retcls = 0;
upgrade_item = 0
foreach(listener in m_listeners)
},
// Conversion Rules
{
{
local ret = listener.OnConvertWeaponSpawn(classname);
weapon_autoshotgun   = "weapon_pumpshotgun_spawn"
if(ret != null && ret != 0) retcls = ret;
weapon_shotgun_spas = "weapon_shotgun_chrome_spawn"
}
weapon_rifle = "weapon_smg_spawn"
return retcls;
weapon_rifle_desert = "weapon_smg_spawn"
}
weapon_rifle_sg552   = "weapon_smg_mp5_spawn"
function TriggerGetDefaultItem(idx)
weapon_rifle_ak47   = "weapon_smg_silenced_spawn"
{
weapon_hunting_rifle = "weapon_smg_silenced_spawn"
local retval = 0;
weapon_sniper_military  = "weapon_shotgun_chrome_spawn"
foreach(listener in m_listeners)
weapon_sniper_awp   = "weapon_shotgun_chrome_spawn"
{
weapon_sniper_scout = "weapon_pumpshotgun_spawn"
local ret = listener.OnGetDefaultItem(idx);
weapon_first_aid_kit = "weapon_pain_pills_spawn"
if(retval == 0 && ret != null) retval = ret;
weapon_molotov = "weapon_molotov_spawn"
}
weapon_pipe_bomb = "weapon_pipe_bomb_spawn"
return retval;
weapon_vomitjar = "weapon_vomitjar_spawn"
}
},
// Default item list
[
"weapon_pain_pills",
"weapon_pistol",
"weapon_hunting_rifle"
]
)
);


m_listeners = []
// Enforce various item spawns to be single pickup.
};
g_GSC.AddListener(
</source>}}
Modules.EntKVEnforcer(Entities,
 
// classnames
=====globaltimers=====
[
{{ExpandBox|<source lang=js>
"weapon_adrenaline_spawn",
// vim: set ts=4
"weapon_pain_pills_spawn",
// Global Timers for L4D2 VScript Mutations
"weapon_melee_spawn",
// Copyright (C) 2012 ProdigySim
"weapon_molotov_spawn",
// All rights reserved.
"weapon_vomitjar_spawn",
// =============================================================================
"weapon_pipebomb_spawn"
],
// models
[],
// key to enforce
"count",
// value to set it to
1
)
);


 
// Entity tracking/removal/limiting
/* Usage:
g_GSC.AddListener(
Create an instance of one of these timers, e.g.
Modules.ItemControl(Entities,
g_Timer = GlobalTimer()
// Limit to value by classname
and/or
{
g_FrameTimer = GlobalFrameTimer()
weapon_adrenaline_spawn = 1
weapon_pain_pills_spawn = 4
Then run the timer's Update() function in your global "update" function.
witch = 1
e.g.
func_playerinfected_clip = 0
function Update()
weapon_molotov_spawn = 1
{
weapon_pipe_bomb_spawn = 1
g_Timer.Update();
weapon_vomitjar_spawn = 1
g_FrameTimer.Update();
},
}
// Limit to value by model name
To add a timer, extend TimerCallback to create a callback.
e.g.
class MyCallback extends TimerCallback
{
function OnTimerElapsed()
{
{
Msg("My Timer has elapsed!!!\n");
["models/props_junk/propanecanister001a.mdl"] = 0,
}
["models/props_equipment/oxygentank01.mdl"] = 0,
}
["models/props_junk/explosive_box001.mdl"] = 1
},
Then register it to the global timer of your choice,
// Remove these items from all saferooms
[
*/
"weapon_adrenaline_spawn",
"weapon_pain_pills_spawn",
//"weapon_melee_spawn",
"weapon_molotov_spawn",
"weapon_pipe_bomb_spawn",
"weapon_vomitjar_spawn"
],
g_MapInfo
)
);
 
// Limit melee weapons to 4
g_GSC.AddListener(Modules.MeleeWeaponControl(Entities, 4));
 
// Remove all gascans on non-scavenge maps
g_GSC.AddListener(Modules.GasCanControl(Entities, g_MapInfo));


// double include protection
if("Timers" in this) return;


Timers <- {};
Msg("GSC/M/L Script run.\n");
</source>}}


class Timers.TimerCallback
====Confogl Additional Scripts====
{
These scripts are included in the main Confogl and <code>globals</code> scripts.
/* OnTimerElapsed()
Executed once the timer is elapsed in a GlobalTimer
*/
function OnTimerElapsed() {}
};


class Timers.GlobalTimer
=====globals=====
{{ExpandBox|<source lang=js>
// Call this once on script start to initialize CompLite's libraries and hook them up to ChallengeScript events.
// parentTable: The parent table where the CompLite library namespace should be created. This
// should be a table which will not be wiped on roundstart. getroottable() is a good idea, defaults to getroottable();
// customNameSpace: The custom name to use for the CompLite Libraries' namespace. Should be passed as a string. Defaults to "CompLite"
// ChallengeScript: Pass a reference to the ChallengeScript table. Defaults to ::DirectorScript.MapScript.ChallengeScript
function InitializeCompLite(parentTable = getroottable() , customNameSpace = "CompLite", ChallengeScript = ::DirectorScript.MapScript.ChallengeScript )
{
{
constructor()
if(customNameSpace in parentTable)
{
{
m_callbacks = array(0);
local CompLite = parentTable[customNameSpace];
m_cbtimes = array(0);
CompLite.Globals.IncrementRoundNumber();
}
CompLite.Globals.GSM.Reset();
// Returns the current time in some format that supports arithmetic operations
 
// Overload this in your final class
if(CompLite.Globals.GetCurrentRound() == 1)
function GetCurrentTime() { assert(null) }
/* Update()
Checks to see which timers have elapsed.
Please run on global frame Update() function
*/
function Update()
{
while(m_cbtimes.len() && m_cbtimes[0] <= GetCurrentTime())
{
{
//Msg("Executing timer at "+GetCurrentTime()+" ("+Time()+") elapsed "+m_cbtimes[0]+"\n");
CompLite.Globals.MapInfo.IdentifyMap(Entities);
local cb = m_callbacks[0];
m_callbacks.remove(0);
m_cbtimes.remove(0);
cb.OnTimerElapsed();
}
}
ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
return CompLite;
}
}
/* AddTimer(time,timer)
local CompLite = parentTable[customNameSpace] <- {};
Register a new timed callback.
time: Time in seconds to wait before executing the timer callback.
IncludeScript("gamestate_model", CompLite);
timer: TimerCallback to execute once the timer has elapsed.
IncludeScript("globaltimers", CompLite);
*/
IncludeScript("utils", CompLite);
function AddTimer(time, timer)
IncludeScript("modules", CompLite);
{
local cbtime = GetCurrentTime() + time;
CompLite.ChallengeScript <- {
//Msg("Adding time at "+GetCurrentTime()+" ("+Time()+") for "+cbtime+"\n");
CompLite = CompLite
// Insert sorted Ascending by end timestamp (cbtime) into callbacks list
DirectorOptions = {
local i = 0;
CompLite = CompLite
// TODO: Binary search
function AllowWeaponSpawn( classname )
while(i < m_callbacks.len() && m_cbtimes[i] < cbtime) i++;
{
if(i == m_callbacks.len())
return CompLite.Globals.GSM.OnAllowWeaponSpawn(classname);
{
}
m_callbacks.push(timer);
function ConvertWeaponSpawn( classname )
m_cbtimes.push(cbtime);
{
return CompLite.Globals.GSM.OnConvertWeaponSpawn(classname);
}
function GetDefaultItem( idx )  
{
return CompLite.Globals.GSM.OnGetDefaultItem(idx);
}
function ConvertZombieClass( id )
{
return CompLite.Globals.GSM.OnConvertZombieClass(id);
}
}
}
else
 
function Update()
{
{
m_callbacks.insert(i,timer);
CompLite.Globals.Timer.Update();
m_cbtimes.insert(i,cbtime);
CompLite.Globals.FrameTimer.Update();
CompLite.Globals.GSM.DoFrameUpdate();
}
}
}
}


m_callbacks = [];
CompLite.Globals <- CompLiteGlobals(CompLite, Director, CompLite.ChallengeScript.DirectorOptions);
m_cbtimes = [];
};


class Timers.GlobalSecondsTimer extends Timers.GlobalTimer
ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
{
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
// Returns the current time in seconds (internal use)
function GetCurrentTime() { return Time(); }
};


class Timers.GlobalFrameTimer extends Timers.GlobalTimer
CompLite.Globals.MapInfo.IdentifyMap(Entities);
{
 
// Returns the current time in frames (internal use)
return CompLite;
function GetCurrentTime()
}
 
class CompLiteGlobals {
constructor(NameSpace, director, dopts)
{
{
return m_curFrame;
Timer = NameSpace.Timers.GlobalSecondsTimer();
FrameTimer = NameSpace.Timers.GlobalFrameTimer();
MapInfo = NameSpace.Utils.MapInfo();
GSC = NameSpace.GameState.GameStateController();
GSM = NameSpace.GameState.GameStateModel(GSC, director);
MobResetti = NameSpace.Utils.ZeroMobReset(director, dopts, FrameTimer);
}
}
/* Update()
 
Checks to see which timers have elapsed and executes callbacks.
function IncrementRoundNumber() { m_iRoundNumber++; }
Please run on global frame Update() function
function GetCurrentRound() { return m_iRoundNumber; }
*/
//public
function Update()
Timer = null;
{
FrameTimer = null;
// TODO: Integer overflow after 180+ hours on the round?
MapInfo = null;
m_curFrame++;
GSM = null;
baseclass.Update();
GSC = null;
}
MobResetti = null;
m_curFrame = 0;
// Need a name reference to base class
// private
static baseclass = Timers.GlobalTimer;
m_iRoundNumber = 0;
};
}
</source>}}
</source>}}


=====utils=====
=====gamestate_model=====
{{ExpandBox|<source lang=js>
{{ExpandBox|<source lang=js>
// vim: set ts=4
// vim: set ts=4
// Utilities for L4D2 Vscript Mutations
// L4D2 GameState Model for Mutation VScripts
// Copyright (C) 2012 ProdigySim
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// All rights reserved.
// =============================================================================
// =============================================================================


// double include protection
if("GameState" in this) return;
GameState <- {
ROUNDSTART_DELAY_INTERVAL = 2
};
IncludeScript("utils", this);


if("Utils" in this) return;
class GameState.GameStateModel
Utils <- {
{
SIClass = {
constructor(controller, director)
Smoker = 1
{
Boomer = 2
m_controller = controller;
Hunter = 3
m_pDirector = director;
Spitter = 4
Jockey = 5
Charger = 6
Witch = 7
Tank = 8
}
}
SIModels = [
"", // null entry
["models/infected/smoker.mdl"],
["models/infected/boomer.mdl", "models/infected/boomette.mdl"],
["models/infected/hunter.mdl"],
["models/infected/spitter.mdl"],
["models/infected/jockey.mdl"],
["models/infected/charger.mdl"],
["models/infected/witch.mdl", "models/infected/witch_bride.mdl"],
["models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl"]
]
SurvivorModels = {
coach = "models/survivors/survivor_coach.mdl"
ellis = "models/survivors/survivor_mechanic.mdl"
nick = "models/survivors/survivor_gambler.mdl"
rochelle = "models/survivors/survivor_producer.mdl"
louis = "models/survivors/survivor_manager.mdl"
bill = "models/survivors/survivor_namvet.mdl"
francis = "models/survivors/survivor_biker.mdl"
zoey = "models/survivors/survivor_teenangst.mdl"
}
MeleeModels = [
"models/weapons/melee/w_bat.mdl",
"models/weapons/melee/w_chainsaw.mdl"
"models/weapons/melee/w_cricket_bat.mdl",
"models/weapons/melee/w_crowbar.mdl",
"models/weapons/melee/w_didgeridoo.mdl",
"models/weapons/melee/w_electric_guitar.mdl",
"models/weapons/melee/w_fireaxe.mdl",
"models/weapons/melee/w_frying_pan.mdl",
"models/weapons/melee/w_golfclub.mdl",
"models/weapons/melee/w_katana.mdl",
"models/weapons/melee/w_machete.mdl",
"models/weapons/melee/w_riotshield.mdl",
"models/weapons/melee/w_tonfa.mdl"
]
};
IncludeScript("globaltimers", this);


/* KeyReset
function DoFrameUpdate()
Create a KeyReset to track the state of a key before you change its value, and
{
reset it to the original value/state when you want to revert it.
if(m_bLastUpdateTankInPlay)
Can detect whether a key existed or not and will delete the key afterwards if it doesn't exists.
{
if(!m_pDirector.IsTankInPlay())
e.g.
{
myKeyReset = KeyReset(DirectorOptions, "JockeyLimit")
m_bLastUpdateTankInPlay = false;
m_controller.TriggerTankLeavesPlay();
then on some event...
}
myKeyReset.set(0); // Set DirectorOptions.JockeyLimit to 0, storing the previous value/state
}
else if(m_pDirector.IsTankInPlay())
and later...
{
myKeyReset.unset(); // Reset DirectorOptions.JockeyLimit to whatever value it was before, or delete
m_bLastUpdateTankInPlay = true;
m_controller.TriggerTankEntersPlay();
 
}
*/
if(!m_bLastUpdateSafeAreaOpened && m_pDirector.HasAnySurvivorLeftSafeArea())
 
{
// Class that will detect the existence and old value of a key and store
m_bLastUpdateSafeAreaOpened = true;
// it for "identical" resetting at a later time.
m_controller.TriggerSafeAreaOpen();
// Assumes that while between Set() and Unset() calls no other entity will modify the
}
// value of this key.
if(!m_bRoundStarted && m_bHeardAWS && m_bHeardCWS && m_bHeardGDI
class Utils.KeyReset
&& m_iRoundStartTime < Time()-m_roundstart_delay)
{
{
constructor(owner, key)
m_bRoundStarted = true;
m_controller.TriggerRoundStart(GetCurrentRound());
}
}
function OnAllowWeaponSpawn( classname )
{
{
m_owner = owner;
m_bHeardAWS = true;
m_key = key;
m_iRoundStartTime = Time();
return m_controller.TriggerAllowWeaponSpawn(classname);
}
}
function set(val)
function OnConvertWeaponSpawn( classname )
{
{
if(!m_bSet)
m_bHeardCWS = true;
{
m_iRoundStartTime = Time();
m_bExists = m_owner.rawin(m_key);
return m_controller.TriggerConvertWeaponSpawn(classname);
if(m_bExists)
{
m_oldVal = m_owner.rawget(m_key);
}
m_bSet = true;
}
m_owner.rawset(m_key,val);
}
}
function unset()
function OnGetDefaultItem( idx )
{
{
if(!m_bSet) return;
m_bHeardGDI = true;
m_iRoundStartTime = Time();
if(m_bExists)
return m_controller.TriggerGetDefaultItem(idx);
{
}
m_owner.rawset(m_key,m_oldVal);
function OnConvertZombieClass(id)
}
{
else
return m_controller.TriggerPCZSpawn(id);
{
}
m_owner.rawdelete(m_key);
 
}
function Reset()
m_bSet = false;
{
m_bRoundStarted = false;
m_bHeardAWS = false;
m_bHeardCWS = false;
m_bHeardGDI = false;
m_iRoundStartTime = 0;
m_bLastUpdateSafeAreaOpened = false;
}
}
m_owner = null;
m_key = null;
m_oldVal = null;
m_bExists = false;
m_bSet = false;
};


// Check for various round-start even. ts before triggering OnRoundStart()
m_bRoundStarted = false;
m_bHeardAWS = false;
m_bHeardCWS = false;
m_bHeardGDI = false;
m_iRoundStartTime = 0;


/* ZeroMobReset
m_bLastUpdateTankInPlay = false;
Class which handles resetting the mob timer without spawning CI.
m_bLastUpdateSafeAreaOpened = false;
e.g.
g_MobTimerCntl = ZeroMobReset(Director, DirectorOptions, g_FrameTimer);
then later on some event
g_MobTimerCntl.ZeroMobReset();
m_controller = null;
m_pDirector = null;
m_roundstart_delay = GameState.ROUNDSTART_DELAY_INTERVAL;
static GetCurrentRound = Utils.GetCurrentRound;
};


*/
class GameState.GameStateListener
// Can reset the mob spawn timer at any point without
// triggering an CI to spawn. Should not demolish any other state settings.
class Utils.ZeroMobReset extends Timers.TimerCallback
{
{
// Initialize with Director, DirectorOptions, and a GlobalFrameTimer
// Called on round start. These may be multiples of these triggered, unfortunately.
constructor(director, dopts, timer)
function OnRoundStart(roundNumber) {}
{
// Called when a player leaves saferoom or the saferoom timer counts down
m_director = director;
function OnSafeAreaOpened() {}
m_timer = timer;
// Called when tank spawns
m_mobsizesetting = KeyReset(dopts, "MobSpawnSize");
function OnTankEntersPlay() {}
}
// Called when tank dies/leaves play
/* ZeroMobReset()
function OnTankLeavesPlay() {}
Resets the director's mob timer.
// Called when a player-controlled zombie is going to be spawned via ConvertZombieClass
Will trigger incoming horde music, but will not spawn any commons.
// This event will be chained--called on all Listeners with the modified id passed into successive calls.
*/
// id: SIClass id of the PCZ to be spawned
function ZeroMobReset()
// return another SIClass value to convert the PCZ spawn.
function OnSpawnPCZ(id) {}
// Called when a player-controlled zombie is going to be spawned via ConvertZombieClass
// After conversions from OnSpawnPCZ have taken place
// id: actual SIClass id to be spawned
function OnSpawnedPCZ(id) {}
// Called when DirectorOptions.GetDefaultItem() is called.
// This event chain will notify all listeners, but only one return value will be used.
// TODO: further abstraction to just have lists returned...
// Should be at the beginning of the round normally.
function OnGetDefaultItem(idx) {}
// Called when DirectorOptions.AllowWeaponSpawn() is called.
// Should be at the beginning of the round normally, after conversions take place.
// This event will stop being called on Listeners when one listener returns false.
// classname: string classname of weapon to allow/disallow
// return true to allow, false to disallow.
function OnAllowWeaponSpawn(classname) {}
// Called when DirectorOptions.ConvertWeaponSpawn is called
// This event will be chained--called on all Listeners with the modified classname passed into successive calls.
// classname: Classname of the weapon that would spawned
// retun the classname that it should be converted to, or 0 for no conversion.
function OnConvertWeaponSpawn(classname) {}
};
 
class GameState.GameStateController
{
function AddListener(listener)
{
{
if(m_bResetInProgress) return;
m_listeners.push(listener)
// set DirectorOptions.MobSpawnSize to 0 so the triggered
// horde won't spawn CI
m_mobsizesetting.set(0);
m_director.ResetMobTimer();
m_timer.AddTimer(1, this)
m_bResetInProgress = true;
}
// Internal use only,
// resets the mob size setting after the mob timer has been set
function OnTimerElapsed()
{
m_mobsizesetting.unset();
m_bResetInProgress = false;
}
}
m_bResetInProgress = false;
m_director = null;
m_timer = null;
m_mobsizesetting = null;
static KeyReset = Utils.KeyReset;
};


class Utils.Sphere {
function TriggerRoundStart(roundNumber)
constructor(center, radius)
{
{
m_vecOrigin = center;
foreach(listener in m_listeners)
m_flRadius = radius;
listener.OnRoundStart(roundNumber);
}
}
function GetOrigin()
function TriggerSafeAreaOpen()
{
{
return m_vecOrigin();
foreach(listener in m_listeners)
listener.OnSafeAreaOpened();
}
}
function GetRadius()
function TriggerTankEntersPlay()
{
{
return m_flRadius;
foreach(listener in m_listeners)
listener.OnTankEntersPlay();
}
}
// point: vector
function TriggerTankLeavesPlay()
function ContainsPoint(point)
{
{
return (m_vecOrigin - point).Length() <= m_flRadius;
foreach(listener in m_listeners)
listener.OnTankLeavesPlay();
}
}
function ContainsEntity(entity)
function TriggerPCZSpawn(id)
{
{
return ContainsPoint(entity.GetOrigin());
local retval = id;
}
foreach(listener in m_listeners)
m_vecOrigin = null;
m_flRadius = null;
};
 
class Utils.MapInfo {
function IdentifyMap(EntList)
{
isIntro = EntList.FindByName(null, "fade_intro") != null
|| EntList.FindByName(null, "lcs_intro") != null;
 
// also will become true in scavenge gamemode!
hasScavengeEvent = EntList.FindByClassname(null, "point_prop_use_target") != null;
 
saferoomPoints = [];
 
if(isIntro)
{
{
local ent = EntList.FindByName(null, "survivorPos_intro_01");
// Allow each listener to try to convert.
if(ent != null) saferoomPoints.push(ent.GetOrigin());
// Not pretty in the long run but I'm okay with it.
local ret = listener.OnSpawnPCZ(retval);
if(ret != null) retval = ret;
}
}


local ent = null;
// Simply notify everyone of the final value
while((ent = EntList.FindByClassname(ent, "prop_door_rotating_checkpoint")) != null)
foreach(listener in m_listeners)
listener.OnSpawnedPCZ(retval)
return retval;
}
function TriggerAllowWeaponSpawn(classname)
{
foreach(listener in m_listeners)
{
{
saferoomPoints.push(ent.GetOrigin());
// Cancel call chain once one listener returns false (says not to spawn it).
if(listener.OnAllowWeaponSpawn(classname) == false) return false;
// Interesting ! semantics
//            null  false
// !ret        true  true
// ret==false  false true
// ret==null  true  false
}
}
 
return true;
if(IsMapC1M2(EntList)) mapname = "c1m2_streets";
else mapname = "unknown";
}
}
function IsPointNearAnySaferoom(point, distance=2000.0)
function TriggerConvertWeaponSpawn(classname)
{
{
// We actually check if any saferoom is near the point...
local retcls = 0;
local sphere = Sphere(point, distance);
foreach(listener in m_listeners)
foreach(pt in saferoomPoints)
{
{
if(sphere.ContainsPoint(pt)) return true;
local ret = listener.OnConvertWeaponSpawn(classname);
if(ret != null && ret != 0) retcls = ret;
}
}
return false;
return retcls;
}
}
function IsEntityNearAnySaferoom(entity, distance=2000.0)
function TriggerGetDefaultItem(idx)
{
{
return IsPointNearAnySaferoom(entity.GetOrigin(), distance);
local retval = 0;
foreach(listener in m_listeners)
{
local ret = listener.OnGetDefaultItem(idx);
if(retval == 0 && ret != null) retval = ret;
}
return retval;
}
}
function IsMapC1M2(EntList)
 
{
m_listeners = []
// Identified by a entity with a given model at a given point
local ent = EntList.FindByModel(null, "models/destruction_tanker/c1m2_cables_far.mdl");
if(ent != null
&& (ent.GetOrigin() - Vector(-6856.0,-896.0,384.664)).Length() < 1.0) return true;
return false;
}
isIntro = false
isFinale = false
hasScavengeEvent = false;
saferoomPoints = null;
mapname = null
chapter = 0
Sphere = Utils.Sphere;
};
};
</source>}}
=====globaltimers=====
{{ExpandBox|<source lang=js>
// vim: set ts=4
// Global Timers for L4D2 VScript Mutations
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// =============================================================================


class Utils.VectorClone {
/* Usage:
constructor(vec)
Create an instance of one of these timers, e.g.
g_Timer = GlobalTimer()
and/or
g_FrameTimer = GlobalFrameTimer()
Then run the timer's Update() function in your global "update" function.
e.g.  
function Update()
{
{
x=vec.x;
g_Timer.Update();
y=vec.y;
g_FrameTimer.Update();
z=vec.z;
}
}
function ToVector()
To add a timer, extend TimerCallback to create a callback.
e.g.
class MyCallback extends TimerCallback
{
{
return Vector(x,y,z);
function OnTimerElapsed()
{
Msg("My Timer has elapsed!!!\n");
}
}
}
x=0.0
y=0.0
Then register it to the global timer of your choice,
z=0.0
};
*/


class Utils.ItemInfo {
// double include protection
constructor(ent)
if("Timers" in this) return;
{
m_vecOrigin = VectorClone(ent.GetOrigin());
//m_vecForward = ent.GetForwardVector();
}
m_vecOrigin = null;
//m_vecForward = null;
static VectorClone = Utils.VectorClone;
};


Utils.KillEntity <- function (ent)
Timers <- {};
{
DoEntFire("!self", "kill", "", 0, null, ent);
}


Utils.ArrayToTable <- function (arr)
class Timers.TimerCallback
{
{
local tab = {};
/* OnTimerElapsed()
foreach(str in arr) tab[str] <- 0;
Executed once the timer is elapsed in a GlobalTimer
return tab;
*/
}
function OnTimerElapsed() {}
};


Utils.ArrayRemoveByValue <- function (arr, value)
class Timers.GlobalTimer
{
{
foreach(id,val in arr)
constructor()
{
{
if(val == value)
m_callbacks = array(0);
{
m_cbtimes = array(0);
arr.remove(id);
break;
}
}
}
}
// Returns the current time in some format that supports arithmetic operations
 
// Overload this in your final class
// return index on found
function GetCurrentTime() { assert(null) }
// return -1 on not found
/* Update()
Utils.ArraySearchByValue <- function (arr, value)
Checks to see which timers have elapsed.
{
Please run on global frame Update() function
foreach(id,val in arr)
*/
function Update()
{
{
if(val == value)
while(m_cbtimes.len() && m_cbtimes[0] <= GetCurrentTime())
{
{
return id;
//Msg("Executing timer at "+GetCurrentTime()+" ("+Time()+") elapsed "+m_cbtimes[0]+"\n");
break;
local cb = m_callbacks[0];
m_callbacks.remove(0);
m_cbtimes.remove(0);
cb.OnTimerElapsed();
}
}
}
}
return -1;
}
/* AddTimer(time,timer)
 
Register a new timed callback.
Utils.IsEntityInMoveHeirarchy <- function (moveChildEnt, moveParentCandidate)
time: Time in seconds to wait before executing the timer callback.
{
timer: TimerCallback to execute once the timer has elapsed.
local curEnt = moveChildEnt;
*/
while(curEnt != null)
function AddTimer(time, timer)
{
{
curEnt = curEnt.GetMoveParent();
local cbtime = GetCurrentTime() + time;
if(curEnt == moveParentCandidate) return true;
//Msg("Adding time at "+GetCurrentTime()+" ("+Time()+") for "+cbtime+"\n");
// Insert sorted Ascending by end timestamp (cbtime) into callbacks list
local i = 0;
// TODO: Binary search
while(i < m_callbacks.len() && m_cbtimes[i] < cbtime) i++;
if(i == m_callbacks.len())
{
m_callbacks.push(timer);
m_cbtimes.push(cbtime);
}
else
{
m_callbacks.insert(i,timer);
m_cbtimes.insert(i,cbtime);
}
}
}
return false;
}


// TODO move/refactor...
m_callbacks = [];
Utils.GetCurrentRound <- function ()  
m_cbtimes = [];
{  
};
return ::CompLite.Globals.GetCurrentRound();
 
}
class Timers.GlobalSecondsTimer extends Timers.GlobalTimer
</source>}}
{
// Returns the current time in seconds (internal use)
function GetCurrentTime() { return Time(); }
};


=====modules=====
class Timers.GlobalFrameTimer extends Timers.GlobalTimer
{
// Returns the current time in frames (internal use)
function GetCurrentTime()
{
return m_curFrame;
}
/* Update()
Checks to see which timers have elapsed and executes callbacks.
Please run on global frame Update() function
*/
function Update()
{
// TODO: Integer overflow after 180+ hours on the round?
m_curFrame++;
baseclass.Update();
}
m_curFrame = 0;
// Need a name reference to base class
static baseclass = Timers.GlobalTimer;
};
</source>}}
 
=====utils=====
{{ExpandBox|<source lang=js>
{{ExpandBox|<source lang=js>
// vim: set ts=4
// vim: set ts=4
// CompLite Mutation Modules
// Utilities for L4D2 Vscript Mutations
// Copyright (C) 2012 ProdigySim
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// All rights reserved.
// =============================================================================
// =============================================================================


if("Modules" in this) return;
Modules <- {};
IncludeScript("gamestate_model", this);
IncludeScript("utils", this);


 
if("Utils" in this) return;
class Modules.MsgGSL extends GameState.GameStateListener
Utils <- {
{
SIClass = {
function OnRoundStart(roundNumber) { Msg("MsgGSL: OnRoundStart("+roundNumber+")\n"); }
Smoker = 1
function OnSafeAreaOpened() { Msg("MsgGSL: OnSafeAreaOpened()\n"); }
Boomer = 2
function OnTankEntersPlay() { Msg("MsgGSL: OnTankEntersPlay()\n"); }
Hunter = 3
function OnTankLeavesPlay() { Msg("MsgGSL: OnTankLeavesPlay()\n"); }
Spitter = 4
function OnSpawnPCZ(id) { Msg("MsgGSL: OnSpawnPCZ("+id+")\n"); }
Jockey = 5
function OnSpawnedPCZ(id) { Msg("MsgGSL: OnSpawnedPCZ("+id+")\n"); }
Charger = 6
function OnGetDefaultItem(idx)
Witch = 7
{
Tank = 8
if(idx == 0)
{
Msg("MsgGSL: OnGetDefaultItem(0) #"+m_defaultItemCnt+"\n");
m_defaultItemCnt++;
}
}
}
// Too much spam for these
SIModels = [
/*
"", // null entry
function OnAllowWeaponSpawn(classname) {}
["models/infected/smoker.mdl"],
function OnConvertWeaponSpawn(classname) {}
["models/infected/boomer.mdl", "models/infected/boomette.mdl"],
*/
["models/infected/hunter.mdl"],
m_defaultItemCnt = 0;
["models/infected/spitter.mdl"],
["models/infected/jockey.mdl"],
["models/infected/charger.mdl"],
["models/infected/witch.mdl", "models/infected/witch_bride.mdl"],
["models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl"]
]
SurvivorModels = {
coach = "models/survivors/survivor_coach.mdl"
ellis = "models/survivors/survivor_mechanic.mdl"
nick = "models/survivors/survivor_gambler.mdl"
rochelle = "models/survivors/survivor_producer.mdl"
louis = "models/survivors/survivor_manager.mdl"
bill = "models/survivors/survivor_namvet.mdl"
francis = "models/survivors/survivor_biker.mdl"
zoey = "models/survivors/survivor_teenangst.mdl"
}
MeleeModels = [
"models/weapons/melee/w_bat.mdl",
"models/weapons/melee/w_chainsaw.mdl"
"models/weapons/melee/w_cricket_bat.mdl",
"models/weapons/melee/w_crowbar.mdl",
"models/weapons/melee/w_didgeridoo.mdl",
"models/weapons/melee/w_electric_guitar.mdl",
"models/weapons/melee/w_fireaxe.mdl",
"models/weapons/melee/w_frying_pan.mdl",
"models/weapons/melee/w_golfclub.mdl",
"models/weapons/melee/w_katana.mdl",
"models/weapons/melee/w_machete.mdl",
"models/weapons/melee/w_riotshield.mdl",
"models/weapons/melee/w_tonfa.mdl"
]
};
};
IncludeScript("globaltimers", this);
/* KeyReset
Create a KeyReset to track the state of a key before you change its value, and
reset it to the original value/state when you want to revert it.
Can detect whether a key existed or not and will delete the key afterwards if it doesn't exists.
e.g.
myKeyReset = KeyReset(DirectorOptions, "JockeyLimit")
then on some event...
myKeyReset.set(0); // Set DirectorOptions.JockeyLimit to 0, storing the previous value/state
and later...
myKeyReset.unset(); // Reset DirectorOptions.JockeyLimit to whatever value it was before, or delete
*/


class Modules.SpitterControl extends GameState.GameStateListener
// Class that will detect the existence and old value of a key and store
// it for "identical" resetting at a later time.
// Assumes that while between Set() and Unset() calls no other entity will modify the
// value of this key.
class Utils.KeyReset
{
{
constructor(director, director_opts, entlist)
constructor(owner, key)
{
{
m_pDirector = director;
m_owner = owner;
m_pSpitterLimit = KeyReset(director_opts, "SpitterLimit");
m_key = key;
m_pEntities = entlist;
}
// Initialize to default order...
function set(val)
SpawnLastUsed = [1, 2, 3, 5, 6];
}
function OnTankEntersPlay()
{
{
m_pSpitterLimit.set(0);
if(!m_bSet)
}
function OnTankLeavesPlay()
{
m_pSpitterLimit.unset();
}
// Check if there is an instance of this SI on the map
function IsGivenSIClassSpawned(id)
{
foreach(mdl in SIModels[id])
{
{
if(m_pEntities.FindByModel(null, mdl) != null)
m_bExists = m_owner.rawin(m_key);
if(m_bExists)
{
{
return true;
m_oldVal = m_owner.rawget(m_key);
}
}
m_bSet = true;
}
}
return false;
m_owner.rawset(m_key,val);
}
}
function OnSpawnPCZ(id)
function unset()
{
{
// If a spitter is going to be spawned during tank,
if(!m_bSet) return;
if(id == SIClass.Spitter && m_pDirector.IsTankInPlay())
if(m_bExists)
{
{
foreach(si in SpawnLastUsed)
m_owner.rawset(m_key,m_oldVal);
{
// Note: Player keeps SI model until they receive a new spawn
// (while dead, they have the model of their last SI class)
if(!IsGivenSIClassSpawned(si)) return si;
}
// default to hunter if we really can't pick another class...
return SIClass.Hunter;
}
}
// Msg("Spawning SI Class "+newClass+".\n");
else
return id;
}
function OnSpawnedPCZ(id)
{
// Mark that this SI to be spawned is most recently spawned now.
if(id != SIClass.Spitter && id <= SIClass.Charger && id >= SIClass.Smoker)
{
{
// Low index = least recent
m_owner.rawdelete(m_key);
// High index = most recent
// Remove the other instance of this class in our array
ArrayRemoveByValue(SpawnLastUsed, id);
SpawnLastUsed.push(id);
}
}
m_bSet = false;
}
}
// List of last spawned time for each SI class
m_owner = null;
SpawnLastUsed = null;
m_key = null;
// reference to director options
m_oldVal = null;
m_pSpitterLimit = null;
m_bExists = false;
m_pDirector = null;
m_bSet = false;
m_pEntities = null;
static KeyReset = Utils.KeyReset;
static SIClass = Utils.SIClass;
static SIModels = Utils.SIModels;
static ArrayRemoveByValue = Utils.ArrayRemoveByValue;
};
};




class Modules.MobControl extends GameState.GameStateListener
/* ZeroMobReset
Class which handles resetting the mob timer without spawning CI.
e.g.
g_MobTimerCntl = ZeroMobReset(Director, DirectorOptions, g_FrameTimer);
then later on some event
g_MobTimerCntl.ZeroMobReset();
 
*/
// Can reset the mob spawn timer at any point without
// triggering an CI to spawn. Should not demolish any other state settings.
class Utils.ZeroMobReset extends Timers.TimerCallback
{
{
constructor(mobresetti)
// Initialize with Director, DirectorOptions, and a GlobalFrameTimer
constructor(director, dopts, timer)
{
{
//m_dopts = director_opts;
m_director = director;
m_resetti = mobresetti;
m_timer = timer;
m_mobsizesetting = KeyReset(dopts, "MobSpawnSize");
}
}
function OnSafeAreaOpened()  
/* ZeroMobReset()
Resets the director's mob timer.
Will trigger incoming horde music, but will not spawn any commons.
*/
function ZeroMobReset()
{
{
m_resetti.ZeroMobReset();
if(m_bResetInProgress) return;
// set DirectorOptions.MobSpawnSize to 0 so the triggered
// horde won't spawn CI
m_mobsizesetting.set(0);
m_director.ResetMobTimer();
m_timer.AddTimer(1, this)
m_bResetInProgress = true;
}
}
// These functions created major problems....
// Internal use only,
/*
// resets the mob size setting after the mob timer has been set
function OnTankEntersPlay()
function OnTimerElapsed()
{
{
m_oldMinTime = m_dopts.MobSpawnMinTime;
m_mobsizesetting.unset();
m_oldMaxTime = m_dopts.MobSpawnMaxTime;
m_bResetInProgress = false;
 
m_dopts.MobSpawnMinTime = 99999;
m_dopts.MobSpawnMaxTime = 99999;
 
m_resetti.ZeroMobReset();
}
}
function OnTankLeavesPlay()
m_bResetInProgress = false;
{
m_director = null;
m_dopts.MobSpawnMinTime = m_oldMinTime;
m_timer = null;
m_dopts.MobSpawnMaxTime = m_oldMaxTime;
m_mobsizesetting = null;
static KeyReset = Utils.KeyReset;
};


m_resetti.ZeroMobReset();
class Utils.Sphere {
}
constructor(center, radius)
m_oldMinTime = 0;
m_oldMaxTime = 0;
m_dopts = null; */
m_resetti = null;
};
 
class Modules.BasicItemSystems extends GameState.GameStateListener
{
constructor(removalTable, convertTable, defaultItemList)
{
{
m_removalTable = removalTable;
m_vecOrigin = center;
m_convertTable = convertTable;
m_flRadius = radius;
m_defaultItemList = defaultItemList;
}
}
function OnAllowWeaponSpawn(classname)
function GetOrigin()
{
{
if ( classname in m_removalTable )
return m_vecOrigin();
{
if(m_removalTable[classname] > 0)
{
//Msg("Found a "+classname+" to keep, "+m_removalTable[classname]+" remain.\n");
m_removalTable[classname]--
}
else if (m_removalTable[classname] < -1)
{
//Msg("Killing just one "+classname+"\n");
m_removalTable[classname]++
return false;
}
else if (m_removalTable[classname] == 0)
{
//Msg("Removed "+classname+"\n")
return false;
}
}
return true;
}
}
function OnConvertWeaponSpawn(classname)
function GetRadius()
{
{
if ( classname in m_convertTable )
return m_flRadius;
{
//Msg("Converting"+classname+" to "+convertTable[classname]+"\n")
return m_convertTable[classname];
}
return 0;
}
}
function OnGetDefaultItem(idx)
// point: vector
function ContainsPoint(point)
{
{
if ( idx < m_defaultItemList.len())
return (m_vecOrigin - point).Length() <= m_flRadius;
{
return m_defaultItemList[idx];
}
return 0;
}
}
m_removalTable = null;
function ContainsEntity(entity)
m_convertTable = null;
{
m_defaultItemList = null;
return ContainsPoint(entity.GetOrigin());
}
m_vecOrigin = null;
m_flRadius = null;
};
};


class Modules.EntKVEnforcer extends GameState.GameStateListener
class Utils.MapInfo {
{
function IdentifyMap(EntList)
constructor(EntList, classes, models, key, value)
{
{
m_pEntities = EntList;
isIntro = EntList.FindByName(null, "fade_intro") != null
m_classes = classes;
|| EntList.FindByName(null, "lcs_intro") != null;
m_models = models;
 
m_key = key;
// also will become true in scavenge gamemode!
switch(typeof value)
hasScavengeEvent = EntList.FindByClassname(null, "point_prop_use_target") != null;
 
saferoomPoints = [];
 
if(isIntro)
{
{
case "bool":
local ent = EntList.FindByName(null, "survivorPos_intro_01");
m_value = value.tointeger();
if(ent != null) saferoomPoints.push(ent.GetOrigin());
m_setFunc = "__KeyValueFromInt";
break;
case "float":
m_value = value.tointeger();
m_setFunc = "__KeyValueFromInt";
break;
case "integer":
m_value = value;
m_setFunc = "__KeyValueFromInt";
break;
case "string":
m_value = value;
m_setFunc = "__KeyValueFromString";
break;
case "instance":
if(value.getclass() == ::Vector)
{
m_value = value;
m_setFunc = "__KeyValueFromVector";
break;
}
default:
m_value = null;
m_setFunc = null;
// Unsupported type!!!
throw "Unsupported type "+(typeof value)+" used with EntKVEnforcer!";
}
}
}
 
function OnRoundStart(roundNumber)
{
local ent = null;
local ent = null;
 
while((ent = EntList.FindByClassname(ent, "prop_door_rotating_checkpoint")) != null)
while((ent = m_pEntities.Next(ent)) != null)
{
{
if(ent.GetClassname() in m_classes)
saferoomPoints.push(ent.GetOrigin());
{
}
ent[m_setFuncName].call(ent, m_key, m_value);
}
}


foreach(mdl in m_models)
if(IsMapC1M2(EntList)) mapname = "c1m2_streets";
else mapname = "unknown";
}
function IsPointNearAnySaferoom(point, distance=2000.0)
{
// We actually check if any saferoom is near the point...
local sphere = Sphere(point, distance);
foreach(pt in saferoomPoints)
{
{
ent = null;
if(sphere.ContainsPoint(pt)) return true;
while((ent = m_pEntities.FindByModel(mdl)) != null)
{
ent[m_setFuncName].call(ent, m_key, m_value);
}
}
}
return false;
}
function IsEntityNearAnySaferoom(entity, distance=2000.0)
{
return IsPointNearAnySaferoom(entity.GetOrigin(), distance);
}
function IsMapC1M2(EntList)
{
// Identified by a entity with a given model at a given point
local ent = EntList.FindByModel(null, "models/destruction_tanker/c1m2_cables_far.mdl");
if(ent != null
&& (ent.GetOrigin() - Vector(-6856.0,-896.0,384.664)).Length() < 1.0) return true;
return false;
}
}
m_pEntities = null;
isIntro = false
m_classes = null;
isFinale = false
m_models = null;
hasScavengeEvent = false;
m_key = null;
saferoomPoints = null;
m_value = null;
mapname = null
m_setFunc = null;
chapter = 0
Sphere = Utils.Sphere;
};
};


class Modules.ItemControl extends GameState.GameStateListener
class Utils.VectorClone {
{
constructor(vec)
constructor(entlist, removalTable, modelRemovalTable, saferoomRemoveList, mapinfo)
{
{
m_entlist = entlist;
x=vec.x;
m_removalTable = removalTable;
y=vec.y;
m_modelRemovalTable = modelRemovalTable;
z=vec.z;
m_saferoomRemoveList = ArrayToTable(saferoomRemoveList);
m_pMapInfo = mapinfo;
}
}
function OnFirstRound()
function ToVector()
{
{
local ent = m_entlist.First();
return Vector(x,y,z);
local classname = "";
}
local tItemEnts = {};
x=0.0
local saferoomEnts = [];
y=0.0
z=0.0
};


// Create an empty array for each item in our list.
class Utils.ItemInfo {
foreach(key,val in m_removalTable)
constructor(ent)
{
{
tItemEnts[key] <- [];
m_vecOrigin = VectorClone(ent.GetOrigin());
}
//m_vecForward = ent.GetForwardVector();
}
m_vecOrigin = null;
//m_vecForward = null;
static VectorClone = Utils.VectorClone;
};


while(ent != null)
Utils.KillEntity <- function (ent)
{
{
classname = ent.GetClassname()
DoEntFire("!self", "kill", "", 0, null, ent);
if(classname in m_saferoomRemoveList && m_pMapInfo.IsEntityNearAnySaferoom(ent, 1600.0))
}
{
// Make a list of items which are in saferooms that need to be removed
// and don't track these entities for other removal.
saferoomEnts.push(ent.weakref());
}
else if(classname in m_removalTable)
{
tItemEnts[classname].push(ent.weakref());
}
ent=m_entlist.Next(ent);
}


local tModelEnts = {};
Utils.ArrayToTable <- function (arr)
{
local tab = {};
foreach(str in arr) tab[str] <- 0;
return tab;
}


foreach(mdl,limit in m_modelRemovalTable)
Utils.ArrayRemoveByValue <- function (arr, value)
{
foreach(id,val in arr)
{
if(val == value)
{
{
local thisMdlEnts = tModelEnts[mdl] <- [];
arr.remove(id);
ent = null;
break;
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
{
// Only use this entity if it's not one of the saferoom entities we're going to remove.
thisMdlEnts.push(ent.weakref());
}
}
}
}
}


// Remove all targeted saferoom items before doing roundstart removals
// return index on found
foreach(entity in saferoomEnts) KillEntity(entity);
// return -1 on not found
local KilledEntList = saferoomEnts;
Utils.ArraySearchByValue <- function (arr, value)
 
{
m_firstRoundEnts = {}
foreach(id,val in arr)
foreach(classname,instances in tItemEnts)
{
if(val == value)
{
{
local cnt = m_removalTable[classname].tointeger();
return id;
local saved_ents = m_firstRoundEnts[classname] <- [];
break;
// We need to choose certain items to save
while( instances.len() > 0 && saved_ents.len() < cnt )
{
local saveIdx = RandomInt(0,instances.len()-1);
// Track this entity's info for future rounds.
saved_ents.push(ItemInfo(instances[saveIdx]));
// Remove this entity from the kill list
instances.remove(saveIdx);
}
Msg("Killing "+instances.len()+" "+classname+", leaving "+saved_ents.len()+" on the map.\n");
foreach(inst in instances)
{
KillEntity(inst);
KilledEntList.push(inst.weakref());
}
}
}
}
return -1;
}


m_firstRoundModelEnts = {}
Utils.IsEntityInMoveHeirarchy <- function (moveChildEnt, moveParentCandidate)
foreach(model,instances in tModelEnts)
{
{
local curEnt = moveChildEnt;
// Don't use killed ents!
while(curEnt != null)
foreach(deadEnt in KilledEntList) ArrayRemoveByValue(instances, deadEnt);
local limit = m_modelRemovalTable[model].tointeger();
local saved_ents = m_firstRoundModelEnts[model] <- [];
while( instances.len() > 0 && saved_ents.len() < limit )
{
local saveIdx = RandomInt(0,instances.len()-1);
// Track this entity's info for future rounds.
saved_ents.push(ItemInfo(instances[saveIdx]));
// Remove this entity from the kill list
instances.remove(saveIdx);
}
Msg("Killing "+instances.len()+" "+model+", leaving "+saved_ents.len()+" on the map.\n");
foreach(inst in instances)
{
KillEntity(inst);
}
}
}
function OnLaterRounds()
{
{
local ent = m_entlist.First();
curEnt = curEnt.GetMoveParent();
local classname = "";
if(curEnt == moveParentCandidate) return true;
local tItemEnts = {};
}
 
return false;
foreach(key,val in m_removalTable)
}
{
tItemEnts[key] <- [];
}
while(ent != null)
{
classname = ent.GetClassname()
if(classname in m_removalTable)
{
tItemEnts[classname].push(ent.weakref());
}
ent=m_entlist.Next(ent);
}


local tModelEnts = {};
// TODO move/refactor...
foreach(mdl,limit in m_modelRemovalTable)
Utils.GetCurrentRound <- function ()  
{
{  
local thisMdlEnts = tModelEnts[mdl] <- [];
return ::CompLite.Globals.GetCurrentRound();
ent = null;
}
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
</source>}}
{
// Only use this entity if it's not one of the saferoom entities we're going to remove.
thisMdlEnts.push(ent.weakref());
}
}


foreach(classname,entList in tItemEnts)
=====modules=====
{
{{ExpandBox|<source lang=js>
local firstItems = m_firstRoundEnts[classname];
// vim: set ts=4
// count to keep alive
// CompLite Mutation Modules
local cnt = firstItems.len();
// Copyright (C) 2012 ProdigySim
if(cnt > entList.len())
// All rights reserved.
{
// =============================================================================
Msg("Warning! Not enough "+classname+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
cnt = entList.len();
}


for(local i = cnt; i < entList.len(); i++)
if("Modules" in this) return;
{
Modules <- {};
KillEntity(entList[i]);
IncludeScript("gamestate_model", this);
}
IncludeScript("utils", this);


for(local i = 0; i < cnt; i++)
{
local vec = VectorClone(firstItems[i].m_vecOrigin);


// Hack. To avoid crashing the server by placing entities on top of each other here,
class Modules.MsgGSL extends GameState.GameStateListener
// we'll just offset the placement of each entity by 1 unit.  
{
// Alternative solutions:
function OnRoundStart(roundNumber) { Msg("MsgGSL: OnRoundStart("+roundNumber+")\n"); }
// 1. Check all tracked entity positions for conflicts and resolve through <insert algorithm here>
function OnSafeAreaOpened() { Msg("MsgGSL: OnSafeAreaOpened()\n"); }
// 2. Move all entities to offset by 1 unit, wait 1 frame, move entities to original (un-offset) position (same frame also crashes)
function OnTankEntersPlay() { Msg("MsgGSL: OnTankEntersPlay()\n"); }
// 3. Convert this code to kill/create entities instead of kill/shuffle entities (not possible atm)
function OnTankLeavesPlay() { Msg("MsgGSL: OnTankLeavesPlay()\n"); }
vec.z += 1.0;
function OnSpawnPCZ(id) { Msg("MsgGSL: OnSpawnPCZ("+id+")\n"); }
entList[i].SetOrigin(vec.ToVector());
function OnSpawnedPCZ(id) { Msg("MsgGSL: OnSpawnedPCZ("+id+")\n"); }
//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
function OnGetDefaultItem(idx)
}
{
Msg("Restored "+cnt+" "+classname+", out of "+entList.len()+" on the map.\n");
if(idx == 0)
{
Msg("MsgGSL: OnGetDefaultItem(0) #"+m_defaultItemCnt+"\n");
m_defaultItemCnt++;
}
}
}
// Too much spam for these
/*
function OnAllowWeaponSpawn(classname) {}
function OnConvertWeaponSpawn(classname) {}
*/
m_defaultItemCnt = 0;
};


foreach(model,entList in tModelEnts)
class Modules.SpitterControl extends GameState.GameStateListener
{
constructor(director, director_opts, entlist)
{
m_pDirector = director;
m_pSpitterLimit = KeyReset(director_opts, "SpitterLimit");
m_pEntities = entlist;
// Initialize to default order...
SpawnLastUsed = [1, 2, 3, 5, 6];
}
function OnTankEntersPlay()
{
m_pSpitterLimit.set(0);
}
function OnTankLeavesPlay()
{
m_pSpitterLimit.unset();
}
// Check if there is an instance of this SI on the map
function IsGivenSIClassSpawned(id)
{
foreach(mdl in SIModels[id])
{
{
local firstItems = m_firstRoundModelEnts[model];
if(m_pEntities.FindByModel(null, mdl) != null)
// count to keep alive
local cnt = firstItems.len();
if(cnt > entList.len())
{
{
Msg("Warning! Not enough "+model+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
return true;
cnt = entList.len();
}
}
 
}
for(local i = cnt; i < entList.len(); i++)
return false;
}
function OnSpawnPCZ(id)
{
// If a spitter is going to be spawned during tank,
if(id == SIClass.Spitter && m_pDirector.IsTankInPlay())
{
foreach(si in SpawnLastUsed)
{
{
KillEntity(entList[i]);
// Note: Player keeps SI model until they receive a new spawn
// (while dead, they have the model of their last SI class)
if(!IsGivenSIClassSpawned(si)) return si;
}
}
 
// default to hunter if we really can't pick another class...
for(local i = 0; i < cnt; i++)
return SIClass.Hunter;
{
entList[i].SetOrigin(firstItems[i].m_vecOrigin.ToVector());
//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
}
Msg("Restored "+cnt+" "+model+", out of "+entList.len()+" on the map.\n");
}
}
// Msg("Spawning SI Class "+newClass+".\n");
return id;
}
}
function OnRoundStart(roundNumber)
function OnSpawnedPCZ(id)
{
{
Msg("ItemControl OnRoundStart()\n");
// Mark that this SI to be spawned is most recently spawned now.
// This will run multiple times per round in certain cases...
if(id != SIClass.Spitter && id <= SIClass.Charger && id >= SIClass.Smoker)
// Notably, on natural map switch (transition) e.g. chapter 1 ends, start chapter 2.
// Just make sure you don't screw up anything...
if(roundNumber == 1)
{
{
OnFirstRound();
// Low index = least recent
}
// High index = most recent
else
// Remove the other instance of this class in our array
{
ArrayRemoveByValue(SpawnLastUsed, id);
OnLaterRounds();
SpawnLastUsed.push(id);
}
}
}
// List of last spawned time for each SI class
SpawnLastUsed = null;
// reference to director options
m_pSpitterLimit = null;
m_pDirector = null;
m_pEntities = null;
static KeyReset = Utils.KeyReset;
static SIClass = Utils.SIClass;
static SIModels = Utils.SIModels;
static ArrayRemoveByValue = Utils.ArrayRemoveByValue;
};




class Modules.MobControl extends GameState.GameStateListener
{
constructor(mobresetti)
{
//m_dopts = director_opts;
m_resetti = mobresetti;
}
}
// pointer to global Entity List
function OnSafeAreaOpened()
m_entlist = null;
{
// point to global mapinfo
m_resetti.ZeroMobReset();
m_pMapInfo = null;
}
// Table of entity classname, limit value pairs
// These functions created major problems....
m_removalTable = null;
/*
m_modelRemovalTable = null;
function OnTankEntersPlay()
m_saferoomRemoveList = null;
{
m_oldMinTime = m_dopts.MobSpawnMinTime;
m_oldMaxTime = m_dopts.MobSpawnMaxTime;
 
m_dopts.MobSpawnMinTime = 99999;
m_dopts.MobSpawnMaxTime = 99999;
 
m_resetti.ZeroMobReset();
}
function OnTankLeavesPlay()
{
m_dopts.MobSpawnMinTime = m_oldMinTime;
m_dopts.MobSpawnMaxTime = m_oldMaxTime;


m_firstRoundEnts = null;
m_resetti.ZeroMobReset();
m_firstRoundModelEnts = null;
}
static ArrayToTable = Utils.ArrayToTable;
m_oldMinTime = 0;
static ArrayRemoveByValue = Utils.ArrayRemoveByValue;
m_oldMaxTime = 0;  
static ItemInfo = Utils.ItemInfo;
m_dopts = null; */
static KillEntity = Utils.KillEntity;
m_resetti = null;
static VectorClone = Utils.VectorClone;
};
};


class Modules.MeleeWeaponControl extends GameState.GameStateListener {
class Modules.BasicItemSystems extends GameState.GameStateListener
constructor(entlist, melee_limit)
{
constructor(removalTable, convertTable, defaultItemList)
{
{
m_pEntities = entlist;
m_removalTable = removalTable;
m_maxSpawns = melee_limit;
m_convertTable = convertTable;
m_defaultItemList = defaultItemList;
}
}
function EntListToItemInfoList(entlist)
function OnAllowWeaponSpawn(classname)
{
{
local infolist = [];
if ( classname in m_removalTable )
foreach (ent in entlist)
{
{
infolist.push(ItemInfo(ent));
if(m_removalTable[classname] > 0)
}
{
return infolist;
//Msg("Found a "+classname+" to keep, "+m_removalTable[classname]+" remain.\n");
}
m_removalTable[classname]--
function OnFirstRound()
}
{
else if (m_removalTable[classname] < -1)
m_firstRoundMelees = {};
{
local meleeEnts = {}
//Msg("Killing just one "+classname+"\n");
local totalCount = 0;
m_removalTable[classname]++
 
return false;
// Enumerate all melee weapon spawns by model
}
foreach(mdl in MeleeModels)
else if (m_removalTable[classname] == 0)
{
// prep table for later
m_firstRoundMelees[mdl] <- []
 
local spawnlist = []
local ent = null;
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
{
{
spawnlist.push(ent.weakref());
//Msg("Removed "+classname+"\n")
totalCount++;
return false;
}
}
meleeEnts[mdl] <- spawnlist;
}
}
 
return true;
if(totalCount < m_maxSpawns)
}
function OnConvertWeaponSpawn(classname)
{
if ( classname in m_convertTable )
{
{
// There are less than m_maxSpawns melee weapons on the map,
//Msg("Converting"+classname+" to "+convertTable[classname]+"\n")
// so we record them all and we're done.
return m_convertTable[classname];
foreach(mdl,spawnlist in meleeEnts)
{
m_firstRoundMelees[mdl] = EntListToItemInfoList(spawnlist);
}
Msg("Only "+totalCount+" melee weapons on the map to track.\n");
}
}
else
return 0;
}
function OnGetDefaultItem(idx)
{
if ( idx < m_defaultItemList.len())
{
{
local savedCnt = 0;
return m_defaultItemList[idx];
}
return 0;
}
m_removalTable = null;
m_convertTable = null;
m_defaultItemList = null;
};


// Save m_maxSpawns of them
class Modules.EntKVEnforcer extends GameState.GameStateListener
while(savedCnt < m_maxSpawns)
{
{
constructor(EntList, classes, models, key, value)
local saveIdx = RandomInt(0,totalCount-1);
{
 
m_pEntities = EntList;
// Iterate through the list until we've hit saveIdx melees.
m_classes = classes;
foreach(mdl, spawnlist in meleeEnts)
m_models = models;
m_key = key;
switch(typeof value)
{
case "bool":
m_value = value.tointeger();
m_setFunc = "__KeyValueFromInt";
break;
case "float":
m_value = value.tointeger();
m_setFunc = "__KeyValueFromInt";
break;
case "integer":
m_value = value;
m_setFunc = "__KeyValueFromInt";
break;
case "string":
m_value = value;
m_setFunc = "__KeyValueFromString";
break;
case "instance":
if(value.getclass() == ::Vector)
{
{
if(saveIdx < spawnlist.len())
m_value = value;
{
m_setFunc = "__KeyValueFromVector";
// Save this item's spawn info.
break;
m_firstRoundMelees[mdl].push(ItemInfo(spawnlist[saveIdx]));
spawnlist.remove(saveIdx);
savedCnt++;
totalCount--;
break;
}
saveIdx -= spawnlist.len();
}
}
}
default:
 
m_value = null;
// remove the remaining weapon spawns from the map.
m_setFunc = null;
foreach(mdl, spawnlist in meleeEnts)
// Unsupported type!!!
{
throw "Unsupported type "+(typeof value)+" used with EntKVEnforcer!";
foreach(melee_ent in spawnlist)
{
KillEntity(melee_ent);
}
}
Msg("Killing "+totalCount+" melee weapons and saving "+savedCnt+" out of "+(totalCount+savedCnt)+" on the map.\n");
}
}
}
}
function OnOtherRounds()
function OnRoundStart(roundNumber)
{
{
local meleeEnts = {}
local ent = null;
local totalCount = 0;


// Enumerate all melee weapon spawns by model
while((ent = m_pEntities.Next(ent)) != null)
foreach(mdl in MeleeModels)
{
{
local spawnlist = []
if(ent.GetClassname() in m_classes)
local ent = null;
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
{
{
spawnlist.push(ent.weakref());
ent[m_setFuncName].call(ent, m_key, m_value);
totalCount++;
}
}
meleeEnts[mdl] <- spawnlist;
}
}


foreach(mdl,infolist in m_firstRoundMelees)
foreach(mdl in m_models)
{
{
local restoreCnt = infolist.len();
ent = null;
 
while((ent = m_pEntities.FindByModel(mdl)) != null)
// Make sure this model isn't unset in this round's ent table
if(!(mdl in meleeEnts))
{
{
Msg("Warning! No "+ mdl +" exist on R2 for restoring!\n");
ent[m_setFuncName].call(ent, m_key, m_value);
continue;
}
}
}
}
m_pEntities = null;
m_classes = null;
m_models = null;
m_key = null;
m_value = null;
m_setFunc = null;
};


local thisMdlEnts = meleeEnts[mdl];
class Modules.ItemControl extends GameState.GameStateListener
{
constructor(entlist, removalTable, modelRemovalTable, saferoomRemoveList, mapinfo)
{
m_entlist = entlist;
m_removalTable = removalTable;
m_modelRemovalTable = modelRemovalTable;
m_saferoomRemoveList = ArrayToTable(saferoomRemoveList);
m_pMapInfo = mapinfo;
}
function OnFirstRound()
{
local ent = m_entlist.First();
local classname = "";
local tItemEnts = {};
local saferoomEnts = [];
 
// Create an empty array for each item in our list.
foreach(key,val in m_removalTable)
{
tItemEnts[key] <- [];
}


// Check that this round's ent list is long enough to spawn last round's melees
while(ent != null)
if(thisMdlEnts.len() < restoreCnt)
{
classname = ent.GetClassname()
if(classname in m_saferoomRemoveList && m_pMapInfo.IsEntityNearAnySaferoom(ent, 1600.0))
{
// Make a list of items which are in saferooms that need to be removed
// and don't track these entities for other removal.
saferoomEnts.push(ent.weakref());
}
else if(classname in m_removalTable)
{
{
restoreCnt = thisMdlEnts.len();
tItemEnts[classname].push(ent.weakref());
Msg("Warning! Not as many of melee weapon ("+ mdl +") available on R2! ("+restoreCnt+" < "+infolist.len()+"\n");
}
}
ent=m_entlist.Next(ent);
}


Msg("Restoring "+restoreCnt+" "+mdl+" out of "+thisMdlEnts.len()+"\n");
local tModelEnts = {};


// Move restoreCnt melees of this model to their spots from R1.
foreach(mdl,limit in m_modelRemovalTable)
for(local i = 0; i < restoreCnt; i++)
{
local thisMdlEnts = tModelEnts[mdl] <- [];
ent = null;
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
{
// Only use this entity if it's not one of the saferoom entities we're going to remove.
thisMdlEnts.push(ent.weakref());
}
}
 
// Remove all targeted saferoom items before doing roundstart removals
foreach(entity in saferoomEnts) KillEntity(entity);
local KilledEntList = saferoomEnts;
 
m_firstRoundEnts = {}
foreach(classname,instances in tItemEnts)
{
local cnt = m_removalTable[classname].tointeger();
local saved_ents = m_firstRoundEnts[classname] <- [];
// We need to choose certain items to save
while( instances.len() > 0 && saved_ents.len() < cnt )
{
local saveIdx = RandomInt(0,instances.len()-1);
// Track this entity's info for future rounds.
saved_ents.push(ItemInfo(instances[saveIdx]));
// Remove this entity from the kill list
instances.remove(saveIdx);
}
Msg("Killing "+instances.len()+" "+classname+", leaving "+saved_ents.len()+" on the map.\n");
foreach(inst in instances)
{
{
local ent = thisMdlEnts[0];
KillEntity(inst);
thisMdlEnts.remove(0);
KilledEntList.push(inst.weakref());
ent.SetOrigin(infolist[i].m_vecOrigin.ToVector());
//ent.SetForwardVector(infolist[i].m_vecForward.ToVector());
}
}
}
}


// Delete the remaining melees from this round.
m_firstRoundModelEnts = {}
foreach(mdl,spawnlist in meleeEnts)
foreach(model,instances in tModelEnts)
{
{
foreach(ent in spawnlist)
// Don't use killed ents!
foreach(deadEnt in KilledEntList) ArrayRemoveByValue(instances, deadEnt);
local limit = m_modelRemovalTable[model].tointeger();
local saved_ents = m_firstRoundModelEnts[model] <- [];
while( instances.len() > 0 && saved_ents.len() < limit )
{
local saveIdx = RandomInt(0,instances.len()-1);
// Track this entity's info for future rounds.
saved_ents.push(ItemInfo(instances[saveIdx]));
// Remove this entity from the kill list
instances.remove(saveIdx);
}
Msg("Killing "+instances.len()+" "+model+", leaving "+saved_ents.len()+" on the map.\n");
foreach(inst in instances)
{
{
KillEntity(ent);
KillEntity(inst);
}
}
}
}
}
}
function OnRoundStart(roundNumber)
function OnLaterRounds()
{
{
if(roundNumber == 1)
local ent = m_entlist.First();
{
local classname = "";
OnFirstRound();
local tItemEnts = {};
}
 
else
foreach(key,val in m_removalTable)
{
{
OnOtherRounds();
tItemEnts[key] <- [];
}
}
}
while(ent != null)
m_pEntities = null;
m_maxSpawns = null;
 
m_firstRoundMelees = null;
 
static MeleeModels = Utils.MeleeModels;
static ItemInfo = Utils.ItemInfo;
static KillEntity = Utils.KillEntity;
};
 
class Modules.HRControl extends GameState.GameStateListener //, extends TimerCallback (no MI support)
{
constructor(entlist, globals, director)
{
m_pEntities = entlist;
m_pGlobals = globals;
m_pDirector = director;
}
function QueueCheck(time)
{
if(!m_bChecking)
{
{
m_pGlobals.Timer.AddTimer(time, this);
classname = ent.GetClassname()
m_bChecking = true;
if(classname in m_removalTable)
{
tItemEnts[classname].push(ent.weakref());
}
ent=m_entlist.Next(ent);
}
}
}
function OnRoundStart(roundNumber)
{
QueueCheck(1.0);
}
function OnTimerElapsed()
{
m_bChecking=false;
if(!m_pDirector.HasAnySurvivorLeftSafeArea()) QueueCheck(5.0);


local ent = null;
local tModelEnts = {};
local hrList = [];
foreach(mdl,limit in m_modelRemovalTable)
while((ent = m_pEntities.FindByClassname(ent, "weapon_hunting_rifle")) != null)
{
{
hrList.push(ent.weakref());
local thisMdlEnts = tModelEnts[mdl] <- [];
ent = null;
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
{
// Only use this entity if it's not one of the saferoom entities we're going to remove.
thisMdlEnts.push(ent.weakref());
}
}
}


if(!m_pGlobals.MapInfo.isIntro)
foreach(classname,entList in tItemEnts)
{
{
if(hrList.len() <= 1) return;
local firstItems = m_firstRoundEnts[classname];
hrList.remove(RandomInt(0,hrList.len()-1));
// count to keep alive
local cnt = firstItems.len();
if(cnt > entList.len())
{
Msg("Warning! Not enough "+classname+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
cnt = entList.len();
}
 
for(local i = cnt; i < entList.len(); i++)
{
KillEntity(entList[i]);
}
 
for(local i = 0; i < cnt; i++)
{
local vec = VectorClone(firstItems[i].m_vecOrigin);
 
// Hack. To avoid crashing the server by placing entities on top of each other here,  
// we'll just offset the placement of each entity by 1 unit.
// Alternative solutions:
// 1. Check all tracked entity positions for conflicts and resolve through <insert algorithm here>
// 2. Move all entities to offset by 1 unit, wait 1 frame, move entities to original (un-offset) position (same frame also crashes)
// 3. Convert this code to kill/create entities instead of kill/shuffle entities (not possible atm)
vec.z += 1.0;
entList[i].SetOrigin(vec.ToVector());
//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
}
Msg("Restored "+cnt+" "+classname+", out of "+entList.len()+" on the map.\n");
}
}


// Delete the rest
foreach(model,entList in tModelEnts)
foreach(hr in hrList)
{
{
KillEntity(hr);
local firstItems = m_firstRoundModelEnts[model];
// count to keep alive
local cnt = firstItems.len();
if(cnt > entList.len())
{
Msg("Warning! Not enough "+model+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
cnt = entList.len();
}
 
for(local i = cnt; i < entList.len(); i++)
{
KillEntity(entList[i]);
}
 
for(local i = 0; i < cnt; i++)
{
entList[i].SetOrigin(firstItems[i].m_vecOrigin.ToVector());
//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
}
Msg("Restored "+cnt+" "+model+", out of "+entList.len()+" on the map.\n");
}
}
}
}
m_pEntities = null;
function OnRoundStart(roundNumber)
m_pTimer = null;
m_pGlobals = null;
m_pDirector = null;
m_bChecking = false;
static KillEntity = Utils.KillEntity;
};
 
class Modules.GasCanControl extends GameState.GameStateListener {
constructor(entList, mapinfo)
{
{
m_pEntities = entList;
Msg("ItemControl OnRoundStart()\n");
m_pMapInfo = mapinfo;
// This will run multiple times per round in certain cases...
}
// Notably, on natural map switch (transition) e.g. chapter 1 ends, start chapter 2.
function OnRoundStart(roundNumber)
// Just make sure you don't screw up anything...
{
if(roundNumber == 1)
// Don't demolish gascans if the map has a scavenge event!
// Unless it's c1m2 because it uses cola yo.
if(m_pMapInfo.hasScavengeEvent && m_pMapInfo.mapname != "c1m2_streets") return;
 
local ent = null;
local list = [];
while((ent = m_pEntities.FindByModel(ent, "models/props_junk/gascan001a.mdl")) != null)
{
{
list.push(ent.weakref());
OnFirstRound();
}
}
Msg("Killing "+list.len()+" gas cans from the map.\n");
else
foreach(can in list)
{
{
KillEntity(can);
OnLaterRounds();
}
}
}
}
m_pEntities = null;
// pointer to global Entity List
m_entlist = null;
// point to global mapinfo
m_pMapInfo = null;
m_pMapInfo = null;
// Table of entity classname, limit value pairs
m_removalTable = null;
m_modelRemovalTable = null;
m_saferoomRemoveList = null;
m_firstRoundEnts = null;
m_firstRoundModelEnts = null;
static ArrayToTable = Utils.ArrayToTable;
static ArrayRemoveByValue = Utils.ArrayRemoveByValue;
static ItemInfo = Utils.ItemInfo;
static KillEntity = Utils.KillEntity;
static KillEntity = Utils.KillEntity;
}
static VectorClone = Utils.VectorClone;
</source>}}
};


==EMS Mutations==
class Modules.MeleeWeaponControl extends GameState.GameStateListener {
These have been introduced with the [[L4D2 EMS]] Update
constructor(entlist, melee_limit)
===Dash===
{
{{ExpandBox|<source lang=js>
m_pEntities = entlist;
////////////////////////////////
m_maxSpawns = melee_limit;
// "Dash" is another extended mutation demo mode to show some of the capabilities available in script
}
//
function EntListToItemInfoList(entlist)
// the players must run near a sequence of waypoints (well, ok, lampposts) in order
{
// As they reach each one, we light up the next one, and also summon a new panic wave from the next locale
local infolist = [];
// Designers use the Entity Placement tool to lay down a sequence of waypoints
foreach (ent in entlist)
// Waypoint naming:
{
// If you name your waypoints sequentially, e.g., waypoint_001, waypoint_002 flagpole waypoints will be used.
infolist.push(ItemInfo(ent));
// See scripted_c5m2_park_dash.nut, c5m2_park_entities_dash.txt for an example.
}
//
return infolist;
// If you prefix your waypoints with "short_" e.g., short_waypoint_001, short_waypoint_002, then shorter
}
// waypoints will be used.
function OnFirstRound()
// See scripted_c5m4_quarter_dash.nut, c5m4_quarter_entities_dash.txt for an example.
{
//
m_firstRoundMelees = {};
// When survivors reach the final waypoint, we do a TANK wave, and they get a final time based on when they finish
local meleeEnts = {}
//
local totalCount = 0;
// The waypoint itself has a fairly big script to detect nearby players, and track which ones have reached it
//  as well as to trigger visual indications of progress, and "waypoint finished" calls back to this script
// And this script on startup has to sort the waypoints to make the in-order list


// printl(" Loading Dash mode")
// Enumerate all melee weapon spawns by model
foreach(mdl in MeleeModels)
{
// prep table for later
m_firstRoundMelees[mdl] <- []


MutationOptions <-
local spawnlist = []
{  
local ent = null;
// since we are going to move the spawn point to "ahead" on the track as we hit waypoints
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
PreferredMobDirection = SPAWN_NEAR_POSITION
{
PreferredMobPositionRange = 600
spawnlist.push(ent.weakref());
totalCount++;
}
meleeEnts[mdl] <- spawnlist;
}


// we should change this to zero
if(totalCount < m_maxSpawns)
PreferredMobPosition = Vector( 0,0,0 )
{
// There are less than m_maxSpawns melee weapons on the map,
// so we record them all and we're done.
foreach(mdl,spawnlist in meleeEnts)
{
m_firstRoundMelees[mdl] = EntListToItemInfoList(spawnlist);
}
Msg("Only "+totalCount+" melee weapons on the map to track.\n");
}
else
{
local savedCnt = 0;


SpawnSetRule = SPAWN_POSITIONAL
// Save m_maxSpawns of them
SpawnSetRadius = 2000
while(savedCnt < m_maxSpawns)
SpawnSetPosition = Vector( -7150, -3647, -97 )
{
local saveIdx = RandomInt(0,totalCount-1);


WanderingZombieDensityModifier = 0 // get rid of wanderers
// Iterate through the list until we've hit saveIdx melees.
foreach(mdl, spawnlist in meleeEnts)
{
if(saveIdx < spawnlist.len())
{
// Save this item's spawn info.
m_firstRoundMelees[mdl].push(ItemInfo(spawnlist[saveIdx]));
spawnlist.remove(saveIdx);
savedCnt++;
totalCount--;
break;
}
saveIdx -= spawnlist.len();
}
}


cm_ShouldEscortHumanPlayers = 1
// remove the remaining weapon spawns from the map.
cm_AggressiveSpecials = 1
foreach(mdl, spawnlist in meleeEnts)
{
foreach(melee_ent in spawnlist)
{
KillEntity(melee_ent);
}
}
Msg("Killing "+totalCount+" melee weapons and saving "+savedCnt+" out of "+(totalCount+savedCnt)+" on the map.\n");
}


BoomerLimit  = 0
}
ChargerLimit = 0
function OnOtherRounds()
HunterLimit  = 0
{
JockeyLimit  = 0
local meleeEnts = {}
SpitterLimit = 0
local totalCount = 0;
SmokerLimit  = 0
MaxSpecials  = 0
CommonLimit  = 20
MegaMobSize = 20
TankLimit    = 0
WitchLimit = 0


function EndScriptedMode()
// Enumerate all melee weapon spawns by model
{
foreach(mdl in MeleeModels)
local finished = true;
foreach ( val in g_RoundState.WaypointList )
{
{
if ( val.GetScriptScope().active == true )
local spawnlist = []
local ent = null;
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
{
{
finished = false
spawnlist.push(ent.weakref());
break;
totalCount++;
}
}
meleeEnts[mdl] <- spawnlist;
}
}


if ( finished )
foreach(mdl,infolist in m_firstRoundMelees)
{
{
// Finished the course
local restoreCnt = infolist.len();
return 0 // SCENARIO_RESTART
 
// Make sure this model isn't unset in this round's ent table
if(!(mdl in meleeEnts))
{
Msg("Warning! No "+ mdl +" exist on R2 for restoring!\n");
continue;
}
 
local thisMdlEnts = meleeEnts[mdl];
 
// Check that this round's ent list is long enough to spawn last round's melees
if(thisMdlEnts.len() < restoreCnt)
{
restoreCnt = thisMdlEnts.len();
Msg("Warning! Not as many of melee weapon ("+ mdl +") available on R2! ("+restoreCnt+" < "+infolist.len()+"\n");
}
 
Msg("Restoring "+restoreCnt+" "+mdl+" out of "+thisMdlEnts.len()+"\n");
 
// Move restoreCnt melees of this model to their spots from R1.
for(local i = 0; i < restoreCnt; i++)
{
local ent = thisMdlEnts[0];
thisMdlEnts.remove(0);
ent.SetOrigin(infolist[i].m_vecOrigin.ToVector());
//ent.SetForwardVector(infolist[i].m_vecForward.ToVector());
}
}
}
else
 
// Delete the remaining melees from this round.
foreach(mdl,spawnlist in meleeEnts)
{
{
return 1 // SCENARIO_SURVIVORS_DEAD
foreach(ent in spawnlist)
{
KillEntity(ent);
}
}
}
}
}
 
function OnRoundStart(roundNumber)
/*  Not using this method anymore
function GetScoreboardFilename( endreason )
{
{
switch ( endreason )
if(roundNumber == 1)
{
OnFirstRound();
}
else
{
{
case 1:
OnOtherRounds();
return "Resource/UI/scriptedmodeplaceholderlost.res"
case 0:
return "Resource/UI/scriptedmodeplaceholderwon.res"
default:
return "Resource/UI/scriptedmodeplaceholder.res"
}
}
}
}
*/
m_pEntities = null;
m_maxSpawns = null;


// challenge mode experiment - make sure old CM type stuff works fine in new mutation code
m_firstRoundMelees = null;
cm_HeadshotOnly = 0


DefaultItems =
static MeleeModels = Utils.MeleeModels;
[
static ItemInfo = Utils.ItemInfo;
"weapon_rifle_m60",
static KillEntity = Utils.KillEntity;
"weapon_pistol_magnum",
};
"adrenaline",
"pipe_bomb"
]


// Set characters up with default items
class Modules.HRControl extends GameState.GameStateListener //, extends TimerCallback (no MI support)
function GetDefaultItem( idx )
{
constructor(entlist, globals, director)
{
m_pEntities = entlist;
m_pGlobals = globals;
m_pDirector = director;
}
function QueueCheck(time)
{
{
if ( idx < DefaultItems.len() )
if(!m_bChecking)
{
{
return DefaultItems[idx];
m_pGlobals.Timer.AddTimer(time, this);
m_bChecking = true;
}
}
return 0;
}
}
function OnRoundStart(roundNumber)
}
{
QueueCheck(1.0);
}
function OnTimerElapsed()
{
m_bChecking=false;
if(!m_pDirector.HasAnySurvivorLeftSafeArea()) QueueCheck(5.0);


// SessionState for this mode - mostly about the waypoint tracking/which on next/etc/etc
local ent = null;
MutationState <-
local hrList = [];
{
while((ent = m_pEntities.FindByClassname(ent, "weapon_hunting_rifle")) != null)
StartActive = true
{
CurrentWaypoint = 0
hrList.push(ent.weakref());
FinalWaypoint = 0
}
JustHitWaypoint = false
YouWon = false
FirstWaypointHit = false
TimerWaypoint = 0  // for a soon to be written more flexible setup
MobWaypoint = 0
CommonIncrement = 0
}


// callback for waypoint generation - as we spawn each one we add it to the list
if(!m_pGlobals.MapInfo.isIntro)
function SetWaypointCB( waypointObj, rarity )
{
{
if(hrList.len() <= 1) return;
local waypointScr = waypointObj.GetScriptScope()
hrList.remove(RandomInt(0,hrList.len()-1));
waypointScr.myID <- g_ModeScript.MutationState.FinalWaypoint++
}


// Delete the rest
// if we're using a custom list set the initial touch count on this waypoint
foreach(hr in hrList)
if( "CustomWaypointList" in g_MapScript )
{
waypointScr.initialTouchCount = g_MapScript.CustomWaypointList[ g_RoundState.WaypointList.len() ].startingTouchCount
KillEntity(hr);
 
}
g_RoundState.WaypointList.append(waypointObj)
}
 
// the startbox needs a custom precache function if you are using it
function Precache()
{
Startbox_Precache()
}
 
// called on startup - setup a few callbacks if they arent there
// and then go ahead and teleport players to start, spawn the startbox, activate first waypoint
function OnGameplayStart()
{
CheckOrSetMapCallback( "MapOverrideOptions", @() false )
CheckOrSetMapCallback( "MapGameplayStart", @() false )
 
Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )
 
//teleport players to the start point
if (!TeleportPlayersToStartPoints( "gamemode_playerstart" ) )
printl(" ** TeleportPlayersToStartPoints: Spawn point count or player count incorrect! Verify that there are 4 of each.")
 
// create a start box
if ( !SpawnStartBox( "startbox_origin" ) )
{
printl("Note: SpawnStartBox() called but there is no startbox_origin in map.")
}
}
m_pEntities = null;
m_pTimer = null;
m_pGlobals = null;
m_pDirector = null;
m_bChecking = false;
static KillEntity = Utils.KillEntity;
};


// If the map script supplies a CustomWaypointList of points spawn the entities in that order
class Modules.GasCanControl extends GameState.GameStateListener {
if( "CustomWaypointList" in g_MapScript )
constructor(entList, mapinfo)
{
{
SpawnCustomWaypointList()
m_pEntities = entList;
m_pMapInfo = mapinfo;
}
}
else
function OnRoundStart(roundNumber)
{
{
// Spawn waypoints in order
// Don't demolish gascans if the map has a scavenge event!
SortAndSpawnWaypointList()
// Unless it's c1m2 because it uses cola yo.
}
if(m_pMapInfo.hasScavengeEvent && m_pMapInfo.mapname != "c1m2_streets") return;


if (g_RoundState.WaypointList.len() > 0)
local ent = null;
{
local list = [];
EntFire( g_RoundState.WaypointList[0].GetName(), "StartGlowing" )
while((ent = m_pEntities.FindByModel(ent, "models/props_junk/gascan001a.mdl")) != null)
EntFire( g_RoundState.WaypointList[0].GetName(), "fireuser1" )
{
g_RoundState.WaypointList[0].GetScriptScope().active = true
list.push(ent.weakref());
SessionOptions.PreferredMobPosition = g_RoundState.WaypointList[0].GetOrigin()
}
}
Msg("Killing "+list.len()+" gas cans from the map.\n");
 
foreach(can in list)
SessionOptions.ScriptedStageType = STAGE_SETUP
SessionOptions.ScriptedStageValue = 1000
 
MapGameplayStart()
}
 
//=========================================================
// Uses the CustomWaypointList defined in the map script to
// spawn waypoints. The info_item_position entities are
// collected in the order they appear in the list and then
// the waypoints are spawned in that order.
//=========================================================
function SpawnCustomWaypointList()
{
// collect the info_item_position entities into a list
local currentEnt  = null
local tempWaypointList = []
 
foreach( idx, val in CustomWaypointList )
{
currentEnt = Entities.FindByName( null, val.targetName )
// store the waypoint if we found it, otherwise complain
if( currentEnt )
{
{
tempWaypointList.append( currentEnt )
KillEntity(can);
}
else
{
printl( " ** Waypoint Spawn ERROR**  I can't find the waypoint named: " + val.targetName )
}
}
}
}
 
m_pEntities = null;
SpawnWaypointList( tempWaypointList )
m_pMapInfo = null;
static KillEntity = Utils.KillEntity;
}
}
</source>}}


//=========================================================
==EMS Mutations==
// Collect all the info_item_position entities that are named
These have been introduced with the [[L4D2 EMS]] Update
// "short_waypoint_*" OR "waypoint_*", put them into a list
===Dash===
// and sort it by suffix.  This will allow us to spawn all
{{ExpandBox|<source lang=js>
// the waypoints in order so they can know their touch order
////////////////////////////////
//=========================================================
// "Dash" is another extended mutation demo mode to show some of the capabilities available in script
function SortAndSpawnWaypointList()
//
{
// the players must run near a sequence of waypoints (well, ok, lampposts) in order
local currentWaypoint = Entities.FindByClassname( null, "info_item_position" )
// As they reach each one, we light up the next one, and also summon a new panic wave from the next locale
local tempWaypointList = []
// Designers use the Entity Placement tool to lay down a sequence of waypoints
// Waypoint naming:
// If you name your waypoints sequentially, e.g., waypoint_001, waypoint_002 flagpole waypoints will be used.
// See scripted_c5m2_park_dash.nut, c5m2_park_entities_dash.txt for an example.
//
// If you prefix your waypoints with "short_" e.g., short_waypoint_001, short_waypoint_002, then shorter
// waypoints will be used.
// See scripted_c5m4_quarter_dash.nut, c5m4_quarter_entities_dash.txt for an example.
//
// When survivors reach the final waypoint, we do a TANK wave, and they get a final time based on when they finish
//
// The waypoint itself has a fairly big script to detect nearby players, and track which ones have reached it
//  as well as to trigger visual indications of progress, and "waypoint finished" calls back to this script
// And this script on startup has to sort the waypoints to make the in-order list
 
// printl(" Loading Dash mode")


while( currentWaypoint )
MutationOptions <-
{
{  
local name = currentWaypoint.GetName()
// since we are going to move the spawn point to "ahead" on the track as we hit waypoints
if( ( name.find( "waypoint_" ) == 0 ) || ( name.find( "short_waypoint_" ) == 0 ) )
PreferredMobDirection = SPAWN_NEAR_POSITION
{
PreferredMobPositionRange = 600
tempWaypointList.append( currentWaypoint )
}


currentWaypoint = Entities.FindByClassname( currentWaypoint, "info_item_position" )
// we should change this to zero
}
PreferredMobPosition = Vector( 0,0,0 )


// sort the list by suffix
SpawnSetRule = SPAWN_POSITIONAL
tempWaypointList.sort(@(a,b) a.GetName().slice(-4) <=> b.GetName().slice(-4) )
SpawnSetRadius = 2000
SpawnSetPosition = Vector( -7150, -3647, -97 )


SpawnWaypointList( tempWaypointList )
WanderingZombieDensityModifier = 0 // get rid of wanderers
}


//=========================================================
cm_ShouldEscortHumanPlayers = 1
// Spawn the provided list of waypoints on their positions
cm_AggressiveSpecials = 1
//=========================================================
function SpawnWaypointList( list )
{
local waypointGroup = g_MapScript.GetEntityGroup( "DashWaypoint" )
local shortWaypointGroup = g_MapScript.GetEntityGroup( "DashWaypointShort" )


// set callbacks
BoomerLimit  = 0
shortWaypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
ChargerLimit = 0
waypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
HunterLimit  = 0
JockeyLimit  = 0
SpitterLimit = 0
SmokerLimit  = 0
MaxSpecials  = 0
CommonLimit  = 20
MegaMobSize = 20
TankLimit    = 0
WitchLimit = 0


// spawn the waypoints
function EndScriptedMode()
foreach( idx, ent in list )
{
{
// does our waypoint start with "short_"?  If so, it is a short waypoint! Spawn it.
local finished = true;
if( ent.GetName().find( "short_" ) == 0  )
foreach ( val in g_RoundState.WaypointList )
{
{
SpawnSingleAt( shortWaypointGroup, ent.GetOrigin(), ent.GetAngles() )
if ( val.GetScriptScope().active == true )
{
finished = false
break;
}
}
 
if ( finished )
{
// Finished the course
return 0 // SCENARIO_RESTART
}
}
else // assuming it must be a normal waypoint since it isn't "short_"
else
{
{
SpawnSingleAt( waypointGroup, ent.GetOrigin(), ent.GetAngles() )
return 1 // SCENARIO_SURVIVORS_DEAD
}
}
}
}
}


function DashDisplayScores( )
/* Not using this method anymore
{
function GetScoreboardFilename( endreason )
local final_time = HUDReadTimer(1)
local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
HUDPlace( HUD_FAR_RIGHT, 0.28, 0.50, 0.44, 0.06 )
HUDPlace( HUD_FAR_LEFT, 0.28, 0.58, 0.44, 0.06 )
HUDPlace( HUD_LEFT_BOT,  0.28, 0.66, 0.44, 0.06 )
HUDPlace( HUD_RIGHT_BOT, 0.28, 0.74, 0.44, 0.06 )
local ticker_str = score_strings.yourtime
if ("finish" in score_strings)
ticker_str = ticker_str + "\n" + score_strings.finish
ticker_str = ticker_str + "\nBest Times So Far"
Ticker_SetBlink( false )  // or true if you came in first? hmmm....
Ticker_NewStr( ticker_str, 120 )
foreach (idx, val in score_strings.topscores)
{
{
local hudfield = "score"+(idx+1)
switch ( endreason )
DashHUD.Fields[hudfield].dataval = val
{
DashHUD.Fields[hudfield].flags = DashHUD.Fields[hudfield].flags & ~HUD_FLAG_NOTVISIBLE
case 1:
return "Resource/UI/scriptedmodeplaceholderlost.res"
case 0:
return "Resource/UI/scriptedmodeplaceholderwon.res"
default:
return "Resource/UI/scriptedmodeplaceholder.res"
}
}
}
Scoring_SaveTable( SessionState.MapName, SessionState.ModeName )
*/
}


// this array holds the waypoints in order, up to SessionState.FinalWaypoint (though really, could just use len() for safety?
// challenge mode experiment - make sure old CM type stuff works fine in new mutation code
g_RoundState.WaypointList <- []
cm_HeadshotOnly = 0


// called at basically each waypoint via ForceNextStage - or when a PANIC ends we just go to a delay
DefaultItems =
function GetNextStage()
[
{
"weapon_rifle_m60",
smDbgPrint("GetNextStage called")
"weapon_pistol_magnum",
"adrenaline",
"pipe_bomb"
]


if ( SessionState.YouWon )
// Set characters up with default items
function GetDefaultItem( idx )
{
{
smDbgPrint("Really winningz, going to rezultz, yo!")
if ( idx < DefaultItems.len() )
SessionOptions.ScriptedStageType = STAGE_RESULTS
SessionOptions.ScriptedStageValue = 10
DashDisplayScores()
}
else if ( SessionState.JustHitWaypoint && SessionState.FinalWaypoint > 0 )
{
if ( !SessionState.FirstWaypointHit ) // could be start line, or waypoint 0
{  // hit firstone, kick things off
smDbgPrint("Hit First Waypoint!")
SessionState.FirstWaypointHit = true
HUDManageTimers( 1 , TIMER_COUNTUP, 0)  // generalize this?
 
// calculate the number of common infected to add each time a waypoint is completed
SessionState.CommonIncrement = (100 - SessionOptions.CommonLimit) / SessionState.FinalWaypoint
if( !MapOverrideOptions() )
{
{
// really, do we need this? or can we just always do panic here else a delay???
return DefaultItems[idx];
local PercentageComplete = SessionState.CurrentWaypoint * 1.0 / SessionState.FinalWaypoint
}
return 0;
SessionState.JustHitWaypoint = false
}
SessionOptions.ScriptedStageType = STAGE_PANIC
}
SessionOptions.ScriptedStageValue = 1
foreach (val in SpecialNames)
{
local limit = RandomInt(1,3) + PercentageComplete * 3
SessionOptions[val + "Limit"] = limit
}
SessionOptions.MaxSpecials = RandomInt(2,6) + PercentageComplete * 3
if (SessionOptions.CommonLimit < 100)
SessionOptions.CommonLimit += SessionState.CommonIncrement


if (SessionOptions.MegaMobSize < 50 )
// SessionState for this mode - mostly about the waypoint tracking/which on next/etc/etc
SessionOptions.MegaMobSize += SessionState.CommonIncrement
MutationState <-
{
StartActive = true
CurrentWaypoint = 0
FinalWaypoint = 0
JustHitWaypoint = false
YouWon = false
FirstWaypointHit = false
TimerWaypoint = 0  // for a soon to be written more flexible setup
MobWaypoint = 0
CommonIncrement = 0
}


if (SessionState.CurrentWaypoint == SessionState.FinalWaypoint - 1 )
// callback for waypoint generation - as we spawn each one we add it to the list
{
function SetWaypointCB( waypointObj, rarity )
Ticker_NewStr("One gate to go!")
{
SessionOptions.TankLimit = 1 // how do i force a tank from script?
local waypointScr = waypointObj.GetScriptScope()
SessionOptions.ScriptedStageType = STAGE_ESCAPE  // i guess i do an escape stage
waypointScr.myID <- g_ModeScript.MutationState.FinalWaypoint++
}
else // display some help text on the ticker
{
switch( SessionState.CurrentWaypoint )
{
case 1:
Ticker_NewStr( "Each time you activate a new waypoint more infected will attack.", 15 )
break
case 2:
Ticker_NewStr( "You cleared the waypoint! Here come more infected!", 15 )
break
}
}
}
}
else
{
SessionOptions.ScriptedStageType = STAGE_DELAY
SessionOptions.ScriptedStageValue = 1000
SessionOptions.MaxSpecials = 0
}


smDbgPrint("Dash setting Mode " + SessionOptions.ScriptedStageType + " val " + SessionOptions.ScriptedStageValue)
// if we're using a custom list set the initial touch count on this waypoint
if( "CustomWaypointList" in g_MapScript )
waypointScr.initialTouchCount = g_MapScript.CustomWaypointList[ g_RoundState.WaypointList.len() ].startingTouchCount


// we also want to positionally spawn at the mob target - though really i'd like to do some distance/scale stuff, too
g_RoundState.WaypointList.append(waypointObj)
SessionOptions.SpawnSetPosition = SessionOptions.PreferredMobPosition
}
}


// because call DWD is where we active/deactive the waypoints, we can just treat the start line as secret pre-waypoint
// the startbox needs a custom precache function if you are using it
function SurvivorLeftStartBox()
function Precache()
{
{
SessionState.JustHitWaypoint = true  // and since the main loop never looks up into the array, that is o.k.
Startbox_Precache()
Director.ForceNextStage()
}
}


// each time a waypoint is hit this is called to get us moving forward
// called on startup - setup a few callbacks if they arent there
// i.e. we need to activate the next waypoint, see if it is the final one, track counts, and so on
// and then go ahead and teleport players to start, spawn the startbox, activate first waypoint
function DashWaypointDone( id )
function OnGameplayStart()
{
{
smDbgPrint("Dash Waypoint finished! " + id)
CheckOrSetMapCallback( "MapOverrideOptions", @() false )
CheckOrSetMapCallback( "MapGameplayStart", @() false )
 
Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )


// sanity check!
//teleport players to the start point
if ( id != SessionState.CurrentWaypoint )
if (!TeleportPlayersToStartPoints( "gamemode_playerstart" ) )
printl("Hey! you think you are done with waypoint " + id + " but current is " + SessionState.CurrentWaypoint )
printl(" ** TeleportPlayersToStartPoints: Spawn point count or player count incorrect! Verify that there are 4 of each.")


EntFire( g_RoundState.WaypointList[id].GetName(), "StopGlowing" )
// create a start box
g_RoundState.WaypointList[id].GetScriptScope().active = false
if ( !SpawnStartBox( "startbox_origin" ) )
// printl( "Deactivated waypoint " + id )
local next_id = ++SessionState.CurrentWaypoint
if ( next_id >= SessionState.FinalWaypoint )
{
{
// printl("Youz da Winner, yo!")
printl("Note: SpawnStartBox() called but there is no startbox_origin in map.")
SessionState.CurrentWaypoint--  // for safety
}
SessionState.YouWon = true


// stop the clock
// If the map script supplies a CustomWaypointList of points spawn the entities in that order
HUDManageTimers( 1, TIMER_STOP, 0 )
if( "CustomWaypointList" in g_MapScript )
{
SpawnCustomWaypointList()
}
}
else
else
{
{
EntFire( g_RoundState.WaypointList[next_id].GetName(), "StartGlowing" )
// Spawn waypoints in order
EntFire( g_RoundState.WaypointList[next_id].GetName(), "fireuser1" )
SortAndSpawnWaypointList()
//printl("Moving on to next waypoint " + next_id )
g_RoundState.WaypointList[next_id].GetScriptScope().active = true
SessionOptions.PreferredMobPosition = g_RoundState.WaypointList[next_id].GetOrigin()
}
}
SessionState.JustHitWaypoint = true
Director.ForceNextStage()
}


// add Criteria for the new Response Rule speech criteria system
if (g_RoundState.WaypointList.len() > 0)
// i.e. this is to add data to the RR table the characters use to choose statements
{
function AddCriteria( criteriaTable )
EntFire( g_RoundState.WaypointList[0].GetName(), "StartGlowing" )
{
EntFire( g_RoundState.WaypointList[0].GetName(), "fireuser1" )
criteriaTable.PercentComplete <- SessionState.FinalWaypoint > 0 ? (SessionState.CurrentWaypoint*1.0)/SessionState.FinalWaypoint : 0
g_RoundState.WaypointList[0].GetScriptScope().active = true
// printl("Dash AddCriteria added " + criteriaTable.PercentComplete)
SessionOptions.PreferredMobPosition = g_RoundState.WaypointList[0].GetOrigin()
}
}


//-------------------------------------------------------
SessionOptions.ScriptedStageType = STAGE_SETUP
// Include all entity group interfaces needed for this mode
SessionOptions.ScriptedStageValue = 1000
//-------------------------------------------------------
IncludeScript( "entitygroups/dash_waypoint_group" )
IncludeScript( "entitygroups/dash_waypoint_short_group" )


DashHUD <- {}
MapGameplayStart()
}


// HUD setup/control - our HUD is pretty simple in dash
//=========================================================
function SetupModeHUD( )
// Uses the CustomWaypointList defined in the map script to
// spawn waypoints.  The info_item_position entities are
// collected in the order they appear in the list and then
// the waypoints are spawned in that order.
//=========================================================
function SpawnCustomWaypointList()
{
{
DashHUD =
// collect the info_item_position entities into a list
local currentEnt  = null
local tempWaypointList = []
 
foreach( idx, val in CustomWaypointList )
{
{
Fields =  
currentEnt = Entities.FindByName( null, val.targetName )
// store the waypoint if we found it, otherwise complain
if( currentEnt )
{
{
waypoint = { slot = HUD_RIGHT_TOP, name = "waypoint", staticstring = "Waypoint: ", datafunc = @() SessionState.CurrentWaypoint },
tempWaypointList.append( currentEnt )
timer    = { slot = HUD_LEFT_TOP, name = "timer", staticstring = "Time: ", special = HUD_SPECIAL_TIMER1 },
}
 
else
// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
{
// we will move the Ticker down to be a "header" - then need 4 slots for top 4
printl( " ** Waypoint Spawn ERROR** I can't find the waypoint named: " + val.targetName )
score1  = { slot = HUD_FAR_RIGHT, name = "score1", dataval = "Score1", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
score2  = { slot = HUD_FAR_LEFT,  name = "score2", dataval = "Score2", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
score3  = { slot = HUD_LEFT_BOT, name = "score3", dataval = "Score3", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
score4  = { slot = HUD_RIGHT_BOT, name = "score4", dataval = "Score4", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
}
}
}
}


Ticker_AddToHud( DashHUD, "Hurry past the poles, go for a best time" )
SpawnWaypointList( tempWaypointList )
HUDSetLayout( DashHUD )
}
}
</source>}}


===Holdout===
//=========================================================
{{ExpandBox|<source lang=js>
// Collect all the info_item_position entities that are named
///////////////////////////////////////////////////////////////////////////////
// "short_waypoint_*" OR "waypoint_*", put them into a list
// The Holdout game mode! Brought to you by the new Mutation system!
// and sort it by suffix. This will allow us to spawn all
// A Holdout Is...
// the waypoints in order so they can know their touch order
//   Mostly a demo of the new mutation system, but a game mode as well
//=========================================================
//   A Continuous cycle of attack/cooldown/attack/cooldown
function SortAndSpawnWaypointList()
//  in code, implemented as 3 phases
{
//        PANIC - spawn some enemies around the players
local currentWaypoint = Entities.FindByClassname( null, "info_item_position" )
//        CLEAROUT - wait until the enemies are (mostly) all gone
local tempWaypointList = []
//        DELAY - a cooldown time before the next wave of enemies
 
// There are several "special behaviors" as this happens
while( currentWaypoint )
//  The Escape wave can happen at a specified wave #, or as a result of in game action (timer running down, for instance)
{
//  There can be special events that take over based on the current Stage #
local name = currentWaypoint.GetName()
if( ( name.find( "waypoint_" ) == 0 ) || ( name.find( "short_waypoint_" ) == 0 ) )
{
tempWaypointList.append( currentWaypoint )
}


// This is a very Structured mode - it uses stagetables and other "helpers" in the mutation system
currentWaypoint = Entities.FindByClassname( currentWaypoint, "info_item_position" )
// You dont need to use these in your own mutations, though you are of course welcome to
}


// as a map, you should go implement the following functions, returning a "stageTable"
// sort the list by suffix
// or null to ignore
tempWaypointList.sort(@(a,b) a.GetName().slice(-4) <=> b.GetName().slice(-4) )
//  DoMapEventCheck()
//  DoMapSetup()
//  GetMapEscapeStage()
//  IsMapSpecificStage()   // dont like this! potential out of sync/parallel variables
//  GetMapSpecificStage()  //    maybe IsMap sets a "stageSpecific" that GetMap can use?
//  GetAttackStage()
//  GetMapClearoutStage()
//  GetMapDelayStage()
///////////////////////////////////////////////////////////////////////////////


//---------------------------------------------------------
SpawnWaypointList( tempWaypointList )
// This is the SessionState - any non-director data all your session code wants to be able to look at
// So we hold data about what wave, some UI controls, and info about managing warnings
MutationState <-
{
HUDWaveInfo = true          // do you want the Wave # in middle of UI
HUDRescueTimer = false      // or would you rather have the rescue timer (cant have both)
HUDTickerText = "Holdout as long as you can"
RescueStarted = false
RawStageNum = -1
ForcedEscapeStage = -1
ScriptedStageWave = 0
CooldownEndWarningTime = 8.5 // seconds before end of cooldown to play warning
CooldownEndWarningChance = 15 // percent chance to play warning
CooldownEndWarningFrequency = 0 // how many waves to wait before playing another warning
LastWaveCooldownWarningPlayed = -1 // the last wave that the cooldown warning played on
}
}


//---------------------------------------------------------
//=========================================================
// the DirectorOptions defaults for Holdout - though the maps will override many of these per wave and phase
// Spawn the provided list of waypoints on their positions
MutationOptions <-
//=========================================================
function SpawnWaypointList( list )
{
{
PreferredMobDirection = SPAWN_NO_PREFERENCE
local waypointGroup = g_MapScript.GetEntityGroup( "DashWaypoint" )
SpawnSetRule = SPAWN_ANYWHERE
local shortWaypointGroup = g_MapScript.GetEntityGroup( "DashWaypointShort" )
JournalString = ""
SpecialInfectedAssault = 0
AllowWitchesInCheckpoints = 1
AllowCrescendoEvents = 0
EnforceFinaleNavSpawnRules = 0
IgnoreNavThreatAreas = 1
ZombieDiscardRange = 10000


WanderingZombieDensityModifier = 0
// set callbacks
BoomerLimit  = 0
shortWaypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
ChargerLimit = 0
waypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
HunterLimit  = 0
JockeyLimit  = 0
SpitterLimit = 0
SmokerLimit  = 0
MaxSpecials  = 0
CommonLimit  = 20
TankLimit    = 0
MegaMobMaxSize = 100
MegaMobMinSize = 75
PanicWavePauseMax = 5
PanicWavePauseMin = 1
AddToSpawnTimer = 0
ShouldAllowMobsWithTank = true
ShouldAllowSpecialsWithTank = true
ZombieSpawnRange = 800
ZombieTankHealth = 6000 // SP default is 4000
BileMobSize = 20
EscapeSpawnTanks = true
ZombieDontClear = 1
MegaMobSize = 50      // i have no idea why this defaults to not between min and max


cm_ShouldEscortHumanPlayers = 1
// spawn the waypoints
cm_AggressiveSpecials = 1
foreach( idx, ent in list )
}
 
//=========================================================
// Utilities and helpers
//=========================================================
 
const PHASE_PANIC = 1
const PHASE_CLEAR = 2
const PHASE_DELAY = 0
 
function StageNumToWaveBase( stagenum )
{
return (stagenum + 2) / 3  // the +2 is because of our initial startup raw phases...
}
 
function WaveBaseToStageNum( wavebase, substage = PHASE_DELAY )
{
return wavebase * 3 + substage
}
 
// Are players permitted to pick up this object by pressing their USE key?
// return true for yes, false for no.
function CanPickupObject( object )
{
// mines
if ( object.GetName().find( "mine_1_body" ) )
{
{
return true
// does our waypoint start with "short_"?  If so, it is a short waypoint! Spawn it.
}
if( ent.GetName().find( "short_" ) == 0  )
{
// resource drops
SpawnSingleAt( shortWaypointGroup, ent.GetOrigin(), ent.GetAngles() )
if ( object.GetName().find( "prop_resource" ) )
}
{
else // assuming it must be a normal waypoint since it isn't "short_"
return true
{
}
SpawnSingleAt( waypointGroup, ent.GetOrigin(), ent.GetAngles() )
 
}
// check the map script for a PickupObject function for extra qualified items
// TODO: we should do this more generally!
local canPickup = false
if( "PickupObject" in g_MapScript )
{
canPickup = g_MapScript.PickupObject( object )
}
}
return canPickup
}
}


// Called from other systems when they want the next combat wave to be the escape
function DashDisplayScores( )
function StartEscapeWave()
{
{
SessionState.ForcedEscapeStage =  ( ( SessionState.RawStageNum + 2 ) / 3) * 3 + 1
local final_time = HUDReadTimer(1)
// printl( "Prepping Escape Wave for Stage " + SessionState.ForcedEscapeStage + " from " + SessionState.RawStageNum )
}
 
// show the final score and timing
function HoldoutDisplayScores( )
{
local final_time = Time() - SessionState.RealStartTime
local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
Line 8,712: Line 9,119:
ticker_str = ticker_str + "\n" + score_strings.finish
ticker_str = ticker_str + "\n" + score_strings.finish
ticker_str = ticker_str + "\nBest Times So Far"
ticker_str = ticker_str + "\nBest Times So Far"
Ticker_SetBlink( false )  // or true if you came in first?
Ticker_SetBlink( false )  // or true if you came in first? hmmm....
Ticker_NewStr( ticker_str, 120 )
Ticker_NewStr( ticker_str, 120 )
foreach (idx, val in score_strings.topscores)
foreach (idx, val in score_strings.topscores)
{
{
local hudfield = "score"+(idx+1)
local hudfield = "score"+(idx+1)
HoldoutHUD.Fields[hudfield].dataval = val
DashHUD.Fields[hudfield].dataval = val
HoldoutHUD.Fields[hudfield].flags = HoldoutHUD.Fields[hudfield].flags & ~HUD_FLAG_NOTVISIBLE
DashHUD.Fields[hudfield].flags = DashHUD.Fields[hudfield].flags & ~HUD_FLAG_NOTVISIBLE
}
}
Scoring_SaveTable( SessionState.MapName, SessionState.ModeName )
Scoring_SaveTable( SessionState.MapName, SessionState.ModeName )
}
}


// if a chopper rescue, this is where we get called
// this array holds the waypoints in order, up to SessionState.FinalWaypoint (though really, could just use len() for safety?
function RescuedByCopter( )
g_RoundState.WaypointList <- []
 
// called at basically each waypoint via ForceNextStage - or when a PANIC ends we just go to a delay
function GetNextStage()
{
{
// for now
smDbgPrint("GetNextStage called")
HoldoutDisplayScores()
}


// @TODO - this is basically "dont shoot at mines" right? why is it here?
if ( SessionState.YouWon )
function BotQuery( queryflag, entity, defaultvalue )
{
switch( queryflag )
{
{
case BOT_QUERY_NOTARGET:
smDbgPrint("Really winningz, going to rezultz, yo!")
SessionOptions.ScriptedStageType = STAGE_RESULTS
SessionOptions.ScriptedStageValue = 10
DashDisplayScores()
}
else if ( SessionState.JustHitWaypoint && SessionState.FinalWaypoint > 0 )
{
if ( !SessionState.FirstWaypointHit ) // could be start line, or waypoint 0
{  // hit firstone, kick things off
smDbgPrint("Hit First Waypoint!")
SessionState.FirstWaypointHit = true
HUDManageTimers( 1 , TIMER_COUNTUP, 0)  // generalize this?
 
// calculate the number of common infected to add each time a waypoint is completed
SessionState.CommonIncrement = (100 - SessionOptions.CommonLimit) / SessionState.FinalWaypoint
if( !MapOverrideOptions() )
{
{
local classname = entity.GetClassname()
// really, do we need this? or can we just always do panic here else a delay???
local targetname = entity.GetName()
local PercentageComplete = SessionState.CurrentWaypoint * 1.0 / SessionState.FinalWaypoint
if ( targetname && targetname.find( "mine_1_body" ) )
SessionState.JustHitWaypoint = false
SessionOptions.ScriptedStageType = STAGE_PANIC
SessionOptions.ScriptedStageValue = 1
foreach (val in SpecialNames)
{
{
return false;
local limit = RandomInt(1,3) + PercentageComplete * 3
SessionOptions[val + "Limit"] = limit
}
}
return true;
SessionOptions.MaxSpecials = RandomInt(2,6) + PercentageComplete * 3
}
if (SessionOptions.CommonLimit < 100)
}
SessionOptions.CommonLimit += SessionState.CommonIncrement
 
return defaultvalue;
if (SessionOptions.MegaMobSize < 50 )
}
SessionOptions.MegaMobSize += SessionState.CommonIncrement


//----------------------------------------------------
if (SessionState.CurrentWaypoint == SessionState.FinalWaypoint - 1 )
// Rather than checking every time to see if the map has defined each callback
{
// instead we are going to check for each callback on startup
Ticker_NewStr("One gate to go!")
//   if they arent there, we will insert a simple NOP lambda (if they are, we leave them)
SessionOptions.TankLimit = 1 // how do i force a tank from script?
// For what these callbacks do, check top of file where they are described
SessionOptions.ScriptedStageType = STAGE_ESCAPE  // i guess i do an escape stage
function OnGameplayStart()
}
{
else // display some help text on the ticker
CheckOrSetMapCallback( "DoMapEventCheck", @() false )
{
CheckOrSetMapCallback( "DoMapSetup", @() null)
switch( SessionState.CurrentWaypoint )
CheckOrSetMapCallback( "GetMapEscapeStage", @() null )
{
CheckOrSetMapCallback( "IsMapSpecificStage", @() false )
case 1:
CheckOrSetMapCallback( "GetMapSpecificStage", @() null )
Ticker_NewStr( "Each time you activate a new waypoint more infected will attack.", 15 )
CheckOrSetMapCallback( "GetAttackStage", @() null )
break
CheckOrSetMapCallback( "GetMapClearoutStage", @() null )
CheckOrSetMapCallback( "GetMapDelayStage", @() null )
case 2:
Ticker_NewStr( "You cleared the waypoint! Here come more infected!", 15 )
break
}
}
}
}
else
{
SessionOptions.ScriptedStageType = STAGE_DELAY
SessionOptions.ScriptedStageValue = 1000
SessionOptions.MaxSpecials = 0
}


DoMapSetup()   // and then call the MapSpecific startup callback right away
smDbgPrint("Dash setting Mode " + SessionOptions.ScriptedStageType + " val " + SessionOptions.ScriptedStageValue)
}


//=========================================================
// we also want to positionally spawn at the mob target - though really i'd like to do some distance/scale stuff, too
//=========================================================
SessionOptions.SpawnSetPosition = SessionOptions.PreferredMobPosition
function OnActivate()
{
// @todo: put scoring back into holdout!
Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )
 
// this is so we can do some non-wave-based thinking
ScriptedMode_AddSlowPoll( HoldoutSlowPollUpdate )
}
}


//=========================================================
// because call DWD is where we active/deactive the waypoints, we can just treat the start line as secret pre-waypoint
//=========================================================
function SurvivorLeftStartBox()
function OnShutdown()
{
{
ScriptedMode_RemoveSlowPoll( HoldoutSlowPollUpdate )
SessionState.JustHitWaypoint = true  // and since the main loop never looks up into the array, that is o.k.
Director.ForceNextStage()
}
}


HoldoutHUD <- {}
// each time a waypoint is hit this is called to get us moving forward
//=========================================================
// i.e. we need to activate the next waypoint, see if it is the final one, track counts, and so on
// since we have this mutation specific HUD but individual maps can use it differently
function DashWaypointDone( id )
// it is a bit more complicated than a normal HUD Setup
{
// - use SessionState to tell the system which extra fields you want (Wave #, Timer, etc)
smDbgPrint("Dash Waypoint finished! " + id)
// - check and warn if you try to set conflicting fields
 
function SetupModeHUD()
// sanity check!
if ( id != SessionState.CurrentWaypoint )
printl("Hey! you think you are done with waypoint " + id + " but current is " + SessionState.CurrentWaypoint )
 
EntFire( g_RoundState.WaypointList[id].GetName(), "StopGlowing" )
g_RoundState.WaypointList[id].GetScriptScope().active = false
// printl( "Deactivated waypoint " + id )
local next_id = ++SessionState.CurrentWaypoint
if ( next_id >= SessionState.FinalWaypoint )
{
// printl("Youz da Winner, yo!")
SessionState.CurrentWaypoint--  // for safety
SessionState.YouWon = true
 
// stop the clock
HUDManageTimers( 1, TIMER_STOP, 0 )
}
else
{
EntFire( g_RoundState.WaypointList[next_id].GetName(), "StartGlowing" )
EntFire( g_RoundState.WaypointList[next_id].GetName(), "fireuser1" )
//printl("Moving on to next waypoint " + next_id )
g_RoundState.WaypointList[next_id].GetScriptScope().active = true
SessionOptions.PreferredMobPosition = g_RoundState.WaypointList[next_id].GetOrigin()
}
SessionState.JustHitWaypoint = true
Director.ForceNextStage()
}
 
// add Criteria for the new Response Rule speech criteria system
// i.e. this is to add data to the RR table the characters use to choose statements
function AddCriteria( criteriaTable )
{
criteriaTable.PercentComplete <- SessionState.FinalWaypoint > 0 ? (SessionState.CurrentWaypoint*1.0)/SessionState.FinalWaypoint : 0
// printl("Dash AddCriteria added " + criteriaTable.PercentComplete)
}
 
//-------------------------------------------------------
// Include all entity group interfaces needed for this mode
//-------------------------------------------------------
IncludeScript( "entitygroups/dash_waypoint_group" )
IncludeScript( "entitygroups/dash_waypoint_short_group" )
 
DashHUD <- {}
 
// HUD setup/control - our HUD is pretty simple in dash
function SetupModeHUD( )
{
{
HoldoutHUD =
DashHUD =
{
{
Fields =  
Fields =  
{
{
cooldown_time = { slot = HUD_LEFT_TOP, name = "cooldown", special = HUD_SPECIAL_COOLDOWN, flags = HUD_FLAG_COUNTDOWN_WARN | HUD_FLAG_BEEP },
waypoint = { slot = HUD_RIGHT_TOP, name = "waypoint", staticstring = "Waypoint: ", datafunc = @() SessionState.CurrentWaypoint },
supply        = { slot = HUD_RIGHT_TOP, name = "supply", staticstring = "Supplies: ", datafunc = @() g_MapScript.Resources.CurrentCount },
timer    = { slot = HUD_LEFT_TOP, name = "timer", staticstring = "Time: ", special = HUD_SPECIAL_TIMER1 },


// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
Line 8,810: Line 9,287:
}
}
}
}
 
if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
Ticker_AddToHud( DashHUD, "Hurry past the poles, go for a best time" )
HUDSetLayout( DashHUD )
RescueTimer_Init( HoldoutHUD, HUD_MID_BOX, HUD_MID_BOT )
if (SessionState.HUDWaveInfo)
printl("Warning: Cannot have both Rescuetimer and Wave HUD elements at once");
}
else if ( "HUDWaveInfo" in SessionState && SessionState.HUDWaveInfo )
{
HoldoutHUD.Fields.wave <- {slot = HUD_MID_TOP, name = "wave", staticstring = "Wave: ", datafunc = @() SessionState.ScriptedStageWave }
}
 
if( "HUDTickerTimeout" in SessionState )
{
TickerTimeout = SessionState.HUDTickerTimeout
}
if ( "HUDTickerText" in SessionState )
{
if ( SessionState.HUDTickerText.len() > 0 )
{
Ticker_AddToHud( HoldoutHUD, SessionState.HUDTickerText )
}
}
 
HUDSetLayout( HoldoutHUD );
}
}
</source>}}


//=========================================================
===Holdout===
// The scripted mode calls this function each time the generator turns on.  
{{ExpandBox|<source lang=js>
// The generator usually turns on because someone poured a can of fuel into it.
///////////////////////////////////////////////////////////////////////////////
//=========================================================
// The Holdout game mode! Brought to you by the new Mutation system!
function OnGeneratorStart()
// A Holdout Is...
{
//   Mostly a demo of the new mutation system, but a game mode as well
g_MapScript.RescueTimer_Start()
//  A Continuous cycle of attack/cooldown/attack/cooldown
}
//  in code, implemented as 3 phases
//       PANIC - spawn some enemies around the players
//        CLEAROUT - wait until the enemies are (mostly) all gone
//        DELAY - a cooldown time before the next wave of enemies
// There are several "special behaviors" as this happens
//  The Escape wave can happen at a specified wave #, or as a result of in game action (timer running down, for instance)
//  There can be special events that take over based on the current Stage #


//=========================================================
// This is a very Structured mode - it uses stagetables and other "helpers" in the mutation system
// Counterpart to OnGeneratorStart(), this is called when the generator sputters and stops
// You dont need to use these in your own mutations, though you are of course welcome to
//  usually because it has run out of fuel.
//=========================================================
function OnGeneratorStop()
{
g_MapScript.RescueTimer_Stop()
}


//=========================================================
// as a map, you should go implement the following functions, returning a "stageTable"
// this is the base "system maintenance" for holdout mode timers and UI
// or null to ignore
// It checks things like has rescue started, should we do a warning about cooldown about to end, etc
//  DoMapEventCheck()
//=========================================================
//  DoMapSetup()
function HoldoutSlowPollUpdate()
//  GetMapEscapeStage()
//  IsMapSpecificStage()    // dont like this! potential out of sync/parallel variables
//  GetMapSpecificStage()  //    maybe IsMap sets a "stageSpecific" that GetMap can use?
//   GetAttackStage()
//   GetMapClearoutStage()
//  GetMapDelayStage()
///////////////////////////////////////////////////////////////////////////////
 
//---------------------------------------------------------
// This is the SessionState - any non-director data all your session code wants to be able to look at
// So we hold data about what wave, some UI controls, and info about managing warnings
MutationState <-
{
{
RescueTimer_Tick()
HUDWaveInfo = true          // do you want the Wave # in middle of UI
HUDRescueTimer = false      // or would you rather have the rescue timer (cant have both)
HUDTickerText = "Holdout as long as you can"
RescueStarted = false
RawStageNum = -1
ForcedEscapeStage = -1
ScriptedStageWave = 0
CooldownEndWarningTime = 8.5 // seconds before end of cooldown to play warning
CooldownEndWarningChance = 15 // percent chance to play warning
CooldownEndWarningFrequency = 0 // how many waves to wait before playing another warning
LastWaveCooldownWarningPlayed = -1 // the last wave that the cooldown warning played on
}


if ( !SessionState.RescueStarted )
//---------------------------------------------------------
{
// the DirectorOptions defaults for Holdout - though the maps will override many of these per wave and phase
if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
MutationOptions <-
{
{
local seconds = g_MapScript.RescueTimer_Get()
PreferredMobDirection = SPAWN_NO_PREFERENCE
SpawnSetRule = SPAWN_ANYWHERE
// start the rescue if the generator has run long enough
JournalString = ""
if( seconds <= 0 )
SpecialInfectedAssault = 0
{
AllowWitchesInCheckpoints = 1
SessionState.RescueStarted = true
AllowCrescendoEvents = 0
g_ModeScript.StartEscapeWave()
EnforceFinaleNavSpawnRules = 0
}
IgnoreNavThreatAreas = 1
}
ZombieDiscardRange = 10000
}


// CooldownEndWarningChance is the % chance that a random player will vocalize a warning
WanderingZombieDensityModifier = 0
// when a cooldown is close to ending
BoomerLimit  = 0
if( "CooldownEndWarningChance" in SessionState )
ChargerLimit = 0
{
HunterLimit  = 0
local timeLeft = HUDReadTimer( HUD_SPECIAL_COOLDOWN )
JockeyLimit  = 0
SpitterLimit = 0
SmokerLimit  = 0
MaxSpecials  = 0
CommonLimit  = 20
TankLimit    = 0
MegaMobMaxSize = 100
MegaMobMinSize = 75
PanicWavePauseMax = 5
PanicWavePauseMin = 1
AddToSpawnTimer = 0
ShouldAllowMobsWithTank = true
ShouldAllowSpecialsWithTank = true
ZombieSpawnRange = 800
ZombieTankHealth = 6000 // SP default is 4000
BileMobSize = 20
EscapeSpawnTanks = true
ZombieDontClear = 1
MegaMobSize = 50      // i have no idea why this defaults to not between min and max


if ( ( timeLeft < SessionState.CooldownEndWarningTime ) && ( timeLeft > 0 ) && ( SessionState.LastWaveCooldownWarningPlayed + SessionState.CooldownEndWarningFrequency < SessionState.ScriptedStageWave ) )
cm_ShouldEscortHumanPlayers = 1
{
cm_AggressiveSpecials = 1
SessionState.LastWaveCooldownWarningPlayed = SessionState.ScriptedStageWave
}


// roll the dice...
//=========================================================
local chance = RandomInt(0, 100 )
// Utilities and helpers
//=========================================================


if( chance < SessionState.CooldownEndWarningChance )
const PHASE_PANIC = 1
{
const PHASE_CLEAR = 2
// jackpot! collect the players into an array and select one at random to speak a line
const PHASE_DELAY = 0
local playerEnt = null
local playerArray = []


while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
function StageNumToWaveBase( stagenum )
{
{
if (playerEnt.IsSurvivor() )
return (stagenum + 2) / 3  // the +2 is because of our initial startup raw phases...
{
}
playerArray.append( playerEnt)
}
}


local idx = RandomInt( 0, 3)
function WaveBaseToStageNum( wavebase, substage = PHASE_DELAY )
 
{
if( idx < playerArray.len() )
return wavebase * 3 + substage
{
// fire entity IO at "!activator" and pass the player ent as the activator
EntFire( "!activator", "SpeakResponseConcept", "PlayerHurryUp", 0, playerArray[idx] )
}
}
}
}
}
}


function JournalFunc()
// Are players permitted to pick up this object by pressing their USE key?
{
// return true for yes, false for no.
DirectorOptions.JournalString = "{ resources = " + g_MapScript.Resources.CurrentCount
function CanPickupObject( object )
if (SessionState.HUDRescueTimer)
{
DirectorOptions.JournalString += ", rescue = " + RescueTimer_Get()
// mines
if ("JournalMapFunc" in this)
if ( object.GetName().find( "mine_1_body" ) )
DirectorOptions.JournalString += JournalMapFunc()
{
DirectorOptions.JournalString += " }"
return true
}
// resource drops
if ( object.GetName().find( "prop_resource" ) )
{
return true
}
 
// check the map script for a PickupObject function for extra qualified items
// TODO: we should do this more generally!
local canPickup = false
if( "PickupObject" in g_MapScript )
{
canPickup = g_MapScript.PickupObject( object )
}
return canPickup
}
}


//=========================================================
// Called from other systems when they want the next combat wave to be the escape
// A little digression of testing/demoing a "script based clearout"
function StartEscapeWave()
//  You can just do C++ based clearout (to let the mob population clearout) of course
//  But from script, one can use a Poll function and GetInfectedStats to manage it yourself
//  See sm_utilities for the actual code/systems if you want to do something like it
 
// Whether to use the new Script based clearout or not - false here just means use the default c++ one
::g_ScriptClearout <- true
 
// if you are going to use the new clearout - here is the table for determining it's behavior
holdout_ClearoutTable <-
{
{
commons = 2
SessionState.ForcedEscapeStage = ( ( SessionState.RawStageNum + 2 ) / 3) * 3 + 1
specials = 0
// printl( "Prepping Escape Wave for Stage " + SessionState.ForcedEscapeStage + " from " + SessionState.RawStageNum )
tanks = 0
witches = 0
plateautime = 5
plateaucommons = 5
plateauspecials = 1
stopspecials = true
}
}


///////////////////////////////////////////////////////////////////////////////
// show the final score and timing
//
function HoldoutDisplayScores( )
// "Main Loop" of Holdout
{
//
local final_time = Time() - SessionState.RealStartTime
// GetNextStage gets called whenever the director finishes the last thing you told it to do
local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
//  or if you do a ForceNextStage yourself
HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
// In holdout, we use GetNextStage to manage the game, cycling through 3 types of Stage (as described at top)
HUDPlace( HUD_FAR_RIGHT, 0.28, 0.50, 0.44, 0.06 )
// At each stage, we make some of our callbacks (that we checked and set up in OnGameplayStart)
HUDPlace( HUD_FAR_LEFT,  0.28, 0.58, 0.44, 0.06 )
// To determine what options we want for that stage - usually in the form of a "stagetable"
HUDPlace( HUD_LEFT_BOT,  0.28, 0.66, 0.44, 0.06 )
//   which is basically just a set of DirectorOptions parameters and a few extra fields
HUDPlace( HUD_RIGHT_BOT, 0.28, 0.74, 0.44, 0.06 )
// And then we go and set all those fields in the director, set the stage type, and send the director off
local ticker_str = score_strings.yourtime
//
if ("finish" in score_strings)
///////////////////////////////////////////////////////////////////////////////
ticker_str = ticker_str + "\n" + score_strings.finish
ticker_str = ticker_str + "\nBest Times So Far"
Ticker_SetBlink( false )   // or true if you came in first?
Ticker_NewStr( ticker_str, 120 )
foreach (idx, val in score_strings.topscores)
{
local hudfield = "score"+(idx+1)
HoldoutHUD.Fields[hudfield].dataval = val
HoldoutHUD.Fields[hudfield].flags = HoldoutHUD.Fields[hudfield].flags & ~HUD_FLAG_NOTVISIBLE
}
Scoring_SaveTable( SessionState.MapName, SessionState.ModeName )
}


function GetNextStage()
// if a chopper rescue, this is where we get called
function RescuedByCopter( )
{
{
local use_stage = null
// for now
HoldoutDisplayScores()
}


local stageNum = ++SessionState.RawStageNum
// @TODO - this is basically "dont shoot at mines" right? why is it here?
 
function BotQuery( queryflag, entity, defaultvalue )
SessionState.ScriptedStageWave = ( stageNum + 2 ) / 3
{
 
switch( queryflag )
// first, special event management
{
DoMapEventCheck()
case BOT_QUERY_NOTARGET:
{
local classname = entity.GetClassname()
local targetname = entity.GetName()
if ( targetname && targetname.find( "mine_1_body" ) )
{
return false;
}
return true;
}
}
// fire game events as we begin and end cooldown states - initial map stage 0 is considered a cooldown state
return defaultvalue;
if( ( stageNum % 3 ) == PHASE_PANIC )
}
FireScriptEvent( "on_cooldown_end", null )
else if( ( ( stageNum % 3 ) == PHASE_DELAY ) || ( stageNum == 0 ) )
FireScriptEvent( "on_cooldown_begin", null )


// error check!
//----------------------------------------------------
if (SessionState.ForcedEscapeStage != -1 && stageNum > SessionState.ForcedEscapeStage)
// Rather than checking every time to see if the map has defined each callback
printl("Ummmmmm.... something has gone very wrong... we passed escape stage but are still picking next stages...");
// instead we are going to check for each callback on startup
//  if they arent there, we will insert a simple NOP lambda (if they are, we leave them)
// For what these callbacks do, check top of file where they are described
function OnGameplayStart()
{
CheckOrSetMapCallback( "DoMapEventCheck", @() false )
CheckOrSetMapCallback( "DoMapSetup", @() null)
CheckOrSetMapCallback( "GetMapEscapeStage", @() null )
CheckOrSetMapCallback( "IsMapSpecificStage", @() false )
CheckOrSetMapCallback( "GetMapSpecificStage", @() null )
CheckOrSetMapCallback( "GetAttackStage", @() null )
CheckOrSetMapCallback( "GetMapClearoutStage", @() null )
CheckOrSetMapCallback( "GetMapDelayStage", @() null )
 
DoMapSetup()    // and then call the MapSpecific startup callback right away
}
 
//=========================================================
//=========================================================
function OnActivate()
{
// @todo: put scoring back into holdout!
Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )
 
// this is so we can do some non-wave-based thinking
ScriptedMode_AddSlowPoll( HoldoutSlowPollUpdate )
}
 
//=========================================================
//=========================================================
function OnShutdown()
{
ScriptedMode_RemoveSlowPoll( HoldoutSlowPollUpdate )
}


if ( stageNum == 1) // 1st real attack stage
HoldoutHUD <- {}
//=========================================================
// since we have this mutation specific HUD but individual maps can use it differently
// it is a bit more complicated than a normal HUD Setup
// - use SessionState to tell the system which extra fields you want (Wave #, Timer, etc)
// - check and warn if you try to set conflicting fields
function SetupModeHUD()
{
HoldoutHUD =
{
{
SessionState.RealStartTime <- Time()
Fields =
Director.ResetSpecialTimers()
{
}
cooldown_time = { slot = HUD_LEFT_TOP, name = "cooldown", special = HUD_SPECIAL_COOLDOWN, flags = HUD_FLAG_COUNTDOWN_WARN | HUD_FLAG_BEEP },
supply        = { slot = HUD_RIGHT_TOP, name = "supply", staticstring = "Supplies: ", datafunc = @() g_MapScript.Resources.CurrentCount },


// now the generalized stage sequencing...
// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
if ( stageNum == 0 )
// we will move the Ticker down to be a "header" - then need 4 slots for top 4
{
score1  = { slot = HUD_FAR_RIGHT, name = "score1", dataval = "Score1", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
DirectorOptions.ScriptedStageType = STAGE_SETUP
score2  = { slot = HUD_FAR_LEFT,  name = "score2", dataval = "Score2", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
if ("HUDTickerText" in SessionState)
score3  = { slot = HUD_LEFT_BOT,  name = "score3", dataval = "Score3", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
Ticker_NewStr(SessionState.HUDTickerText)  // since the time it got set as start in HUD load is ages ago
score4  = { slot = HUD_RIGHT_BOT, name = "score4", dataval = "Score4", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
}
}
}
else if ( stageNum == SessionState.ForcedEscapeStage )
{
if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
use_stage = GetMapEscapeStage()
RescueTimer_Init( HoldoutHUD, HUD_MID_BOX, HUD_MID_BOT )
if (SessionState.HUDWaveInfo)
printl("Warning: Cannot have both Rescuetimer and Wave HUD elements at once");
}
else if ( "HUDWaveInfo" in SessionState && SessionState.HUDWaveInfo )
{
HoldoutHUD.Fields.wave <- {slot = HUD_MID_TOP, name = "wave", staticstring = "Wave: ", datafunc = @() SessionState.ScriptedStageWave }
}
}
else if ( IsMapSpecificStage() )
 
use_stage = GetMapSpecificStage()  // dont like this! potential out of sync/parallel variables
if( "HUDTickerTimeout" in SessionState )
else if ( ( stageNum % 3 ) == PHASE_PANIC )
{
{
use_stage = GetAttackStage()
TickerTimeout = SessionState.HUDTickerTimeout
if (::g_ScriptClearout)
ClearoutNotifyPanicStart( holdout_ClearoutTable )
}
else if ( ( stageNum % 3 ) == PHASE_CLEAR )
{
if (::g_ScriptClearout)
{
ClearoutStart( holdout_ClearoutTable )
DirectorOptions.ScriptedStageType = STAGE_DELAY
DirectorOptions.ScriptedStageValue = -1 // Infinite
}
else
use_stage = GetMapClearoutStage()
}
else if ( ( stageNum % 3 ) == PHASE_DELAY )
{
use_stage = GetMapDelayStage()
}
}
if ( "HUDTickerText" in SessionState )
{
if ( SessionState.HUDTickerText.len() > 0 )
{
Ticker_AddToHud( HoldoutHUD, SessionState.HUDTickerText )
}
}


// Put the special infected into assault mode - really want to do this sooner somehow? at maxspecials 0?
HUDSetLayout( HoldoutHUD );
switch ( DirectorOptions.ScriptedStageType )
}
{
case STAGE_CLEAROUT:
case STAGE_DELAY:
case STAGE_ESCAPE:
DirectorOptions.SpecialInfectedAssault = 1
break;
default:
DirectorOptions.SpecialInfectedAssault = 0
}


if ( use_stage != null )
//=========================================================
{
// The scripted mode calls this function each time the generator turns on.
if ( "stageDefaults" in g_MapScript )
// The generator usually turns on because someone poured a can of fuel into it.
StageInfo_Execute( use_stage, stageDefaults )        // should use_stage just delegate?
//=========================================================
else
function OnGeneratorStart()
StageInfo_Execute( use_stage )
{
g_MapScript.RescueTimer_Start()
if ( "NextDelay" in use_stage )
SessionState.NextDelayTime <- use_stage.NextDelay    // just overwrite, so we dont need to if check
else
SessionState.NextDelayTime <- 30                    // better default from somewhere?
}
 
smDbgPrint( "Setting type " + DirectorOptions.ScriptedStageType + " and val " + DirectorOptions.ScriptedStageValue )
}
}
</source>}}


===ShootZones===
//=========================================================
{{ExpandBox|<source lang=js>
// Counterpart to OnGeneratorStart(), this is called when the generator sputters and stops
ModeSpawns <-
//  usually because it has run out of fuel.
[
//=========================================================
["ShootzoneTrigger", "shootzone_trigger_spawn_*", "shootzone_trigger_group", SPAWN_FLAGS.SPAWN],
function OnGeneratorStop()
[ "ShootzoneSound", SPAWN_FLAGS.NOSPAWN ],
]
 
 
MutationState <-
{
{
allPlayers = []
g_MapScript.RescueTimer_Stop()
playersInShootzone = []
allShootzones = []
enabledShootzones = []
lastEnabledShootzoneID = -1
FirstTime = true
}
}


 
//=========================================================
MutationOptions <-
// this is the base "system maintenance" for holdout mode timers and UI
// It checks things like has rescue started, should we do a warning about cooldown about to end, etc
//=========================================================
function HoldoutSlowPollUpdate()
{
{
    CommonLimit = 30 // Maximum number of common zombies alive in the world at the same time
RescueTimer_Tick()
//MegaMobSize = 20 // Total number of common zombies in a mob. (never more than CommonLimit at one time)
//WanderingZombieDensityModifier = 0 // lets get rid of the wandering zombies
MaxSpecials  = 0 // removes all special infected from spawning
TankLimit    = 0 // removes all tanks from spawning
WitchLimit  = 0 // removes all witches from spawning
//BoomerLimit  = 0
//ChargerLimit = 0
//HunterLimit  = 0
//JockeyLimit  = 0
//SpitterLimit = 0
//SmokerLimit  = 0
}


 
if ( !SessionState.RescueStarted )
ShootzonesHUD <-
{
Fields =
{
{
player1  = { slot = HUD_LEFT_TOP,  name = "player1", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
player2  = { slot = HUD_LEFT_BOT,  name = "player2", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
{
player3  = { slot = HUD_RIGHT_TOP, name = "player3", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
local seconds = g_MapScript.RescueTimer_Get()
player4  = { slot = HUD_RIGHT_BOT, name = "player4", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
// start the rescue if the generator has run long enough
if( seconds <= 0 )
{
SessionState.RescueStarted = true
g_ModeScript.StartEscapeWave()
}
}
}
}
}


// CooldownEndWarningChance is the % chance that a random player will vocalize a warning
// when a cooldown is close to ending
if( "CooldownEndWarningChance" in SessionState )
{
local timeLeft = HUDReadTimer( HUD_SPECIAL_COOLDOWN )


function ShootzoneThinkSpawnCB( entity, rarity )
if ( ( timeLeft < SessionState.CooldownEndWarningTime ) && ( timeLeft > 0 ) && ( SessionState.LastWaveCooldownWarningPlayed + SessionState.CooldownEndWarningFrequency < SessionState.ScriptedStageWave ) )
{
{
printl( "Think Spawn!!!" )
SessionState.LastWaveCooldownWarningPlayed = SessionState.ScriptedStageWave


//entity.ValidateScriptScope()
// roll the dice...
entity.GetScriptScope().ShootzoneThink <- function()
local chance = RandomInt(0, 100 )
{
g_ModeScript.RecomputePlayersInShootzones()
}
}


if( chance < SessionState.CooldownEndWarningChance )
{
// jackpot! collect the players into an array and select one at random to speak a line
local playerEnt = null
local playerArray = []


function OnGameplayStart()
while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
{
{
printl( "Starting SHOOTZONES!" )
if (playerEnt.IsSurvivor() )
printl( "Max Flow Distance: " + GetMaxFlowDistance() )
{
playerArray.append( playerEnt)
}
}


//Add all the players
local idx = RandomInt( 0, 3)
local playerEnt = null
while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
{
if ( playerEnt.IsSurvivor() )
{
playerEnt.ValidateScriptScope()
playerEnt.GetScriptScope().currentShootzone <- -1
//playerEnt.GetScriptScope().currentShootzone = -1
SessionState.allPlayers.append( playerEnt)
}
}


//Initialize the HUD
if( idx < playerArray.len() )
foreach ( index, playerEnt in SessionState.allPlayers )
{
{
// fire entity IO at "!activator" and pass the player ent as the activator
local hudField = "player" + ( index + 1 )
EntFire( "!activator", "SpeakResponseConcept", "PlayerHurryUp", 0, playerArray[idx] )
ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": OUT"
ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
}
 
EnableShootzones()
 
//ScriptedMode_AddUpdate( RecomputePlayersInShootzones.bindenv(this) )
 
local shootzone_think =
{
function GetSpawnList()     { return [ EntityGroup.SpawnTables.shootzone_think ] }
function GetEntityGroup()   { return EntityGroup }
EntityGroup =
{
SpawnTables =
{
shootzone_think =
{
initialSpawn = true
SpawnInfo =
{
classname = "logic_script"
angles = Vector( 0, 0, 90 )
targetname = "shootzone_think"
origin = Vector( 0, 0, 0 )
vscripts = "NULL_SCRIPT_NAME"
thinkfunction = "ShootzoneThink"
}
}
}
} // SpawnTables
}
} // EntityGroup
}
}
}
local shootzoneThinkGroup = shootzone_think.GetEntityGroup()
shootzoneThinkGroup.SpawnTables[ "shootzone_think" ].PostPlaceCB <- ShootzoneThinkSpawnCB
g_MapScript.SpawnSingleAt( shootzoneThinkGroup, Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )
g_MapScript.SpawnSingleAt( g_MapScript.GetEntityGroup( "ShootzoneSound" ), Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )
}
}


 
function JournalFunc()
function OnActivate()
{
{
DirectorOptions.JournalString = "{ resources = " + g_MapScript.Resources.CurrentCount
if (SessionState.HUDRescueTimer)
DirectorOptions.JournalString += ", rescue = " + RescueTimer_Get()
if ("JournalMapFunc" in this)
DirectorOptions.JournalString += JournalMapFunc()
DirectorOptions.JournalString += " }"
}
}


//=========================================================
// A little digression of testing/demoing a "script based clearout"
//  You can just do C++ based clearout (to let the mob population clearout) of course
//  But from script, one can use a Poll function and GetInfectedStats to manage it yourself
//  See sm_utilities for the actual code/systems if you want to do something like it
// Whether to use the new Script based clearout or not - false here just means use the default c++ one
::g_ScriptClearout <- true


function OnEntityGroupRegistered( name, group )
// if you are going to use the new clearout - here is the table for determining it's behavior
holdout_ClearoutTable <-
{
{
if ( name == "ShootzoneTrigger" )
commons = 2
{
specials = 0
group.GetEntityGroup().SpawnTables[ "shootzone_script" ].PostPlaceCB <- ShootzoneScriptSpawnCB
tanks = 0
}
witches = 0
plateautime = 5
plateaucommons = 5
plateauspecials = 1
stopspecials = true
}
}


///////////////////////////////////////////////////////////////////////////////
//
// "Main Loop" of Holdout
//
// GetNextStage gets called whenever the director finishes the last thing you told it to do
//  or if you do a ForceNextStage yourself
// In holdout, we use GetNextStage to manage the game, cycling through 3 types of Stage (as described at top)
// At each stage, we make some of our callbacks (that we checked and set up in OnGameplayStart)
// To determine what options we want for that stage - usually in the form of a "stagetable"
//  which is basically just a set of DirectorOptions parameters and a few extra fields
// And then we go and set all those fields in the director, set the stage type, and send the director off
//
///////////////////////////////////////////////////////////////////////////////


function ShootzoneScriptSpawnCB( entity, rarity )
function GetNextStage()
{
{
printl( "Script spawned: " + entity + ", name: " + entity.GetName() )
local use_stage = null


local shootzoneScope = entity.GetScriptScope()
local stageNum = ++SessionState.RawStageNum


//Set the id of the shootzone
SessionState.ScriptedStageWave = ( stageNum + 2 ) / 3
shootzoneScope.id = SessionState.allShootzones.len()
printl( "Shootzone ID: " + shootzoneScope.id )
//Flow distance for the shootzone
shootzoneScope.flowDistance = GetFlowDistanceForPosition( shootzoneScope.origin )
shootzoneScope.flowPercent = GetFlowPercentForPosition( shootzoneScope.origin, true )
shootzoneScope.flowPercent1 = GetFlowPercentForPosition( shootzoneScope.origin, false )
printl( "Flow Distance: " + shootzoneScope.flowDistance )
printl( "Flow Percent: " + shootzoneScope.flowPercent )


//Add the shootzone to the list of all the shootzones
// first, special event management
SessionState.allShootzones.append( entity )
DoMapEventCheck()
}
// fire game events as we begin and end cooldown states - initial map stage 0 is considered a cooldown state
if( ( stageNum % 3 ) == PHASE_PANIC )
FireScriptEvent( "on_cooldown_end", null )
else if( ( ( stageNum % 3 ) == PHASE_DELAY ) || ( stageNum == 0 ) )
FireScriptEvent( "on_cooldown_begin", null )


// error check!
if (SessionState.ForcedEscapeStage != -1 && stageNum > SessionState.ForcedEscapeStage)
printl("Ummmmmm.... something has gone very wrong... we passed escape stage but are still picking next stages...");


// HUD setup/control - our HUD is pretty simple in dash
if ( stageNum == 1) // 1st real attack stage
function SetupModeHUD( )
{
{
SessionState.RealStartTime <- Time()
HUDSetLayout( ShootzonesHUD )
Director.ResetSpecialTimers()
}


HUDPlace( HUD_LEFT_TOP, 0.01, 0.06, 0.2, 0.04 )
//  now the generalized stage sequencing...
HUDPlace( HUD_LEFT_BOT, 0.01, 0.11, 0.2, 0.04 )
if ( stageNum == 0 )
HUDPlace( HUD_RIGHT_TOP, 0.01, 0.16, 0.2, 0.04 )
{
HUDPlace( HUD_RIGHT_BOT, 0.01, 0.21, 0.2, 0.04 )
DirectorOptions.ScriptedStageType = STAGE_SETUP
}
if ("HUDTickerText" in SessionState)
 
Ticker_NewStr(SessionState.HUDTickerText) // since the time it got set as start in HUD load is ages ago
 
}
function FindShootzoneClosestToPlayers()
else if ( stageNum == SessionState.ForcedEscapeStage )
{
{
 
use_stage = GetMapEscapeStage()
}
}
 
else if ( IsMapSpecificStage() )
function EnableShootzones()
use_stage = GetMapSpecificStage()  // dont like this! potential out of sync/parallel variables
{
else if ( ( stageNum % 3 ) == PHASE_PANIC )
//Cycle through the shootzones and keep 3 enabled at all times
{
if ( SessionState.allShootzones.len() < 3 )
use_stage = GetAttackStage()
if (::g_ScriptClearout)
ClearoutNotifyPanicStart( holdout_ClearoutTable )
}
else if ( ( stageNum % 3 ) == PHASE_CLEAR )
{
{
foreach ( shootzone in SessionState.allShootzones )
if (::g_ScriptClearout)
{
{
shootzone.GetScriptScope().EnableShootzone()
ClearoutStart( holdout_ClearoutTable )
SessionState.enabledShootzones.append( shootzone )
DirectorOptions.ScriptedStageType = STAGE_DELAY
 
DirectorOptions.ScriptedStageValue = -1 // Infinite
SessionState.lastEnabledShootzoneID = shootzone.GetScriptScope().id
}
}
else
use_stage = GetMapClearoutStage()
}
}
else
else if ( ( stageNum % 3 ) == PHASE_DELAY )
{
{
local index = SessionState.lastEnabledShootzoneID + 1
use_stage = GetMapDelayStage()
while ( SessionState.enabledShootzones.len() < 3 )
}
{
if ( index >= SessionState.allShootzones.len() )
{
index = 0;
}


//Enable the next available shootzone in the list
// Put the special infected into assault mode - really want to do this sooner somehow? at maxspecials 0?
local currentShootzone = SessionState.allShootzones[index]
switch ( DirectorOptions.ScriptedStageType )
if ( currentShootzone.GetScriptScope().isEnabled == false )
{
{
case STAGE_CLEAROUT:
currentShootzone.GetScriptScope().EnableShootzone()
case STAGE_DELAY:
SessionState.enabledShootzones.append( currentShootzone )
case STAGE_ESCAPE:
DirectorOptions.SpecialInfectedAssault = 1
break;
default:
DirectorOptions.SpecialInfectedAssault = 0
}


SessionState.lastEnabledShootzoneID = currentShootzone.GetScriptScope().id
if ( use_stage != null )
}
{
if ( "stageDefaults" in g_MapScript )
StageInfo_Execute( use_stage, stageDefaults )        // should use_stage just delegate?
else
StageInfo_Execute( use_stage )
if ( "NextDelay" in use_stage )
SessionState.NextDelayTime <- use_stage.NextDelay    // just overwrite, so we dont need to if check
else
SessionState.NextDelayTime <- 30                    // better default from somewhere?
}


index++
smDbgPrint( "Setting type " + DirectorOptions.ScriptedStageType + " and val " + DirectorOptions.ScriptedStageValue )
}
}
}
}
</source>}}


===ShootZones===
{{ExpandBox|<source lang=js>
ModeSpawns <-
[
["ShootzoneTrigger", "shootzone_trigger_spawn_*", "shootzone_trigger_group", SPAWN_FLAGS.SPAWN],
[ "ShootzoneSound", SPAWN_FLAGS.NOSPAWN ],
]


function RecomputePlayersInShootzones()
 
MutationState <-
{
{
// if ( SessionState.FirstTime )
allPlayers = []
// {
playersInShootzone = []
// foreach ( shootzone in SessionState.allShootzones )
allShootzones = []
// {
enabledShootzones = []
// local shootzoneScope = shootzone.GetScriptScope()
lastEnabledShootzoneID = -1
// DebugDrawText( shootzoneScope.origin, "Distance: " + shootzoneScope.flowDistance + "\nPercent: " + shootzoneScope.flowPercent + "%", false, 100000 )
FirstTime = true
// }
}
//
// SessionState.FirstTime = false;
// }


//Keep track of players currently in a shootzone
local prevShootzonePlayers = clone( SessionState.playersInShootzone )


//Recompute which players are in an active shootzone
MutationOptions <-
SessionState.playersInShootzone.clear()
{
 
    CommonLimit = 30 // Maximum number of common zombies alive in the world at the same time
foreach ( playerEnt in SessionState.allPlayers )
//MegaMobSize = 20 // Total number of common zombies in a mob. (never more than CommonLimit at one time)
{
//WanderingZombieDensityModifier = 0 // lets get rid of the wandering zombies
foreach ( shootzone in SessionState.enabledShootzones )
MaxSpecials  = 0 // removes all special infected from spawning
{
TankLimit    = 0 // removes all tanks from spawning
local shootzoneScope = shootzone.GetScriptScope()
WitchLimit  = 0 // removes all witches from spawning
if ( shootzoneScope.isActive )
//BoomerLimit  = 0
{
//ChargerLimit = 0
local playerToShootzone = playerEnt.GetOrigin() - shootzoneScope.origin
//HunterLimit  = 0
local playerDistance = playerToShootzone.Length()
//JockeyLimit  = 0
//SpitterLimit = 0
//SmokerLimit  = 0
}


if ( playerDistance < 150 )
{
playerEnt.GetScriptScope().currentShootzone = shootzoneScope.id
SessionState.playersInShootzone.append( playerEnt )
break;
}
}
}
}


//foreach ( shootzone in SessionState.allShootzones )
ShootzonesHUD <-
//{
{
//local shootzoneScope = shootzone.GetScriptScope()
Fields =
//local vecto = SessionState.allPlayers[0].GetOrigin() - shootzoneScope.origin
//local dist = vecto.Length()
//
//if ( dist < 500 )
//{
//DebugDrawText( shootzoneScope.origin, "Distance: " + shootzoneScope.flowDistance + "\nPercent: " + shootzoneScope.flowPercent + "%\nPercent2: " + shootzoneScope.flowPercent1, false, 0.15 )
//
//DebugDrawLine( shootzoneScope.origin, SessionState.allPlayers[0].GetOrigin(), 255, 0, 0, false, 0.2 )
//}
//}
//
foreach ( index, playerEnt in SessionState.allPlayers )
{
{
//This player just entered a shootzone
player1  = { slot = HUD_LEFT_TOP,  name = "player1", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
if ( prevShootzonePlayers.find( playerEnt ) == null &&
player2  = { slot = HUD_LEFT_BOT,  name = "player2", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
SessionState.playersInShootzone.find( playerEnt ) != null )
player3  = { slot = HUD_RIGHT_TOP, name = "player3", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
{
player4  = { slot = HUD_RIGHT_BOT, name = "player4", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
//Start playing the sound for players that just entered the shootzone
}
printl( playerEnt.GetPlayerName() + " entered shootzone " + playerEnt.GetScriptScope().currentShootzone )
}
EmitSoundOnClient( "c2m4.BadMan1", playerEnt )
}


//This player just exited a shootzone
if ( prevShootzonePlayers.find( playerEnt ) != null &&
SessionState.playersInShootzone.find( playerEnt ) == null )
{
//Stop playing the sound for players that just exited the shootzone
printl( playerEnt.GetPlayerName() +" exited shootzone " + playerEnt.GetScriptScope().currentShootzone )


playerEnt.GetScriptScope().currentShootzone = -1
function ShootzoneThinkSpawnCB( entity, rarity )
StopSoundOn( "c2m4.BadMan1", playerEnt )
{
}
printl( "Think Spawn!!!" )


local inOrOut = "OUT"
//entity.ValidateScriptScope()
foreach ( shootzonePlayer in SessionState.playersInShootzone )
entity.GetScriptScope().ShootzoneThink <- function()
{
{
if ( shootzonePlayer.GetPlayerName() == playerEnt.GetPlayerName() )
g_ModeScript.RecomputePlayersInShootzones()
{
inOrOut = "IN"
break;
}
}
 
//Set the player HUD flags
local hudField = "player" + ( index + 1 )
ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": " + inOrOut
ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
}
}
}
}




function ShootzoneTimedOut( shootzoneID )
function OnGameplayStart()
{
{
foreach ( index, shootzone in SessionState.enabledShootzones )
printl( "Starting SHOOTZONES!" )
printl( "Max Flow Distance: " + GetMaxFlowDistance() )
 
//Add all the players
local playerEnt = null
while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
{
{
if ( shootzone.GetScriptScope().id == shootzoneID )
if ( playerEnt.IsSurvivor() )
{
{
SessionState.enabledShootzones.remove( index )
playerEnt.ValidateScriptScope()
break
playerEnt.GetScriptScope().currentShootzone <- -1
//playerEnt.GetScriptScope().currentShootzone = -1
SessionState.allPlayers.append( playerEnt)
}
}
}
//Initialize the HUD
foreach ( index, playerEnt in SessionState.allPlayers )
{
local hudField = "player" + ( index + 1 )
ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": OUT"
ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
}
}


EnableShootzones()
EnableShootzones()
}


//ScriptedMode_AddUpdate( RecomputePlayersInShootzones.bindenv(this) )


function AllowTakeDamage( damageTable )
local shootzone_think =
{
foreach ( playerEnt in SessionState.allPlayers )
{
{
//If the attacker is a player
function GetSpawnList()      { return [ EntityGroup.SpawnTables.shootzone_think ] }
if ( playerEnt == damageTable.Attacker )
function GetEntityGroup()    { return EntityGroup }
EntityGroup =
{
{
//If the attacking player is in a shootzone then do damage
SpawnTables =
foreach ( shootzonePlayer in SessionState.playersInShootzone )
{
{
if ( shootzonePlayer == damageTable.Attacker )
shootzone_think =  
{
{
//printl( "Damage done by shootzone player: " + shootzonePlayer.GetPlayerName() )
initialSpawn = true
return true
SpawnInfo =
{
classname = "logic_script"
angles = Vector( 0, 0, 90 )
targetname = "shootzone_think"
origin = Vector( 0, 0, 0 )
vscripts = "NULL_SCRIPT_NAME"
thinkfunction = "ShootzoneThink"
}
}
}
}
} // SpawnTables
} // EntityGroup
}


//Attacking player is not in a shootzone
local shootzoneThinkGroup = shootzone_think.GetEntityGroup()
//printl( "Attacking player is not in shootzone: " + playerEnt.GetPlayerName() )
shootzoneThinkGroup.SpawnTables[ "shootzone_think" ].PostPlaceCB <- ShootzoneThinkSpawnCB
return false
g_MapScript.SpawnSingleAt( shootzoneThinkGroup, Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )
}
g_MapScript.SpawnSingleAt( g_MapScript.GetEntityGroup( "ShootzoneSound" ), Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )
}


//Attacker is not a player
return true
}
}
</source>}}


===GunBrain===
{{ExpandBox|<source lang=js>
//=========================================================
//=========================================================


GunStatsTable <- null
function OnActivate()
 
{
TARGET_INVALID <- -1
}
TARGET_SURVIVOR <- 9


MAX_TRACK_PLAYERS <- 10 // note - to change this we would need to update the server index to generate our graphs. There are only 10 graph servers so you can't go past the index 9.


// we need a barebones HUD for our ticker that shows up at the begining of the game
function OnEntityGroupRegistered( name, group )
function SetupModeHUD( )
{
{
ModeHUD <-
if ( name == "ShootzoneTrigger" )
{
{
Fields =
group.GetEntityGroup().SpawnTables[ "shootzone_script" ].PostPlaceCB <- ShootzoneScriptSpawnCB
{
}
}
}
Ticker_AddToHud( ModeHUD, "" )
}


// load the ModeHUD table
HUDSetLayout( ModeHUD )
}


/////////////////////////////////////////////////
function ShootzoneScriptSpawnCB( entity, rarity )
// Mod Command interface
{
/////////////////////////////////////////////////
printl( "Script spawned: " + entity + ", name: " + entity.GetName() )


GB_HELP_TEXT <-
local shootzoneScope = entity.GetScriptScope()
[
 
"About GunBrain - Damage stats are stored on the server for each player. The motd points to an htm file that contains graphs of the stats.",
//Set the id of the shootzone
"Type these commands into the chat window to perform the following actions.",
shootzoneScope.id = SessionState.allShootzones.len()
"---",
printl( "Shootzone ID: " + shootzoneScope.id )
"gb_block <playername> - Remove stats for this player and prevent them from recording stats in the future. (useful for Bots)",
"gb_allow <playername> - Allow a stats blocked player to record stats again.",
//Flow distance for the shootzone
"gb_update - Update the files on disk with the most recent version of the stats (this automatically happens at level end).",
shootzoneScope.flowDistance = GetFlowDistanceForPosition( shootzoneScope.origin )
"gb_dump - Dump all the stats to the console.",
shootzoneScope.flowPercent = GetFlowPercentForPosition( shootzoneScope.origin, true )
"gb_reset - Start fresh collecting stats.",
shootzoneScope.flowPercent1 = GetFlowPercentForPosition( shootzoneScope.origin, false )
"gb_backup - creates gunbrainstats.bak with the current data",
printl( "Flow Distance: " + shootzoneScope.flowDistance )
"gb_restore - loads data from gunbrainstats.bak",
printl( "Flow Percent: " + shootzoneScope.flowPercent )
"gb_bot_block - Block all bots from recording stats",
 
]
//Add the shootzone to the list of all the shootzones
SessionState.allShootzones.append( entity )
}
 
 
// HUD setup/control - our HUD is pretty simple in dash
function SetupModeHUD( )
{
HUDSetLayout( ShootzonesHUD )
 
HUDPlace( HUD_LEFT_TOP, 0.01, 0.06, 0.2, 0.04 )
HUDPlace( HUD_LEFT_BOT, 0.01, 0.11, 0.2, 0.04 )
HUDPlace( HUD_RIGHT_TOP, 0.01, 0.16, 0.2, 0.04 )
HUDPlace( HUD_RIGHT_BOT, 0.01, 0.21, 0.2, 0.04 )
}
 
 
function FindShootzoneClosestToPlayers()
{
 
}


function InterceptChat( str, srcEnt )
function EnableShootzones()
{
{
if ( str.find("gb_help") != null )
//Cycle through the shootzones and keep 3 enabled at all times
if ( SessionState.allShootzones.len() < 3 )
{
{
foreach( idx, Line in GB_HELP_TEXT )
foreach ( shootzone in SessionState.allShootzones )
{
{
Say( null, Line, true )
shootzone.GetScriptScope().EnableShootzone()
SessionState.enabledShootzones.append( shootzone )
 
SessionState.lastEnabledShootzoneID = shootzone.GetScriptScope().id
}
}
}
}
else if( srcEnt != null )
else
{
{
if ( str.find("gb_block ") )
local index = SessionState.lastEnabledShootzoneID + 1
while ( SessionState.enabledShootzones.len() < 3 )
{
{
local commandStart = str.find("gb_block ")
if ( index >= SessionState.allShootzones.len() )
local name = str.slice( commandStart + 9 )
{
name = name.slice(0,-1)
index = 0;
CommandAddBlockPlayer( name.toupper() )
}
SessionState.GBStatsDirty = true
 
//Enable the next available shootzone in the list
local currentShootzone = SessionState.allShootzones[index]
if ( currentShootzone.GetScriptScope().isEnabled == false )
{
currentShootzone.GetScriptScope().EnableShootzone()
SessionState.enabledShootzones.append( currentShootzone )
 
SessionState.lastEnabledShootzoneID = currentShootzone.GetScriptScope().id
}
 
index++
}
}
else if ( str.find("gb_allow ") )
{
local commandStart = str.find("gb_allow ")
local name = str.slice( commandStart + 9 )
name = name.slice(0,-1)
CommandRemoveBlockPlayer( name.toupper() )
}
else if ( str.find("gb_update") )
{
SessionState.GBStatsDirty = true
}
else if ( str.find("gb_dump") )
{
CommandDumpData()
}
else if ( str.find("gb_reset") )
{
CommandResetData()
SessionState.GBStatsDirty = true
}
else if ( str.find("gb_backup") )
{
CommandBackupData()
}
else if ( str.find("gb_restore") )
{
CommandRestoreData()
SessionState.GBStatsDirty = true
}
else if ( str.find("gb_bot_block") )
{
CommandAddBlockPlayer( "ELLIS" )
CommandAddBlockPlayer( "COACH" )
CommandAddBlockPlayer( "NICK" )
CommandAddBlockPlayer( "ROCHELLE" )
CommandAddBlockPlayer( "FRANCIS" )
CommandAddBlockPlayer( "LOUIS" )
CommandAddBlockPlayer( "ZOEY" )
CommandAddBlockPlayer( "BILL" )
}
}
}
}
}


function IsPlayerBlocked( playerName )
 
function RecomputePlayersInShootzones()
{
{
foreach( index, name in GunStatsTable.Blocked )
// if ( SessionState.FirstTime )
{
// {
if( name == playerName )
// foreach ( shootzone in SessionState.allShootzones )
{
// {
return true
// local shootzoneScope = shootzone.GetScriptScope()
}
// DebugDrawText( shootzoneScope.origin, "Distance: " + shootzoneScope.flowDistance + "\nPercent: " + shootzoneScope.flowPercent + "%", false, 100000 )
}
// }
return false
//
}
// SessionState.FirstTime = false;
// }


function CommandAddBlockPlayer( playerName )
//Keep track of players currently in a shootzone
{
local prevShootzonePlayers = clone( SessionState.playersInShootzone )
Say( null, "Blocking stats for " + playerName, true )


if ( GunStatsTable.Blocked.len() >= 64 )
//Recompute which players are in an active shootzone
return
SessionState.playersInShootzone.clear()


if ( IsPlayerBlocked( playerName ) )
foreach ( playerEnt in SessionState.allPlayers )
{
{
return
foreach ( shootzone in SessionState.enabledShootzones )
{
local shootzoneScope = shootzone.GetScriptScope()
if ( shootzoneScope.isActive )
{
local playerToShootzone = playerEnt.GetOrigin() - shootzoneScope.origin
local playerDistance = playerToShootzone.Length()
 
if ( playerDistance < 150 )
{
playerEnt.GetScriptScope().currentShootzone = shootzoneScope.id
SessionState.playersInShootzone.append( playerEnt )
break;
}
}
}
}
}
GunStatsTable.Blocked.push( playerName )


foreach( index, Player in GunStatsTable.Players )
//foreach ( shootzone in SessionState.allShootzones )
//{
//local shootzoneScope = shootzone.GetScriptScope()
//local vecto = SessionState.allPlayers[0].GetOrigin() - shootzoneScope.origin
//local dist = vecto.Length()
//
//if ( dist < 500 )
//{
//DebugDrawText( shootzoneScope.origin, "Distance: " + shootzoneScope.flowDistance + "\nPercent: " + shootzoneScope.flowPercent + "%\nPercent2: " + shootzoneScope.flowPercent1, false, 0.15 )
//
//DebugDrawLine( shootzoneScope.origin, SessionState.allPlayers[0].GetOrigin(), 255, 0, 0, false, 0.2 )
//}
//}
//
foreach ( index, playerEnt in SessionState.allPlayers )
{
{
if( playerName == Player.name )
//This player just entered a shootzone
if ( prevShootzonePlayers.find( playerEnt ) == null &&
SessionState.playersInShootzone.find( playerEnt ) != null )
{
{
Say( null, "Removing stats for " + playerName, true )
//Start playing the sound for players that just entered the shootzone
GunStatsTable.Players.remove(index)
printl( playerEnt.GetPlayerName() + " entered shootzone " + playerEnt.GetScriptScope().currentShootzone )
return
EmitSoundOnClient( "c2m4.BadMan1", playerEnt )
}
 
//This player just exited a shootzone
if ( prevShootzonePlayers.find( playerEnt ) != null &&
SessionState.playersInShootzone.find( playerEnt ) == null )
{
//Stop playing the sound for players that just exited the shootzone
printl( playerEnt.GetPlayerName() +" exited shootzone " + playerEnt.GetScriptScope().currentShootzone )
 
playerEnt.GetScriptScope().currentShootzone = -1
StopSoundOn( "c2m4.BadMan1", playerEnt )
}
 
local inOrOut = "OUT"
foreach ( shootzonePlayer in SessionState.playersInShootzone )
{
if ( shootzonePlayer.GetPlayerName() == playerEnt.GetPlayerName() )
{
inOrOut = "IN"
break;
}
}
}
//Set the player HUD flags
local hudField = "player" + ( index + 1 )
ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": " + inOrOut
ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
}
}
}
}


function CommandRemoveBlockPlayer( playerName )
 
function ShootzoneTimedOut( shootzoneID )
{
{
Say( null, "Removing block for " + playerName, true )
foreach ( index, shootzone in SessionState.enabledShootzones )
foreach( index, name in GunStatsTable.Blocked )
{
{
if( name == playerName )
if ( shootzone.GetScriptScope().id == shootzoneID )
{
{
GunStatsTable.Blocked.remove(index)
SessionState.enabledShootzones.remove( index )
return
break
}
}
}
}
}


function CommandUpdateData()
EnableShootzones()
{
CommitDamageData()
}
}


function CommandDumpData()
{
printl( TableToString( GunStatsTable ) )
}


function CommandResetData()
function AllowTakeDamage( damageTable )
{
{
GunStatsTable.clear()
foreach ( playerEnt in SessionState.allPlayers )
VerifyStatsTableStorage()
{
printl( TableToString( GunStatsTable ) )
//If the attacker is a player
}
if ( playerEnt == damageTable.Attacker )
 
{
function CommandBackupData()
//If the attacking player is in a shootzone then do damage
{
foreach ( shootzonePlayer in SessionState.playersInShootzone )
Say( null, "Backing up data", true )
{
StringToFile( "GunBrainData.bak" , TableToString( GunStatsTable ) )
if ( shootzonePlayer == damageTable.Attacker )
{
//printl( "Damage done by shootzone player: " + shootzonePlayer.GetPlayerName() )
return true
}
}
 
//Attacking player is not in a shootzone
//printl( "Attacking player is not in shootzone: " + playerEnt.GetPlayerName() )
return false
}
}
 
//Attacker is not a player
return true
}
}
</source>}}
===GunBrain===
{{ExpandBox|<source lang=js>
//=========================================================
//=========================================================
GunStatsTable <- null
TARGET_INVALID <- -1
TARGET_SURVIVOR <- 9


function CommandRestoreData()
MAX_TRACK_PLAYERS <- 10 // note - to change this we would need to update the server index to generate our graphs. There are only 10 graph servers so you can't go past the index 9.
 
// we need a barebones HUD for our ticker that shows up at the begining of the game
function SetupModeHUD( )
{
{
GunStatsTable.clear()
ModeHUD <-
 
local saved_data = FileToString( "GunBrainData.bak" )
if (saved_data != null)
{
{
local compileDataFunc = compilestring( "local temp_table = " + saved_data + " return temp_table" )
Fields =
GunStatsTable = compileDataFunc()
{
}
}
else
{
GunStatsTable <- {}
}
}
Ticker_AddToHud( ModeHUD, "" )


VerifyStatsTableStorage()
// load the ModeHUD table
HUDSetLayout( ModeHUD )
}
}


/////////////////////////////////////////////////
/////////////////////////////////////////////////
// Startup
// Mod Command interface
/////////////////////////////////////////////////
/////////////////////////////////////////////////


function OnGameplayStart()
GB_HELP_TEXT <-
{
[
printl("Running GunBrain the enhanced gun stats mod")
"About GunBrain - Damage stats are stored on the server for each player. The motd points to an htm file that contains graphs of the stats.",
}
"Type these commands into the chat window to perform the following actions.",
"---",
"gb_block <playername> - Remove stats for this player and prevent them from recording stats in the future. (useful for Bots)",
"gb_allow <playername> - Allow a stats blocked player to record stats again.",
"gb_update - Update the files on disk with the most recent version of the stats (this automatically happens at level end).",
"gb_dump - Dump all the stats to the console.",
"gb_reset - Start fresh collecting stats.",
"gb_backup - creates gunbrainstats.bak with the current data",
"gb_restore - loads data from gunbrainstats.bak",
"gb_bot_block - Block all bots from recording stats",
]


function OnActivate()
function InterceptChat( str, srcEnt )
{
{
printl( " ** On Activate" )
if ( str.find("gb_help") != null )
 
SessionState.GBStatsDirty <- false
ScriptedMode_AddUpdate( StatsUpdate )
SessionState.GBStatsTick <- 0
 
Ticker_SetBlink( true )
Ticker_NewStr( "Welcome to GunBrain! Chat <gb_help> or open the MotD for available commands", 15 )
PrepareStats()
 
CommitDamageData()
}
 
function PrepareStats()
{
local saved_data = FileToString( "GunBrainData" )
if (saved_data != null)
{
{
local compileDataFunc = compilestring( "local temp_table = " + saved_data + " return temp_table" )
foreach( idx, Line in GB_HELP_TEXT )
GunStatsTable = compileDataFunc()
{
Say( null, Line, true )
}
}
}
else
else if( srcEnt != null )
{
{
GunStatsTable <- {}
if ( str.find("gb_block ") )
}
{
 
local commandStart = str.find("gb_block ")
VerifyStatsTableStorage()
local name = str.slice( commandStart + 9 )
 
name = name.slice(0,-1)
foreach( index, Player in GunStatsTable.Players )
CommandAddBlockPlayer( name.toupper() )
{
SessionState.GBStatsDirty = true
printl( "   " + index + " = " + Player.name );
}
}
else if ( str.find("gb_allow ") )
}
{
 
local commandStart = str.find("gb_allow ")
/////////////////////////////////////////////////
local name = str.slice( commandStart + 9 )
// Data Management
name = name.slice(0,-1)
/////////////////////////////////////////////////
CommandRemoveBlockPlayer( name.toupper() )
}
else if ( str.find("gb_update") )
{
SessionState.GBStatsDirty = true
}
else if ( str.find("gb_dump") )
{
CommandDumpData()
}
else if ( str.find("gb_reset") )
{
CommandResetData()
SessionState.GBStatsDirty = true
}
else if ( str.find("gb_backup") )
{
CommandBackupData()
}
else if ( str.find("gb_restore") )
{
CommandRestoreData()
SessionState.GBStatsDirty = true
}
else if ( str.find("gb_bot_block") )
{
CommandAddBlockPlayer( "ELLIS" )
CommandAddBlockPlayer( "COACH" )
CommandAddBlockPlayer( "NICK" )
CommandAddBlockPlayer( "ROCHELLE" )
CommandAddBlockPlayer( "FRANCIS" )
CommandAddBlockPlayer( "LOUIS" )
CommandAddBlockPlayer( "ZOEY" )
CommandAddBlockPlayer( "BILL" )
}
}
}


function StatsUpdate()
function IsPlayerBlocked( playerName )
{
{
SessionState.GBStatsTick++
foreach( index, name in GunStatsTable.Blocked )
 
if( SessionState.GBStatsDirty || SessionState.GBStatsTick == 30 )
{
{
g_ModeScript.CommandUpdateData()
if( name == playerName )
 
{
SessionState.GBStatsDirty = false
return true
SessionState.GBStatsTick = 0
}
 
Say( null, "Updating Stats", true )
}
}
return false
}
}


function VerifyStatsTableStorage()
function CommandAddBlockPlayer( playerName )
{
{
if ( !( "Players" in GunStatsTable ) )
Say( null, "Blocking stats for " + playerName, true )
 
if ( GunStatsTable.Blocked.len() >= 64 )
return
 
if ( IsPlayerBlocked( playerName ) )
{
{
printl("Adding new players list")
return
GunStatsTable.Players <- []
}
}
if ( !( "Blocked" in GunStatsTable ) )
GunStatsTable.Blocked.push( playerName )
 
foreach( index, Player in GunStatsTable.Players )
{
{
GunStatsTable.Blocked <- []
if( playerName == Player.name )
{
Say( null, "Removing stats for " + playerName, true )
GunStatsTable.Players.remove(index)
return
}
}
}
}
}


function CommitDamageData()
function CommandRemoveBlockPlayer( playerName )
{
{
StringToFile( "GunBrainData" , TableToString( GunStatsTable ) )
Say( null, "Removing block for " + playerName, true )
 
foreach( index, name in GunStatsTable.Blocked )
local damageImgString = "<p>"
foreach( idx, Line in GB_HELP_TEXT )
{
{
damageImgString = damageImgString + Line + "<br>"
if( name == playerName )
{
GunStatsTable.Blocked.remove(index)
return
}
}
}
damageImgString = damageImgString + "</p>"
damageImgString = damageImgString + CreateOutgoingDamageChartString() + CreateIncomingDamageChartString()
foreach( index, Player in GunStatsTable.Players )
{
damageImgString = damageImgString + CreatePlayerAccuracyChartString( index )
}
foreach( index, Player in GunStatsTable.Players )
{
damageImgString = damageImgString + CreatePlayerDamageChartString( index )
}
StringToFile( "gunbrainstats.htm" , damageImgString )
SendToServerConsole( "motdfile ems\\gunbrainstats.htm" )
ReloadMOTD()
}
}


function OnShutdown()
function CommandUpdateData()
{
{
CommitDamageData()
CommitDamageData()
}
}


/////////////////////////////////////////////////
function CommandDumpData()
// Damage Event handling
{
/////////////////////////////////////////////////
printl( TableToString( GunStatsTable ) )
}


function AllowTakeDamage( damageTable )
function CommandResetData()
{
{
// check to see if this wasn't a weapon
GunStatsTable.clear()
if( !( "GetClassname" in damageTable.Weapon ) )
VerifyStatsTableStorage()
{
printl( TableToString( GunStatsTable ) )
// don't store shove damage
}
}
else
{
if( damageTable.Attacker.GetClassname() == "player" && damageTable.Attacker.IsSurvivor() )
{
local target_type = TARGET_INVALID


if ( damageTable.Victim.GetClassname() == "infected" )
function CommandBackupData()
{
{
target_type = ZOMBIE_NORMAL
Say( null, "Backing up data", true )
}
StringToFile( "GunBrainData.bak" , TableToString( GunStatsTable ) )
else if ( damageTable.Victim.GetClassname() == "player" )
{
if( damageTable.Victim.IsSurvivor() )
{
target_type = TARGET_SURVIVOR
}
else
{
target_type = damageTable.Victim.GetZombieType()
}
}
else if ( damageTable.Victim.GetClassname() == "witch" )
{
target_type = ZOMBIE_WITCH
}
// remove WEAPON_ from the name
local weapName = damageTable.Weapon.GetClassname()
weapName = weapName.slice( 7 )
 
AddHit( damageTable.Attacker.GetPlayerName().toupper(), weapName.toupper(), damageTable.Victim.GetHealth() > 0 ? damageTable.Victim.GetHealth() : 0, damageTable.DamageDone, damageTable.DamageType & DMG_HEADSHOT, target_type )
}
else
{
printl("bad target")
}
}
return true
}
}


function OnGameEvent_weapon_fire( params )
function CommandRestoreData()
{
{
if ( "count" in params )
GunStatsTable.clear()
 
local saved_data = FileToString( "GunBrainData.bak" )
if (saved_data != null)
{
local compileDataFunc = compilestring( "local temp_table = " + saved_data + " return temp_table" )
GunStatsTable = compileDataFunc()
}
else
{
{
local attacker = GetPlayerFromUserID( params.userid )
GunStatsTable <- {}
local weapon = params.weapon
}
local shots = params.count


AddShots( attacker.GetPlayerName().toupper(), weapon.toupper(), shots )
VerifyStatsTableStorage()
}
}
}


function OnGameEvent_player_hurt( params )
/////////////////////////////////////////////////
// Startup
/////////////////////////////////////////////////
 
function OnGameplayStart()
{
printl("Running GunBrain the enhanced gun stats mod")
}
 
function OnActivate()
{
printl( " ** On Activate" )
 
SessionState.GBStatsDirty <- false
ScriptedMode_AddUpdate( StatsUpdate )
SessionState.GBStatsTick <- 0
 
Ticker_SetBlink( true )
Ticker_NewStr( "Welcome to GunBrain! Chat <gb_help> or open the MotD for available commands", 15 )
PrepareStats()
 
CommitDamageData()
}
 
function PrepareStats()
{
{
local victim = GetPlayerFromUserID( params.userid )
local saved_data = FileToString( "GunBrainData" )
if ( !victim.IsSurvivor() )
if (saved_data != null)
{
local compileDataFunc = compilestring( "local temp_table = " + saved_data + " return temp_table" )
GunStatsTable = compileDataFunc()
}
else
{
{
return
GunStatsTable <- {}
}
}


if( IsPlayerBlocked( victim.GetPlayerName().toupper() ) )
VerifyStatsTableStorage()
return


if( "attackerentid" in params )
foreach( index, Player in GunStatsTable.Players )
{
{
local attacker = EntIndexToHScript( params.attackerentid )
printl( "    " + index + " = " + Player.name );
}
}
 
/////////////////////////////////////////////////
// Data Management
/////////////////////////////////////////////////


local target_type = TARGET_INVALID
function StatsUpdate()
{
SessionState.GBStatsTick++


if( params.attackerentid == 0 && params.attacker == 0 )
if( SessionState.GBStatsDirty || SessionState.GBStatsTick == 30 )
{
{
// looks like an invalid attacker
g_ModeScript.CommandUpdateData()
}
 
else if ( attacker.GetClassname() == "infected" )
SessionState.GBStatsDirty = false
{
SessionState.GBStatsTick = 0
target_type = ZOMBIE_NORMAL
}
else if ( attacker.GetClassname() == "player" )
{
if( attacker.IsSurvivor() )
{
target_type = TARGET_SURVIVOR
}
else
{
target_type = attacker.GetZombieType()
}
}
else if ( attacker.GetClassname() == "witch" )
{
target_type = ZOMBIE_WITCH
}


local player = FindOrAddPlayerData( victim.GetPlayerName().toupper() )
Say( null, "Updating Stats", true )
if ( player != null )
{
AddIncomingDamage( player, params.health, params.dmg_health, target_type )
}
}
}
}
}


/////////////////////////////////////////////////
function VerifyStatsTableStorage()
// Stat recording.
/////////////////////////////////////////////////
 
function AddHit( playerName, weaponName, victimHealth, damage, headShot, target_type )
{
{
if( IsPlayerBlocked( playerName ) )
if ( !( "Players" in GunStatsTable ) )
return
{
printl("Adding new players list")
// printl( "playerName " + playerName + " weaponName " + weaponName + " victimHealth " + victimHealth + " damage " + damage + " headShot " + headShot )
GunStatsTable.Players <- []
 
}
local Weapon = FindOrAddWeaponData( playerName, weaponName )
if ( !( "Blocked" in GunStatsTable ) )
 
if ( Weapon != null )
{
{
if( target_type != TARGET_INVALID )
GunStatsTable.Blocked <- []
{
AddWeaponHit( Weapon, victimHealth, damage, headShot, target_type )
}
 
local player = FindOrAddPlayerData( playerName )
if ( player != null )
{
AddTypeDamage( player, victimHealth, damage, target_type )
}
}
}
}
}


function AddShots( playerName, weaponName, shots )
function CommitDamageData()
{
{
if( IsPlayerBlocked( playerName ) )
StringToFile( "GunBrainData" , TableToString( GunStatsTable ) )
return


// printl( "playerName " + playerName + " weaponName " + weaponName + " shots " + shots )
local damageImgString = "<p>"
 
local Weapon = FindOrAddWeaponData( playerName, weaponName )
foreach( idx, Line in GB_HELP_TEXT )
if ( Weapon  != null )
{
{
Weapon.shots += shots
damageImgString = damageImgString + Line + "<br>"
}
}
}


function CreateNewPlayerTable( playerName )
damageImgString = damageImgString + "</p>"
{
local player = { name=playerName, WeaponData = [], TypeDamage = [], DamageTaken = [] }
damageImgString = damageImgString + CreateOutgoingDamageChartString() + CreateIncomingDamageChartString()
player.TypeDamage.resize(10,0)
foreach( index, Player in GunStatsTable.Players )
player.DamageTaken.resize(10,0)
{
return player
damageImgString = damageImgString + CreatePlayerAccuracyChartString( index )
}
foreach( index, Player in GunStatsTable.Players )
{
damageImgString = damageImgString + CreatePlayerDamageChartString( index )
}
StringToFile( "gunbrainstats.htm" , damageImgString )
SendToServerConsole( "motdfile ems\\gunbrainstats.htm" )
ReloadMOTD()
}
}


function CreateNewWeaponTable( weaponName )
function OnShutdown()
{
{
local weapon = { name=weaponName, shots = 0, hits = 0, effectiveDamage = 0, overKill = 0, deadDamage = 0, headShots = 0, FFHits = 0 }
CommitDamageData()
return weapon
}
}


function FindOrAddPlayerData( playerName )
/////////////////////////////////////////////////
{
// Damage Event handling
local playerIdx = -1
/////////////////////////////////////////////////


foreach( indexP, Player in GunStatsTable.Players )
function AllowTakeDamage( damageTable )
{
// check to see if this wasn't a weapon
if( !( "GetClassname" in damageTable.Weapon ) )
{
{
if( playerName == Player.name )
// don't store shove damage
{
}
playerIdx = indexP
else
break
}
}
 
if ( playerIdx == -1 )
{
{
if ( GunStatsTable.Players.len() >= MAX_TRACK_PLAYERS )
if( damageTable.Attacker.GetClassname() == "player" && damageTable.Attacker.IsSurvivor() )
{
{
return null
local target_type = TARGET_INVALID
}
else
{
local player = CreateNewPlayerTable( playerName )
playerIdx = GunStatsTable.Players.len()
GunStatsTable.Players.push( player )
}
}


return GunStatsTable.Players[playerIdx]
if ( damageTable.Victim.GetClassname() == "infected" )
}
{
 
target_type = ZOMBIE_NORMAL
function FindOrAddWeaponData( playerName, weaponName )
}
{
else if ( damageTable.Victim.GetClassname() == "player" )
local foundPlayer = null
{
local bFoundWeapon = false
if( damageTable.Victim.IsSurvivor() )
 
{
local weapIdx = -1
target_type = TARGET_SURVIVOR
local playerIdx = -1
}
 
else
foreach( indexP, Player in GunStatsTable.Players )
{
if( playerName == Player.name )
{
playerIdx = indexP
foreach( indexW, Weapon in Player.WeaponData )
{
if( weaponName == Weapon.name )
{
{
return Weapon
target_type = damageTable.Victim.GetZombieType()
}
}
}
}
break
else if ( damageTable.Victim.GetClassname() == "witch" )
}
{
}
target_type = ZOMBIE_WITCH
}
// remove WEAPON_ from the name
local weapName = damageTable.Weapon.GetClassname()
weapName = weapName.slice( 7 )


if ( playerIdx == -1 )
AddHit( damageTable.Attacker.GetPlayerName().toupper(), weapName.toupper(), damageTable.Victim.GetHealth() > 0 ? damageTable.Victim.GetHealth() : 0, damageTable.DamageDone, damageTable.DamageType & DMG_HEADSHOT, target_type )
{
if ( GunStatsTable.Players.len() >= MAX_TRACK_PLAYERS )
{
return null
}
}
else
else
{
{
local player = CreateNewPlayerTable( playerName )
printl("bad target")
playerIdx = GunStatsTable.Players.len()
GunStatsTable.Players.push( player )
}
}
}
}
 
return true
local weapon = CreateNewWeaponTable( weaponName )
GunStatsTable.Players[playerIdx].WeaponData.push( weapon )
return weapon
}
}


function AddIncomingDamage( player, victimHealth, damage, target_type )
function OnGameEvent_weapon_fire( params )
{
{
if ( target_type == TARGET_INVALID )
if ( "count" in params )
return
 
local effectiveDamage = damage
if ( damage > victimHealth )
{
{
effectiveDamage = victimHealth
local attacker = GetPlayerFromUserID( params.userid )
}
local weapon = params.weapon
local shots = params.count


player.DamageTaken[target_type] += effectiveDamage
AddShots( attacker.GetPlayerName().toupper(), weapon.toupper(), shots )
}
 
function AddTypeDamage( player, victimHealth, damage, target_type )
{
if ( target_type == TARGET_INVALID )
return
 
local effectiveDamage = damage
if ( damage > victimHealth )
{
effectiveDamage = victimHealth
}
}
player.TypeDamage[target_type] += effectiveDamage
}
}


function AddWeaponHit( weaponData, victimHealth, damage, headShot, target_type )
function OnGameEvent_player_hurt( params )
{
{
if ( target_type == TARGET_SURVIVOR )
local victim = GetPlayerFromUserID( params.userid )
if ( !victim.IsSurvivor() )
{
{
weaponData.FFHits++
return
return
}
}


if ( weaponData.name == "MELEE" || weaponData.name == "CHAINSAW" )
if( IsPlayerBlocked( victim.GetPlayerName().toupper() ) )
{
return
weaponData.shots++
}


// printl( "victimHealth " + victimHealth + " damage " + damage + " headShot " + headShot + " target_type " + target_type )
if( "attackerentid" in params )
 
if ( damage < 0 )
damage = 0
local effectiveDamage = damage
local overKillDamage = 0
local deadDamage = 0
 
if ( victimHealth <= 0 )
{
{
effectiveDamage = 0
local attacker = EntIndexToHScript( params.attackerentid )
overKillDamage = 0
deadDamage = damage
}
if ( damage > victimHealth )
{
effectiveDamage = victimHealth
overKillDamage = damage - victimHealth
}


weaponData.hits++
local target_type = TARGET_INVALID
weaponData.effectiveDamage += effectiveDamage
weaponData.overKill += overKillDamage
weaponData.deadDamage += deadDamage


if ( headShot > 0 )
if( params.attackerentid == 0 && params.attacker == 0 )
{
{
weaponData.headShots++
// looks like an invalid attacker
}
else if ( attacker.GetClassname() == "infected" )
{
target_type = ZOMBIE_NORMAL
}
else if ( attacker.GetClassname() == "player" )
{
if( attacker.IsSurvivor() )
{
target_type = TARGET_SURVIVOR
}
else
{
target_type = attacker.GetZombieType()
}
}
else if ( attacker.GetClassname() == "witch" )
{
target_type = ZOMBIE_WITCH
}
 
local player = FindOrAddPlayerData( victim.GetPlayerName().toupper() )
if ( player != null )
{
AddIncomingDamage( player, params.health, params.dmg_health, target_type )
}
}
}
// printl( "hits " + weaponData.hits + "  effectiveDamage " + weaponData.effectiveDamage + "  overKill " + weaponData.overKill + "  headShots " + weaponData.headShots + "  shots " + weaponData.shots )
}
}


/////////////////////////////////////////////////
/////////////////////////////////////////////////
// Chart URL building
// Stat recording.
/////////////////////////////////////////////////
/////////////////////////////////////////////////


function ComputeChartHeight( desiredHeight )
function AddHit( playerName, weaponName, victimHealth, damage, headShot, target_type )
{
{
local height = desiredHeight
if( IsPlayerBlocked( playerName ) )
return
// printl( "playerName " + playerName + " weaponName " + weaponName + " victimHealth " + victimHealth + " damage " + damage + " headShot " + headShot )
 
local Weapon = FindOrAddWeaponData( playerName, weaponName )


if( height < 220 )
if ( Weapon != null )
{
{
height = 220
if( target_type != TARGET_INVALID )
}
{
if( height > 500 )
AddWeaponHit( Weapon, victimHealth, damage, headShot, target_type )
{
}
height = 500
 
local player = FindOrAddPlayerData( playerName )
if ( player != null )  
{
AddTypeDamage( player, victimHealth, damage, target_type )
}
}
}
return height
}
}
function CreateOutgoingDamageChartString()
 
function AddShots( playerName, weaponName, shots )
{
{
local chart_url = "<img src="
if( IsPlayerBlocked( playerName ) )
chart_url = chart_url + "\""
return
chart_url = chart_url + "http://"
chart_url = chart_url + 8
chart_url = chart_url + ".chart.apis.google.com/chart"


local target_names = ["Common","Smoker","Boomer","Hunter","Spitter","Jockey","Charger","Witch","Tank","Survivor"]
// printl( "playerName " + playerName + " weaponName " + weaponName + " shots " + shots )
 
local Weapon = FindOrAddWeaponData( playerName, weaponName )
if ( Weapon  != null )
{
Weapon.shots += shots
}
}
 
function CreateNewPlayerTable( playerName )
{
local player = { name=playerName, WeaponData = [], TypeDamage = [], DamageTaken = [] }
player.TypeDamage.resize(10,0)
player.DamageTaken.resize(10,0)
return player
}
 
function CreateNewWeaponTable( weaponName )
{
local weapon = { name=weaponName, shots = 0, hits = 0, effectiveDamage = 0, overKill = 0, deadDamage = 0, headShots = 0, FFHits = 0 }
return weapon
}
 
function FindOrAddPlayerData( playerName )
{
local playerIdx = -1


foreach( indexP, Player in GunStatsTable.Players )
{
{
local line = "?chxl=1:"
if( playerName == Player.name )
foreach( index, player in GunStatsTable.Players )
{
{
// player names are backwards so go in revers order
playerIdx = indexP
line = line + "|" + GunStatsTable.Players[ (GunStatsTable.Players.len() - 1) - index ].name
break
}
}
chart_url = chart_url + line
}
}
chart_url = chart_url + "&chxr=0,0,100"
chart_url = chart_url + "&chxt=x,y"
chart_url = chart_url + "&chbh=a"
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 30*( 1 + GunStatsTable.Players.len() ) ) ) )
chart_url = chart_url + "&cht=bhs"
chart_url = chart_url + "&chco=D1C304,43D948,B6D943,0E8F17,6CA0E0,04C7D1,0E04D1,D104B2,8A04D1,DE2E12"
chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000"


local maxDamage = 100
if ( playerIdx == -1 )
foreach( indexP, player in GunStatsTable.Players )
{
{
local playerDamageTotal = 0
if ( GunStatsTable.Players.len() >= MAX_TRACK_PLAYERS )
foreach( index_target, damage in player.TypeDamage )
{
{
playerDamageTotal += damage
return null
}
}
if( playerDamageTotal > maxDamage )
else
{
{
maxDamage = playerDamageTotal
local player = CreateNewPlayerTable( playerName )
playerIdx = GunStatsTable.Players.len()
GunStatsTable.Players.push( player )
}
}
}
}


local damageScalar = 1000.0/maxDamage
return GunStatsTable.Players[playerIdx]
}


{
function FindOrAddWeaponData( playerName, weaponName )
local line = "&chd=t"
{
foreach( index_target, target in target_names )
local foundPlayer = null
{
local bFoundWeapon = false
if( index_target == 0 ) { line = line + ":" } else { line = line + "|" }


foreach( indexP, player in GunStatsTable.Players )
local weapIdx = -1
{
local playerIdx = -1
if( indexP == 0 ) { line = line + "" } else { line = line + "," }


local num = damageScalar*player.TypeDamage[index_target]
foreach( indexP, Player in GunStatsTable.Players )
line = line + "" + floor( num )
{
if( playerName == Player.name )
{
playerIdx = indexP
foreach( indexW, Weapon in Player.WeaponData )
{
if( weaponName == Weapon.name )
{
return Weapon
}
}
}
break
}
}
chart_url = chart_url + line
}
}


if ( playerIdx == -1 )
{
{
local line = "&chdl="
if ( GunStatsTable.Players.len() >= MAX_TRACK_PLAYERS )
foreach( index, name in target_names )
{
return null
}
else
{
{
if( index != 0 ) { line = line + "|" }
local player = CreateNewPlayerTable( playerName )
line = line + name
playerIdx = GunStatsTable.Players.len()
GunStatsTable.Players.push( player )
}
}
chart_url = chart_url + line
}
}
chart_url = chart_url + "&chtt=" + "Outgoing Player damage by Target"
chart_url = chart_url + "\"" + "\\><br><br><br><br>"


return chart_url
local weapon = CreateNewWeaponTable( weaponName )
GunStatsTable.Players[playerIdx].WeaponData.push( weapon )
return weapon
}
}


function CreateIncomingDamageChartString()
function AddIncomingDamage( player, victimHealth, damage, target_type )
{
{
local chart_url = "<img src="
if ( target_type == TARGET_INVALID )
chart_url = chart_url + "\""
return
chart_url = chart_url + "http://"
chart_url = chart_url + 9
chart_url = chart_url + ".chart.apis.google.com/chart"
 
local target_names = ["Common","Smoker","Boomer","Hunter","Spitter","Jockey","Charger","Witch","Tank","Survivor"]


local effectiveDamage = damage
if ( damage > victimHealth )
{
{
local line = "?chxl=1:"
effectiveDamage = victimHealth
foreach( index, player in GunStatsTable.Players )
}
{
 
// player names are backwards so go in revers order
player.DamageTaken[target_type] += effectiveDamage
line = line + "|" + GunStatsTable.Players[ (GunStatsTable.Players.len() - 1) - index ].name
}
}
chart_url = chart_url + line
}


chart_url = chart_url + "&chxr=0,0,100"
function AddTypeDamage( player, victimHealth, damage, target_type )
chart_url = chart_url + "&chxt=x,y"
{
chart_url = chart_url + "&chbh=a"
if ( target_type == TARGET_INVALID )
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 30*( 1 + GunStatsTable.Players.len() ) ) ) )
return
chart_url = chart_url + "&cht=bhs"
chart_url = chart_url + "&chco=D1C304,43D948,B6D943,0E8F17,6CA0E0,04C7D1,0E04D1,D104B2,8A04D1,DE2E12"
chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000"


local maxDamage = 100
local effectiveDamage = damage
foreach( indexP, player in GunStatsTable.Players )
if ( damage > victimHealth )
{
{
local playerDamageTotal = 0
effectiveDamage = victimHealth
foreach( index_target, damage in player.DamageTaken )
{
playerDamageTotal += damage
}
if( playerDamageTotal > maxDamage )
{
maxDamage = playerDamageTotal
}
}
}


local damageScalar = 1000.0/maxDamage
player.TypeDamage[target_type] += effectiveDamage
}


{
function AddWeaponHit( weaponData, victimHealth, damage, headShot, target_type )
local line = "&chd=t"
{
foreach( index_target, target in target_names )
if ( target_type == TARGET_SURVIVOR )
{
{
if( index_target == 0 ) { line = line + ":" } else { line = line + "|" }
weaponData.FFHits++
return
}


foreach( indexP, player in GunStatsTable.Players )
if ( weaponData.name == "MELEE" || weaponData.name == "CHAINSAW" )
{
{
if( indexP == 0 ) { line = line + "" } else { line = line + "," }
weaponData.shots++
 
local num = damageScalar*player.DamageTaken[index_target]
line = line + "" + floor( num )
}
}
chart_url = chart_url + line
}
}


{
// printl( "victimHealth " + victimHealth + " damage " + damage + " headShot " + headShot + " target_type " + target_type )
local line = "&chdl="
 
foreach( index, name in target_names )
if ( damage < 0 )
{
damage = 0
if( index != 0 ) { line = line + "|" }
line = line + name
local effectiveDamage = damage
}
local overKillDamage = 0
chart_url = chart_url + line
local deadDamage = 0
 
if ( victimHealth <= 0 )
{
effectiveDamage = 0
overKillDamage = 0
deadDamage = damage
}
if ( damage > victimHealth )
{
effectiveDamage = victimHealth
overKillDamage = damage - victimHealth
}
 
weaponData.hits++
weaponData.effectiveDamage += effectiveDamage
weaponData.overKill += overKillDamage
weaponData.deadDamage += deadDamage
 
if ( headShot > 0 )
{
weaponData.headShots++
}
}
chart_url = chart_url + "&chtt=" + "Incoming Player damage by Source"
chart_url = chart_url + "\"" + "\\><br><br><br><br>"


return chart_url
// printl( "hits " + weaponData.hits + "  effectiveDamage " + weaponData.effectiveDamage + "  overKill " + weaponData.overKill + "  headShots " + weaponData.headShots + "  shots " + weaponData.shots )
}
}


function CreatePlayerAccuracyChartString( player_index )
 
/////////////////////////////////////////////////
// Chart URL building
/////////////////////////////////////////////////
 
function ComputeChartHeight( desiredHeight )
{
local height = desiredHeight
 
if( height < 220 )
{
height = 220
}
if( height > 500 )
{
height = 500
}
return height
}
function CreateOutgoingDamageChartString()
{
{
local chart_url = "<img src="
local chart_url = "<img src="
chart_url = chart_url + "\""
chart_url = chart_url + "\""
chart_url = chart_url + "http://"
chart_url = chart_url + "http://"
chart_url = chart_url + player_index
chart_url = chart_url + 8
chart_url = chart_url + ".chart.apis.google.com/chart"
chart_url = chart_url + ".chart.apis.google.com/chart"


local Player = GunStatsTable.Players[player_index]
local target_names = ["Common","Smoker","Boomer","Hunter","Spitter","Jockey","Charger","Witch","Tank","Survivor"]
 
{
{
local line = "?chxl=1:"
local line = "?chxl=1:"
foreach( indexW, Weapon in Player.WeaponData )
foreach( index, player in GunStatsTable.Players )
{
{
// weapon names are listed backwards so store them such
// player names are backwards so go in revers order
line = line + "|" + Player.WeaponData[ (Player.WeaponData.len() - 1) - indexW ].name
line = line + "|" + GunStatsTable.Players[ (GunStatsTable.Players.len() - 1) - index ].name
}
}
chart_url = chart_url + line
chart_url = chart_url + line
}
}
 
chart_url = chart_url + "&chxr=0,0,100"
chart_url = chart_url + "&chxr=0,0,100"
chart_url = chart_url + "&chxt=x,y"
chart_url = chart_url + "&chxt=x,y"
chart_url = chart_url + "&chbh=a"
chart_url = chart_url + "&chbh=a"
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 36*( 1.6 + GunStatsTable.Players[player_index].WeaponData.len() ) )) )
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 30*( 1 + GunStatsTable.Players.len() ) ) ) )
chart_url = chart_url + "&cht=bhs"
chart_url = chart_url + "&cht=bhs"
chart_url = chart_url + "&chco=3D7930,A2C180,DCBA80,DC5030"
chart_url = chart_url + "&chco=D1C304,43D948,B6D943,0E8F17,6CA0E0,04C7D1,0E04D1,D104B2,8A04D1,DE2E12"
chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000"
chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000"


{
local maxDamage = 100
local line = "&chd=t"
foreach( indexP, player in GunStatsTable.Players )
foreach( indexW, Weapon in Player.WeaponData )
{
local playerDamageTotal = 0
foreach( index_target, damage in player.TypeDamage )
{
{
if( indexW == 0 ) { line = line + ":" } else { line = line + "," }
playerDamageTotal += damage
 
local num = ( Weapon.headShots / (0.001*(Weapon.shots + 1) ) )
line = line + "" + floor( num )
}
}
line = line + "|"
if( playerDamageTotal > maxDamage )
foreach( indexW, Weapon in Player.WeaponData )
{
{
if( indexW != 0 ) { line = line + "," }
maxDamage = playerDamageTotal
}
}
 
local damageScalar = 1000.0/maxDamage


local num = ( (Weapon.hits - Weapon.headShots) / (0.001*(Weapon.shots + 0.01) ) )
{
line = line + "" + floor( num )
local line = "&chd=t"
}
foreach( index_target, target in target_names )
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
{
if( indexW != 0 ) { line = line + "," }
if( index_target == 0 ) { line = line + ":" } else { line = line + "|" }


local num = ( ( Weapon.shots - ( Weapon.hits + Weapon.FFHits ) ) / (0.001*(Weapon.shots + 0.01) ) )
foreach( indexP, player in GunStatsTable.Players )
line = line + "" + floor( num )
{
}
if( indexP == 0 ) { line = line + "" } else { line = line + "," }
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
if( indexW != 0 ) { line = line + "," }


local num = ( Weapon.FFHits / (0.001*(Weapon.shots + 0.01) ) )
local num = damageScalar*player.TypeDamage[index_target]
line = line + "" + floor( num )
line = line + "" + floor( num )
}
}
}
chart_url = chart_url + line
chart_url = chart_url + line
}
}


chart_url = chart_url + "&chdl=Headshot|Non-Headshot Hit|Miss|Friendly Fire"
{
chart_url = chart_url + "&chtt=" + Player.name + " Shot Hit Type"
local line = "&chdl="
foreach( index, name in target_names )
{
if( index != 0 ) { line = line + "|" }
line = line + name
}
chart_url = chart_url + line
}
chart_url = chart_url + "&chtt=" + "Outgoing Player damage by Target"
chart_url = chart_url + "\"" + "\\><br><br><br><br>"
chart_url = chart_url + "\"" + "\\><br><br><br><br>"


Line 10,275: Line 10,866:
}
}


function CreatePlayerDamageChartString( player_index )
function CreateIncomingDamageChartString()
{
{
local chart_url = "<img src="
local chart_url = "<img src="
chart_url = chart_url + "\""
chart_url = chart_url + "\""
chart_url = chart_url + "http://"
chart_url = chart_url + "http://"
chart_url = chart_url + player_index
chart_url = chart_url + 9
chart_url = chart_url + ".chart.apis.google.com/chart"
chart_url = chart_url + ".chart.apis.google.com/chart"


local Player = GunStatsTable.Players[player_index]
local target_names = ["Common","Smoker","Boomer","Hunter","Spitter","Jockey","Charger","Witch","Tank","Survivor"]
 
{
{
local line = "?chxl=1:"
local line = "?chxl=1:"
foreach( indexW, Weapon in Player.WeaponData )
foreach( index, player in GunStatsTable.Players )
{
{
// weapon names are listed backwards so store them such
// player names are backwards so go in revers order
line = line + "|" + Player.WeaponData[ (Player.WeaponData.len() - 1) - indexW ].name
line = line + "|" + GunStatsTable.Players[ (GunStatsTable.Players.len() - 1) - index ].name
}
}
chart_url = chart_url + line
chart_url = chart_url + line
}
}
 
chart_url = chart_url + "&chxr=0,0,75"
chart_url = chart_url + "&chxr=0,0,100"
chart_url = chart_url + "&chxt=x,y"
chart_url = chart_url + "&chxt=x,y"
chart_url = chart_url + "&chbh=a"
chart_url = chart_url + "&chbh=a"
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 36*( 1.6 + GunStatsTable.Players[player_index].WeaponData.len() ) )) )
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 30*( 1 + GunStatsTable.Players.len() ) ) ) )
chart_url = chart_url + "&cht=bhs"
chart_url = chart_url + "&cht=bhs"
chart_url = chart_url + "&chco=1B84E0,43C5CC,A4B3B0"
chart_url = chart_url + "&chco=D1C304,43D948,B6D943,0E8F17,6CA0E0,04C7D1,0E04D1,D104B2,8A04D1,DE2E12"
chart_url = chart_url + "&chds=0,75,0,75,0,75"
chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000"
 
local maxDamage = 100
foreach( indexP, player in GunStatsTable.Players )
{
local playerDamageTotal = 0
foreach( index_target, damage in player.DamageTaken )
{
playerDamageTotal += damage
}
if( playerDamageTotal > maxDamage )
{
maxDamage = playerDamageTotal
}
}
 
local damageScalar = 1000.0/maxDamage


{
{
local line = "&chd=t"
local line = "&chd=t"
foreach( indexW, Weapon in Player.WeaponData )
foreach( index_target, target in target_names )
{
{
if( indexW == 0 )
if( index_target == 0 ) { line = line + ":" } else { line = line + "|" }
 
foreach( indexP, player in GunStatsTable.Players )
{
{
line = line + ":"
if( indexP == 0 ) { line = line + "" } else { line = line + "," }
}
 
else
local num = damageScalar*player.DamageTaken[index_target]
{
line = line + "" + floor( num )
line = line + ","
}
}
line = line + ( Weapon.effectiveDamage / (Weapon.shots + 1) )
}
}
line = line + "|"
chart_url = chart_url + line
foreach( indexW, Weapon in Player.WeaponData )
}
 
{
local line = "&chdl="
foreach( index, name in target_names )
{
{
if( indexW != 0 )
if( index != 0 ) { line = line + "|" }
{
line = line + name
line = line + ","
}
line = line + ( Weapon.overKill / (Weapon.shots + 1) )
}
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
if( indexW != 0 )
{
line = line + ","
}
line = line + ( Weapon.deadDamage / (Weapon.shots + 1) )
}
}
chart_url = chart_url + line
chart_url = chart_url + line
}
}
 
chart_url = chart_url + "&chtt=" + "Incoming Player damage by Source"
chart_url = chart_url + "&chdl=Effective+Damage|Overkill+Damage|Dead Target+Damage"
chart_url = chart_url + "&chtt=" + Player.name + " Damage+Breakdown(per pellet)"
chart_url = chart_url + "\"" + "\\><br><br><br><br>"
chart_url = chart_url + "\"" + "\\><br><br><br><br>"


Line 10,344: Line 10,942:
}
}


function CreatePlayerAccuracyChartString( player_index )
{
local chart_url = "<img src="
chart_url = chart_url + "\""
chart_url = chart_url + "http://"
chart_url = chart_url + player_index
chart_url = chart_url + ".chart.apis.google.com/chart"


/////////////////////////////////////////////////
local Player = GunStatsTable.Players[player_index]
// Table manipulation
/////////////////////////////////////////////////
 
// These two helper functions can call themselves and each other so if want either you would need both.
function TableToString( table ) 
{
local table_string = "{\n"
foreach (idx, key in table)
{
{
if ( typeof(key) == "table" )
local line = "?chxl=1:"
foreach( indexW, Weapon in Player.WeaponData )
{
{
table_string = table_string + idx + "=\n" + TableToString( key ) + ",\n"
// weapon names are listed backwards so store them such
}
line = line + "|" + Player.WeaponData[ (Player.WeaponData.len() - 1) - indexW ].name
else if ( typeof(key) == "array" )
{
table_string = table_string + idx + "=\n" + ArrayToString( key ) + ",\n"
}
else if ( typeof(key) == "string" )
{
table_string = table_string + idx + "=\"" + key + "\",\n"
}
else
{
table_string = table_string + idx + "=" + key + ",\n"
}
}
chart_url = chart_url + line
}
}
return table_string + "}"
}


function ArrayToString( array )
chart_url = chart_url + "&chxr=0,0,100"
{
chart_url = chart_url + "&chxt=x,y"
local array_string = "[\n"
chart_url = chart_url + "&chbh=a"
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 36*( 1.6 + GunStatsTable.Players[player_index].WeaponData.len() ) )) )
foreach (idx, key in array)
chart_url = chart_url + "&cht=bhs"
{
chart_url = chart_url + "&chco=3D7930,A2C180,DCBA80,DC5030"
if ( typeof(key) == "table" )
chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000"
 
{
local line = "&chd=t"
foreach( indexW, Weapon in Player.WeaponData )
{
{
array_string = array_string + TableToString( key ) + ",\n"
if( indexW == 0 ) { line = line + ":" } else { line = line + "," }
 
local num = ( Weapon.headShots / (0.001*(Weapon.shots + 1) ) )
line = line + "" + floor( num )
}
}
else if ( typeof(key) == "array" )
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
{
array_string = array_string + ArrayToString( key ) + ",\n"
if( indexW != 0 ) { line = line + "," }
 
local num = ( (Weapon.hits - Weapon.headShots) / (0.001*(Weapon.shots + 0.01) ) )
line = line + "" + floor( num )
}
}
else if ( typeof(key) == "string" )
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
{
array_string = array_string + "\"" + key + "\",\n"
if( indexW != 0 ) { line = line + "," }
 
local num = ( ( Weapon.shots - ( Weapon.hits + Weapon.FFHits ) ) / (0.001*(Weapon.shots + 0.01) ) )
line = line + "" + floor( num )
}
}
else
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
{
array_string = array_string + key + ",\n"
if( indexW != 0 ) { line = line + "," }
 
local num = ( Weapon.FFHits / (0.001*(Weapon.shots + 0.01) ) )
line = line + "" + floor( num )
}
}
chart_url = chart_url + line
}
}
return array_string + "]"
}
</source>}}


===Tank Run===
chart_url = chart_url + "&chdl=Headshot|Non-Headshot Hit|Miss|Friendly Fire"
{{ExpandBox|<source lang=js>
chart_url = chart_url + "&chtt=" + Player.name + " Shot Hit Type"
//-----------------------------------------------------
chart_url = chart_url + "\"" + "\\><br><br><br><br>"
Msg("Activating Tank Run\n");


if ( !IsModelPrecached( "models/infected/hulk.mdl" ) )
return chart_url
PrecacheModel( "models/infected/hulk.mdl" );
}
if ( !IsModelPrecached( "models/infected/hulk_dlc3.mdl" ) )
PrecacheModel( "models/infected/hulk_dlc3.mdl" );
if ( !IsModelPrecached( "models/infected/hulk_l4d1.mdl" ) )
PrecacheModel( "models/infected/hulk_l4d1.mdl" );


MutationOptions <-
function CreatePlayerDamageChartString( player_index )
{
{
cm_ShouldHurry = 1
local chart_url = "<img src="
cm_InfiniteFuel = 1
chart_url = chart_url + "\""
cm_CommonLimit = 0
chart_url = chart_url + "http://"
cm_DominatorLimit = 0
chart_url = chart_url + player_index
cm_MaxSpecials = 0
chart_url = chart_url + ".chart.apis.google.com/chart"
cm_ProhibitBosses = true
cm_AggressiveSpecials = true
BoomerLimit = 0
SmokerLimit = 0
HunterLimit = 0
ChargerLimit = 0
SpitterLimit = 0
JockeyLimit = 0
cm_WitchLimit = 0
cm_TankLimit = 8
MobMinSize = 0
MobMaxSize = 0
NoMobSpawns = true
EscapeSpawnTanks = false


// convert items that aren't useful
local Player = GunStatsTable.Players[player_index]
weaponsToConvert =
{
{
weapon_defibrillator = "weapon_first_aid_kit_spawn"
local line = "?chxl=1:"
ammo = "upgrade_laser_sight"
foreach( indexW, Weapon in Player.WeaponData )
}
 
function ConvertWeaponSpawn( classname )
{
if ( classname in weaponsToConvert )
{
{
return weaponsToConvert[classname];
// weapon names are listed backwards so store them such
line = line + "|" + Player.WeaponData[ (Player.WeaponData.len() - 1) - indexW ].name
}
}
return 0;
chart_url = chart_url + line
}
}
chart_url = chart_url + "&chxr=0,0,75"
chart_url = chart_url + "&chxt=x,y"
chart_url = chart_url + "&chbh=a"
chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 36*( 1.6 + GunStatsTable.Players[player_index].WeaponData.len() ) )) )
chart_url = chart_url + "&cht=bhs"
chart_url = chart_url + "&chco=1B84E0,43C5CC,A4B3B0"
chart_url = chart_url + "&chds=0,75,0,75,0,75"


DefaultItems =
{
[
local line = "&chd=t"
"weapon_pistol_magnum",
foreach( indexW, Weapon in Player.WeaponData )
]
 
function GetDefaultItem( idx )
{
if ( idx < DefaultItems.len() )
{
{
return DefaultItems[idx];
if( indexW == 0 )
{
line = line + ":"
}
else
{
line = line + ","
}
line = line + ( Weapon.effectiveDamage / (Weapon.shots + 1) )
}
}
return 0;
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
if( indexW != 0 )
{
line = line + ","
}
line = line + ( Weapon.overKill / (Weapon.shots + 1) )
}
line = line + "|"
foreach( indexW, Weapon in Player.WeaponData )
{
if( indexW != 0 )
{
line = line + ","
}
line = line + ( Weapon.deadDamage / (Weapon.shots + 1) )
}
chart_url = chart_url + line
}
}
chart_url = chart_url + "&chdl=Effective+Damage|Overkill+Damage|Dead Target+Damage"
chart_url = chart_url + "&chtt=" + Player.name + " Damage+Breakdown(per pellet)"
chart_url = chart_url + "\"" + "\\><br><br><br><br>"
return chart_url
}
}


MutationState <-
{
TankModelsBase = [ "models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl", "models/infected/hulk_l4d1.mdl" ]
TankModels = [ "models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl", "models/infected/hulk_l4d1.mdl" ]
ModelCheck = false
FinaleStarted = false
FinaleStartTime = 0
TriggerRescue = false
RescueDelay = 600
LastSpawnTime = 0
SpawnInterval = 20
DoubleTanks = false
TankBiled = {}
TanksAlive = 0
TanksDisabled = false
TankHealth = 4000
BileHurtTankThink = false
SpawnTankThink = false
TriggerRescueThink = false
LeftSafeAreaThink = false
FinaleType = -1
}


local triggerFinale = Entities.FindByClassname( null, "trigger_finale" );
/////////////////////////////////////////////////
if ( triggerFinale )
// Table manipulation
MutationState.FinaleType = NetProps.GetPropInt( triggerFinale, "m_type" );
/////////////////////////////////////////////////


if ( MutationState.FinaleType != 4 )
// These two helper functions can call themselves and each other so if want either you would need both.
function TableToString( table )
{
{
function GetNextStage()
local table_string = "{\n"
foreach (idx, key in table)
{
{
if ( SessionState.TriggerRescue )
if ( typeof(key) == "table" )
{
{
SessionOptions.ScriptedStageType = STAGE_ESCAPE;
table_string = table_string + idx + "=\n" + TableToString( key ) + ",\n"
TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags | HUD_FLAG_NOTVISIBLE;
return;
}
}
if ( SessionState.FinaleStarted )
else if ( typeof(key) == "array" )
{
{
SessionOptions.ScriptedStageType = STAGE_DELAY;
table_string = table_string + idx + "=\n" + ArrayToString( key ) + ",\n"
SessionOptions.ScriptedStageValue = -1;
}
else if ( typeof(key) == "string" )
{
table_string = table_string + idx + "=\"" + key + "\",\n"
}
else
{
table_string = table_string + idx + "=" + key + ",\n"
}
}
}
}
return table_string + "}"
}
}


function AllowTakeDamage( damageTable )
function ArrayToString( array )
{
{
if ( !damageTable.Attacker || !damageTable.Victim || !damageTable.Inflictor )
local array_string = "[\n"
return true;
foreach (idx, key in array)
if ( damageTable.Attacker.IsPlayer() && damageTable.Victim.IsPlayer() )
{
{
if ( damageTable.Attacker.IsSurvivor() && damageTable.Victim.GetZombieType() == 8 )
if ( typeof(key) == "table" )
{
array_string = array_string + TableToString( key ) + ",\n"
}
else if ( typeof(key) == "array" )
{
array_string = array_string + ArrayToString( key ) + ",\n"
}
else if ( typeof(key) == "string" )
{
{
if ( damageTable.Inflictor.GetClassname() == "pipe_bomb_projectile" )
array_string = array_string + "\"" + key + "\",\n"
damageTable.DamageDone = 500;
}
}
}
else
return true;
}
 
function TriggerRescueThink()
{
if ( (Time() - SessionState.FinaleStartTime) >= SessionState.RescueDelay )
{
SessionState.TriggerRescue = true;
Director.ForceNextStage();
SessionState.TriggerRescueThink = false;
}
}
 
function SpawnTankThink()
{
if ( (SessionState.TanksAlive < 8) && ((Time() - SessionState.LastSpawnTime) >= SessionState.SpawnInterval || SessionState.LastSpawnTime == 0) )
{
local success = ZSpawn( { type = 8 } );
if ( success )
{
{
if ( SessionState.DoubleTanks )
array_string = array_string + key + ",\n"
ZSpawn( { type = 8 } );
SessionState.LastSpawnTime = Time();
}
}
}
}
return array_string + "]"
}
}
</source>}}
===TankRun===
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
Msg("Activating Tank Run\n");


function LeftSafeAreaThink()
if ( !IsModelPrecached( "models/infected/hulk.mdl" ) )
{
PrecacheModel( "models/infected/hulk.mdl" );
local player = null;
if ( !IsModelPrecached( "models/infected/hulk_dlc3.mdl" ) )
while ( player = Entities.FindByClassname( player, "player" ) )
PrecacheModel( "models/infected/hulk_dlc3.mdl" );
if ( !IsModelPrecached( "models/infected/hulk_l4d1.mdl" ) )
PrecacheModel( "models/infected/hulk_l4d1.mdl" );
 
MutationOptions <-
{
cm_TankRun = true
cm_ShouldHurry = true
cm_InfiniteFuel = true
cm_AllowPillConversion = false
cm_CommonLimit = 0
cm_DominatorLimit = 0
cm_MaxSpecials = 0
cm_ProhibitBosses = true
cm_AggressiveSpecials = true
 
BoomerLimit = 0
SmokerLimit = 0
HunterLimit = 0
ChargerLimit = 0
SpitterLimit = 0
JockeyLimit = 0
cm_WitchLimit = 0
cm_TankLimit = 8
 
MobMinSize = 0
MobMaxSize = 0
NoMobSpawns = true
EscapeSpawnTanks = false
 
// convert items that aren't useful
weaponsToConvert =
{
ammo = "upgrade_laser_sight"
}
 
function ConvertWeaponSpawn( classname )
{
{
if ( ( !player.IsValid() ) || ( NetProps.GetPropInt( player, "m_iTeamNum" ) != 2 ) )
if ( classname in weaponsToConvert )
continue;
if ( ResponseCriteria.GetValue( player, "instartarea" ) == "0" )
{
{
SessionState.LeftSafeAreaThink = false;
return weaponsToConvert[classname];
SessionState.SpawnTankThink = true;
break;
}
}
else
return 0;
continue;
}
}
}


function BileHurtTankThink()
DefaultItems =
{
[
foreach( tank, survivor in SessionState.TankBiled )
"weapon_pistol_magnum",
]
 
function GetDefaultItem( idx )
{
{
tank.TakeDamage( 100, 0, survivor );
if ( idx < DefaultItems.len() )
{
return DefaultItems[idx];
}
return 0;
}
}
}
}


function CheckDifficultyForTankHealth( difficulty )
MutationState <-
{
{
local health = 0;
TankModelsBase = [ "models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl", "models/infected/hulk_l4d1.mdl" ]
if ( difficulty == "easy" )
TankModels = [ "models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl", "models/infected/hulk_l4d1.mdl" ]
health = 2000;
ModelCheck = false
else if ( difficulty == "normal" )
FinaleStarted = false
health = 3000;
TriggerRescue = false
else if ( difficulty == "hard" )
RescueDelay = 600
health = 4000;
LastAlarmTankTime = 0
else if ( difficulty == "impossible" )
LastSpawnTime = 0
health = 5000;
SpawnInterval = 20
DoubleTanks = false
if ( SessionState.MapName == "c1m1_hotel" )
TankBiled = {}
SessionState.TankHealth = (health / 5);
TanksDisabled = false
else
TankHealth = 4000
SessionState.TankHealth = health;
BileHurtTankThink = false
SpawnTankThink = false
TriggerRescueThink = false
LeftSafeAreaThink = false
CheckPrimaryWeaponThink = false
FinaleType = -1
}
 
function GetNumTanks()
{
local infStats = {};
GetInfectedStats( infStats );
return infStats.Tanks;
}
}


function OnGameEvent_round_start_post_nav( params )
if ( IsMissionFinalMap() )
{
{
local spawner = null;
local triggerFinale = Entities.FindByClassname( null, "trigger_finale" );
while ( spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ) )
if ( triggerFinale )
{
{
if ( spawner.IsValid() )
MutationState.FinaleType = NetProps.GetPropInt( triggerFinale, "m_type" );
if ( NetProps.GetPropInt( triggerFinale, "m_bIsSacrificeFinale" ) )
{
{
local population = NetProps.GetPropString( spawner, "m_szPopulation" );
function OnGameEvent_generator_started( params )
{
if ( population == "tank" || population == "river_docks_trap" )
if ( !SessionState.FinaleStarted )
continue;
return;
else
 
spawner.Kill();
HUDManageTimers( 0, TIMER_COUNTDOWN, HUDReadTimer( 0 ) - 30 );
if ( GetNumTanks() < SessionOptions.cm_TankLimit )
ZSpawn( { type = 8 } );
}
}
}
}
}
local ammo = null;
 
while ( ammo = Entities.FindByModel( ammo, "models/props/terror/ammo_stack.mdl" ) )
TankRunHUD <- {};
ammo.Kill();
function SetupModeHUD()
if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c6m3_port" || SessionState.MapName == "c13m4_cutthroatcreek" )
{
{
SessionOptions.cm_TankLimit = 0;
TankRunHUD =
SessionState.TanksDisabled = true;
{
Fields =
{
rescue_time = { slot = HUD_MID_TOP, name = "rescue", special = HUD_SPECIAL_TIMER0, flags = HUD_FLAG_COUNTDOWN_WARN | HUD_FLAG_BEEP | HUD_FLAG_ALIGN_CENTER | HUD_FLAG_NOTVISIBLE },
}
}
HUDSetLayout( TankRunHUD );
}
}
CheckDifficultyForTankHealth( Convars.GetStr( "z_difficulty" ).tolower() );
}


function OnGameEvent_difficulty_changed( params )
if ( MutationState.FinaleType != 4 )
{
{
CheckDifficultyForTankHealth( params["strDifficulty"].tolower() );
function GetNextStage()
}
{
if ( SessionState.TriggerRescue )
{
SessionOptions.ScriptedStageType = STAGE_ESCAPE;
return;
}
if ( SessionState.FinaleStarted )
{
SessionOptions.ScriptedStageType = STAGE_DELAY;
SessionOptions.ScriptedStageValue = -1;
}
}
}


function OnGameEvent_player_left_safe_area( params )
function OnGameEvent_finale_start( params )
{
if ( SessionState.TanksDisabled )
return;
local player = GetPlayerFromUserID( params["userid"] );
if ( !player )
{
{
SessionState.SpawnTankThink = true;
if ( SessionState.MapName == "c6m3_port" )
return;
{
SessionOptions.cm_TankLimit = 8;
SessionState.TanksDisabled = false;
SessionState.SpawnTankThink = true;
}
 
if ( SessionState.FinaleType == 4 )
return;
 
HUDManageTimers( 0, TIMER_COUNTDOWN, SessionState.RescueDelay );
TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags & ~HUD_FLAG_NOTVISIBLE;
 
SessionState.DoubleTanks = true;
SessionState.SpawnInterval = 40;
SessionState.FinaleStarted = true;
SessionState.TriggerRescueThink = true;
}
 
function OnGameEvent_gauntlet_finale_start( params )
{
if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c13m4_cutthroatcreek" )
{
SessionOptions.cm_TankLimit = 8;
SessionState.TanksDisabled = false;
SessionState.SpawnTankThink = true;
}
}
}
local instartarea = ResponseCriteria.GetValue( player, "instartarea" );
if ( instartarea == "1" )
SessionState.LeftSafeAreaThink = true;
else
SessionState.SpawnTankThink = true;
}


function OnGameEvent_finale_start( params )
function OnGameEvent_finale_vehicle_leaving( params )
{
if ( SessionState.MapName == "c6m3_port" )
{
{
SessionOptions.cm_TankLimit = 8;
SessionState.SpawnTankThink = false;
SessionState.TanksDisabled = false;
SessionState.SpawnTankThink = true;
}
}
if ( SessionState.FinaleType == 4 )
return;
SessionState.DoubleTanks = true;
SessionState.SpawnInterval = 40;
SessionState.FinaleStarted = true;
SessionState.FinaleStartTime = Time();
SessionState.TriggerRescueThink = true;
HUDManageTimers( 0, TIMER_COUNTDOWN, SessionState.RescueDelay );
TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags & ~HUD_FLAG_NOTVISIBLE;
}
}


function OnGameEvent_gauntlet_finale_start( params )
function AllowTakeDamage( damageTable )
{
{
if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c13m4_cutthroatcreek" )
if ( !damageTable.Attacker || !damageTable.Victim || !damageTable.Inflictor )
return true;
 
if ( damageTable.Victim.IsPlayer() && damageTable.Attacker.IsPlayer() )
{
{
SessionOptions.cm_TankLimit = 8;
if ( damageTable.Attacker.IsSurvivor() && damageTable.Victim.GetZombieType() == 8 )
SessionState.TanksDisabled = false;
{
SessionState.SpawnTankThink = true;
if ( damageTable.Inflictor.GetClassname() == "pipe_bomb_projectile" )
damageTable.DamageDone = 500;
 
if ( damageTable.Weapon )
{
local weaponClass = damageTable.Weapon.GetClassname();
if ( weaponClass == "weapon_pistol" )
damageTable.DamageDone = (damageTable.DamageDone * 1.25);
else if ( weaponClass == "weapon_melee" )
damageTable.DamageDone = (damageTable.DamageDone * 1.334);
}
}
}
}
return true;
}
}


function OnGameEvent_finale_vehicle_leaving( params )
function TriggerRescueThink()
{
{
SessionState.SpawnTankThink = false;
if ( HUDReadTimer( 0 ) <= 0 )
}
{
SessionState.TriggerRescue = true;
SessionState.TriggerRescueThink = false;
Director.ForceNextStage();


function OnGameEvent_mission_lost( params )
TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags | HUD_FLAG_NOTVISIBLE;
{
HUDManageTimers( 0, TIMER_DISABLE, 0 );
SessionState.SpawnTankThink = false;
}
}
}


function OnGameEvent_player_now_it( params )
function SpawnTankThink()
{
{
local attacker = GetPlayerFromUserID( params["attacker"] );
if ( SessionOptions.cm_TankLimit == 0 )
local victim = GetPlayerFromUserID( params["userid"] );
if ( !attacker || !victim )
return;
return;
 
if ( attacker.IsSurvivor() && victim.GetZombieType() == 8 )
if ( (GetNumTanks() < SessionOptions.cm_TankLimit) && ((Time() - SessionState.LastSpawnTime) >= SessionState.SpawnInterval || SessionState.LastSpawnTime == 0) )
{
{
if ( victim in SessionState.TankBiled )
if ( ZSpawn( { type = 8 } ) )
return;
{
if ( SessionState.DoubleTanks )
victim.OverrideFriction( Convars.GetFloat( "vomitjar_duration_infected_bot" ), 2.0 );
ZSpawn( { type = 8 } );
SessionState.TankBiled.rawset( victim, attacker );
SessionState.LastSpawnTime = Time();
if ( SessionState.TankBiled.len() == 1 )
}
SessionState.BileHurtTankThink = true;
}
}
}
}


function OnGameEvent_player_no_longer_it( params )
function LeftSafeAreaThink()
{
{
local victim = GetPlayerFromUserID( params["userid"] );
for ( local player; player = Entities.FindByClassname( player, "player" ); )
if ( !victim )
return;
if ( victim.GetZombieType() == 8 )
{
{
if ( victim in SessionState.TankBiled )
if ( NetProps.GetPropInt( player, "m_iTeamNum" ) != 2 )
continue;
 
if ( ResponseCriteria.GetValue( player, "instartarea" ) == "0" )
{
{
SessionState.TankBiled.rawdelete( victim );
SessionState.LeftSafeAreaThink = false;
if ( SessionState.TankBiled.len() == 0 )
SessionState.SpawnTankThink = true;
SessionState.BileHurtTankThink = false;
break;
}
}
}
}
}
}


function OnGameEvent_tank_spawn( params )
function BileHurtTankThink()
{
foreach( tank, survivor in SessionState.TankBiled )
{
tank.TakeDamage( 100, 0, survivor );
}
}
 
function CheckDifficultyForTankHealth( difficulty )
{
local health = [2000, 3000, 4000, 5000];
SessionState.TankHealth = health[difficulty];
}
 
if ( Director.IsFirstMapInScenario() )
{
{
local tank = GetPlayerFromUserID( params["userid"] );
function CheckPrimaryWeaponThink()
if ( !tank )
return;
SessionState.TanksAlive++;
tank.SetMaxHealth( SessionState.TankHealth );
tank.SetHealth( SessionState.TankHealth );
local modelName = tank.GetModelName();
if ( !SessionState.ModelCheck )
{
{
SessionState.ModelCheck = true;
local startArea = null;
local startPos = null;
if ( SessionState.TankModelsBase.find( modelName ) == null )
for ( local survivorSpawn; survivorSpawn = Entities.FindByClassname( survivorSpawn, "info_survivor_position" ); )
{
{
SessionState.TankModelsBase.append( modelName );
local area = NavMesh.GetNearestNavArea( survivorSpawn.GetOrigin(), 100, false, false );
SessionState.TankModels.append( modelName );
if ( (area) && (area.HasSpawnAttributes( 128 ) || area.HasSpawnAttributes( 2048 )) )
}
{
}
startArea = area;
startPos = survivorSpawn.GetOrigin();
local tankModels = SessionState.TankModels;
break;
if ( tankModels.len() == 0 )
}
SessionState.TankModels.extend( SessionState.TankModelsBase );
}
local foundModel = tankModels.find( modelName );
 
if ( foundModel != null )
if ( !startPos )
return;
 
for ( local weapon; weapon = Entities.FindByClassnameWithin( weapon, "weapon_*", startPos, 1200 ); )
{
if ( weapon.GetOwnerEntity() )
continue;
 
local weaponName = weapon.GetClassname();
local primaryNames = [ "smg", "rifle", "shotgun", "sniper", "grenade" ];
if ( weaponName == "weapon_spawn" )
{
weaponName = NetProps.GetPropString( weapon, "m_iszWeaponToSpawn" );
if ( weaponName.find( "pistol" ) != null )
continue;
primaryNames.append( "any" );
}
 
foreach( name in primaryNames )
{
if ( weaponName.find( name ) != null )
return;
}
}
 
local itemNames = [ "weapon_pistol_spawn", "weapon_pistol_magnum_spawn", "weapon_spawn", "weapon_melee_spawn" ];
foreach( name in itemNames )
{
local item = Entities.FindByClassnameNearest( name, startPos, 600 );
if ( item )
{
local nearestArea = NavMesh.GetNearestNavArea( item.GetOrigin(), 100, false, false );
if ( nearestArea )
startArea = nearestArea;
break;
}
}
 
local w = [ "any_smg", "tier1_shotgun" ];
for ( local i = 0; i < 2; i++ )
SpawnEntityFromTable( "weapon_spawn", { spawn_without_director = 1, weapon_selection = w[i], count = 5, spawnflags = 3, origin = (startArea.FindRandomSpot() + Vector(0,0,50)), angles = Vector(RandomInt(0,90),RandomInt(0,90),90) } );
}
 
function OnGameplayStart()
{
SessionState.CheckPrimaryWeaponThink = true;
}
}
 
function OnGameEvent_round_start_post_nav( params )
{
for ( local spawner; spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ); )
{
local population = NetProps.GetPropString( spawner, "m_szPopulation" );
 
if ( population == "tank" || population == "river_docks_trap" )
continue;
else
spawner.Kill();
}
for ( local ammo; ammo = Entities.FindByModel( ammo, "models/props/terror/ammo_stack.mdl" ); )
ammo.Kill();
 
if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c6m3_port" || SessionState.MapName == "c13m4_cutthroatcreek" )
{
SessionOptions.cm_TankLimit = 0;
SessionState.TanksDisabled = true;
}
 
CheckDifficultyForTankHealth( GetDifficulty() );
EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.TankRunThink()", 1.0 );
}
 
function OnGameEvent_difficulty_changed( params )
{
CheckDifficultyForTankHealth( params["newDifficulty"] );
}
 
function OnGameEvent_player_left_safe_area( params )
{
if ( SessionState.TanksDisabled )
return;
 
local player = GetPlayerFromUserID( params["userid"] );
if ( !player )
{
SessionState.SpawnTankThink = true;
return;
}
 
if ( ResponseCriteria.GetValue( player, "instartarea" ) == "1" )
SessionState.LeftSafeAreaThink = true;
else
SessionState.SpawnTankThink = true;
}
 
function OnGameEvent_player_disconnect( params )
{
local player = GetPlayerFromUserID( params["userid"] );
if ( !player )
return;
 
if ( player.GetZombieType() == 8 && player in SessionState.TankBiled )
{
SessionState.TankBiled.rawdelete( player );
if ( SessionState.TankBiled.len() == 0 )
SessionState.BileHurtTankThink = false;
}
}
 
function OnGameEvent_mission_lost( params )
{
SessionState.SpawnTankThink = false;
}
 
function OnGameEvent_player_now_it( params )
{
local attacker = GetPlayerFromUserID( params["attacker"] );
local victim = GetPlayerFromUserID( params["userid"] );
 
if ( !attacker || !victim )
return;
 
if ( attacker.IsSurvivor() && victim.GetZombieType() == 8 )
{
if ( victim in SessionState.TankBiled )
return;
 
victim.SetFriction( 2.0 );
SessionState.TankBiled.rawset( victim, attacker );
if ( SessionState.TankBiled.len() == 1 )
SessionState.BileHurtTankThink = true;
}
}
 
function OnGameEvent_player_no_longer_it( params )
{
local victim = GetPlayerFromUserID( params["userid"] );
 
if ( !victim )
return;
 
if ( victim.GetZombieType() == 8 && victim in SessionState.TankBiled )
{
victim.SetFriction( 1.0 );
SessionState.TankBiled.rawdelete( victim );
if ( SessionState.TankBiled.len() == 0 )
SessionState.BileHurtTankThink = false;
}
}
 
function OnGameEvent_triggered_car_alarm( params )
{
if ( (GetNumTanks() < SessionOptions.cm_TankLimit) && ((Time() - SessionState.LastAlarmTankTime) >= SessionState.SpawnInterval || SessionState.LastAlarmTankTime == 0) )
{
if ( ZSpawn( { type = 8 } ) )
SessionState.LastAlarmTankTime = Time();
}
}
 
function OnGameEvent_tank_spawn( params )
{
local tank = GetPlayerFromUserID( params["userid"] );
if ( !tank )
return;
 
tank.SetMaxHealth( SessionState.TankHealth );
tank.SetHealth( SessionState.TankHealth );
local modelName = tank.GetModelName();
 
if ( !SessionState.ModelCheck )
{
SessionState.ModelCheck = true;
 
if ( SessionState.TankModelsBase.find( modelName ) == null )
{
SessionState.TankModelsBase.append( modelName );
SessionState.TankModels.append( modelName );
}
}
 
local tankModels = SessionState.TankModels;
if ( tankModels.len() == 0 )
SessionState.TankModels.extend( SessionState.TankModelsBase );
local foundModel = tankModels.find( modelName );
if ( foundModel != null )
{
tankModels.remove( foundModel );
return;
}
 
local randomElement = RandomInt( 0, tankModels.len() - 1 );
local randomModel = tankModels[ randomElement ];
tankModels.remove( randomElement );
 
tank.SetModel( randomModel );
}
 
function OnGameEvent_tank_killed( params )
{
local tank = GetPlayerFromUserID( params["userid"] );
 
if ( tank in SessionState.TankBiled )
{
SessionState.TankBiled.rawdelete( tank );
if ( SessionState.TankBiled.len() == 0 )
SessionState.BileHurtTankThink = false;
}
 
if ( SessionState.FinaleStarted )
HUDManageTimers( 0, TIMER_COUNTDOWN, HUDReadTimer( 0 ) - 10 );
}
 
function TankRunThink()
{
if ( SessionState.LeftSafeAreaThink )
LeftSafeAreaThink();
if ( SessionState.SpawnTankThink )
SpawnTankThink();
if ( SessionState.TriggerRescueThink )
TriggerRescueThink();
if ( SessionState.BileHurtTankThink )
BileHurtTankThink();
if ( SessionState.CheckPrimaryWeaponThink )
{
{
tankModels.remove( foundModel );
CheckPrimaryWeaponThink();
return;
SessionState.CheckPrimaryWeaponThink = false;
}
}
local randomElement = RandomInt( 0, tankModels.len() - 1 );
local randomModel = tankModels[ randomElement ];
tankModels.remove( randomElement );
tank.SetModel( randomModel );
}
function OnGameEvent_tank_killed( params )
{
SessionState.TanksAlive--;
local tank = GetPlayerFromUserID( params["userid"] );
if ( tank in SessionState.TankBiled )
{
SessionState.TankBiled.rawdelete( tank );
if ( SessionState.TankBiled.len() == 0 )
SessionState.BileHurtTankThink = false;
}
if ( SessionState.FinaleStarted )
{
SessionState.RescueDelay -= 10;
HUDManageTimers( 0, TIMER_COUNTDOWN, HUDReadTimer( 0 ) - 10 );
}
}
function Update()
{
if ( SessionState.LeftSafeAreaThink )
LeftSafeAreaThink();
if ( SessionState.SpawnTankThink )
SpawnTankThink();
if ( SessionState.TriggerRescueThink )
TriggerRescueThink();
if ( SessionState.BileHurtTankThink )
BileHurtTankThink();
if ( Director.GetCommonInfectedCount() > 0 )
if ( Director.GetCommonInfectedCount() > 0 )
{
{
local infected = null;
for ( local infected; infected = Entities.FindByClassname( infected, "infected" ); )
while ( infected = Entities.FindByClassname( infected, "infected" ) )
infected.Kill();
{
if ( infected.IsValid() )
infected.Kill();
}
}
}
 
TankRunHUD <- {};
function SetupModeHUD()
{
TankRunHUD =
{
Fields =
{
rescue_time = { slot = HUD_MID_TOP, name = "rescue", special = HUD_SPECIAL_TIMER0, flags = HUD_FLAG_COUNTDOWN_WARN | HUD_FLAG_BEEP | HUD_FLAG_ALIGN_CENTER | HUD_FLAG_NOTVISIBLE },
}
}
}
HUDSetLayout( TankRunHUD );
EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.TankRunThink()", 1.0 );
}
}
</source>}}
</source>}}
Line 10,847: Line 11,684:
*[[Info_director|info_director]]
*[[Info_director|info_director]]
*[[List of L4D2 Cvars]]
*[[List of L4D2 Cvars]]
*[https://github.com/Nesciuse/Official-Vscripts-Decompiled Official Vscripts Decompiled]

Latest revision as of 06:51, 2 April 2025

English (en)Translate (Translate)

SquirrelLeft 4 Dead 2 These are all official L4D2 mutation scripts and their included scripts. Most scripts have been decrypted on February 26th 2021 with L4D2 VScript Editor Beta 0.5 by Cynick and may be updated at any time.

Valve Mutations

L4d1


//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
//=============================================================================
Msg("Activating Mutation L4D1\n");

if ( !IsModelPrecached( "models/infected/smoker_l4d1.mdl" ) )
	PrecacheModel( "models/infected/smoker_l4d1.mdl" );
if ( !IsModelPrecached( "models/infected/boomer_l4d1.mdl" ) )
	PrecacheModel( "models/infected/boomer_l4d1.mdl" );
if ( !IsModelPrecached( "models/infected/hunter_l4d1.mdl" ) )
	PrecacheModel( "models/infected/hunter_l4d1.mdl" );
if ( !IsModelPrecached( "models/infected/hulk_l4d1.mdl" ) )
	PrecacheModel( "models/infected/hulk_l4d1.mdl" );

DirectorOptions <-
{
	ActiveChallenge = 1

	SpitterLimit = 0
	JockeyLimit = 0
	ChargerLimit = 0

	EscapeSpawnTanks = true

	weaponsToConvert =
	{
		weapon_shotgun_spas				= "weapon_autoshotgun_spawn"
		weapon_defibrillator			= "weapon_pain_pills_spawn"
		weapon_ammo_pack				= "weapon_first_aid_kit_spawn"
		weapon_sniper_awp				= "weapon_hunting_rifle_spawn"
		weapon_sniper_military			= "weapon_hunting_rifle_spawn"
		weapon_sniper_scout				= "weapon_hunting_rifle_spawn"
		weapon_vomitjar					= "weapon_molotov_spawn"
		weapon_adrenaline				= "weapon_pain_pills_spawn"
		weapon_pistol_magnum			= "weapon_pistol_spawn"
		weapon_shotgun_chrome			= "weapon_pumpshotgun_spawn"
		weapon_rifle_ak47				= "weapon_rifle_spawn"
		weapon_rifle_desert				= "weapon_rifle_spawn"
		weapon_rifle_sg552				= "weapon_rifle_spawn"
		weapon_smg_mp5					= "weapon_smg_spawn"
		weapon_smg_silenced				= "weapon_smg_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}

	weaponsToRemove =
	{
		weapon_rifle_m60 = 0
		weapon_grenade_launcher = 0
		weapon_chainsaw = 0
		weapon_melee = 0
		weapon_upgradepack_explosive = 0
		weapon_upgradepack_incendiary = 0
		upgrade_item = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}

	function ShouldAvoidItem( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return true;
		}
		return false;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	EntFire( "worldspawn", "AddOutput", "timeofday 0" );
	EntFire( "weapon_*", "AddOutput", "skin 0" );
	EntFire( "weapon_*", "AddOutput", "weaponskin -1" );
	EntFire( "trigger_upgrade_laser_sight", "Kill" );

	foreach( wep, val in DirectorOptions.weaponsToRemove )
		EntFire( wep + "_spawn", "Kill" );
	foreach( wep, val in DirectorOptions.weaponsToConvert )
	{
		for ( local wep_spawner; wep_spawner = Entities.FindByClassname( wep_spawner, wep + "_spawn" ); )
		{
			local spawnTable =
			{
				origin = wep_spawner.GetOrigin(),
				angles = wep_spawner.GetAngles().ToKVString(),
				targetname = wep_spawner.GetName(),
				count = NetProps.GetPropInt( wep_spawner, "m_itemCount" ),
				spawnflags = NetProps.GetPropInt( wep_spawner, "m_spawnflags" )
			}
			wep_spawner.Kill();
			SpawnEntityFromTable(val, spawnTable);
		}
	}

	if ( Director.IsL4D1Campaign() )
	{
		DirectorOptions.WaterSlowsMovement <- false;

		if ( SessionState.ModeName == "l4d1coop" || SessionState.ModeName == "l4d1vs" )
		{
			if ( IsMissionFinalMap() )
			{
				if ( SessionState.MapName != "c7m3_port" )
				{
					local finale = Entities.FindByClassname( null, "trigger_finale" );
					if ( finale )
						NetProps.SetPropInt( finale, "m_type", 0 );
				}
			}
			else
			{
				if ( SessionState.MapName == "c10m4_mainstreet" )
				{
					local relay = Entities.FindByName( null, "forklift_relay" );
					if ( relay )
					{
						EntityOutputs.RemoveOutput( relay, "OnTrigger", "director", "BeginScript", "c10m4_onslaught" );
						EntityOutputs.AddOutput( relay, "OnTrigger", "director", "ForcePanicEvent", "", 9.0, -1 );
					}
					EntFire( "onslaught1", "Kill" );
				}
				else if ( SessionState.MapName == "c11m4_terminal" )
				{
					local van = Entities.FindByName( null, "van_button" );
					if ( van )
					{
						EntityOutputs.RemoveOutput( van, "OnPressed", "@director", "", "" );
						EntityOutputs.AddOutput( van, "OnPressed", "@director", "ForcePanicEvent", "", 3.0, -1 );
					}
					local relay = Entities.FindByName( null, "alarm_on_relay" );
					if ( relay )
					{
						EntityOutputs.RemoveOutput( relay, "OnTrigger", "@director", "", "" );
						EntityOutputs.RemoveOutput( relay, "OnTrigger", "alarm_safety_relay", "", "" );
						EntityOutputs.AddOutput( relay, "OnTrigger", "@director", "ForcePanicEvent", "", 0.0, -1 );
						EntityOutputs.AddOutput( relay, "OnTrigger", "alarm_off_relay", "Trigger", "", 15.0, -1 );
					}
					EntFire( "van_follow_trigger", "Kill" );
					EntFire( "van_endscript_relay", "Kill" );
					EntFire( "onslaught_hint_trigger", "Kill" );
				}
				else if ( SessionState.MapName == "c12m3_bridge" )
				{
					local relay = Entities.FindByName( null, "train_engine_relay" );
					if ( relay )
					{
						EntityOutputs.RemoveOutput( relay, "OnTrigger", "director", "BeginScript", "c12m3_onslaught" );
						EntityOutputs.AddOutput( relay, "OnTrigger", "director", "ForcePanicEvent", "", 2.0, -1 );
					}
					EntFire( "zombie_spawn1", "Kill" );
					EntFire( "onslaught_hint_template", "Kill" );
				}
				else if ( SessionState.MapName == "c12m4_barn" )
					EntFire( "window_trigger", "Kill" );
			}
		}
	}
}

if ( HasPlayerControlledZombies() )
{
	if ( !IsModelPrecached( "models/v_models/weapons/v_claw_smoker_l4d1.mdl" ) )
		PrecacheModel( "models/v_models/weapons/v_claw_smoker_l4d1.mdl" );
	if ( !IsModelPrecached( "models/v_models/weapons/v_claw_boomer_l4d1.mdl" ) )
		PrecacheModel( "models/v_models/weapons/v_claw_boomer_l4d1.mdl" );
	if ( !IsModelPrecached( "models/v_models/weapons/v_claw_hunter_l4d1.mdl" ) )
		PrecacheModel( "models/v_models/weapons/v_claw_hunter_l4d1.mdl" );
	if ( !IsModelPrecached( "models/v_models/weapons/v_claw_hulk_l4d1.mdl" ) )
		PrecacheModel( "models/v_models/weapons/v_claw_hulk_l4d1.mdl" );

	function OnGameEvent_item_pickup( params )
	{
		local player = GetPlayerFromUserID( params["userid"] );

		if ( ( !player ) || ( player.IsSurvivor() ) )
			return;

		local modelName = player.GetModelName();
		if ( ( modelName.find( "l4d1" ) != null ) || ( modelName == "models/infected/hulk_dlc3.mdl" ) )
			return;

		local function SetClawModel( modelName )
		{
			local claw = player.GetActiveWeapon();
			local viewmodel = NetProps.GetPropEntity( player, "m_hViewModel" );

			if ( ( !claw ) || ( !viewmodel ) )
				return;

			claw.SetModel( modelName );
			NetProps.SetPropInt( viewmodel, "m_nModelIndex", NetProps.GetPropInt( claw, "m_nModelIndex" ) );
			NetProps.SetPropString( viewmodel, "m_ModelName", modelName );
		}

		switch( player.GetZombieType() )
		{
			case 1:
			{
				player.SetModel( "models/infected/smoker_l4d1.mdl" );
				SetClawModel( "models/v_models/weapons/v_claw_smoker_l4d1.mdl" );
				break;
			}
			case 2:
			{
				player.SetModel( "models/infected/boomer_l4d1.mdl" );
				SetClawModel( "models/v_models/weapons/v_claw_boomer_l4d1.mdl" );
				break;
			}
			case 3:
			{
				player.SetModel( "models/infected/hunter_l4d1.mdl" );
				SetClawModel( "models/v_models/weapons/v_claw_hunter_l4d1.mdl" );
				break;
			}
			case 8:
			{
				player.SetModel( "models/infected/hulk_l4d1.mdl" );
				SetClawModel( "models/v_models/weapons/v_claw_hulk_l4d1.mdl" );
				break;
			}
			default:
				break;
		}
	}
}

function OnGameEvent_player_spawn( params )
{
	local player = GetPlayerFromUserID( params["userid"] );

	if ( ( !player ) || ( player.IsSurvivor() ) )
		return;

	local modelName = player.GetModelName();
	if ( ( modelName.find( "l4d1" ) != null ) || ( modelName == "models/infected/hulk_dlc3.mdl" ) )
		return;

	switch( player.GetZombieType() )
	{
		case 1:
		{
			player.SetModel( "models/infected/smoker_l4d1.mdl" );
			break;
		}
		case 2:
		{
			player.SetModel( "models/infected/boomer_l4d1.mdl" );
			break;
		}
		case 3:
		{
			player.SetModel( "models/infected/hunter_l4d1.mdl" );
			break;
		}
		case 8:
		{
			player.SetModel( "models/infected/hulk_l4d1.mdl" );
			break;
		}
		default:
			break;
	}
}

function OnGameEvent_player_death( params )
{
	if ( !("userid" in params) )
		return;

	local victim = GetPlayerFromUserID( params["userid"] );

	if ( ( !victim ) || ( !victim.IsSurvivor() ) )
		return;

	local prevRagdoll = NetProps.GetPropEntity( victim, "m_hRagdoll" );
	if ( prevRagdoll != null )
		return;

	local clOrigin = victim.GetOrigin();

	local ragdoll = null;
	// cs_ragdoll can crash if proper netprops aren't set, some future-proofing
	// get rid of uninitialized ragdoll if something goes wrong here
	try
	{
		ragdoll = SpawnEntityFromTable( "cs_ragdoll", {} )
		NetProps.SetPropVector( ragdoll, "m_vecOrigin", clOrigin );
		NetProps.SetPropVector( ragdoll, "m_vecRagdollOrigin", clOrigin );
		NetProps.SetPropInt( ragdoll, "m_nModelIndex", NetProps.GetPropInt( victim, "m_nModelIndex" ) );
		NetProps.SetPropInt( ragdoll, "m_iTeamNum", NetProps.GetPropInt( victim, "m_iTeamNum" ) );
		NetProps.SetPropEntity( ragdoll, "m_hPlayer", victim );
		NetProps.SetPropInt( ragdoll, "m_iDeathPose", NetProps.GetPropInt( victim, "m_nSequence" ) );
		NetProps.SetPropInt( ragdoll, "m_iDeathFrame", NetProps.GetPropInt( victim, "m_flAnimTime" ) );
		NetProps.SetPropInt( ragdoll, "m_bClientSideAnimation", 1 );
		NetProps.SetPropInt( ragdoll, "m_iTeamNum", NetProps.GetPropInt( victim, "m_iTeamNum" ) );
		NetProps.SetPropInt( ragdoll, "m_nForceBone", NetProps.GetPropInt( victim, "m_nForceBone" ) );
		NetProps.SetPropInt( ragdoll, "m_ragdollType", 4 );
		NetProps.SetPropInt( ragdoll, "m_survivorCharacter", NetProps.GetPropInt( victim, "m_survivorCharacter" ) );
		NetProps.SetPropEntity( victim, "m_hRagdoll", ragdoll );

		//EntFire( "survivor_death_model", "Kill" );
		// EntFire is too slow and you can see one-frame image of death model
		for ( local body; body = Entities.FindByClassname( body, "survivor_death_model" ); )
		{
			body.Kill();
		}
	}
	catch (err)
	{
		if ( ragdoll != null && ragdoll.IsValid() )
			ragdoll.Kill();

		EntFire( "survivor_death_model", "BecomeRagdoll" );
	}
}

L4d1coop - L4D1 Co-op


//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
//=============================================================================

IncludeScript( "l4d1" )

L4d1vs - L4D1 Versus


//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
//=============================================================================

IncludeScript( "l4d1" )

L4d1survival - L4D1 Survival


//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
//=============================================================================

IncludeScript( "l4d1" )

Mutation1 - Last Man on Earth


//-----------------------------------------------------
Msg("Activating Mutation 1\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_NoSurvivorBots = 1
	cm_CommonLimit = 0
	cm_DominatorLimit = 1
	cm_MaxSpecials = 2
	cm_SpecialRespawnInterval = 60
	cm_AutoReviveFromSpecialIncap = 1
	cm_AllowPillConversion = 0

	BoomerLimit = 0
	MobMaxPending = 0
	SurvivorMaxIncapacitatedCount = 1
	SpecialInitialSpawnDelayMin = 5
	SpecialInitialSpawnDelayMax = 30
	TankHitDamageModifierCoop = 0.5
	
	// convert items that aren't useful
	weaponsToConvert =
	{
		weapon_pipe_bomb =	"weapon_molotov_spawn"
		weapon_vomitjar =	"weapon_molotov_spawn"
		weapon_defibrillator =	"weapon_first_aid_kit_spawn"

		weapon_smg =		"weapon_rifle_spawn"
		weapon_pumpshotgun =	"weapon_autoshotgun_spawn"
		weapon_smg_silenced =	"weapon_rifle_desert_spawn"
		weapon_shotgun_chrome =	"weapon_shotgun_spas_spawn"
		weapon_smg_mp5 =	"weapon_rifle_sg552_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}	
}

function OnGameEvent_round_start_post_nav( params )
{
	local spawner = null;
	while ( spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ) )
	{
		if ( spawner.IsValid() )
		{
			local population = NetProps.GetPropString( spawner, "m_szPopulation" );
			
			if ( population == "boomer" || population == "hunter" || population == "smoker" || population == "jockey"
				|| population == "charger" || population == "spitter" || population == "new_special" || population == "church"
					|| population == "tank" || population == "witch" || population == "witch_bride" || population == "river_docks_trap" )
				continue;
			else
				spawner.Kill();
		}
	}
	
	if ( Director.GetMapName() == "c5m5_bridge" || Director.GetMapName() == "c6m3_port" )
		DirectorOptions.cm_MaxSpecials = 0;
	
	foreach( wep, val in DirectorOptions.weaponsToConvert )
	{
		local wep_spawner = null;
		while ( wep_spawner = Entities.FindByClassname( wep_spawner, wep + "_spawn" ) )
		{
			if ( wep_spawner.IsValid() )
			{
				local spawnTable =
				{
					origin = wep_spawner.GetOrigin(),
					angles = wep_spawner.GetAngles().ToKVString(),
					targetname = wep_spawner.GetName(),
					count = NetProps.GetPropInt( wep_spawner, "m_itemCount" ),
					spawnflags = NetProps.GetPropInt( wep_spawner, "m_spawnflags" )
				}
				wep_spawner.Kill();
				SpawnEntityFromTable(val, spawnTable);
			}
		}
	}
}

function OnGameEvent_finale_start( params )
{
	if ( Director.GetMapName() == "c6m3_port" )
		DirectorOptions.cm_MaxSpecials = 2;
}

function OnGameEvent_gauntlet_finale_start( params )
{
	if ( Director.GetMapName() == "c5m5_bridge" )
		DirectorOptions.cm_MaxSpecials = 2;
}

function Update()
{
	if ( Director.GetCommonInfectedCount() > 0 )
	{
		local infected = null;
		while ( infected = Entities.FindByClassname( infected, "infected" ) )
		{
			if ( infected.IsValid() )
				infected.Kill();
		}
	}
}

Mutation2 - Headshot!


//-----------------------------------------------------
Msg("Activating Mutation 2\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_HeadshotOnly = 1
}

Mutation3 - Bleed Out


//-----------------------------------------------------
Msg("Activating Mutation 3\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	weaponsToConvert =
	{
		weapon_first_aid_kit = "weapon_pain_pills_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}	

	// Challenge vars
	cm_TempHealthOnly = 1
	cm_AllowPillConversion = 0
	cm_ShouldHurry = 1
	cm_MaxSpecials = 4
	cm_SpecialRespawnInterval = 45
	cm_WanderingZombieDensityModifier = 0.003

	// Global vars
	MobSpawnMinTime = 5
	MobSpawnMaxTime = 10
	MobMinSize = 10
	MobMaxSize = 15
	MobMaxPending = 30
	SustainPeakMinTime = 10
	SustainPeakMaxTime = 15
	IntensityRelaxThreshold = 0.95
	RelaxMinInterval = 3
	RelaxMaxInterval = 5
	RelaxMaxFlowTravel = 50
	PreferredMobDirection = SPAWN_BEHIND_SURVIVORS

	TempHealthDecayRate = 0.001
	function RecalculateHealthDecay()
	{
		if ( Director.HasAnySurvivorLeftSafeArea() )
		{
			TempHealthDecayRate = 0.27 // pain_pills_decay_rate default
		}
	}
}

function Update()
{
	DirectorOptions.RecalculateHealthDecay();
}

Mutation4 - Hard Eight


//-----------------------------------------------------
Msg("Activating Mutation 4\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_SpecialRespawnInterval = 15
	cm_MaxSpecials = 8
	cm_BaseSpecialLimit = 2

	DominatorLimit = 8
}

Mutation5 - Four Swordsmen


//-----------------------------------------------------
Msg("Activating Mutation 5\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_CommonLimit = 0
	cm_DominatorLimit = 8
	cm_MaxSpecials = 8
	cm_SpecialRespawnInterval = 15

	SpecialInitialSpawnDelayMin = 5
	SpecialInitialSpawnDelayMax = 30
	
	// convert items that aren't useful
	weaponsToConvert =
	{
		weapon_pipe_bomb =	"weapon_molotov_spawn"
		weapon_vomitjar =	"weapon_molotov_spawn"
		weapon_defibrillator =	"weapon_first_aid_kit_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}	

	weaponsToRemove =
	{
		weapon_pistol = 0
		weapon_pistol_magnum = 0
		weapon_smg = 0
		weapon_pumpshotgun = 0
		weapon_autoshotgun = 0
		weapon_rifle = 0
		weapon_hunting_rifle = 0
		weapon_smg_silenced = 0
		weapon_shotgun_chrome = 0
		weapon_rifle_desert = 0
		weapon_sniper_military = 0
		weapon_shotgun_spas = 0
		weapon_grenade_launcher = 0
		weapon_rifle_ak47 = 0
		weapon_smg_mp5 = 0
		weapon_rifle_sg552 = 0
		weapon_sniper_awp = 0
		weapon_sniper_scout = 0
		weapon_rifle_m60 = 0
		weapon_melee = 0
		weapon_chainsaw = 0
		weapon_upgradepack_incendiary = 0
		weapon_upgradepack_explosive = 0
		ammo = 0
		upgrade_item = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}

	function ShouldAvoidItem( classname )
	{
		if ( ( classname != "weapon_melee" ) && ( classname in weaponsToRemove ) )
		{
			return true;
		}
		return false;
	}

	DefaultItems =
	[
		"katana",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	local spawner = null;
	while ( spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ) )
	{
		if ( spawner.IsValid() )
		{
			local population = NetProps.GetPropString( spawner, "m_szPopulation" );
			
			if ( population == "boomer" || population == "hunter" || population == "smoker" || population == "jockey"
				|| population == "charger" || population == "spitter" || population == "new_special" || population == "church"
					|| population == "tank" || population == "witch" || population == "witch_bride" || population == "river_docks_trap" )
				continue;
			else
				spawner.Kill();
		}
	}
	
	if ( Director.GetMapName() == "c5m5_bridge" || Director.GetMapName() == "c6m3_port" )
		DirectorOptions.cm_MaxSpecials = 0;
	
	EntFire( "weapon_spawn", "Kill" );
	foreach( wep, val in DirectorOptions.weaponsToRemove )
		EntFire( wep + "_spawn", "Kill" );
}

function OnGameEvent_finale_start( params )
{
	if ( Director.GetMapName() == "c6m3_port" )
		DirectorOptions.cm_MaxSpecials = 8;
}

function OnGameEvent_gauntlet_finale_start( params )
{
	if ( Director.GetMapName() == "c5m5_bridge" )
		DirectorOptions.cm_MaxSpecials = 8;
}

function Update()
{
	if ( Director.GetCommonInfectedCount() > 0 )
	{
		local infected = null;
		while ( infected = Entities.FindByClassname( infected, "infected" ) )
		{
			if ( infected.IsValid() )
				infected.Kill();
		}
	}
}

Mutation6

This 'mutation' is missing from gamemodes.txt since release, apparently. The .nuc was removed from the game files between 2.0.2.7 and 2.0.4.5.


//-----------------------------------------------------
Msg("Activating Mutation 6\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_MaxSpecials = 0
	cm_CommonLimit = 0
	cm_WitchLimit = 0
	cm_TankLimit = 0
	cm_ProhibitBosses = 1
	cm_NoRescueClosets = 1
}

Mutation7 - Chainsaw Massacre


//-----------------------------------------------------
Msg("Activating Mutation 7\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_InfiniteFuel = 1
	cm_ShouldHurry = 1

	weaponsToRemove =
	{
		weapon_pistol = 0
		weapon_pistol_magnum = 0
		weapon_smg = 0
		weapon_pumpshotgun = 0
		weapon_autoshotgun = 0
		weapon_rifle = 0
		weapon_hunting_rifle = 0
		weapon_smg_silenced = 0
		weapon_shotgun_chrome = 0
		weapon_rifle_desert = 0
		weapon_sniper_military = 0
		weapon_shotgun_spas = 0
		weapon_grenade_launcher = 0
		weapon_rifle_ak47 = 0
		weapon_smg_mp5 = 0
		weapon_rifle_sg552 = 0
		weapon_sniper_awp = 0
		weapon_sniper_scout = 0
		weapon_rifle_m60 = 0
		weapon_melee = 0
		weapon_chainsaw = 0
		weapon_upgradepack_incendiary = 0
		weapon_upgradepack_explosive = 0
		ammo = 0
		upgrade_item = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}

	function ShouldAvoidItem( classname )
	{
		if ( ( classname != "weapon_chainsaw" ) && ( classname in weaponsToRemove ) )
		{
			return true;
		}
		return false;
	}

	DefaultItems =
	[
		"weapon_chainsaw",
		"weapon_pistol",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	EntFire( "weapon_spawn", "Kill" );
	foreach( wep, val in DirectorOptions.weaponsToRemove )
		EntFire( wep + "_spawn", "Kill" );
}

Mutation8 - Iron Man


//-----------------------------------------------------
Msg("Activating Mutation 8\n");


DirectorOptions <- 
{
	ActiveChallenge = 1

	cm_AllowSurvivorRescue = 0

	weaponsToRemove =
	{
		ammo = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}	
}

Mutation9 - Last Gnome On Earth


//-----------------------------------------------------
Msg("Activating Mutation 9\n");


DirectorOptions <- 
{
	ActiveChallenge = 1

	cm_VIPTarget = 1
	cm_ShouldHurry = 1
}

Mutation10 - Room For One


//-----------------------------------------------------
Msg("Activating Mutation 10\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_FirstManOut = 1
}

Mutation11 - Healthpackalypse!


//-----------------------------------------------------
Msg("Activating Mutation 11\n");

DirectorOptions <-
{
	ActiveChallenge = 1

	weaponsToRemove =
	{
		weapon_first_aid_kit = 0
		weapon_pain_pills = 0
		weapon_adrenaline = 0
		weapon_defibrillator = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}		
}

Mutation12 - Realism Versus


//-----------------------------------------------------
Msg("Activating Mutation 12\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_AllowPillConversion = 0

	ZombieGhostDelayMin = 15
	ZombieGhostDelayMax = 25

	weaponsToRemove =
	{
		weapon_first_aid_kit = 0
		weapon_defibrillator = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}		

	DefaultItems =
	[
		"weapon_first_aid_kit",
		"weapon_pistol",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}	
}

Mutation13 - Follow the Liter


//-----------------------------------------------------
Msg("Activating Mutation 13\n");


DirectorOptions <- 
{
	ActiveChallenge = 1

	cm_SingleScavengeCluster = 1

	ScavengeScoreBonusTime = 30
}

Mutation14 - Gib Fest


//-----------------------------------------------------
Msg("Activating Mutation 14\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	weaponsToRemove =
	{
		weapon_pistol = 0
		weapon_pistol_magnum = 0
		weapon_smg = 0
		weapon_pumpshotgun = 0
		weapon_autoshotgun = 0
		weapon_rifle = 0
		weapon_hunting_rifle = 0
		weapon_smg_silenced = 0
		weapon_shotgun_chrome = 0
		weapon_rifle_desert = 0
		weapon_sniper_military = 0
		weapon_shotgun_spas = 0
		weapon_grenade_launcher = 0
		weapon_rifle_ak47 = 0
		weapon_smg_mp5 = 0
		weapon_rifle_sg552 = 0
		weapon_sniper_awp = 0
		weapon_sniper_scout = 0
		weapon_rifle_m60 = 0
		weapon_melee = 0
		weapon_chainsaw = 0
		ammo = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}

	function ShouldAvoidItem( classname )
	{
		if ( ( classname != "weapon_rifle_m60" && classname != "weapon_pistol_magnum" ) && ( classname in weaponsToRemove ) )
		{
			return true;
		}
		return false;
	}

	DefaultItems =
	[
		"weapon_rifle_m60",
		"weapon_pistol_magnum",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	EntFire( "weapon_spawn", "Kill" );
	foreach( wep, val in DirectorOptions.weaponsToRemove )
		EntFire( wep + "_spawn", "Kill" );
}

Mutation15 - Versus Survival


//-----------------------------------------------------
Msg("Activating Mutation 15\n");

DirectorOptions <-
{
	ActiveChallenge = 1

	cm_ProhibitBosses = false
	cm_CommonLimit = 25
	cm_TankLimit = 1
	ZombieTankHealth = 2667

	SurvivalSetupTime = 90

	weaponsToRemove =
	{
		weapon_upgradepack_explosive = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}
}

function OnGameEvent_survival_round_start( params )
{
	local validStartNames = { func_button_timed = "OnTimeUp", func_button = "OnPressed", script_func_button = "OnPressed", trigger_finale = "UseStart", prop_door_rotating = "OnOpen" };
	local function GetSurvivalStartEntity()
	{
		local validIO = { logic_relay = { input = "Trigger", output = "OnTrigger" }, logic_timer = { input = "Enable", output = "OnTimer" } };
		local function CheckOutputs( entity, outputName )
		{
			local ent = entity;
			local output = outputName;
			while ( ent )
			{
				local target = null;
				local targetOutput = "";
				local nElements = EntityOutputs.GetNumElements( ent, output );
				for ( local i = 0; i < nElements; i++ )
				{
					local outputs = {};
					EntityOutputs.GetOutputTable( ent, output, outputs, i );
					local outputTarget = Entities.FindByName( null, outputs.target );
					if ( !outputTarget )
						continue;

					if ( outputTarget.GetClassname() == "info_director" && outputs.input.find( "PanicEvent" ) != null )
						return outputTarget;
					else
					{
						if ( (outputTarget.GetClassname() in validIO) && (outputs.input == validIO[outputTarget.GetClassname()].input) )
						{
							target = outputTarget;
							targetOutput = validIO[outputTarget.GetClassname()].output;
						}
					}
				}
				ent = target;
				output = targetOutput;
			}
			return null;
		}

		foreach( classname, output in validStartNames )
		{
			for ( local ent; ent = Entities.FindByClassname( ent, classname ); )
			{
				if ( !EntityOutputs.HasAction( ent, output ) )
					continue;

				local target = CheckOutputs( ent, output );
				if ( (!target) || (target.GetClassname() != "info_director") )
					continue;

				return ent;
			}
		}
		return null;
	}

	EntFire( "info_director", "PanicEvent" );

	local startEntity = GetSurvivalStartEntity();
	if ( startEntity )
	{
		if ( startEntity.GetClassname() == "func_button_timed" || startEntity.GetClassname() == "trigger_finale" )
		{
			local nElements = EntityOutputs.GetNumElements( startEntity, validStartNames[startEntity.GetClassname()] );
			for ( local i = 0; i < nElements; i++ )
			{
				local outputs = {};
				EntityOutputs.GetOutputTable( startEntity, validStartNames[startEntity.GetClassname()], outputs, i );
				EntFire( outputs.target, outputs.input, outputs.parameter, outputs.delay );
			}
		}
		else
		{
			EntFire( startEntity.GetName(), "Unlock" );
			EntFire( startEntity.GetName(), "Press" );
			EntFire( startEntity.GetName(), "Open" );
		}
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	local bRemoveClips = false;
	switch( SessionState.MapName )
	{
		// For comments and elaboration regarding these map fixes, see:
		//	Collection of Versus Survival & Taaannnk! Mutation stuck spawn fixes
		//	https://gist.github.com/Tsuey/e8996ad10bb7fb72e7d6b2aaa7bcd5db
		// Most of these fixes should eventually be replaced with a fuller clip brush re-work of all base maps for modders/PvP,
		// such as we already did for TLS at least with regard to new player SI areas opened on c8m3 (seen with ShowUpdate tool).

		case "c1m2_streets":
		{
			EntFire( g_UpdateName + "_yeswayturnpike_clipb", "Kill", null, 30 );		// Delay oddly required
			bRemoveClips = true;
			break;
		}
		case "c3m1_plankcountry":
		{
			EntFire( g_UpdateName + "_meticulous_funcinfclip01", "Kill", null, 30 );	// Delay oddly required
			make_axiswarp( "_axiswarp_semitrailer", "y-", 96, "-550 -8 0", "740 8 256", "-9505 10720 155" );
			bRemoveClips = true;
			break;
		}
		case "c5m4_quarter":
		{
			bRemoveClips = true;
			break;
		}
		case "c8m2_subway":
		{
			make_prop( "dynamic", "_cosmetic_oobstep", "models/props/cs_office/vending_machine.mdl", "7366 3801 270", "90 0 0", "shadow_no", "solid_no" );
			break;
		}
		case "c9m2_lots":
		{
			kill_entity( Entities.FindByClassnameNearest( "prop_dynamic", Vector( 8521, 5815, 348 ), 1 ) );
			break;
		}
		case "c10m2_drainage":
		{
			kill_entity( Entities.FindByClassnameNearest( "prop_dynamic", Vector( -8972, -7890, -320 ), 1 ) );
			make_ladder( "_ladder_versussurvivalfence1_cloned_tunnelmid", "-9478.5 -7280 -384", "155 45 -410" );
			make_ladder( "_ladder_versussurvivalfence2_cloned_tunnelmid", "-9478.5 -7280 -384", "155 0 -410" );
			make_ladder( "_ladder_versussurvivalfence3_cloned_tunnelmid", "-9478.5 -7280 -384", "155 -45 -410" );
			bRemoveClips = true;
			break;
		}
		case "c10m4_mainstreet":
		{
			make_clip( "_tankstuck_endbarricade", "SI Players", 1, "-400 -900 0", "400 900 1700", "3822 -3970 0" );
			make_clip( "_tankstuck_excesscorner", "SI Players", 1, "-200 -100 0", "200 100 1700", "-2520 -5048 -64" );
			break;
		}
		case "c11m2_offices":
		{
			kill_funcinfclip( 1043.94 );	// Final street left barricade
			break;
		}
		case "c12m3_bridge":
		{
			kill_funcinfclip( 311.003 );	// Wrongway at end of train tunnel
			break;
		}
		default:
			break;
	}

	if ( bRemoveClips )
		EntFire( "func_playerinfected_clip", "Kill" );
}

Mutation16 - Hunting Party


//-----------------------------------------------------
Msg("Activating Mutation 16\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_SpecialSlotCountdownTime = 15
	cm_SpecialRespawnInterval = 1
	cm_MaxSpecials = 4

	HunterLimit = 4
	BoomerLimit = 0
	SmokerLimit = 0
	SpitterLimit = 0
	ChargerLimit = 0
	JockeyLimit = 0
	
	DominatorLimit = 4
}

Mutation17 - Lone Gunman


//-----------------------------------------------------
Msg("Activating Mutation 17\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_NoSurvivorBots = 1

	cm_CommonLimit = 10
	cm_BaseCommonAttackDamage = 15
	cm_WanderingZombieDensityModifier = 0.015

	HunterLimit = 0
	SmokerLimit = 0
	ChargerLimit = 0
	JockeyLimit = 0
	SpitterLimit = 0

	weaponsToRemove =
	{
		weapon_pistol = 0
		weapon_smg = 0
		weapon_pumpshotgun = 0
		weapon_autoshotgun = 0
		weapon_rifle = 0
		weapon_hunting_rifle = 0
		weapon_smg_silenced = 0
		weapon_shotgun_chrome = 0
		weapon_rifle_desert = 0
		weapon_sniper_military = 0
		weapon_shotgun_spas = 0
		weapon_grenade_launcher = 0
		weapon_rifle_ak47 = 0
		weapon_smg_mp5 = 0
		weapon_rifle_sg552 = 0
		weapon_sniper_awp = 0
		weapon_sniper_scout = 0
		weapon_rifle_m60 = 0
		weapon_melee = 0
		weapon_chainsaw = 0
		weapon_rifle_m60 = 0
		ammo = 0
		upgrade_item = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}

	DefaultItems =
	[
		"weapon_pistol_magnum",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	EntFire( "weapon_spawn", "Kill" );
	foreach( wep, val in DirectorOptions.weaponsToRemove )
		EntFire( wep + "_spawn", "Kill" );
}

Mutation18 - Bleed Out Versus


//-----------------------------------------------------
Msg("Activating Mutation 18\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	weaponsToConvert =
	{
		weapon_first_aid_kit = "weapon_pain_pills_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}	

	// Challenge vars
	cm_TempHealthOnly = 1
	cm_AllowPillConversion = 0
	cm_ShouldHurry = 1

	TempHealthDecayRate = 0.001
	function RecalculateHealthDecay()
	{
		if ( Director.HasAnySurvivorLeftSafeArea() )
		{
			TempHealthDecayRate = 0.27 // pain_pills_decay_rate default
		}
	}
}

function Update()
{
	DirectorOptions.RecalculateHealthDecay();
}

Mutation19 - Taaannnk!!


//-----------------------------------------------------
Msg("Activating Mutation 19\n");

DirectorOptions <-
{
	ActiveChallenge = 1

//	cm_frustrationTimer = 0
	cm_TankRun = true
	cm_ShouldHurry = true
	cm_AutoSpawnInfectedGhosts = true

	PreferredSpecialDirection = SPAWN_BEHIND_SURVIVORS
	SpawnBehindSurvivorsDistance = 2000
	TankRunSpawnDelay = 15

	MobMaxPending = 0

	// Always convert to the TANK!!!
	function ConvertZombieClass( iClass )
	{
		return 8;
	}

	weaponsToConvert =
	{
		weapon_first_aid_kit = "weapon_pain_pills_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	local function CheckOutputs( entity, outputName )
	{
		local ent = entity;
		local output = outputName;
		while ( ent )
		{
			local target = null;
			local targetOutput = "";
			local nElements = EntityOutputs.GetNumElements( ent, output );
			for ( local i = 0; i < nElements; i++ )
			{
				local outputs = {};
				EntityOutputs.GetOutputTable( ent, output, outputs, i );
				local outputTarget = Entities.FindByName( null, outputs.target );
				if ( !outputTarget )
					continue;

				if ( outputs.input == "Unlock" || outputs.input == "Enable" || outputs.input == "SetValueTest" || outputs.input == "MoveToFloor" )
					return outputTarget;
				else
				{
					if ( outputTarget.GetClassname() == "logic_relay" && outputs.input == "Trigger" )
					{
						target = outputTarget;
						targetOutput = "OnTrigger";
					}
				}
			}
			ent = target;
			output = targetOutput;
		}
		return null;
	}
	for ( local trigger; trigger = Entities.FindByClassname( trigger, "trigger_multiple" ); )
	{
		if ( NetProps.GetPropInt( trigger, "m_bAllowIncapTouch" ) == 0 || !EntityOutputs.HasAction( trigger, "OnEntireTeamStartTouch" ) )
			continue;

		NetProps.SetPropInt( trigger, "m_bAllowIncapTouch", 0 );
		local target = CheckOutputs( trigger, "OnEntireTeamStartTouch" );
		if ( !target )
			continue;

		if ( target.GetClassname() == "logic_relay" )
		{
			for ( local button; button = Entities.FindByClassname( button, "func_button" ); )
			{
				local nElements = EntityOutputs.GetNumElements( button, "OnPressed" );
				for ( local i = 0; i < nElements; i++ )
				{
					local outputs = {};
					EntityOutputs.GetOutputTable( button, "OnPressed", outputs, i );
					if ( outputs.target == target.GetName() )
					{
						target = button;
						break;
					}
				}
			}
		}
		else if ( target.GetClassname() == "logic_branch" )
		{
			local foundListener = false;
			for ( local listener; listener = Entities.FindByClassname( listener, "logic_branch_listener" ); )
			{
				for ( local i = 0; i < 16; i++ )
				{
					if ( NetProps.GetPropString( listener, "m_nLogicBranchNames[" + i + "]" ) == target.GetName() )
					{
						foundListener = true;
						break;
					}
				}

				if ( foundListener )
				{
					local nElements = EntityOutputs.GetNumElements( listener, "OnAllTrue" );
					for ( local i = 0; i < nElements; i++ )
					{
						local outputs = {};
						EntityOutputs.GetOutputTable( listener, "OnAllTrue", outputs, i );
						if ( outputs.input == "Unlock" )
						{
							target = Entities.FindByName( null, outputs.target );
							break;
						}
					}
					break;
				}
			}
		}

		local validOutputs = { func_button_timed = "OnTimeUp", func_button = "OnPressed", script_func_button = "OnPressed", prop_door_rotating = "OnOpen" };
		if ( !target.GetClassname() in validOutputs )
			continue;

		target.ValidateScriptScope();
		local targetScope = target.GetScriptScope();
		targetScope.OverrideTrigger <- trigger;
		targetScope.UseEnt <- function()
		{
			for ( local player; player = Entities.FindByClassname( player, "player" ); )
			{
				if ( player.IsSurvivor() && player.IsIncapacitated() )
				{
					if ( !OverrideTrigger.IsTouching( player ) )
						player.TakeDamage( player.GetHealth(), 0, null );
				}
			}
			self.DisconnectOutput( validOutputs[target.GetClassname()], "UseEnt" );
		}
		target.ConnectOutput( validOutputs[target.GetClassname()], "UseEnt" );
	}

	local bRemoveClips = false;
	switch( SessionState.MapName )
	{
		// For comments and elaboration regarding these map fixes, see:
		//	Collection of Versus Survival & Taaannnk! Mutation stuck spawn fixes
		//	https://gist.github.com/Tsuey/e8996ad10bb7fb72e7d6b2aaa7bcd5db
		// Most of these fixes should eventually be replaced with a fuller clip brush re-work of all base maps for modders/PvP,
		// such as we already did for TLS at least with regard to new player SI areas opened on c8m3 (seen with ShowUpdate tool).

		case "c1m2_streets":
		{
			EntFire( g_UpdateName + "_yeswayturnpike_clipb", "Kill", null, 30 );		// Delay oddly required
			bRemoveClips = true;
			break;
		}
		case "c2m2_fairgrounds":
		{
			bRemoveClips = true;
			break;
		}
		case "c3m1_plankcountry":
		{
			EntFire( g_UpdateName + "_meticulous_funcinfclip01", "Kill", null, 30 );	// Delay oddly required
			make_axiswarp( "_axiswarp_semitrailer", "y-", 96, "-550 -8 0", "740 8 256", "-9505 10720 155" );
			bRemoveClips = true;
			break;
		}
		case "c5m2_park":
		{
			make_clip( "_tankstuck_startroof", "SI Players", 1, "-106 -240 0", "86 240 196", "-2936 -816 -58" );
			break;
		}
		case "c5m4_quarter":
		{
			bRemoveClips = true;
			break;
		}
		case "c6m1_riverbank":
		{
			make_clip( "_tankstuck_startfence", "SI Players", 1, "-251 -760 0", "173 216 1513", "-261 2872 87" );
			break;
		}
		case "c8m1_apartment":
		{
			make_clip( "_tankstuck_alleystart", "SI Players", 1, "-105 -711 0", "823 57 4132", "2473 455 16" );
			make_clip( "_tankstuck_alleymiddle", "SI Players", 1, "-127 -257 0", "209 256 4468", "-129 3584 16" );
			kill_funcinfclip( 1060.85 );	// Delete clip that blocks the rubble and a cool unique spawn
			break;
		}
		case "c8m2_subway":
		{
			make_clip( "_tankstuck_rubblestart", "SI Players", 1, "-139 -107 0", "119 99 177", "1621 3617 -337", "0 42 0" );
			make_clip( "_tankstuck_rubblemiddle", "SI Players", 1, "-189 -145 0", "300 179 240", "6766 5299 -336", "0 -68 0" );
			make_clip( "_tankstuck_rubblefinal", "SI Players", 1, "-189 -253 0", "140 320 176", "8249 3222 -336", "0 -53 0" );
			make_clip( "_tankstuck_endalley1", "SI Players", 1, "-480 -855 0", "65 293 1400", "10303 3479 16" );
			make_clip( "_tankstuck_endalley2", "SI Players", 1, "-373 -96 0", "395 99 1400", "11637 5342 16" );
			make_clip( "_tankstuck_widestreet", "SI Players", 1, "-842 0 0", "386 555 1400", "10110 6784 8" );
			break;
		}
		case "c8m3_sewers":
		{
			make_clip( "_tankstuck_startrooftops", "SI Players", 1, "-1749 -380 0", "219 332 750", "12261 4212 464" );
			kill_funcinfclip( 606.217 );	// Delete clip behind a fence and inaccessible ladder
			kill_funcinfclip( 733.138 );	// Delete clip for end area backstreet wrongway sign left
			kill_funcinfclip( 762.564 );	// Delete clip for end area backstreet wrongway sign right
			break;
		}
		case "c9m2_lots":
		{
			make_clip( "_tankstuck_startback", "SI Players", 1, "-120 -900 0", "120 900 1370", "-835 -110 -223" );
			break;
		}
		case "c10m1_caves":
		{
			kill_funcinfclip( 1145.18 );	// Spawn left 1st
			kill_funcinfclip( 1127.68 );	// Spawn left 2nd
			kill_funcinfclip( 1110.42 );	// Spawn left 3rd
			kill_funcinfclip( 1059.74 );	// Spawn left 4th
			kill_funcinfclip( 1054.83 );	// Spawn left 5th
			kill_funcinfclip( 1216.59 );	// Tunnelside
			kill_funcinfclip( 1000.2 );	// Cliffside
			kill_funcinfclip( 597.979 );	// End cave connection to map 2 (easter egg spot)
			make_clip( "_tankstuck_tunnelside", "SI Players", 1, "-1700 -200 -1337", "1700 1024 610", "-13902 -10396 656", "0 45 0" );
			break;
		}
		case "c10m2_drainage":
		{
			bRemoveClips = true;
			break;
		}
		case "c10m3_ranchhouse":
		{
			kill_funcinfclip( 668.433 );	// Wrongway in beginning area
			kill_funcinfclip( 2248.58 );	// Treeline after bus/shed
			make_clip( "_tankstuck_cornerpath", "SI Players", 1, "-700 -800 -145", "800 790 1360", "-13598 -9362 68" );
			make_clip( "_tankstuck_mountainwedge", "SI Players", 1, "-100 -100 0", "100 100 1300", "-8388 -3525 300" );
			break;
		}
		case "c10m4_mainstreet":
		{
			make_clip( "_tankstuck_houserow", "SI Players", 1, "-2260 -440 0", "3450 900 1850", "68 158 -100" );
			make_clip( "_tankstuck_endbarricade", "SI Players", 1, "-400 -900 0", "400 900 1700", "3822 -3970 0" );
			make_clip( "_tankstuck_excesscorner", "SI Players", 1, "-200 -100 0", "200 100 1700", "-2520 -5048 -64" );
			break;
		}
		case "c11m1_greenhouse":
		{
			kill_funcinfclip( 1154.43 );	// End area fence barricade
			break;
		}
		case "c11m2_offices":
		{
			make_clip( "_tankstuck_fencenavarea", "SI Players", 1, "-64 -64 0", "64 64 1400", "10314 3862 16" );
			kill_funcinfclip( 1043.94 );	// Final street left barricade
			kill_funcinfclip( 1493.36 );	// Final street right barricade
			break;
		}
		case "c11m3_garage":
		{
			make_clip( "_tankstuck_startfence", "SI Players", 1, "-1200 -80 0", "720 58 1800", "-4947 -3786 16" );
			break;
		}
		case "c12m1_hilltop":
		{
			make_clip( "_tankstuck_wrongwayone", "SI Players", 1, "-320 -800 0", "0 750 1740", "-11967 -10090 450" );
			make_clip( "_tankstuck_wrongwaytwo", "SI Players", 1, "-610 0 0", "180 460 1740", "-11604 -9345 450" );
			break;
		}
		case "c12m2_traintunnel":
		{
			make_clip( "_tankstuck_tunnelend", "SI Players", 1, "-320 -800 -120", "450 258 240", "-1551 -10785 50" );
			break;
		}
		case "c12m3_bridge":
		{
			kill_funcinfclip( 311.003 );	// Wrongway at end of train tunnel
			break;
		}
		case "c12m4_barn":
		{
			make_clip( "_tankstuck_safeback", "SI Players", 1, "-170 -640 -100", "170 225 1700", "7704 -11710 425" );
			break;
		}
		default:
			break;
	}

	if ( bRemoveClips )
		EntFire( "func_playerinfected_clip", "Kill" );
}

Mutation20 - Healing Gnome


//-----------------------------------------------------
Msg("Activating Mutation 20\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	weaponsToRemove =
	{
		weapon_first_aid_kit = 0
		weapon_pain_pills = 0
		weapon_adrenaline = 0
	}

	function AllowWeaponSpawn( classname )
	{
		if ( classname in weaponsToRemove )
		{
			return false;
		}
		return true;
	}

	// Challenge vars
	cm_TempHealthOnly = 1
	cm_AllowPillConversion = 0
	cm_HealingGnome = 1
	
	TempHealthDecayRate = 0.001
	function RecalculateHealthDecay()
	{
		if ( Director.HasAnySurvivorLeftSafeArea() )
		{
			TempHealthDecayRate = 0.27 // pain_pills_decay_rate default
		}
	}
}

function Update()
{
	DirectorOptions.RecalculateHealthDecay();
}

RocketDude

Technically, RocketDude was made by community members for the "The Last Stand" update, but listed as official because it is an official mutation.


//****************************************************************************************
//																						//
//									rocketDude.nut (mainfile)							//
//																						//
//****************************************************************************************

Msg("Activating RocketDude By ReneTM \n")

::rocketdude_version	<- "v1.7.7 build: 17:07:07 Feb 07 2021"
::mapName				<- Director.GetMapName().tolower()
::survivorSet			<- Director.GetSurvivorSet()
local grenadeData = {}




// Different scripts 2 include
// ----------------------------------------------------------------------------------------------------------------------------

IncludeScript("rocketdude/rd_debug")
IncludeScript("rocketdude/rd_utils")
IncludeScript("rocketdude/rd_melee_getter")
IncludeScript("rocketdude/rd_director")
IncludeScript("rocketdude/rd_damage_controll")
IncludeScript("rocketdude/rd_detonation_analysis")
IncludeScript("rocketdude/rd_meds")
IncludeScript("rocketdude/rd_events")
IncludeScript("rocketdude/rd_last_chance")
IncludeScript("rocketdude/rd_decals")
IncludeScript("rocketdude/rd_map_specifics")
IncludeScript("rocketdude/rd_custom_map_support")
IncludeScript("rocketdude/rd_saferoom_timer")
IncludeScript("rocketdude/rd_speedrun_mode")
IncludeScript("rocketdude/rd_mode_description")
IncludeScript("rocketdude/rd_hud_controller")




// Precache models and sounds
// ----------------------------------------------------------------------------------------------------------------------------

precacheSurvivorModels()

precacheRocketDudeModels()

precacheSounds()




// Creates a bullet time when the previous one is atleast 32 seconds ago.
// When this condition is met bullet time will occur with a probability of 3% 
// ----------------------------------------------------------------------------------------------------------------------------

local lastBulletTime = Time()
local lastDiceTime = Time()

::GLOBALS <-
{
	allowBulletTime = false
}

function bulletTime(){
	if(GLOBALS.allowBulletTime){
		if(Time() > lastBulletTime + 32){
			if(Time() >= lastDiceTime + 2){
				if(rollDice(3)){
					lastBulletTime = Time()
					DoEntFire( "!self", "Start", "", 0, timeScaler, timeScaler )
					DoEntFire( "!self", "Stop", "", 1, timeScaler, timeScaler )
				}
				lastDiceTime = Time()
			}
		}
	}
}

function saveGlobals(){
	SaveTable("GLOBAL_SAVINGS", GLOBALS)
}

function restoreGlobals(){
	RestoreTable("GLOBAL_SAVINGS", GLOBALS)
}




// When an instance of a grenade launcher projectile stops existing "doRocketJump" gets called
// ----------------------------------------------------------------------------------------------------------------------------

function grenadeExplodeEvent( impact ){
	foreach( player in getSurvivorsInRange( impact ) ){
		if(player.IsValid()){
			doRocketJump(impact, player)
		}
	}
}




// Returns an array of players within the blast radius
// ----------------------------------------------------------------------------------------------------------------------------

function getSurvivorsInRange(pos){
	local player = null
	while(player = Entities.FindByClassnameWithin(player, "player", pos, 160)){
		if(!player.IsDead()){
			if(!player.IsIncapacitated()){
				yield player
			}
		}
	}
}




// Compares current position of a projectile with the previous one. Force it to explode when it got stuck on prop_dynamic
// ----------------------------------------------------------------------------------------------------------------------------

function dynamicPropCheck(grenade){
	if(grenade in grenadeData){
		if(grenade.IsValid()){
			if((grenadeData[grenade] - grenade.GetOrigin()).Length() < 1){
				NetProps.SetPropInt(grenade, "m_takedamage", 1)
				grenade.TakeDamage(1337, 2, null)
			}	
		}
	}
}




// Get's fired 'OnGameplayStart' (usually after every single loadingscreen)
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameplayStart(){
	
	/* CREATE MUSHROOMS */
	if(IsValveMap()){
		spawnMushrooms()
	}
	
	/* SPAWN MAP SIDED MUSHROOMS */
	spawnMapSidedMushrooms()
	
	/* SET PLAYERS MAX HEALTH AND CURRENT HEALTH TO 200 */
	setPlayersHealth()
	
	/* GENERAL SETTINGS */
	checkCvars()

	/* CREATE BULLET TIME */
	createBulletTimerEntity()

	/* KILL ALL DEATH CAMS */
	removeDeathFallCameras()
	
	/* CREATE THINK TIMER */
	createThinkTimer()

	/* RESTORE SETTINGS LIKE BULLET TIME 1/0 */
	restoreGlobals()
}

::fixEntitiesRemoved <- false

::killFixEntities <- function(){
	if(!fixEntitiesRemoved){
		EntFire( "anv_mapfixes*", "Kill" )
		EntFire( "env_player_blocker", "Kill" )
		EntFire( "rene_relay", "Trigger" )
		fixEntitiesRemoved = true
	}
}




// Called for every player within the detonation radius, it will launch the player in the calculated direction
// ----------------------------------------------------------------------------------------------------------------------------

function doRocketJump(detonationPos, player){

	local hitSurface = getSurfaceValue(detonationPos, player)
	local ignoreDistance = 160
	local playerEyes = player.EyePosition()
	local finalVector = null
	
	local detonationDistance = (detonationPos - playerEyes).Length()
	local playerIsMidAir = NetProps.GetPropInt(player, "m_hGroundEntity") & 1
	local midAirFactor = 0
	local boostdirection = (detonationPos - playerEyes) * - 1
	local eyeToSurface = ((detonationPos - playerEyes).Length())
	local distanceFactor = (160 - eyeToSurface) / 10
	
	// WALL OR FLOOR ?
	if(hitSurface == "WALL"){
		distanceFactor *= 1.5
	}else{
		distanceFactor *= 1.4
	}
	
	if(detonationDistance <= ignoreDistance){
		if(playerIsMidAir){
			midAirFactor = 1
			finalVector = (player.GetVelocity() + boostdirection * midAirFactor * distanceFactor) 
		}else{
			midAirFactor = 0.5
			finalVector = (player.GetVelocity() + boostdirection * midAirFactor * distanceFactor)
		}
		player.SetVelocity(finalVector)
	}
}




// 1. Save current origin of projectiles
// 2. Check if projectile is stuck to prop_dynamic
// 3. Projectiles from bots are ignored ( just in case )
// 4. If the player enabled auto bhop ( by using green mushroom ) use another projectile model and set it to glowing
// 5. Get rockets which travel in a straight line
// 6. Check for invalid rockets and fire grenadeExplodeEvent
// ----------------------------------------------------------------------------------------------------------------------------

local grenadeColor = GetColorInt(Vector(64,64,64))
local grenadeGlowColor = GetColorInt(Vector(255,16,16))

function ProjectileGenerator(){
	local proj = null
	while(proj = Entities.FindByClassname(proj, "grenade_launcher_projectile")){
		yield proj
	}
}

function GrenadeListener(){
	local ent = null
	foreach(ent in ProjectileGenerator()){
		dynamicPropCheck(ent)
		if(!IsPlayerABot(NetProps.GetPropEntity(ent, "m_hThrower"))){
			grenadeData[ent] <- ent.GetOrigin()
		}
	
		local scope = GetValidatedScriptScope(ent)
	
		// Projectile model and color
		if(!("modelChanged" in scope)){
			if(NetProps.GetPropEntity(ent, "m_hThrower") in bunnyPlayers){
				ent.SetModel("models/w_models/weapons/w_rd_grenade_scale_x4_burn.mdl")
			}else{
				ent.SetModel("models/w_models/weapons/w_rd_grenade_scale_x4.mdl")
			}
			NetProps.SetPropInt(ent, "m_clrRender", grenadeColor)
			scope["modelChanged"] <- true
		}

		if(!("creationTimestamp" in scope)){
			scope["creationTimestamp"] <- Time()
		}
	
		// Projectile Glow
		if(Time() > scope["creationTimestamp"] + 0.12){
			if(!("glowEnabled" in scope)){
				if(NetProps.GetPropEntity(ent, "m_hThrower") in bunnyPlayers){
					NetProps.SetPropInt(ent, "m_Glow.m_glowColorOverride", grenadeGlowColor)
					NetProps.SetPropInt(ent, "m_Glow.m_nGlowRangeMin", 32)
					NetProps.SetPropInt(ent, "m_Glow.m_nGlowRange", 8192)
					NetProps.SetPropInt(ent, "m_Glow.m_iGlowType", 3)
					NetProps.SetPropInt(ent, "m_Glow.m_bFlashing", 1)
					scope["glowEnabled"] <- true
				}
			}
		}
		
		// Let rockets travel in a straight line like in TF2
		// Another method would be to change the ent´s gravity.
		// But this would result in reflecting projectiles
		// NetProps.SetPropFloat(nade, "m_flGravity", 0.00000001)
		
		if(!("first_dir_vec" in scope)){
			scope["first_dir_vec"] <- ent.GetVelocity()
			ent.SetVelocity(scope["first_dir_vec"])
		}else{
			ent.SetVelocity(scope["first_dir_vec"])
		}
	}

	// Check if saved instances are still valid. Fire "grenadeExplodeEvent" when this is not the case anymore 
	foreach(grenade, origin in grenadeData){
		if(!grenade.IsValid()){
			grenadeExplodeEvent(origin)
			grenadeData.rawdelete(grenade)
		}
	}
}




// Disable player´s ledge hang, set his max health to 200 and disable fall damage crack
// ----------------------------------------------------------------------------------------------------------------------------

local PlayerSettingsCheckTime = Time()
function PlayerSettings(){
	if(Time() > PlayerSettingsCheckTime + 4){
		foreach(player in GetSurvivors()){
			DoEntFire("!self", "DisableLedgeHang", "", 0.0, player, player)
			DoEntFire("!self", "ignorefalldamagewithoutreset", "99999", 0.0, player, player)
			//
			if(NetProps.GetPropInt(player, "m_iMaxHealth") != 200){
				NetProps.SetPropInt(player, "m_iMaxHealth", 200)
			}
			PlayerSettingsCheckTime = Time()
		}
	}
}


function setPlayersHealth(){
	foreach(player in GetSurvivors()){
		NetProps.SetPropInt(player, "m_iMaxHealth", 200)
		NetProps.SetPropInt(player, "m_iHealth", 200)
	}
}




// While the player´s primary and secondary weapon still have infinite ammo we want to remove used items from player inventory
// Since players are able to receive throwables from mushrooms we need to check if any survivor is standing right on a mushroom
// ----------------------------------------------------------------------------------------------------------------------------

function projectileListener(){
	local projectiles = [ "vomitjar_projectile", "molotov_projectile", "pipe_bomb_projectile" ]
	foreach(projectile in projectiles){
		local throwable = null
		while(throwable = Entities.FindByClassname(throwable, projectile)){
			if(throwable.IsValid()){
				local scope = GetValidatedScriptScope(throwable)
				if(!("inv_edited" in scope)){
					local invTable = {}
					local player = NetProps.GetPropEntity(throwable, "m_hThrower")
					// Special case for c4m3_sugarmill_b ( mapsided projectiles which destroys stuff )
					if(player == null || player.GetClassname() == "pipe_bomb_projectile"){
						return
					}
					GetInvTable(player, invTable)
					if("slot2" in invTable){
						invTable.slot2.Kill()
						scope["inv_edited"] <- true
					}

					foreach(trigger in medkit_triggers){
						DoEntFire("!self", "TouchTest", "", 0.03, trigger, trigger)
					}
				}
			}
		}
	}
}




// Check if mushrooms can be reactivated
// ----------------------------------------------------------------------------------------------------------------------------

function updateMushroomTrigger(){
	foreach(trigger in medkit_triggers){
		
		local scope = GetValidatedScriptScope(trigger)

		if(Time() >= scope.usetime + scope.restoreTime){
			if(scope.usable == false){
				scope.usetime = Time() - scope.restoreTime
				setMedVisibility(1, scope.model)
				scope.usable = true
				DoEntFire("!self", "TouchTest", "", 0, trigger, trigger)
			}
		}
	}
}




// Hold space for auto-bhop ( if player used bhop mushroom )
// ----------------------------------------------------------------------------------------------------------------------------

::bunnyPlayers <- {}

function autobhop(){
	foreach(player,ent in bunnyPlayers){
		if(ent.IsValid()){
			if(!(NetProps.GetPropInt(ent, "m_fFlags") & 1) && NetProps.GetPropInt(ent, "movetype") == 2){
				if(ent.GetButtonMask() & 2){
					ent.OverrideFriction(0.033, 0)
				}
				NetProps.SetPropInt(ent, "m_afButtonDisabled", NetProps.GetPropInt(ent, "m_afButtonDisabled") | 2)
			}else{
				NetProps.SetPropInt(ent, "m_afButtonDisabled", NetProps.GetPropInt(ent, "m_afButtonDisabled") & ~2)
			}
		}else{
			bunnyPlayers.rawdelete(player)
		}
	}
}








// Get's fired every tick from a timer
// ----------------------------------------------------------------------------------------------------------------------------

function Think(){
	checkCvars()
	GrenadeListener()
	PlayerSettings()
	projectileListener()
	updateMushroomTrigger()
	autobhop()
	PlayerFunctions()
	lastChanceCountDown()
	tankrockListener()
	safeRoomTimer()
}

RocketDude Additional Scripts

These scripts are all included in the main RocketDude script.

rd_custom_map_support

//****************************************************************************************
//																						//
//								rd_custom_map_support.nut								//
//																						//
//****************************************************************************************




// Spawn mushrooms from map entities 
// ----------------------------------------------------------------------------------------------------------------------------

spawnMapSidedMushrooms <- function(){
	
	local mushroomTarget = null

	local types = [ "tiny", "small", "medium", "large", "bh", "exp", "item" ]
	
	foreach(type in types){
		while(mushroomTarget = Entities.FindByName(mushroomTarget, "rd_mushroom_" + type)){
			createRD_Medkit(mushroomTarget.GetOrigin(), mushroomTarget.GetAngles().ToKVString(), false, shroomProperties[type])
		}
	}
	
	foreach(type in types){
		while(mushroomTarget = Entities.FindByName(mushroomTarget, "rd_mushroom_" + type + "_rotating")){
			createRD_Medkit(mushroomTarget.GetOrigin(), mushroomTarget.GetAngles().ToKVString(), true, shroomProperties[type])
		}
	}
}




// Timers and stats
// ----------------------------------------------------------------------------------------------------------------------------

::resetPlayerStats <- function(){
	if(activator.IsValid()){
		if(activator in PlayerTimeData){
			if(activator.GetZombieType() == ZombieTypes.SURVIVOR){
				local prevBest = PlayerTimeData[activator].time_best
				PlayerTimeData[activator] <- { timerActive = false, finished = false, startTime = Time(), endTime = Time(), time_best = prevBest, ticks = 0, seconds = 0 }
				ClientPrint(activator, 5, BLUE + activator.GetPlayerName() + " | 00:00")
				EmitAmbientSoundOn("ui/littlereward.wav", 0.5, 100, 110, activator)
			}
		}
	}
}

::startTimer <- function(){
	if(activator.IsValid()){
		if(activator in PlayerTimeData){
			if(activator.GetZombieType() == ZombieTypes.SURVIVOR){
				resetPlayerStats()
				PlayerTimeData[activator].timerActive = true
			}
		}
	}
}

::mapFinished <- function(){
	
	if(PlayerTimeData[activator].finished == true){
		return
	}
	
	printFinalGroundTime(activator)
	PlayerTimeData[activator].finished = true
	resetPlayerStats()
	
	local playerSpawn = Entities.FindByName(null, "rd_player_start")
	if(playerSpawn != null){
		activator.SetOrigin(playerSpawn.GetOrigin())
	}else{
		ClientPrint(null, 5, "ERROR: ENTITY 'rd_player_start' NOT FOUND")
	}
}
rd_damage_controll

//****************************************************************************************
//																						//
//									rd_damage_controll.nut								//
//																						//
//****************************************************************************************



// Fix grenade launcher applying damage to the player on multiple ticks 
// ----------------------------------------------------------------------------------------------------------------------------

lastDamageTimes <- {}

function AllowGrenadeLauncherDamage(player){
	
	if(!(player in lastDamageTimes)){
		lastDamageTimes[player] <- Time() - 1
	}
	
	if( Time() > lastDamageTimes[player] + 0.06 ){
			lastDamageTimes[player] <- Time()
			return true
	}else{
		lastDamageTimes[player] <- Time()
		return false
	}
}




// When to allow damage
// ----------------------------------------------------------------------------------------------------------------------------

function AllowTakeDamage(damageTable){
	local damageType = damageTable["DamageType"]
	local attacker = damageTable["Attacker"]
	local victim = damageTable["Victim"]
	local damageDone = damageTable["DamageDone"]
	
	// TANK ROCK
	if(damageTable.Victim.GetClassname() == "tank_rock"){
		if(attacker != null && attacker.IsPlayer() && attacker.GetZombieType() == 9){
			
			local victimScope = GetValidatedScriptScope(victim)

			if(!("dealtDamage" in victim.GetScriptScope())){
				victimScope["dealtDamage"] <- damageDone
			}else{
				victimScope["dealtDamage"] += damageDone
			}
			if(victimScope["dealtDamage"] >= 50){
				victimScope["dealtDamage"] <- -500
				if(attacker.IsIncapacitated()){
					if(!missionFailed){
						if(last_chance_active){
							stopLastChanceMode()
							attacker.ReviveFromIncap()
						}else{
							if(!allSurvivorsIncap()){
								attacker.ReviveFromIncap()
							}else{
								ClientPrint(null, 5, BLUE + "Time to say goodbye")
							}
						}
						EmitAmbientSoundOn("player/orch_hit_csharp_short", 1, 100, 100, attacker);	
					}
				}else{
					if((attacker.GetHealth() + 5) <= 200){
							attacker.SetHealth(attacker.GetHealth() + 5)
					}else{
						attacker.SetHealth(200)
					}
					attacker.UseAdrenaline(1)
				}
			}
		}
	return true
	}


	// DISABLE FALL DAMAGE
	if(damageType == 32){
		return false
	}
	
	// DISABLE FF
	if(attacker.IsPlayer()){
		if(victim.IsPlayer()){
			if(attacker.GetZombieType() == 9 && victim.GetZombieType() == 9){
				if(IsPlayerABot(attacker)){
					return false
				}
				
				if(damageTable.Weapon == null){
					if(!AllowGrenadeLauncherDamage(victim)){
						return false
					}else{
						damageTable.DamageDone = 1
						return true
					}
				}
			}
		}
	}
	return true
}
rd_debug

//****************************************************************************************
//																						//
//										rd_debug.nut									//
//																						//
//****************************************************************************************

// Check if dev mode is active
// ----------------------------------------------------------------------------------------------------------------------------

::DevModeActive <- function(){
	return Convars.GetFloat("developer")
}




// Switch from mushroom to mushroom
// ----------------------------------------------------------------------------------------------------------------------------

::debugMushroom <- null;

::GoToNextMushroom <- function(ent){
	if(!DevModeActive()){
		return
	}
	
	if(debugMushroom = Entities.FindByModel(debugMushroom, "models/props_collectables/mushrooms_glowing.mdl")){
		NetProps.SetPropInt(ent, "m_MoveType", 8)
		ent.SetOrigin(debugMushroom.GetOrigin() + Vector(0,0,64))
	}else{
		NetProps.SetPropInt(ent, "m_MoveType", 2)
	}
}
rd_decals

//****************************************************************************************
//																						//
//										rd_decals.nut									//
//																						//
//****************************************************************************************




// Will place decals depending on map
// ----------------------------------------------------------------------------------------------------------------------------

::placeRocketDudeDecals <- function(){
	local texture = "decals/rocketdude/rd_logo_glow"
	
	if(mapName in RocketDudeDecals){
		foreach(pos in RocketDudeDecals[mapName]){
			if(mapName == "c14m1_junkyard" || mapName == "c14m2_lighthouse"){
				texture = "decals/rocketdude/rd_logo_glow_big"
			}
			applyDecalAt(pos, texture)
		}
	}
}




// Applies a decal with texture X on position Y
// ----------------------------------------------------------------------------------------------------------------------------

::applyDecalAt <- function (pos, tex){
	local decal = SpawnEntityFromTable( "infodecal", { targetname = "rd_decal", texture = tex, LowPriority = 0, origin = pos } )
	DoEntFire( "!self", "Activate", "", 0.0, decal, decal )
}




// Decal data for maps c1 - c14
// ----------------------------------------------------------------------------------------------------------------------------

::RocketDudeDecals <-
{
	c1m1_hotel			= [ Vector(2064.64,6783.97,2882) ]
	c1m2_streets		= [ Vector(-1174.98,728.091,912.947) ]
	c1m3_mall			= [ Vector(1776.31,-2015.99,642.575) ]
	c1m4_atrium			= [ Vector(-3927.97,-3403.11,856.501) ]

	c2m1_highway		= [ Vector(1338.82,-1736.51,-1087.55) ]
	c2m2_fairgrounds	= [ Vector(-495.969,-1238.1,187.207) ]
	c2m3_coaster		= [ Vector(303.034,3328.03,349.983) ]
	c2m4_barns 			= [ Vector(3717.22,576.031,-127.915) ]
	c2m5_concert 		= [ Vector(-4368.03,2687,236.19) ]

	c3m1_plankcountry 	= [ Vector(-3712,4349.86,-74.6688) ]
	c3m2_swamp 			= [ Vector(-3923.97,4650.4,85.4511) ]
	c3m3_shantytown 	= [ Vector(-5602.41,-1635.03,258.268) ]
	c3m4_plantation 	= [ Vector(1853.52,76.6953,600.031) ]

	c4m1_milltown_a 	= [ Vector(1643.17,4338.16,433.805) ]
	c4m2_sugarmill_a 	= [ Vector(2966.24,-3271.43,1200.15) ]
	c4m3_sugarmill_b 	= [ Vector(1424.04,-6088.03,416.549) ]
	c4m4_milltown_b 	= [ Vector(1437.01,6364.88,334.595) ]
	c4m5_milltown_escape = [ Vector(-5874.37,8072.03,377.766) ]

	c5m1_waterfront		= [ Vector(-2144.03,-2508.57,-334.026) ]
	c5m2_park			= [ Vector(-10102.6,-5538.72,48.0313) ]
	c5m3_cemetery		= [ Vector(3511.2,3503.97,258.566) ]
	c5m4_quarter		= [ Vector(-2060.97,3392.03,132.305) ]
	c5m5_bridge			= [ Vector(14288.3,6326.82,790.031) ]

	c6m1_riverbank		= [ Vector(1301.35,1616.03,578.07) ]
	c6m2_bedlam			= [ Vector(3964.55,4096.03,-549.714) ]
	c6m3_port			= [ Vector(-1258.88,-307.692,480.031) ]

	c7m1_docks			= [ Vector(2835.56,901.987,448.031) ]
	c7m2_barge 			= [ Vector(-10349.8,415.969,341.246) ]
	c7m3_port 			= [ Vector(-2223.97,-537.898,-32.6826) ]

	c8m1_apartment 		= [ Vector(1710.64,4568.03,264.95) ]
	c8m2_subway 		= [ Vector(9519.12,3772.03,71.1739) ]
	c8m3_sewers 		= [ Vector(15280,15322.9,72.8583) ]
	c8m4_interior 		= [ Vector(13064,14173,632.224) ]
	c8m5_rooftop		= [ Vector(7291.74,8976.03,470.528) ]

	c9m1_alleys			= [ Vector(1466.98,-1663.97,-161.193) ]
	c9m2_lots			= [ Vector(4135.19,1263.97,252.13) ]

	c10m1_caves			= [ Vector(-12608,-9505.11,27.5156) ]
	c10m2_drainage		= [ Vector(-9007.97,-9040.36,-377.031) ]
	c10m3_ranchhouse	= [ Vector(-9690.9,-7808.03,545.451) ]
	c10m4_mainstreet	= [ Vector(-1181.2,-4732.03,253.471) ]
	c10m5_houseboat		= [ Vector(2239.22,4086.55,72.0313) ]

	c11m1_greenhouse	= [ Vector(3248.03,961.976,69.2323) ]
	c11m2_offices		= [ Vector(5168.03,137.882,69.3103) ]
	c11m3_garage		= [ Vector(-2448.03,1375.55,217.825) ]
	c11m4_terminal		= [ Vector(1992.03,3128.09,513.913) ]
	c11m5_runway		= [ Vector(-7215.97,14444.1,623.558) ]

	c12m1_hilltop		= [ Vector(-7670.79,-7375.85,1478.86) ] 
	c12m2_traintunnel	= [ Vector(-7996.38,-7685.76,1329.54) ] 
	c12m3_bridge		= [ Vector(1828.55,-12166.7,484.031) ]
	c12m4_barn			= [ Vector(9825.65,-6235.72,861.689) ] 
	c12m5_cornfield		= [ Vector(4736,1515.6,362.583) ]

	c13m1_alpinecreek	= [ Vector(-3956.75,3989.21,461.736) ]
	c13m2_southpinestream = [ Vector(1329.92,993.69,170.069) ]
	c13m3_memorialbridge = [ Vector(6804.03,-4099.88,1720.87) ] 
	c13m4_cutthroatcreek = [ Vector(-731.623,1533.44,-48.6967) ] 

	c14m1_junkyard 		= [ Vector(-3616.64,-9215.38,165.2) ]
	c14m2_lighthouse	= [ Vector(340.733,-240.909,394.865) ] 

}
rd_detonation_analysis

//****************************************************************************************
//																						//
//								rd_detonation_analysis.nut								//
//																						//
//****************************************************************************************




// Distance between player eyepos and wall < distance between player eyepos to floor/ceiling...
// Thats why we want to have a higher value when the hit surface was a wall.
// ----------------------------------------------------------------------------------------------------------------------------

function getSurfaceValue(detonationPos,player){
	
	local traceTableX1 = { start = detonationPos+Vector(2,0,0), end = detonationPos+Vector(-2,0,0), ignore = player, mask = TRACE_MASK_PLAYER_SOLID }
	local traceTableX2 = { start = detonationPos+Vector(-2,0,0), end = detonationPos+Vector(2,0,0), ignore = player, mask = TRACE_MASK_PLAYER_SOLID }

	local traceTableY1 = { start = detonationPos+Vector(0,2,0), end = detonationPos+Vector(0,-2,0), ignore = player, mask = TRACE_MASK_PLAYER_SOLID }
	local traceTableY2 = { start = detonationPos+Vector(0,-2,0), end = detonationPos+Vector(0,2,0), ignore = player, mask = TRACE_MASK_PLAYER_SOLID }

	local traceTableZ1 = { start = detonationPos+Vector(0,0,2), end = detonationPos+Vector(0,0,-2), ignore = player, mask = TRACE_MASK_PLAYER_SOLID }
	local traceTableZ2 = { start = detonationPos+Vector(0,0,-2), end = detonationPos+Vector(0,0,2), ignore = player, mask = TRACE_MASK_PLAYER_SOLID }

	TraceLine(traceTableX1)
	TraceLine(traceTableX2)
	TraceLine(traceTableY1)
	TraceLine(traceTableY2)
	TraceLine(traceTableZ1)
	TraceLine(traceTableZ2)

	// WALL
	if("hit" in traceTableX1 && traceTableX1.hit == true ||  "hit" in traceTableX2 && traceTableX2.hit == true ){
		return "WALL"
	}
	// WALL
	else if("hit" in traceTableY1 && traceTableY1.hit == true ||  "hit" in traceTableY2 && traceTableY2.hit == true ){
		return "WALL"
	}
	// CEILING/FLOOR
	else if("hit" in traceTableZ1 && traceTableZ1.hit == true ||  "hit" in traceTableZ2 && traceTableZ2.hit == true ){
		return "CLOOR"
	}else{
		return "WALL" // Just in case...
	}
}
rd_director

//****************************************************************************************
//																						//
//										rd_director.nut									//
//																						//
//****************************************************************************************




MutationOptions <-
{
	// General
	cm_NoSurvivorBots	= 1
	
	// Special Infected
	MaxSpecials			= 6

	// Convert items
	weaponsToConvert =
	{
		weapon_first_aid_kit =	"weapon_pain_pills_spawn"
	}

	function ConvertWeaponSpawn(classname){
		if (classname in weaponsToConvert){
			return weaponsToConvert[classname]
		}
		return 0
	}	
	
	
	// Controll which weapons are allowed to be spawned
	weaponsToPreserve =
	{
		weapon_pain_pills		= 0
		weapon_adrenaline		= 0
		weapon_melee			= 0
		weapon_first_aid_kit	= 0
		weapon_gascan			= 0
		weapon_pistol_magnum	= 0
		weapon_grenade_launcher = 0
	}

	function AllowWeaponSpawn(classname){
		if(!IsValveMap()){
			if(classname in weaponsToPreserve){
				return true;
			}
		}
		
		if (classname in weaponsToPreserve){
			return true;
		}
		return false;
	}
	
	// Avoid fallen survivors carrying items
	function AllowFallenSurvivorItem(item){
		return false
	}
	
	// Get default items for survivors
	DefaultItems =
	[
		"weapon_grenade_launcher",
		RandomInt(0, 1) ? getAvailableMelee("Sharp") : "weapon_pistol_magnum"
	]

	function GetDefaultItem( idx ){
		if ( idx < DefaultItems.len() ){
			return DefaultItems[idx]
		}
		return 0
	}
}
rd_events

//****************************************************************************************
//																						//
//										rd_events.nut									//
//																						//
//****************************************************************************************




::devs <- {}
	
devs["STEAM_1:0:26359107"] <- { name = "ReneTM", role = "creator" }
devs["STEAM_1:0:16327272"] <- { name = "Derdoron", role = "Beta Tester" }




// Chat commands
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_player_say(params){

	local text,ent = null
	
	if("userid" in params && params.userid == 0){
		return
	}
	
	text = strip(params["text"].tolower())
	ent = GetPlayerFromUserID(params["userid"])

	if(text.len() < 1){
		return
	}
	
	local steamID = ent.GetNetworkIDString()
	local scope = GetValidatedScriptScope(ent)

	switch(text){

		case "!version" :
		if(steamID in devs){
			ClientPrint(null, 5, BLUE + "RocketDude " + rocketdude_version)
		}
		break
		
		case "!countdown" :
		startSafeRoomTimer(ent)
		break
		
		case "!r" :
		restartFromSaferoom(ent)
		break
		
		case "!saveangles" :
		savePlayerEyeAngles(ent)
		break
		
		case "!speedrunmode" :
		speedrunModeToggle(ent)
		break
		
		case "!hud" :
		ChangeHudState(ent)
		break
		
		case "!stats" :
		outputStats(ent)
		break
		
		case "!info" :
		printMutationInfo(ent)
		break
		
		case "!g2mushroom" :
		GoToNextMushroom(ent)
		break
	}
}




function OnGameEvent_player_first_spawn(params){
	
	local orangestar = ORANGE + "★"

	if(params["isbot"] == 0){
		local player = GetPlayerFromUserID(params.userid)
		local steamID = player.GetNetworkIDString()

		if(steamID in devs){
			local invTable = {}
			GetInvTable(player, invTable)
			if("slot1" in invTable && invTable.slot1.GetClassname() == "weapon_melee"){
				if(NetProps.GetPropString(invTable.slot1, "m_strMapSetScriptName") != "crowbar"){
					invTable.slot1.Kill()
					player.GiveItemWithSkin("crowbar", 1)
				}else{
					NetProps.SetPropInt(invTable.slot1, "m_nSkin", 1)
				}
			}
			ClientPrint(null, 5, orangestar + GREEN + " RocketDude " + devs[steamID].role + BLUE + " " + player.GetPlayerName() + WHITE + " joined the game.")
		}
	}
}




// "The right man in the wrong place can make all the difference in the world"
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_player_changename(params){
	local player = GetPlayerFromUserID(params["userid"])
	local invTable = {}
	GetInvTable(player, invTable)
	
	if("newname" in params && params["newname"] == "Dr. Gordon Freeman"){
		if("slot1" in invTable && invTable.slot1.GetClassname() == "weapon_melee"){
			if(NetProps.GetPropString(invTable.slot1, "m_strMapSetScriptName") == "crowbar"){
				NetProps.SetPropInt(invTable.slot1, "m_nSkin", 1)
			}
		}
	}
}




function OnGameEvent_player_spawn(params){

	local player = GetPlayerFromUserID(params["userid"])
	
	if(player.GetZombieType() == 9 && !IsPlayerABot(player)){
		placeRocketDudeDecals()
		teleportToSurvivor(player)
		
		DoEntFire("!self", "DisableLedgeHang", "", 0.0, player, player)
		DoEntFire("!self", "ignorefalldamagewithoutreset", "99999", 0.0, player, player)

		if(NetProps.GetPropInt(player, "m_iMaxHealth") != 200){
			NetProps.SetPropInt(player, "m_iMaxHealth", 200)
		}
	}
}




function teleportToSurvivor(player){
	local target = getClosestSurvivorTo(player)
	
	if(target == null){
		return
	}
	
	if((player.GetOrigin() - target.GetOrigin()).Length() < 256){
		return
	}
	
	if(target.IsValid()){
		if(!target.IsDead() && !target.IsDying()){
			if(Director.HasAnySurvivorLeftSafeArea()){
				player.SetOrigin(target.GetOrigin())
			}
		}
	}
}




function OnGameEvent_player_incapacitated(params){
	
	if("attackerentid" in params){
		if(EntIndexToHScript(params.attackerentid).GetClassname() == "trigger_hurt"){
			return
		}
	}

	if(!lastChanceUsed){
		lastChanceSwitch(params)
	}
}




// When a survivor stands full hp in a mushroom trigger volume the function is unlocked
// again and the survivor gets hurt a touchtest should be done
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_player_hurt(params){
	if(GetPlayerFromUserID(params.userid).GetZombieType() == 9){
		foreach(trigger in medkit_triggers){
			DoEntFire("!self", "TouchTest", "", 0, trigger, trigger)
		}
	}
}




// Disable glows when survivors enter "last chance mode" but fail
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_mission_lost(params){
	disableInfectedGlows()
}




// Called when any player dies. Purpose mostly for bullet time, reviving survivors from being incap and "last chance"
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_player_death(params){
	
	local victim = null
	local attacker = null
	local victimClass = null
	
	if("userid" in params){
		victim = GetPlayerFromUserID(params["userid"])
	} else if("entityid" in params){
		victim = EntIndexToHScript(params["entityid"])
	}
	if("attacker" in params){
		attacker = GetPlayerFromUserID(params["attacker"])
	} else if("attackerentid" in params){
		attacker = EntIndexToHScript(params["attackerentid"])
	}
	
	if(!lastChanceUsed){
		lastChanceSwitch(params)	
	}
	
	if(victim.IsPlayer() && victim.GetZombieType() == 9){
		ClientPrint(null, 5, BLUE + victim.GetPlayerName() + WHITE + " did not finish this map. The map finished them.")
	}
	

	if(victim.GetClassname() != "infected"){
		if(victim.GetClassname() == "witch" || victim.GetZombieType() != 9){	// Killed witch or any Special infected or tank
			if(attacker != null && attacker.IsPlayer()){						// Dont do anything when the map is the killer
				if(attacker.GetZombieType() == 9){
					if(attacker.IsIncapacitated()){
						if(!missionFailed){
							if(last_chance_active){
								stopLastChanceMode()
								attacker.ReviveFromIncap()
							}else{
								if(!allSurvivorsIncap()){
									attacker.ReviveFromIncap()
								}else{
									ClientPrint(null, 5, BLUE + "Time to say goodbye")
								}
							}
							EmitAmbientSoundOn("player/orch_hit_csharp_short", 1, 100, 100, attacker)
						}
					}
				}
			}
		}
	}else{
		if(attacker != null && attacker.GetClassname() == "player" && attacker.GetZombieType() == 9){
			bulletTime()
		}
	}
}




// Set tank's health in relation to the current difficulty
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_tank_spawn(params){
	local tank = EntIndexToHScript(params.tankid)
	local health = 0
	switch(Convars.GetStr("z_difficulty").tolower()){
		case "easy" :
			health = 8000;	break
		case "normal" :
			health = 16000;	break
		case "hard" :
			health = 32000;	break
		case "impossible" :
			health = 64000;	break
	}
	tank.SetMaxHealth(health)
	tank.SetHealth(health)
}




// Set witch health in relation to the current difficulty
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_witch_spawn(params){
	local witch = EntIndexToHScript(params.witchid)
	local health = 0
	
	switch(Convars.GetStr("z_difficulty").tolower()){
		case "easy" :
			health = 2048;	break
		case "normal" :
			health = 4096;	break
		case "hard" :
			health = 8192;	break
		case "impossible" :
			health = 16384; break
	}
	witch.SetMaxHealth(health)
	witch.SetHealth(health)
}




// Avoid multiple rocketlaunchers on the ground 
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_item_pickup(params){
	local player = GetPlayerFromUserID(params["userid"])
	local playerInv = {}
	if(player.GetZombieType() == 9 && !IsPlayerABot(player)){
		GetInvTable(player, playerInv)
		if("slot0" in playerInv){
			if(playerInv["slot0"].GetClassname() != "weapon_grenade_launcher"){
				playerInv["slot0"].Kill()
				player.GiveItem("weapon_grenade_launcher")
			}
		}
		
		// Gives devs a golden crowbar
		if(player.GetNetworkIDString() in devs){
			if("slot1" in playerInv){
				if(NetProps.GetPropString(playerInv.slot1, "m_strMapSetScriptName") == "crowbar"){
					NetProps.SetPropInt(playerInv.slot1, "m_nSkin", 1)
				}
			}
		}
	}
}




function OnGameEvent_weapon_drop(params){
	if("propid" in params){
		local droppedItem = EntIndexToHScript(params.propid)
		if(droppedItem.GetClassname() == "weapon_grenade_launcher"){
			droppedItem.Kill()
		}
	}
}

::entityChangesDone <- false

function OnGameEvent_player_left_checkpoint(params){
	if("userid" in params){
		local player = GetPlayerFromUserID(params["userid"])
		if(player.GetZombieType() == 9 && !IsPlayerABot(player) && !player.IsDead()){
			if(player in PlayerTimeData){
				if(!PlayerTimeData[player].finished){
					PlayerTimeData[player].timerActive = true
					PlayerTimeData[player].startTime = Time()
					PlayerTimeData[player].seconds = 0
					PlayerTimeData[player].ticks = 0
					EmitAmbientSoundOn("ui/beep07.wav", 0.5, 100, 107, player)
					ClientPrint(player, 5, BLUE + player.GetPlayerName() + " | 00:00")
				}
			}
		
			if(!IsPlayerABot(player)){
				if(!entityChangesDone){
					DoEntFire("worldspawn", "RunScriptCode", "killFixEntities()", 2, player, player)
					DoEntFire("worldspawn", "RunScriptCode", "mapSpecifics()", 4, player, player)
					entityChangesDone = true
				}
			}

			DoEntFire("!self", "DisableLedgeHang", "", 0.0, player, player)
			DoEntFire("!self", "ignorefalldamagewithoutreset", "99999", 0.0, player, player)

			if(NetProps.GetPropInt(player, "m_iMaxHealth") != 200){
				NetProps.SetPropInt(player, "m_iMaxHealth", 200)
			}
		}
	}
}




// Like in Portal 1 we want to challange the player to do as less steps as possible or atleast to spend as less time on the 
// ground as possible. Event "player_footstep" is non-functional atm...Valve please fix
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_finale_vehicle_leaving(param){
	finalGroundTimeOutput()
}




function OnGameEvent_finale_win(param){
	finalGroundTimeOutput()
}




function finalGroundTimeOutput(){
	foreach(player,datatable in PlayerTimeData){
		if(player.IsValid()){
			if(!player.IsDead() && !player.IsDying() && !player.IsIncapacitated()){
				if(!PlayerTimeData[player].finished){
					printFinalGroundTime(player)
					ProcessSurvivorTime(player)
				}
			}
		}
	}
}




function ProcessSurvivorTime(ent){
	
	local tTable = PlayerTimeData[ent]
	tTable.endTime = Time()
	
	if(tTable.time_best == 0){
		tTable.time_best = tTable.endTime - tTable.startTime
	}else{
		if((tTable.endTime - tTable.startTime) < tTable.time_best){
			tTable.time_best = (tTable.endTime - tTable.startTime)
		}
	}
	tTable.finished = true
}




::printFinalGroundTime <- function(ent){
	if(!ent.IsDead() && !ent.IsDying() && !ent.IsIncapacitated()){
		local sec = PlayerTimeData[ent].seconds
		local fracs = PlayerTimeData[ent].ticks.tofloat()
		if(fracs > 0){
			fracs = (fracs / 30)
		}
		local groundTime = sec + fracs
		local time_curr = limitDecimalPlaces( Time() - (PlayerTimeData[ent].startTime).tofloat() )
		local midAirPercent = getMidAirPercentage(groundTime, time_curr)
		
		ClientPrint(null, 5, BLUE + ent.GetPlayerName() + WHITE + " finished this map in " + BLUE + time_curr + WHITE + " seconds and spent " + BLUE + midAirPercent + WHITE + " % midair")
		EmitAmbientSoundOn("ui/menu_invalid.wav", 0.75, 100, 110, ent)
		
		local diff = GetSpeedrunStats(ent, time_curr)

		// Speedrun tracker
		
		if( diff != null ){
			if( diff < 0){
				ClientPrint(null, 5, WHITE + "Thats a new personal record for " + mapName + GREEN + " ( "  + diff.tostring() + " seconds )")
				return
			}else if(diff > 0){
				ClientPrint(null, 5, ORANGE + "( +" + ( diff.tostring()) + " seconds )" )
				return
			}
		}
	}
}




::getMidAirPercentage <- function(groundTime, time_curr){
	local airTime = time_curr - groundTime
	local airPercentage = (( airTime / time_curr ) * 100).tofloat()
	return limitDecimalPlaces(airPercentage)
}




::limitDecimalPlaces <- function(var){
	return (var * 100).tointeger() / 100.0
}




// Check if the text from the file is a "number"
// ----------------------------------------------------------------------------------------------------------------------------

::isNumeric <- function(value){
	local newValue
	local dots = 0
	local numbers = ["0","1","2","3","4","5","6","7","8","9","."]
	for(local i=0; i < value.len(); i++){
		local checkChar = value.slice(i, i+1)
		if(checkChar == "."){
			dots++
		}
		if(dots > 1){
			return false
		}
		if(numbers.find(checkChar) == null){
			return false
		}
	}
	return true
}




// Record output for the local player
// ----------------------------------------------------------------------------------------------------------------------------

::GetSpeedrunStats <- function(player, newTime){
	
	// Restrict saving for the local player
	if(!(player == GetListenServerHost())){
		return null
	}
	
	local filePath = "rocketdude/speedrun/"
	local fileName = mapName + ".txt"
	
	local savedTime = FileToString(filePath + fileName)
	
	// Save a file when there is none
	if(savedTime == null || savedTime.len() == 0 || !isNumeric(savedTime)){
		StringToFile(filePath + fileName, newTime.tostring() )
		return null
	}
	
	try{
		savedTime = savedTime.tofloat()
	}catch(exception){
		return null
	}
	
	if(newTime < savedTime){
		StringToFile(filePath + fileName, newTime.tostring() )
	}
	
	return (newTime - savedTime)
}




// Typing sv_cheats 1 on local server would result in every cheat flagged variable reset
// ----------------------------------------------------------------------------------------------------------------------------

function OnGameEvent_server_cvar(param){
	if("cvarname" in param){
		local cvar = param.cvarname
		if(cvar == "sv_cheats"){
			checkCvars()
		}
	}
}




__CollectEventCallbacks(this, "OnGameEvent_", "GameEventCallbacks", RegisterScriptGameEventListener)
rd_hud_controller

//****************************************************************************************
//																						//
//									rd_hud_controller.nut								//
//																						//
//****************************************************************************************




// Flag and position tables
// ----------------------------------------------------------------------------------------------------------------------------

::HUDFlags <- {
	PRESTR = 1
	POSTSTR = 2
	BEEP = 4
	BLINK = 8
	AS_TIME = 16
	COUNTDOWN_WARN = 32
	NOBG = 64
	ALLOWNEGTIMER = 128
	ALIGN_LEFT = 256
	ALIGN_CENTER = 512
	ALIGN_RIGHT = 768
	TEAM_SURVIVORS = 1024
	TEAM_INFECTED = 2048
	TEAM_MASK = 3072
	NOTVISIBLE = 16384
}

::HUDPositions <- {
	LEFT_TOP = 0
	LEFT_BOT = 1
	MID_TOP = 2
	MID_BOT = 3
	RIGHT_TOP = 4
	RIGHT_BOT = 5
	TICKER = 6
	FAR_LEFT = 7
	FAR_RIGHT = 8
	MID_BOX = 9
	SCORE_TITLE = 10
	SCORE_1 = 11
	SCORE_2 = 12
	SCORE_3 = 13
	SCORE_4 = 14
}




// Main hud definition
// ----------------------------------------------------------------------------------------------------------------------------

RD_HUD <-
{
	Fields = 
	{
		timer = { slot = HUDPositions.MID_BOX, flags = HUDFlags.ALIGN_CENTER | HUDFlags.NOBG, name = "timer", datafunc = @()GetPlayerTimes() }
	}
}




// Returns one string for all survivors
// ----------------------------------------------------------------------------------------------------------------------------

::GetPlayerTimes <- function(){
	local str = ""
	
	foreach(ent in GetHumanSurvivors()){
		str += (GetCharacterDisplayName(ent) + ": ")
		if(ent in PlayerTimeData){
			if(!ent.IsDead() && !ent.IsDying()){
				if(PlayerTimeData[ent].timerActive){
					if(!PlayerTimeData[ent].finished){
						str += g_MapScript.TimeToDisplayString(Time() - PlayerTimeData[ent].startTime)
					}else{
						str += "Finished(" + g_MapScript.TimeToDisplayString(PlayerTimeData[ent].endTime - PlayerTimeData[ent].startTime) + ")"
					}
				}else{
					str += "0:00" 
				}
			}else{
				str += "(Dead)"
			}
		}
		str += "  "
	}
	return str
}




// Voting to disable the timer hud
// ----------------------------------------------------------------------------------------------------------------------------

function ChangeHudState(ent){
	local scope = GetValidatedScriptScope(ent)
	scope["speedrun_timer"] <- true
	
	foreach(player in GetHumanSurvivors()){
		local playerscope = GetValidatedScriptScope(player)
		if(!("speedrun_timer" in playerscope)){
			ClientPrint(null, 5, GREEN + ent.GetPlayerName() + WHITE + " voted to " + ( IsTimerHudActive() ? "disable" : "enable" ) + " the timer hud")
			return
		}
	}
	
	if(IsTimerHudActive()){
		DisableTimerHud()
	}else{
		EnableTimerHud()
	}
	
	foreach(player in GetHumanSurvivors()){
		local scope = GetValidatedScriptScope(player)
		if("speedrun_timer" in scope){
			player.GetScriptScope().rawdelete("speedrun_timer")
		}
	}
	
	ClientPrint(null, 5, WHITE + "Hud has been " + GREEN + ( IsTimerHudActive() ? "enabled" : "disabled") )
}




// Enable / disable the hud
// ----------------------------------------------------------------------------------------------------------------------------

function EnableTimerHud(){
	RD_HUD.Fields.timer.flags <- RD_HUD.Fields.timer.flags & ~HUDFlags.NOTVISIBLE
	HUDPlace(HUDPositions.MID_BOX, 0.0, 0.0, 1.0, 0.05)
}




function EnableTickerHud(){
	Ticker_AddToHud(RD_HUD, "!info in chat to print mutation details to your console", true)
	HUDPlace(HUDPositions.TICKER, 0.0, 0.0, 0.99, 0.25)
	Ticker_SetTimeout(16)
	Ticker_SetBlinkTime(16)
	RD_HUD.Fields.ticker.flags <- HUDFlags.ALIGN_CENTER | HUDFlags.NOBG | HUDFlags.BLINK
}




function DisableTimerHud(){
	RD_HUD.Fields.timer.flags <- RD_HUD.Fields.timer.flags | HUDFlags.NOTVISIBLE
}




// Checks if the timer hud is currently visible
// ----------------------------------------------------------------------------------------------------------------------------

function IsTimerHudActive(){
	return !(RD_HUD.Fields.timer.flags & HUDFlags.NOTVISIBLE)
}


EnableTimerHud()

EnableTickerHud()

HUDSetLayout(RD_HUD)
rd_last_chance

//****************************************************************************************
//																						//
//									rd_last_chance.nut									//
//																						//
//****************************************************************************************




// Basic functions to check if survivors are dead / capped
// ----------------------------------------------------------------------------------------------------------------------------

function allSurvivorsIncap(){
	foreach(survivor in GetSurvivors()){
		//if(!survivor.IsIncapacitated() && !survivor.IsHangingFromLedge() && !survivor.IsDead() && !survivor.IsDying() && !playerIsCapped(survivor)){  // 20200913
		if(!survivor.IsIncapacitated() && !survivor.IsDead() && !survivor.IsDying()){
			return false
		}
	}
	return true
}

function allSurvivorsDead(){
	foreach(survivor in GetSurvivors()){
		if(!survivor.IsDead() && !survivor.IsHangingFromLedge()){
			return false
		}
	}
	return true
}

function playerIsCapped(player){
	local infected = null;
	local keys = ["m_pummelVictim","m_carryVictim","m_pounceVictim","m_jockeyVictim","m_tongueVictim"]
	while(infected = Entities.FindByClassname(infected, "player")){
		if(infected.GetZombieType() != 9){
			foreach(key in keys){
				if(NetProps.GetPropEntity(infected, key) == player){
					return true
				}
			}
		}
	}
	return false
}




// When all survivors are incapacitated, give them 10 seconds to kill any special infected or boss infected ( BL-Style )
// ----------------------------------------------------------------------------------------------------------------------------

::last_chance_active	<- false
lastChanceCheckStamp	<- Time()
remainingReviveTime		<- 10
::missionFailed			<- false
::lastChanceUsed		<- false



// Called on player_death and player_incapacitated it will call enableLastChanceVision()
// when all survivors are incap but not all are dead
// ----------------------------------------------------------------------------------------------------------------------------

function lastChanceSwitch(params){
	if(!allSurvivorsDead()){
		if("userid" in params && GetPlayerFromUserID(params.userid).GetZombieType() == 9){
			if(allSurvivorsIncap() && !last_chance_active){
				enableLastChanceVision();
				lastChanceUsed = true
				last_chance_active = true
				remainingReviveTime = 10
				Convars.SetValue("director_no_death_check", 1)
				lastChanceCheckStamp = Time()
			}
		}
	}
}




// Called every tick, it will outout a countdown to timeout the survivors
// ----------------------------------------------------------------------------------------------------------------------------

function lastChanceCountDown(){
	if(last_chance_active){
		if(Time() >= lastChanceCheckStamp + 1 ){
			lastChanceCheckStamp = Time()
			if(allSurvivorsIncap()){
				if(remainingReviveTime == 0){
					last_chance_active = false
					Convars.SetValue("director_no_death_check", 0)
					ClientPrint(null, 5, BLUE + "Sorry, you don't have any time left to repopulate the world.")
					missionFailed = true
				}else{
					ClientPrint(null, 5, BLUE + remainingReviveTime)
				}
				remainingReviveTime --
			}else{
				last_chance_active = false
				Convars.SetValue("director_no_death_check", 0)
			}
		}
	}
}




// Will switch survivors vision to "black and white" and enables outline glows for special and boss infected
// ----------------------------------------------------------------------------------------------------------------------------

function enableLastChanceVision(){
	whiteScreen();
	local player = null
	local witch = null
	local rock = null
	
	while(player = Entities.FindByClassname(player, "player")){
		if(player.GetZombieType() != 9 && !player.IsDead() && !player.IsDying()){
			NetProps.SetPropInt(player, "m_Glow.m_iGlowType", 3)
			setInfectedGlowColor(player)
		}
		if(player.GetZombieType() == 9){
			player.SetReviveCount(2)
		}
	}
	while(witch = Entities.FindByClassname(witch, "witch")){
		if(witch.IsValid()){
			NetProps.SetPropInt(witch, "m_Glow.m_iGlowType", 3)
			setInfectedGlowColor(witch)
		}
	}
	while(rock = Entities.FindByClassname(rock, "tank_rock")){
		if(rock.IsValid()){
			NetProps.SetPropInt(rock, "m_Glow.m_iGlowType", 3)
			setInfectedGlowColor(rock)
		}
	}
}




// After a survivor gets a kill on any special or boss infected this should get called also
// ----------------------------------------------------------------------------------------------------------------------------

function stopLastChanceMode(){
	local player = null;
	local witch = null;
	local rock = null;
	while(player = Entities.FindByClassname(player, "player")){
		NetProps.SetPropInt(player, "m_Glow.m_iGlowType", 0)
		if(player.GetZombieType() == 9){
			player.SetReviveCount(1)
		}
	}
	while(witch = Entities.FindByClassname(witch, "witch")){
		NetProps.SetPropInt(witch, "m_Glow.m_iGlowType", 0)
	}
	while(rock = Entities.FindByClassname(rock, "tank_rock")){
		if(rock.IsValid()){
			NetProps.SetPropInt(rock, "m_Glow.m_iGlowType", 0)
		}
	}
}




// Called on mission_fail it will disable all infected glows of "last chance"
// ----------------------------------------------------------------------------------------------------------------------------

function disableInfectedGlows(){
	local player = null;
	local witch = null;
	while(player = Entities.FindByClassname(player, "player")){
		NetProps.SetPropInt(player, "m_Glow.m_iGlowType", 0)
	}
	while(witch = Entities.FindByClassname(witch, "witch")){
		NetProps.SetPropInt(witch, "m_Glow.m_iGlowType", 0)
	}
}




// Fade effect for entering the last chance vision
// ----------------------------------------------------------------------------------------------------------------------------

function whiteScreen(){
	foreach(player in GetHumanSurvivors()){
		ScreenFade(player, 200, 200, 200, 255, 1, 0.5, 1)
	}
}




// Change infected glows in "last_chance_mode" depending on difficulty
// ----------------------------------------------------------------------------------------------------------------------------

function setInfectedGlowColor(inf){
	local vector = null;
	switch(Convars.GetStr("z_difficulty").tolower()){
		case "easy"			: vector = Vector(255,45,180);	break;
		case "normal"		: vector = Vector(0,255,0);		break;
		case "hard"			: vector = Vector(200,0,0);		break;
		case "impossible"	: vector = Vector(120,120,120);	break;
		default				: vector = Vector(165,0,255);	break;
	}
	local color = vector.x
	color += 256 * vector.y
	color += 65536 * vector.z
	NetProps.SetPropInt(inf, "m_Glow.m_glowColorOverride", color)
}




// Returns vector color as int
// ----------------------------------------------------------------------------------------------------------------------------

function GetColorInt(col){
	
	if(typeof(col) == "Vector"){
		local color = col.x
		color += 256 * col.y
		color += 65536 * col.z
		return color
	}else if(typeof(col) == "string"){
		local colorArray = split(col, " ")
		local r = colorArray[0].tointeger()
		local g = colorArray[1].tointeger()
		local b = colorArray[2].tointeger()
		local color = r
		color += 256 * g
		color += 65536 * b
		return color
	}
}
rd_map_specifics

//****************************************************************************************
//																						//
//									rd_map_specifics.nut								//
//																						//
//****************************************************************************************




// Some maps require minor adjustments to improve gameplay. 
// ----------------------------------------------------------------------------------------------------------------------------

::mapSpecifics <- function(){
	switch(mapName){
		case "c1m1_hotel"				:	rd_specifics_c1m1();	break
		case "c1m2_streets"				:	rd_specifics_c1m2();	break
		case "c1m3_mall"				:	rd_specifics_c1m3();	break
		case "c5m5_bridge"				:	rd_specifics_c5m5();	break
		case "c7m3_port"				: 	rd_specifics_c7m3();	break
		case "c8m4_interior"			:	rd_specifics_c8m4();	break
		case "c8m5_rooftop"				:	rd_specifics_c8m5();	break
		case "c13m4_cutthroatcreek"		:	rd_specifics_c13m4();	break
		case "c14m1_junkyard"			:	rd_specifics_c14m1();	break
		case "c14m2_lighthouse"			:	rd_specifics_c14m2();	break
		default							:							break
	}
}




// Since we dont have any fall damage at all we should not stop players from just dropping down the hotel
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c1m1 <- function(){
	local deathTriggers =
	[
		Vector(3200.000,5312.000,1648.000),
		Vector(2944.000,5888.000,1648.000),
		Vector(2936.000,6932.000,1648.000),
		Vector(1516.800,8000.000,1520.000),
		Vector(632.000,6944.000,1648.000),
		Vector(1600.000,4608.000,1648.000),
		Vector(0.000,5632.000,1648.000)
	]
	foreach(triggerPos in deathTriggers){
		local ent = null
		if(ent = Entities.FindByClassnameNearest( "trigger_hurt", triggerPos, 4 )){
			ent.Kill()
		}
	}
}




// Force start of parish finale
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c5m5 <- function (){
	local ent = null
	if(ent = Entities.FindByClassname(null, "player")){
		DoEntFire( "radio_fake_button", "use", "", 1, ent, ent)
		DoEntFire( "finale", "use", "", 8, ent, ent)
		DoEntFire( "relay_init_heli", "Trigger", "", 12, ent, ent)
		DoEntFire( "relay_start_heli", "Trigger", "", 16, ent, ent)
	}
}




// Remove liftshaft_portal3_brush and rooftop_opening_clip
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c8m5 <- function(){
	local ent = null
	if(ent = Entities.FindByClassnameNearest( "func_brush", Vector(7248.000000, 9168.000000, 7144.000000), 4)){
		ent.Kill()
	}
}




// Force start of cold stream finale
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c13m4 <- function (){
	local ent = null
	if(ent = Entities.FindByClassname(null, "player")){
		DoEntFire( "startbldg_door_button", "use", "", 1, ent, ent)
		DoEntFire( "finale", "use", "", 12, ent, ent)
	}
}




// Change damage type of trigger_hurt ( water area in front of the end saferoom )
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c14m1 <- function(){
	local ent = null
	if(ent = Entities.FindByClassnameNearest( "trigger_hurt_ghost", Vector(-4580,9352,-732), 4)){
		TriggerSetDamageType(ent, damageTypes.GENERIC)
	}
}




// Kill trigger which re-enables the ledgehang
// Change damage type of trigger_hurt ( water of rescue vehicle zone )
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c14m2 <- function(){
	local ent = null
	
	if(ent = Entities.FindByClassnameNearest( "trigger_multiple", Vector(-4352,3928,1096), 4)){
		ent.Kill()
	}
	if(ent = Entities.FindByClassnameNearest( "trigger_hurt", Vector(-4608,7168,-256), 4)){
		TriggerSetDamageType(ent, damageTypes.GENERIC)
	}
	if(ent = Entities.FindByClassnameNearest("func_brush", Vector(275.000000, 930.000000, 1360.000000), 4)){
		ent.Kill()
	}
}




// Re-execute the elevator fix once
// ----------------------------------------------------------------------------------------------------------------------------


::rd_specifics_c8m4 <- function(){
	EntFire( "worldspawn", "RunScriptFile", "c8m4_elevatorfix" )
}




// Utils to manipulate entities
// ----------------------------------------------------------------------------------------------------------------------------

::TriggerSetDamageType <- function(ent, type){
	if(ent.GetClassname() == "trigger_hurt" || ent.GetClassname() == "trigger_hurt_ghost"){
		NetProps.SetPropInt(ent, "m_bitsDamageInflict", type)
	}
}




// Lets make c7m3 playable alone
// ----------------------------------------------------------------------------------------------------------------------------

::KillOldFinaleButton <- function(){
	local triggerFinale = null
		if(triggerFinale = Entities.FindByClassname(triggerFinale, "trigger_finale")){
			triggerFinale.Kill()
		}
	
	local br = null
	if(br = Entities.FindByClassname(null,"trigger_escape")){
		br.Kill()
	}
}




::rd_specifics_c7m3 <- function(){
	KillOldFinaleButton()
	EntFire("worldspawn", "RunScriptCode", "SpawnNewFinaleButton()", 1.0)
	EntFire("worldspawn", "RunScriptCode", "SpawnNewFinaleButton()", 1.0)
	EntFire("generator_start_model", "Disable", "", 1.5)
	EntFire("generator_start_model", "Enable", "", 2.0)
}




::SpawnNewFinaleButton <- function(){
	
	local keyvalues =
	{
		targetname = "generator_start_model",
		StartDisabled = "1",
		model = "models/props_vehicles/floodlight_generator_switch01.mdl",
		disableshadows = "1",
		UseDelay = "0",
		type = "2",
		origin = Vector(-456.187, -571.935, 2.57401),
		connections =
		{
			FinaleStart =
			{
				cmd1 = "bridge_ragdoll_fader�Enable��5�-1"
				cmd2 = "bridge_ragdoll_fader�Disable��8�-1"
				cmd3 = "bridge_move_sound�PlaySound��5�-1"
				cmd4 = "bridge_elevator�MoveToFloor�Bottom�5�-1"
				cmd5 = "store_brush�Enable��0�-1"
				cmd6 = "generator_brush�Enable��0�-1"
				//Editors Note: These � characters will not copy over properly. Websites cannot display them.
				//In Notepad++ replace them by selecting them, holding down ALT and entering 027 on Numblock. You'll get a black ESC character.	
			}
			FinaleEscapeStarted =
			{
				cmd1 = "relay_escape�Trigger��0�-1"
			}
		}
	}
	
	local ent = SpawnEntityFromTable("trigger_finale", keyvalues)
	NetProps.SetPropEntity(Entities.FindByName(null, "finale_start_button"), "m_sGlowEntity", ent)
}




// Convert all weapons in shop ( except deagle ) to weapon_grenade_launcher_spawn
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c1m2 <- function(){

	local ent = null
	local weapons =
	[
		"weapon_pistol_spawn",
		"weapon_autoshotgun_spawn",
		"weapon_chainsaw_spawn",
		"weapon_hunting_rifle_spawn",
		"weapon_pumpshotgun_spawn",
		"weapon_rifle_ak47_spawn",
		"weapon_rifle_desert_spawn",
		"weapon_rifle_m60_spawn",
		"weapon_rifle_sg552_spawn",
		"weapon_rifle_spawn",
		"weapon_shotgun_chrome_spawn",
		"weapon_shotgun_spas_spawn",
		"weapon_smg_mp5_spawn",
		"weapon_smg_silenced_spawn",
		"weapon_smg_spawn",
		"weapon_sniper_awp_spawn",
		"weapon_sniper_military_spawn",
		"weapon_sniper_scout_spawn"
	]
	
	foreach(weapon in weapons){
		while(ent = Entities.FindByClassname(ent, weapon)){
			SpawnEntityFromTable("weapon_grenade_launcher_spawn",
			{
				weaponskin = -1
				angles = ent.GetAngles().ToKVString()
				origin = ent.GetOrigin()
				spawnflags = 2
				body = 0
				solid = 0
				count = 1
				disableshadows = 0
				skin = 0
				glowrange = 0
				model = "models/w_models/weapons/w_grenade_launcher.mdl"
			})
			ent.Kill()
		}
	}
}




// Makes event glass also breakable by the rocket launcher's projectiles
// ----------------------------------------------------------------------------------------------------------------------------

::rd_specifics_c1m3 <- function(){
	local multiFilterOld = Entities.FindByName(null, "filter_breakglass")
	
	if(multiFilterOld && multiFilterOld.IsValid()){
		multiFilterOld.Kill()
	}
	

	SpawnEntityFromTable("filter_activator_class",
	{
			targetname = "filter_grenade_launcher_projectile"
			Negated = 0
			filterclass = "grenade_launcher_projectile"
	})
	
	SpawnEntityFromTable("filter_multi",
	{
		targetname = "filter_breakglass"
		Filter01 = "filter_bullet"
		Filter02 = "filter_melee"
		Filter03 = "filter_grenade_launcher_projectile"
		//
		filtertype = 1
		Negated = 0
	})
}
rd_meds

//****************************************************************************************
//																						//
//										rd_meds.nut										//
//																						//
//****************************************************************************************



// All mushroom triggers to iterate over
// ----------------------------------------------------------------------------------------------------------------------------

::medkit_triggers	<- []




// Returns handle of the created prop_dynamic with a 'mushroom model'
// ----------------------------------------------------------------------------------------------------------------------------

::RD_healtkit_model <- function(pos, ang, shroomdata){
	local model = SpawnEntityFromTable("prop_dynamic_override",
	{
		// General
		targetname = "RD_HP_MODEL", origin = pos, angles = ang, body = 0, DefaultAnim = "idle", DisableBoneFollowers = 1,
		// Shadows and fade
		disablereceiveshadows = 1, disableshadows = 1, disableX360 = 0, ExplodeDamage = 0, ExplodeRadius = 0, fademaxdist = 0, fademindist = -1, fadescale = 0,
		// Glows
		glowbackfacemult = 1.0, glowcolor = shroomdata.glowColor, glowrange = shroomdata.glowRange, glowrangemin = 0, glowstate = shroomdata.glowstate, health = 0,
		// Model & Animation
		LagCompensate = 0, MaxAnimTime = 10, maxcpulevel = 0, maxgpulevel = 0, MinAnimTime = 5,
		mincpulevel = 0, mingpulevel = 0, model = "models/props_collectables/mushrooms_glowing.mdl", PerformanceMode = 0, pressuredelay = 0,
		RandomAnimation = 0, renderamt = 255, rendercolor = shroomdata.modelColor, renderfx = 0, rendermode = 0, SetBodyGroup = 0, skin = 0,
		solid = 0, spawnflags = 0, updatechildren = 0
	})
	model.SetModelScale(shroomdata.modelScaleMax, 0)
	return model
}




// Creates a survivor only filter for the mushrooms
// ----------------------------------------------------------------------------------------------------------------------------

SpawnEntityFromTable("filter_activator_team", { targetname = "RD_FILTER_SURVIVOR", origin = Vector(0,0,0), Negated = 0, filterteam = 2 } )
::worldspawn <- Entities.FindByClassname(null, "worldspawn")
::worldspawn.ValidateScriptScope()




// Returns handle of trigger to execute the healing function
// ----------------------------------------------------------------------------------------------------------------------------

::RD_healthkit_trigger <- function(pos, shroomdata){
	local triggerMin = shroomdata.triggerSize[0]
	local triggerMax = shroomdata.triggerSize[1]
	local zOffset = triggerMax.z
	local HP_Value = shroomdata.hp
	
	local triggerName = "RD_HP_TRIGGER"
	//
	local triggerTable =
	{
		targetname    = triggerName
		StartDisabled = 0
		spawnflags    = 1
		allowincap    = 1
		entireteam    = 0
		filtername    = "RD_FILTER_SURVIVOR"
		origin        = pos + Vector(0,0,zOffset)
	}

	local trigger = SpawnEntityFromTable( "trigger_multiple", triggerTable)
	//
	setTriggerSize(trigger,triggerMin,triggerMax)
	NetProps.SetPropInt(trigger, "m_Collision.m_nSolidType", 2)
	//
	EntFire( triggerName, "AddOutput", "OnStartTouch worldspawn:RunScriptCode:survivorMedKitTouch(activator):0:-1" )
	EntFire( triggerName, "AddOutput", "OnTouching worldspawn:RunScriptCode:survivorMedKitTouch(activator):0:-1" )
	//
	if(DevModeActive()){
		DebugDrawBox( triggerTable.origin, Vector(32,32,32), Vector(-32,-32,-32), 255, 255, 255, 0, 16)
	}
	return trigger
}




// Sets the trigger size in relation to the mushroom size
// ----------------------------------------------------------------------------------------------------------------------------

function setTriggerSize(trigger, vectorMins, vectorMaxs){
	if(trigger.IsValid()){
		if(typeof(vectorMins) == "Vector"){
			if(typeof(vectorMaxs) == "Vector"){
				NetProps.SetPropVector(trigger, "m_Collision.m_vecMins", vectorMins)
				NetProps.SetPropVector(trigger, "m_Collision.m_vecMaxs", vectorMaxs)
			}else{
				error("setTriggerSize error: vectorMaxs ment to be datatype vector")
			}
		}else{
			error("setTriggerSize error: vectorMins ment to be datatype vector")
		}
	}
}




// Creates a set of trigger and prop_dynamic_override ( healing mushroom )
// ----------------------------------------------------------------------------------------------------------------------------

::createRD_Medkit <- function(pos, ang, rotating, shroomdata){
	local HP_Val = shroomdata.hp

	local trigger = RD_healthkit_trigger(pos, shroomdata);
	local model = RD_healtkit_model(pos, ang, shroomdata);
	
	local mushroomTable =
	{
		model = model
		trigger = trigger
		restoreTime = shroomdata.restoreTime
		usetime = Time() - shroomdata.restoreTime
		usable = true
		hp = HP_Val
		modelScaleMin = shroomdata.modelScaleMin
		modelScaleMax = shroomdata.modelScaleMax
		glowstate = shroomdata.glowstate
		action = shroomdata.action
		flashColor = shroomdata.flashColor
	}
	medkit_triggers.append(trigger)
	
	addTableToEntityScope(trigger, mushroomTable)
	addTableToEntityScope(model, mushroomTable)
	
	if(rotating){
		AttachRotatorTo(model)
	}
}

::addTableToEntityScope <- function(ent, table){
	
	local scope = GetValidatedScriptScope(ent)
	
	foreach(key, value in table){
		scope[key] <- value
	}
}




// Mushrooms properties
// ----------------------------------------------------------------------------------------------------------------------------
::shroomProperties <-
{
	large 	= 	{ hp = 75, action = "HP", restoreTime = 16, modelColor = "255 185 0", flashColor = "255 185 0", glowRange = 256, glowstate = 3, glowColor = "255 185 0", GlowColorRelation = true, modelScaleMax = 7.0, modelScaleMin = 1.0, triggerSize = [ Vector(-28,-28,-28), Vector(28,28,28) ] }
	medium 	=	{ hp = 50, action = "HP", restoreTime = 16, modelColor = "220 0 255", flashColor = "220 0 255", glowRange = 256, glowstate = 3, glowColor = "220 0 255", GlowColorRelation = true, modelScaleMax = 5.0, modelScaleMin = 1.0, triggerSize = [ Vector(-16,-16,-16), Vector(16,16,16) ] }
	small	=	{ hp = 25, action = "HP", restoreTime = 16, modelColor = "0 105 255", flashColor = "0 105 255", glowRange = 256, glowstate = 3, glowColor = "0 105 255", GlowColorRelation = true, modelScaleMax = 3.0, modelScaleMin = 1.0, triggerSize = [ Vector(-16,-16,-16), Vector(16,16,16) ] }
	tiny	=	{ hp = 10, action = "HP", restoreTime = 16, modelColor = "255 255 255", flashColor = "255 255 255", glowRange = 256, glowstate = 3, glowColor = "255 255 255", GlowColorRelation = true, modelScaleMax = 2.0, modelScaleMin = 1.0, triggerSize = [ Vector(-16,-16,-16), Vector(16,16,16) ] }
	exp		=	{ hp = 0, action = "EXP", restoreTime = 8, modelColor = "0 0 0", flashColor = "0 0 0", glowRange = 128, glowstate = 3, glowColor = "255 0 0", GlowColorRelation = false, modelScaleMax = 4.0, modelScaleMin = 1.0, triggerSize = [ Vector(-16,-16,-16), Vector(16,16,16) ] }
	item	=	{ hp = 0, action = "ITEM", restoreTime = 16, modelColor = "0 0 255", flashColor = "0 0 255", glowRange = 512, glowstate = 3, glowColor = "0 0 255", GlowColorRelation = true, modelScaleMax = 4.0, modelScaleMin = 1.0, triggerSize = [ Vector(-16,-16,-16), Vector(16,16,16) ] }
	bh		=	{ hp = 0, action = "BH", restoreTime = 1, modelColor = "0 255 0", flashColor = "0 255 0", glowRange = 1024, glowstate = 3, glowColor = "0 255 0", GlowColorRelation = true, modelScaleMax = 4.0, modelScaleMin = 1.0, triggerSize = [ Vector(-16,-16,-16), Vector(16,16,16) ] }
}

foreach(dataset in shroomProperties){
	if(dataset.glowstate == 3 && dataset.GlowColorRelation){
		dataset.glowColor = getColorWithIntensity(dataset.modelColor, 77)
	}
}




// After a mushroom heals a player it should be invisible for 10 seconds
// ----------------------------------------------------------------------------------------------------------------------------

::setMedVisibility <- function(x, ent){
	
	local scope = GetValidatedScriptScope(ent)
	
	if(x == 0){
		NetProps.SetPropInt(ent, "m_Glow.m_iGlowType", 0)
		NetProps.SetPropInt(ent, "m_fEffects", NetProps.GetPropInt(ent, "m_fEffects") | (1 << 5))
		mushroomSizer(ent,scope.modelScaleMin, 0)
	}else{
		NetProps.SetPropInt(ent, "m_fEffects", 0)
		NetProps.SetPropInt(ent, "m_Glow.m_iGlowType", scope.glowstate)
		mushroomSizer(ent,scope.modelScaleMax, 0.1)
		EmitAmbientSoundOn("level/popup.wav", 1, 100, 100, ent)
	}
}




// Let's the mushroom "grow"
// ----------------------------------------------------------------------------------------------------------------------------

::mushroomSizer <- function(ent, scale, time){
	ent.SetModelScale(scale, time)
}




// Survivor touches a mushroom trigger
// ----------------------------------------------------------------------------------------------------------------------------

::survivorMedKitTouch <- function(player){
	
	// Has to be set when this fuction gets called via OnTouching because the activator is the trigger itself
	
	if(player.GetClassname() == "trigger_multiple"){
		player = Entities.FindByClassnameNearest("player", player.GetOrigin(), 256)
	}
	
	local playerPos = player.GetOrigin()
	local medkit_trigger = Entities.FindByNameNearest("RD_HP_TRIGGER", playerPos, 256)
	
	local scope = GetValidatedScriptScope(medkit_trigger)
	
	local HP_Val = scope.hp
	local action = scope.action
	local flashColor = split(scope.flashColor," ")
	
	local medkit_model = scope.model
	local mushroomUsed = false
	
	if(!IsPlayerABot(player)){
		if(!missionFailed){
			if(Time() >= scope.usetime + scope.restoreTime){
				if(action == "HP"){
					if(player.GetHealth() < player.GetMaxHealth() || player.IsIncapacitated()){
						healPlayer(player, HP_Val)
						mushroomUsed = true
					}
				}else if(action == "BH"){
					if(!(player in bunnyPlayers)){
						playerBecomesBunny(player)
						player.UseAdrenaline(7)
						mushroomUsed = true
					}
				}else if(action == "EXP"){
					executeExplosion(medkit_trigger)
					mushroomUsed = true
				}else if(action == "ITEM"){
					if(!player.IsIncapacitated()){
						local invTable = {}
						GetInvTable(player, invTable)
						if(!("slot2" in invTable)){
							giveRandomItem(player, medkit_trigger)
							mushroomUsed = true
						}
					}
				}
				if(mushroomUsed){
					scope.usetime = Time()
					scope.usable = false
					setMedVisibility(0, medkit_model)
					ScreenFade(player, flashColor[0].tointeger(), flashColor[1].tointeger(), flashColor[2].tointeger(), 128, 1.0, 0, 1)
				}
			}
		}
	}
}




// Sounds used by the mushrooms 
// ----------------------------------------------------------------------------------------------------------------------------

::mushroomSounds <-
{
	explosions =
	[
		"player/boomer/explode/explo_medium_09.wav",
		"player/boomer/explode/explo_medium_10.wav",
		"player/boomer/explode/explo_medium_14.wav"
	]
	misc =
	[
		"level/gnomeftw.wav",
		"player/laser_on.wav",
		"ui/menu_invalid.wav"
	]
}





::precacheSounds <- function(){
	foreach(collection in mushroomSounds){
		foreach(sound in collection){
			PrecacheSound(sound)
		}
	}
}




// Mushroom action ( explosion )
// ----------------------------------------------------------------------------------------------------------------------------

::executeExplosion <- function(ent){
	local location = ent.GetOrigin()
	local expTable =
	{
		origin = location
		fireballsprite = "sprites/zerogxplode.spr"
		ignoredClass = 0
		targetname = "exp"
		iMagnitude = 128
		spawnflags = 0
		iRadiusOverride = 128
		rendermode = 5
	}
	
	local exp = SpawnEntityFromTable("env_explosion", expTable)
	DoEntFire("!self", "explode", "", 0, exp, exp)
	local sounds = mushroomSounds.explosions
	EmitAmbientSoundOn(sounds[RandomInt(0, sounds.len() - 1)], 0.8, 100, 147, ent)
}




// Mushroom action ( bunny-hop )
// ----------------------------------------------------------------------------------------------------------------------------

::playerBecomesBunny <- function(player){
	if(!(player in bunnyPlayers)){
		if(player.IsValid()){
			bunnyPlayers[player] <- player
			ClientPrint(null, 5, "\x03" + player.GetPlayerName() + "\x01" + " is a bunny now.")
			EmitAmbientSoundOn("player/laser_on.wav", 1, 100, 180, player)
		}
	}
}




// Mushroom action ( random item )
// ----------------------------------------------------------------------------------------------------------------------------

::giveRandomItem <- function(player, shroom){
	local throwables = [ "weapon_pipe_bomb", "weapon_vomitjar", "weapon_molotov" ]
	player.GiveItem(throwables[RandomInt(0, throwables.len() - 1)])
	EmitAmbientSoundOn(mushroomSounds.misc[0], 0.5, 100, 170, shroom)
}




// Mushroom action ( healing )
// ----------------------------------------------------------------------------------------------------------------------------

::healPlayer <- function(player, val){
	
	local sndPitch = 100;
	switch(GetCharacterDisplayName(player)){
		case "Rochelle"	:	sndPitch = 115; break;
		case "Zoey"		: 	sndPitch = 135; break;
		default			: 	sndPitch = 100; break;
	}
	
	EmitAmbientSoundOn("player/items/pain_pills/pills_use_1.wav", 1, 100, sndPitch, player)
	StopAmbientSoundOn("player/heartbeatloop.wav", player) 
	player.UseAdrenaline(7)
	local newHP = player.GetHealth() + val
	local playerMaxHealth = player.GetMaxHealth()
	
	if(player.IsIncapacitated())
	{
		player.ReviveFromIncap()
		player.SetReviveCount(0)
		player.SetHealthBuffer(0)
		player.SetHealth(val)
	}
	else
	{
		if(newHP >= playerMaxHealth)
		{
			//NetProps.SetPropInt(player,"m_isGoingToDie",0)
			//NetProps.SetPropInt(player,"m_isIncapacitated",0)
			player.ReviveFromIncap()
			player.SetReviveCount(0)
			player.SetHealthBuffer(0)
			player.SetHealth(playerMaxHealth)
		}
		else
		{
			//NetProps.SetPropInt(player,"m_isGoingToDie",0)
			//NetProps.SetPropInt(player,"m_isIncapacitated",0)
			player.ReviveFromIncap()
			player.SetReviveCount(0)
			player.SetHealthBuffer(0)
			player.SetHealth(newHP)
		}
	}
}




// Called "OnGameplayStart" it will spawn mushrooms for the current map 
// ----------------------------------------------------------------------------------------------------------------------------

spawnMushrooms <- function(){
	if(mapName in mushroomPositions){
		foreach(DS in mushroomPositions[mapName]){
			createRD_Medkit(DS.origin, DS.angles, DS.rotating, shroomProperties[DS.type])
		}
	}
}




// When specified in mushroom definition the passed entity gets attached to a rotation entity
// ----------------------------------------------------------------------------------------------------------------------------

AttachRotatorTo <- function(ent){
	local pos = ent.GetOrigin()
	local rotName = UniqueString("_mushroom_rot")
	local rotator = SpawnEntityFromTable("func_rotating", { targetname = rotName, origin = pos, spawnflags = 67})
	NetProps.SetPropVector(rotator, "m_Collision.m_vecMins", Vector(1,1,1))
	NetProps.SetPropVector(rotator, "m_Collision.m_vecMaxs", Vector(-1,-1,-1))
	DoEntFire("!self", "SetParent", "!activator", 0.00, rotator, ent);

}
rd_melee_getter

//****************************************************************************************
//																						//
//									rd_melee_getter.nut									//
//																						//
//****************************************************************************************

sharpMeleeData <-
[
	{ model = "models/v_models/weapons/v_knife_t.mdl", itemName = "knife", alias = "knife" },
	{ model = "models/weapons/melee/v_crowbar.mdl", itemName = "crowbar", alias = "crowbar" },
	{ model = "models/weapons/melee/v_fireaxe.mdl", itemName = "fireaxe", alias = "fireaxe" },
	{ model = "models/weapons/melee/v_katana.mdl", itemName = "katana", alias = "katana" },
	{ model = "models/weapons/melee/v_machete.mdl", itemName = "machete", alias = "machete" },
	{ model = "models/weapons/melee/v_pitchfork.mdl", itemName = "pitchfork", alias = "pitchfork" }
]

bluntMeleeData <-
[
	{ model = "models/weapons/melee/v_riotshield.mdl", itemName = "riotshield" , alias = "riotshield" },
	{ model = "models/weapons/melee/v_shovel.mdl", itemName = "shovel", alias = "shovel" },
	{ model = "models/weapons/melee/v_bat.mdl", itemName = "baseball_bat", alias = "bat" },
	{ model = "models/weapons/melee/v_cricket_bat.mdl", itemName = "cricket_bat", alias = "cricket" },
	{ model = "models/weapons/melee/v_golfclub.mdl", itemName = "golfclub", alias = "golfclub" },
	{ model = "models/weapons/melee/v_tonfa.mdl", itemName = "tonfa", alias = "tonfa" },
	{ model = "models/weapons/melee/v_electric_guitar.mdl", itemName = "electric_guitar", alias = "guitar" },
	{ model = "models/weapons/melee/v_frying_pan.mdl", itemName = "frying_pan", alias = "pan" }
]




// Before giving the melee check if it's available for the current map
// ----------------------------------------------------------------------------------------------------------------------------

function GiveMelee(player, melee){

	if(meleeAliases == null){
		meleeAliases = getAvailableMeleeAliases()
	}

	local sharps = GetAvailableSharpMelees()
	local blunts = GetAvailableBluntMelees()

	if(sharps.find(melee) != null || blunts.find(melee) != null){
		player.GiveItem(melee)
	}else{
		ClientPrint(null, 5, "The " + melee + " is not available on current map.")
		ClientPrint(null, 5, "Take one of those: " + meleeAliases)
	}
}

meleeAliases <- null;
function getAvailableMeleeAliases(){
	local aliases = "";
	foreach(dataSet in sharpMeleeData){
		if(IsModelPrecached(dataSet.model)){
			aliases += dataSet.alias + ", "
		}
	}
		foreach(dataSet in bluntMeleeData){
		if(IsModelPrecached(dataSet.model)){
			aliases += dataSet.alias + ", "
		}
	}
	return aliases;
}

function GetAvailableSharpMelees(){
	local sharps = []
	foreach(dataSet in sharpMeleeData){
		if(IsModelPrecached(dataSet.model)){
			sharps.append(dataSet.itemName)
		}
	}
	return sharps;
}

function GetAvailableBluntMelees(){
	local blunts = []
	foreach(dataSet in bluntMeleeData){
		if(IsModelPrecached(dataSet.model)){
			blunts.append(dataSet.itemName)
		}
	}
	return blunts;
}




// Will give the player a random sharp/blunt weapon depending which melee is available on the current map
// ----------------------------------------------------------------------------------------------------------------------------

function getAvailableMelee(attribute){
	local sharps = GetAvailableSharpMelees()
	local blunts = GetAvailableBluntMelees()
	local melee = null;
	if(sharps.len() > 0 && blunts.len() > 0){
		if(attribute == "Sharp"){
			melee = sharps[RandomInt(0, sharps.len() - 1)]
		}else{
			melee = blunts[RandomInt(0, blunts.len() - 1)]
		}
		return melee;
	}else{
		return "bat"
	}
}
rd_mode_description

//****************************************************************************************
//																						//
//								rd_mode_description.nut									//
//																						//
//****************************************************************************************




// Output the most important facts about this mode to the players console
// ----------------------------------------------------------------------------------------------------------------------------

::printMutationInfo <- function(ent){
	
	local line = "_______________________________________________________________________________________________"
	local txt = [
	
		"-------------------------------- RocketDude by ReneTM --------------------------------"
		" "
		"In this mutation, there are no bot survivors and the human Survivors start with 200 health."
		"They are equipped with a Grenade Launcher with infinite ammo and clip size, and either a sharp melee weapon or a Magnum."
		"The Grenade Launcher's grenades do full damage to infected but only 1 to the survivors."
		"They travel in a straight line like in TF2. There are only pills and adrenaline shots to pickup."
		"All medkits and defibs are gone. There are different kinds of mushrooms spread over the map."
		"They can be picked up by walking over them. Some may be hard to reach."
		" "
		line
		" "
		" "
		" "
		" "
		"Green       : Server sided autobhop"
		"Dark Blue   : Random throwable if the players inventory allows it"
		"Black       : Explosive! I would not touch them"
		" "
		"Yellow      : +75 HP"
		"Pink        : +50 HP"
		"Blue        : +25 HP"
		"White       : +10 HP"
		" "
		"! Touching those health mushrooms also removes any tempoary health and \"black and white\" state !"
		line
		" "
		" "
		"Specialties:"
		" "
		"When all survivors are on the ground and the round would restart \"the last chance\" mode gets activated."
		"This mode being active, the survivors will have the chance, to get revived by killing any special or boss infected."
		"This mode can only be active once. Survivors will be black and white after this mode."
		"Destroying \"Skeeting\" tank-rocks mid-air will give you +5hp."
		"When you are incapped, but other survivors are still up, you can get revived by killing any special or boss infected."
		"Survivors are able to crawl while being incapped. Keep your eyes open for mushrooms."
		" "
		line
		" "
		" "
		"Chatcommands:"
		" "
		"!hud           -> Vote to enable/disable the timer hud"
		"!countdown     -> Vote to start a countdown while being in saferoom"
		" "
		"Local usage only:"
		"----------------"
		"!speedrunmode  -> Vote to toggle between speedrunmode which disables all infected and normal mode"
		"!saveangles    -> Annoyed by survivors looking the wrong direction? Save your eye angles :)"
		"!stats         -> Print players besttimes to console"
		"!r             -> Vote to jump back to the saferoom to retry"
	]
	
	foreach(line in txt){
		ClientPrint(ent, 5, GREEN + line)
	}
	for(local i = 0; i < 10; i++){
		ClientPrint(ent, 5, " ")
	}
	ClientPrint(ent, 5, GREEN + "Check the console for information!")
}
rd_saferoom_timer

//****************************************************************************************
//																						//
//									rd_saferoom_timer.nut								//
//																						//
//****************************************************************************************



::countDownActive		<- false
::countDownStamp		<- Time()
::countdownTime			<- 3

if(!IsSoundPrecached("ui/beep07.wav")){
	PrecacheSound("ui/beep07.wav")
}

if(!IsSoundPrecached("ui/beep22.wav")){
	PrecacheSound("ui/beep22.wav")
}




// Executed by the chatcommand !countdown it will check of everybody wants to make a countdown
// ----------------------------------------------------------------------------------------------------------------------------

::saferoomTimerUseStamp <- Time()

::startSafeRoomTimer <- function(ent){
	
	if(!AllInStartSafeRoom()){
		return
	}
	
	if(!(mapName in survivorSpawnPoints)){
		ClientPrint(null, 5, "This function is only available for maps of c1-c14.")
		return
	}
	
	if(NetProps.GetPropInt(Entities.FindByClassname(null, "terror_gamerules"), "m_bInIntro")){
		return
	}
	
	if(!(Time() > saferoomTimerUseStamp + 5)){
		ClientPrint(null, 5, "This function can only be used every 5 seconds.")
		return
	}

	local playerscope = GetValidatedScriptScope(ent)
	if(!("countdown_vote" in playerscope)){
		playerscope["countdown_vote"] <- true
		foreach(player in GetHumanSurvivors()){
			local playerscope = GetValidatedScriptScope(player)
			if(!("countdown_vote" in playerscope)){
				ClientPrint(null, 5, GREEN + ent.GetPlayerName() + WHITE + " voted to start a countdown.")
				return
			}
		}
	}
	
	if(!countDownActive){
		countDownActive = true
		countDownStamp = Time()
		FreezePlayers()
		SetPlayersStartPositions()
		saferoomTimerUseStamp = Time()
		foreach(player in GetHumanSurvivors()){
			player.ValidateScriptScope()
			player.GetScriptScope().rawdelete("countdown_vote")
		}
	}
}




// Gets called from Think() function
// ----------------------------------------------------------------------------------------------------------------------------

::safeRoomTimer <- function(){
	if(countDownActive){
		if(Time() > countDownStamp + 1){
			if(countdownTime > 0){
				printTimeToChat()
				countdownTime--
				countDownStamp = Time()
			}else{
				printGoToChat()
				countDownActive = false
				countdownTime = 3
			}
		}
	}
}




// Prints timer to chat and outputs sound to all players
// ----------------------------------------------------------------------------------------------------------------------------

::printTimeToChat <- function(){
	
	local players = GetHumanSurvivors()
	
	ClientPrint(null, 5,  GREEN + countdownTime)
	foreach(player in players){
		EmitAmbientSoundOn("ui/beep07.wav", 0.75, 100, 100, player)	
	}
}

::printGoToChat <- function(){

	local players = GetHumanSurvivors()

	ClientPrint(null, 5,  ORANGE + "GO!")
	UnfreezePlayers()
	foreach(player in players){
		EmitAmbientSoundOn("ui/beep22.wav", 0.75, 100, 100, player)
	}
}




// Check if all survivors are in the beginning saferoom ( Re-entering possible )
// ----------------------------------------------------------------------------------------------------------------------------

::AllInStartSafeRoom <- function(){
	foreach(player in GetHumanSurvivors()){
		if(ResponseCriteria.GetValue(player, "instartarea" ) == "0"){
			return false
		}
	}
	return true
}


::FreezePlayers <- function(){
	foreach(player in GetHumanSurvivors()){
		NetProps.SetPropInt(player, "movetype", 0)
	}
}

::UnfreezePlayers <- function(){
	foreach(player in GetHumanSurvivors()){
		NetProps.SetPropInt(player, "movetype", 2)
	}	
}




// Sets the origin of all present players to one of in the saferoom positioned info_survivor_position entities.
// This method is only ment to be used when all survivors are in the saferoom!
// ----------------------------------------------------------------------------------------------------------------------------

::SetPlayersStartPositions <- function(){

	local offsets = [ Vector(16,16,0), Vector(16,-16,0), Vector(-16,-16,0), Vector(-16,16,0) ]
	
	local counter = 0
	
	foreach(player in GetHumanSurvivors()){
		if(counter == 4){
			counter = 0
		}
		player.SetOrigin(survivorSpawnPoints[ mapName ] + offsets[counter])
		counter++
	}
}




// These are player origins for all beginning saferooms ( c1-c14 ). We spawn every player with an offset
// ----------------------------------------------------------------------------------------------------------------------------

::survivorSpawnPoints <-
{
	c1m1_hotel				= Vector(599.989,5630.53,2851.46)
	c1m2_streets			= Vector(2360.92,5124.73,452.031)
	c1m3_mall				= Vector(6628.92,-1433.91,28.0313)
	c1m4_atrium				= Vector(-2090.7,-4624.16,540.031)

	c2m1_highway			= Vector(10895.7,7873.87,-548.574)
	c2m2_fairgrounds		= Vector(1636.83,2719.81,8.03125)
	c2m3_coaster			= Vector(4343.23,2048.8,-59.9688)
 	c2m4_barns				= Vector(3116.18,3330.29,-183.969)
	c2m5_concert			= Vector(-825.861,2292.01,-251.969)

	c3m1_plankcountry		= Vector(-12546.3,10456.1,248.893)
	c3m2_swamp 				= Vector(-8160.61,7615.23,16.0313)
	c3m3_shantytown			= Vector(-5709.34,2143.82,140.031)
	c3m4_plantation			= Vector(-5011.12,-1672.01,-92.8118)
	
	c4m1_milltown_a			= Vector(-7073.8,7719.17,117.924)
	c4m2_sugarmill_a		= Vector(3628.58,-1679.04,236.531)
	c4m3_sugarmill_b		= Vector(-1803.96,-13698.4,134.031)
	c4m4_milltown_b			= Vector(3910.57,-1569.59,236.281)
	c4m5_milltown_escape 	= Vector(-3367.6,7853.06,124.031)
	
	c5m1_waterfront			= Vector(798.114,676.213,-477.969)
	c5m2_park				= Vector(-3966.59,-1261.61,-339.969)
	c5m3_cemetery			= Vector(6402.67,8410.15,4.03125)
	c5m4_quarter			= Vector(-3291.22,4889.5,72.0313)
	c5m5_bridge				= Vector(-12039.5,5833.28,132.031)

	c6m1_riverbank			= Vector(911.249,3774.63,98.0062)
	c6m2_bedlam				= Vector(3157.03,-1218.18,-291.969)
	c6m3_port				= Vector(-2410.05,-456.402,-251.969)
	
	c7m1_docks				= Vector(13838.7,2583.8,36.2599)
	c7m2_barge				= Vector(10726.2,2445.25,180.031)
	c7m3_port				= Vector(1123.67,3230.13,174.531)
	
	c8m1_apartment			= Vector(2004.74,913.544,436.031)
	c8m2_subway				= Vector(2907.06,3063.35,20.0313)
	c8m3_sewers				= Vector(10948.2,4729.64,20.0313)
	c8m4_interior			= Vector(12376.7,12567.9,20.0313)
	c8m5_rooftop			= Vector(5405.11,8388.72,5540.03)

	c9m1_alleys				= Vector(-9951.59,-8578.78,-2.66612)
	c9m2_lots				= Vector(278.24,-1301.6,-171.969)
	
	c10m1_caves				= Vector(-11745.4,-14862.1,-214.225)
	c10m2_drainage 			= Vector(-11014.6,-9103.6,-587.969)
	c10m3_ranchhouse		= Vector(-8263.9,-5550.87,-20.9688)
	c10m4_mainstreet		= Vector(-3082.28,-23.326,164.031)
	c10m5_houseboat			= Vector(2120.59,4742.14,-59.9688)
	
	c11m1_greenhouse		= Vector(6815.1,-672.996,772.031)
	c11m2_offices			= Vector(5278.98,2785.43,52.0313)
	c11m3_garage			= Vector(-5368.26,-3069.3,20.0313)
	c11m4_terminal			= Vector(-447.003,3577.39,300.031)
	c11m5_runway			= Vector(-6716.97,12066.8,156.031)
	
	c12m1_hilltop			= Vector(-8073.41,-15118.7,283.495)
	c12m2_traintunnel		= Vector(-6611.49,-6712.19,352.031)
	c12m3_bridge			= Vector(-927.759,-10382.7,-59.9688)
	c12m4_barn				= Vector(7754.14,-11374,444.031)
	c12m5_cornfield			= Vector(10480.9,-566.041,-24.9688)
	
	c13m1_alpinecreek		= Vector(-3012.43,-589.132,68.0313)
	c13m2_southpinestream	= Vector(8566.48,7505.06,500.031)
	c13m3_memorialbridge	= Vector(-4372.79,-5150.93,100.031)
	c13m4_cutthroatcreek	= Vector(-3387.78,-9284.08,364.031)
	
	c14m1_junkyard			= Vector(-4189.09,-10697.9,-296.294)
	c14m2_lighthouse		= Vector(2220.41,-1103.17,452.031)
}




// Prevent players from using restartFromSaferoom on a finale
// ----------------------------------------------------------------------------------------------------------------------------

::valveFinaleMaps <-
[
	"c1m4_atrium"
	"c2m5_concert"
	"c3m4_plantation"
	"c4m5_milltown_escape"
	"c5m5_bridge"
	"c6m3_port"
	"c7m3_port"
	"c8m5_rooftop"
	"c9m2_lots"
	"c10m5_houseboat"
	"c11m5_runway"
	"c12m5_cornfield"
	"c13m4_cutthroatcreek"
	"c14m2_lighthouse"
]




//insafespot
//incheckpoint
rd_speedrun_mode

//****************************************************************************************
//																						//
//										rd_speedrun_mode.nut							//
//																						//
//****************************************************************************************




// Playing locally allows the player to enable the speedrun mode.
// !speedrunmode to toggle it. !r to respawn in the saferoom
// ----------------------------------------------------------------------------------------------------------------------------

::speedrunModeEnabled <- false

::speedrunModeToggle <- function(ent){
	
	if(!OnLocalServer()){
		return
	}
	
	ent.ValidateScriptScope()
	local scope = ent.GetScriptScope()
	scope["speedrun_mode"] <- true
	
	foreach(player in GetHumanSurvivors()){
		local playerscope = player.GetScriptScope()
		if(!("speedrun_mode" in playerscope)){
			ClientPrint(null, 5, GREEN + ent.GetPlayerName() + WHITE + " voted to " + ( speedrunModeEnabled ? "disable" : "enable" ) + " the speedrun mode!")
			return
		}
	}
	
	speedrunModeEnabled = !speedrunModeEnabled
	
	local speedrunDirector =
	{
		ProhibitBosses	= [true, false]
		MobMaxSize		= [0, 30]
		CommonLimit		= [0, 30]
		MobSpawnMinTime	= [9999, 90]
		MobSpawnMaxTime	= [9999, 180]
		MaxSpecials		= [0, 6]
		SpecialRespawnInterval = [9999, 45]
	}
	
	foreach(var,valarr in speedrunDirector){
		if(speedrunModeEnabled){
			SessionOptions[var.tostring()] <- valarr[0]
			removeAllInfected()
		}else{
			SessionOptions[var.tostring()] <- valarr[1]
		}
	}
	
	foreach(player in GetHumanSurvivors()){
		player.GetScriptScope().rawdelete("speedrun_mode")
	}
	
	ClientPrint(null, 5, WHITE + "Speedrun mode " + GREEN + ( speedrunModeEnabled ? "enabled" : "disabled") )
}




// While being in speedrun mode the survivor can jump back to the saferoom to restart his run
// ----------------------------------------------------------------------------------------------------------------------------

::restartFromSaferoom <- function(ent){
	
	if(!speedrunModeEnabled){
		ClientPrint(null, 5, WHITE + "Jumping back to the saferoom is restricted to " + GREEN + "speedrunmode!")
		return
	}
	
	if(!(Director.GetMapName() in survivorSpawnPoints)){
		return
	}
	
	if(valveFinaleMaps.find(Director.GetMapName()) != null){
		ClientPrint(null, 5, WHITE + "Jumping back to the saferoom is disabled for finales")
		return
	}
	
	local scope = GetValidatedScriptScope(ent)
	
	scope["go_back_2_safe"] <- true
	
	foreach(player in GetHumanSurvivors()){
		local playerscope = player.GetScriptScope()
		if(!("go_back_2_safe" in playerscope)){
			ClientPrint(null, 5, GREEN + ent.GetPlayerName() + WHITE + " voted to go back to the saferoom!")
			return
		}
	}
	
	foreach(player in GetHumanSurvivors()){
		local playerscope = GetValidatedScriptScope(player)
		playerscope.rawdelete("go_back_2_safe")
		local prevBest = PlayerTimeData[player].time_best
		PlayerTimeData[player] <- { timerActive = false, finished = false, startTime = Time(), endTime = Time(), time_best = prevBest, ticks = 0, seconds = 0 }
		bunnyPlayers.rawdelete(player)
		NetProps.SetPropInt(player, "m_afButtonDisabled", NetProps.GetPropInt(player, "m_afButtonDisabled") & ~2)
		GLOBALS.allowBulletTime = false
		
		// Set player eye angles when there are any saved
		if(player in SavedplayerEyeAngles){
			player.SnapEyeAngles(SavedplayerEyeAngles[player].EyeAngles)
		}
		player.SetVelocity(Vector(0,0,256))
	}
	
	SetPlayersStartPositions()
	setPlayersHealth()
}




// Prints all times of known maps to the console
// ----------------------------------------------------------------------------------------------------------------------------

::outputStats <- function(ent){
	
	if(ent != GetListenServerHost()){
		return
	}
	
	foreach(mapname in valveMaps){
		local str = null
		str = FileToString("rocketdude/speedrun/" + mapname + ".txt")
		if(str != null){
			str = strip(str)
			if(isNumeric(str)){
				ClientPrint(ent, 5, GREEN + mapname + GetCharChain(" ", 32 - mapname.len()) + ": " + str)
			}else{
				ClientPrint(ent, 5, ORANGE + mapname + GetCharChain(" ", 32 - mapname.len()) + ": INVALID DATA IN TEXT FILE")
			}
		}else{
			ClientPrint(ent, 5, BLUE + mapname + GetCharChain(" ", 32 - mapname.len()) + ": NO RECORD YET")
		}
	}
	for(local i = 0; i < 10; i++){
		ClientPrint(ent, 5, " ")
	}
	ClientPrint(ent, 5, GREEN + "Check the console for your stats!")
}




// Removes all infected like nb_delete_all does
// ----------------------------------------------------------------------------------------------------------------------------

::removeAllInfected <- function(){
	local player = null
	local witch = null
	local common = null
	
	while(player = Entities.FindByClassname(player, "player")){
		if(IsPlayerABot(player) && player.GetZombieType() != 9){
			player.Kill()
		}
	}
	
	while(witch = Entities.FindByClassname(witch, "witch")){
		if(witch.IsValid()){
			witch.Kill()
		}
	}
	
	while(common = Entities.FindByClassname(common, "infected")){
		if(common.IsValid()){
			common.Kill()
		}
	}
}




// Save player angles for speedrunning
// ----------------------------------------------------------------------------------------------------------------------------

::SavedplayerEyeAngles <- {}

function savePlayerEyeAngles(ent){
	
	if(!OnLocalServer()){
		ClientPrint(null, 5, WHITE + "Saving player angles is restricted to local servers!")
		return
	}
	
	if(!speedrunModeEnabled){
		ClientPrint(null, 5, WHITE + "Enable speedrunmode first ( " + GREEN + "!speedrunmode" + WHITE + " in chat )!")
		return
	}
	
	if(ResponseCriteria.GetValue(ent, "instartarea" ) == "0"){
		ClientPrint(null, 5, WHITE + "Move to the start area to save your eye angles!")
		return
	}
	
	if(ent.IsValid()){
		SavedplayerEyeAngles[ent] <- { EyeAngles = ent.EyeAngles() }
		ClientPrint(null, 5, WHITE + "Your eye angles got " + GREEN + "saved!")
	}
}
rd_utils

//****************************************************************************************
//																						//
//										rd_utils.nut									//
//																						//
//****************************************************************************************


getroottable()["TRACE_MASK_ALL"] <- -1
getroottable()["TRACE_MASK_SHOT"] <- 1174421507
getroottable()["TRACE_MASK_VISION"] <- 33579073
getroottable()["TRACE_MASK_NPC_SOLID"] <- 33701899
getroottable()["TRACE_MASK_PLAYER_SOLID"] <- 33636363
getroottable()["TRACE_MASK_VISIBLE_AND_NPCS"] <- 33579137


getroottable()["WHITE"]		<- "\x01"
getroottable()["BLUE"]		<- "\x03"
getroottable()["ORANGE"]	<- "\x04"
getroottable()["GREEN"]		<- "\x05"


::ZombieTypes <-
{
	COMMON		= 0
	SMOKER		= 1
	BOOMER		= 2
	HUNTER		= 3
	SPITTER		= 4
	JOCKEY		= 5
	CHARGER		= 6
	WITCH		= 7
	TANK		= 8
	SURVIVOR	= 9
	MOB			= 10
	WITCHBRIDE	= 11
	MUDMEN		= 12
}

::TEAMS <-
{ 
	SPECTATOR	= 1
	SURVIVOR	= 2
	INFECTED	= 3
}

::damageTypes <-
{
	GENERIC			= 0
	CRUSH			= 1
	BULLET			= 2
	SLASH			= 4
	BURN			= 8
	VEHICLE			= 16
	FALL			= 32
	BLAST			= 64
	CLUB			= 128
	SHOCK			= 256
	SONIC			= 512
	ENERGYBEAM		= 1024
	DROWN			= 16384
	PARALYSE		= 32768
	NERVEGAS		= 65536
	POISON			= 131072
	RADIATION		= 262144
	DROWNRECOVER	= 524288
	ACID			= 1048576
	SLOWBURN		= 2097152
	REMOVENORAGDOLL	= 4194304
}


// Creates the think timer which calls "Think()" every tick
// ----------------------------------------------------------------------------------------------------------------------------

function createThinkTimer(){
	local timer = null
	while (timer = Entities.FindByName(null, "thinkTimer")){
		timer.Kill()
	}
	timer = SpawnEntityFromTable("logic_timer", { targetname = "thinkTimer", RefireTime = 0.01 })
	timer.ValidateScriptScope()
	timer.GetScriptScope()["scope"] <- this

	timer.GetScriptScope()["func"] <- function (){
		scope.Think()
	}
	timer.ConnectOutput("OnTimer", "func")
	EntFire("!self", "Enable", null, 0, timer)
}




// Remove all deathcams e.g on c8m5_rooftop
// ----------------------------------------------------------------------------------------------------------------------------

function removeDeathFallCameras(){
	local deathCam = null
	while (deathCam = Entities.FindByClassname(deathCam, "point_deathfall_camera")){
		deathCam.Kill()
	}
}




// All needed cvars
// ----------------------------------------------------------------------------------------------------------------------------

::cvars <-
{
	// Survivor settings
	sv_infinite_ammo = 1
	survivor_allow_crawling = 1
	survivor_crawl_speed = 45
	first_aid_kit_max_heal = 200
	survivor_respawn_with_guns = 0
	first_aid_heal_percent = 0.8
	z_grab_ledges_solo = 1
	z_tank_incapacitated_decay_rate = 5
	// Grenadelauncher settings
	grenadelauncher_velocity = 1100
	grenadelauncher_startpos_right = 0
	grenadelauncher_startpos_forward = 16
	grenadelauncher_vel_variance = 0
	grenadelauncher_vel_up = 0
	// Force settings
	phys_explosion_force = 4096
	melee_force_scalar = 16
	melee_force_scalar_combat_character = 512
	phys_pushscale = 512
	// Infected settings
	z_force_attack_from_sound_range = 512
	z_brawl_chance = 1
	// Medicals
	pain_pills_health_threshold = 199
	pain_pills_health_value = 100
	// Items
	sv_infected_riot_control_tonfa_probability = 0
	sv_infected_ceda_vomitjar_probability = 0
	// Votes
	sv_vote_creation_timer = 8
	sv_vote_plr_map_limit = 128
	// Misc
	z_spawn_flow_limit = 99999
	director_afk_timeout = 99999
	mp_allowspectators = 0
	// Disable Placeholder bots
	director_transition_timeout = 1
}




local cvarChangeTime = Time()

function checkCvars(){
	if(Time() > cvarChangeTime + 4){
		foreach(var, value in cvars){
			if(Convars.GetFloat(var) != value.tofloat()){
				Convars.SetValue(var, value)
			}
		}
		cvarChangeTime = Time()
	}
}




// Create a func_timescale entity for the "bullet time"
// ----------------------------------------------------------------------------------------------------------------------------

timeScaler <- null
function createBulletTimerEntity(){
	while (timeScaler = Entities.FindByName(null, "timeScaler")){
		timeScaler.Kill()
	}
	timeScaler = SpawnEntityFromTable("func_timescale",
		{
			targetname = "timeScaler"
			acceleration = 0.05
			angles = "0 0 0"
			origin = Vector(0, 0, 0)
			blendDataMultiplier = 3.0
			minBlendRate = 0.1
			desiredTimescale = 0.25
		}
	)
}




// We roll a dice with probability of X to decide if event Y will occur 
// ----------------------------------------------------------------------------------------------------------------------------

function rollDice(probability){
	local roll = RandomInt(1, 100)
	
	if(probability == 100){
		return true
	}else if(roll <= probability){
		return true
	}
	return false
}




// Returns the closest survivor in any radius
// ----------------------------------------------------------------------------------------------------------------------------

function getClosestSurvivorTo(ent){
	local survivor = null
	local previousDistance = 0.0
	local closest = null
	local currentDistance = null
	
	foreach(survivor in GetSurvivors()){
		if(survivor != ent){
			if(previousDistance == 0.0){
				previousDistance = (ent.GetOrigin() - survivor.GetOrigin()).Length()
				closest = survivor
			}else{
				currentDistance = (ent.GetOrigin() - survivor.GetOrigin()).Length()
				if(currentDistance < previousDistance){
					previousDistance = currentDistance
					closest = survivor
				}
			}
		}
	}
	return closest
}




// Returns array of all players (bots included)
// ----------------------------------------------------------------------------------------------------------------------------

function GetSurvivors(){
	local ent = null
	while (ent = Entities.FindByClassname(ent, "player")){
		if (ent.GetZombieType() == 9){
			yield ent
		}
	}
}


// Returns array of all survivors (bots excluded)
// ----------------------------------------------------------------------------------------------------------------------------

::GetHumanSurvivors <- function(){
	local ent = null
	while (ent = Entities.FindByClassname(ent, "player")){
		if (ent.GetZombieType() == 9 && !IsPlayerABot(ent)){
			yield ent
		}
	}
}




// Validates script scope and returns it
// ----------------------------------------------------------------------------------------------------------------------------

::GetValidatedScriptScope <- function(ent){
	ent.ValidateScriptScope()
	return ent.GetScriptScope()
}




// Precache survivor models so game wont crash due to the "cm_NoSurvivorBots = 1" bug...Valve please fix
// ----------------------------------------------------------------------------------------------------------------------------

function precacheSurvivorModels(){
	
	local path = "models/survivors/"
	local models =
	[
		"survivor_coach.mdl", "survivor_gambler.mdl", "survivor_manager.mdl", "survivor_mechanic.mdl",
		"survivor_namvet.mdl", "survivor_biker.mdl", "survivor_producer.mdl", "survivor_teenangst.mdl"
	]

	foreach(model in models){
		if (!IsModelPrecached( path + model)){
			PrecacheModel(path + model)
		}
	}
}




// Precache projectile models and mushroom
// ----------------------------------------------------------------------------------------------------------------------------

function precacheRocketDudeModels(){
	
	local models =
	[
		"models/props_collectables/mushrooms_glowing.mdl",
		"models/w_models/weapons/w_rd_grenade_scale_x4_burn.mdl",
		"models/w_models/weapons/w_rd_grenade_scale_x4.mdl"
	]
	
	foreach(model in models){
		if(!IsModelPrecached(model)){
			PrecacheModel(model)
		}
	}
}




// Check if the current map is a valve map
// ----------------------------------------------------------------------------------------------------------------------------

::IsValveMap <- function(){
	if (valveMaps.find(mapName) == null){
		return false
	}
	return true
}




// Array of maps c1 - c14
// ----------------------------------------------------------------------------------------------------------------------------

::valveMaps <- [
	// DEAD CENTER
	"c1m1_hotel"
	"c1m2_streets"
	"c1m3_mall"
	"c1m4_atrium"
	// DARK CARNIVAL
	"c2m1_highway"
	"c2m2_fairgrounds"
	"c2m3_coaster"
	"c2m4_barns"
	"c2m5_concert"
	// SWAMP FEVER
	"c3m1_plankcountry"
	"c3m2_swamp"
	"c3m3_shantytown"
	"c3m4_plantation"
	// HARD RAIN
	"c4m1_milltown_a"
	"c4m2_sugarmill_a"
	"c4m3_sugarmill_b"
	"c4m4_milltown_b"
	"c4m5_milltown_escape"
	// THE PARISH
	"c5m1_waterfront"
	"c5m1_waterfront_sndscape"
	"c5m2_park"
	"c5m3_cemetery"
	"c5m4_quarter"
	"c5m5_bridge"
	// THE PASSING
	"c6m1_riverbank"
	"c6m2_bedlam"
	"c6m3_port"
	// THE SACRIFICE
	"c7m1_docks"
	"c7m2_barge"
	"c7m3_port"
	// NO MERCY
	"c8m1_apartment"
	"c8m2_subway"
	"c8m3_sewers"
	"c8m4_interior"
	"c8m5_rooftop"
	// CRASH COURSE
	"c9m1_alleys"
	"c9m2_lots"
	// DEATH TOLL
	"c10m1_caves"
	"c10m2_drainage"
	"c10m3_ranchhouse"
	"c10m4_mainstreet"
	"c10m5_houseboat"
	// DEAD AIR
	"c11m1_greenhouse"
	"c11m2_offices"
	"c11m3_garage"
	"c11m4_terminal"
	"c11m5_runway"
	// BLOOD HARVEST
	"c12m1_hilltop"
	"c12m2_traintunnel"
	"c12m3_bridge"
	"c12m4_barn"
	"c12m5_cornfield"
	// COLD STREAM
	"c13m1_alpinecreek"
	"c13m2_southpinestream"
	"c13m3_memorialbridge"
	"c13m4_cutthroatcreek"
	// THE LAST STAND
	"c14m1_junkyard"
	"c14m2_lighthouse"
]




// Mushroom positions of maps ( c1 -14 )
// ----------------------------------------------------------------------------------------------------------------------------

::mushroomPositions <-
{
	c1m1_hotel =
	[
		{ origin = Vector(910.302,5475.81,2656.03), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(3430.85,7487.11,1664.03), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(2168.26,5703.24,2464.03), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(2480.38,6217.6,2656.03), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(2311.68,7656.3,2464.03), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(1924.48,5762.97,1336.03), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(540.982,4832.06,1320.03), angles = "0 0 0", rotating = false, type = "large" }
	]
	c1m2_streets =
	[
		{ origin = Vector(1059.62,4854.1,704.031), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-6132.59,-1135.09,472.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-3897.52,2238.46,320.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-1253.71,777.393,811.381), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(-5343.9,-2082.83,456.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-8619.01,-2111.07,963.138), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-7171.31,-4490.4,1224.7), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(1598.73,4225.02,521.433), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-2223.24,982.305,41.804), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-4588.86,1475.42,440.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-8635.88,-4498.73,440.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c1m3_mall =
	[
		{ origin = Vector(6447.99,-2648.58,288.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(1294.77,-2343.76,325.803), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(675.635,-4834.9,536.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(3600.03,-2384.03,825.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(4008.45,-290.606,0.03125), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(2266.34,-1561.81,536.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-198.189,-5201.84,415.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-1891.39,-4127.36,574.433), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(6997.87,-1362.59,152.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(3896.59,-2873.28,318.958), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(2754.07,-1866.65,280.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(2414.66,-2412.11,536.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(1584.63,-5446.67,364.031), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(-1239.21,-4472.65,318.958), angles = "0 0 0", rotating = false, type = "large" }
	]
	c1m4_atrium =
	[
		{ origin = Vector(-1806.74,-4825.39,621.031), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-2388.27,-3878.41,824.031), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(-3856.95,-3123.88,536.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-2572.11,-5303.06,553.52), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-6031.35,-3306.58,792.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-3340.16,-4001.26,744.781), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-5103.33,-3918.44,408.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-4451.52,-3207.95,106.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-3209.12,-3865.85,107.617), angles = "0 90 0", rotating = false, type = "large" }
		{ origin = Vector(-5343.23,-4186.55,1080.03), angles = "0 0 0", rotating = false, type = "large" }
	]
	c2m1_highway =
	[
		{ origin = Vector(9530.42,8430.98,-176.474), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(2273.79,4287.25,-936.719), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(2188.58,3274.16,-807.969), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(3462.18,8450.17,-843.079), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-1168.83,2091.84,-1738.7), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(6915.73,7505.84,-675.791), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(3015.77,6931.89,-899.608), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(2914.95,4895.79,-507.969), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(1163.45,2193.78,-1213.73), angles = "0 0 0", rotating = false, type = "large" }
		{ origin = Vector(-1334.14,-2035.05,-510.97), angles = "0 0 0", rotating = false, type = "large" }
	]
	c2m2_fairgrounds =
	[
		{ origin = Vector(3966.348633,-465.165253,48.42), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-1985.2,-3877.99,32.0313), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-3616.02,-721.635,0.03125), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(1576.99,1513.79,8.03125), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-3329.16,-4239.81,352.595), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(2710.8,516.643,200.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-2126.08,318.905,128.031), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(-874.256,-1539.82,128.031), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(-1493.91,-4415.5,-1.77925), angles = "0 0 0", rotating = false, type = "large" }
	]
	c2m3_coaster =
	[
		{ origin = Vector(2048.36,3296.04,196.031), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-1582.4,1617.42,128.281), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-87.7301,3973.93,208.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(2797.84,1638.93,-35.5456), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(-3968.39,1550.79,413.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-66.8414,3611.93,208.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-2754.75,1143.2,620.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-3743.81,3732.83,544.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c2m4_barns =
	[
		{ origin = Vector(3138.24,3629.02,-3.96875), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-2756.8,1308.9,-43.9688), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(1081.55,1024.68,-147.969), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(2166.15,1419.64,18.4938), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-226.231,886.084,387.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-1918.83,95.4541,32.0313), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-3042.29,1694.38,-255.969), angles = "0 0 0", rotating = false, type = "large" }
		{ origin = Vector(3715.38,662.404,-191.969), angles = "0 0 0", rotating = false, type = "tiny" }
	]
	c2m5_concert =
	[
		{ origin = Vector(-1096.98,2354.9,-255.969), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-2308.02,3211.99,140.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-2637.91,3340.46,312.678), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-448.975,2760.9,-255.969), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-1979.98,2505.26,191.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-2679.04,2500.67,191.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c3m1_plankcountry =
	[
		{ origin = Vector(-10586.4,10170.8,571.008), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-5590.18,6843.73,220.031), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(-980.031,4902.7,144.16), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-1067.97,4902.7,144.16), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-1022.58,4902.7,144.16), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-8121.03,7213.5,266.604), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(-5392.27,5891.52,256.034), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(-6212.27,7861.63,48.0313), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-3386.78,6069.38,698.369), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(-2159.44,8640.77,253.1), angles = "0 0 0", rotating = false, type = "large" }
		{ origin = Vector(-1022.87,4946.13,332.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c3m2_swamp =
	[
		{ origin = Vector(-8114.13,5131.93,295.391), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-1801.48,2818.93,47.6229), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(-1884.05,3218.16,36.4844), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(1936.43,1250.89,19.2292), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(7614.55,3192.79,122.329), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-1895.61,3104.54,226.169), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(4779.86,1102.69,41.265), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(8702.53,512.321,569.088), angles = "0 0 0", rotating = false, type = "large" }
	]
	c3m3_shantytown =
	[
		{ origin = Vector(-5293.03,1199.51,871.913), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-4088.94,-3094.7,189.711), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(-659.526,-2485.02,4.4949), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-4605.58,-236.376,181.425), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(1532.24,-4869.25,24.0313), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-2651.48,-931.619,74.849), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-5504.8,-3256.66,308.68), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(3013.28,-4477.09,86.1035), angles = "0 90 0", rotating = false, type = "large" }
	]
	c3m4_plantation =
	[
		{ origin = Vector(-3650.61,-1527.09,542.849), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(1665.36,900.156,127.237), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(1788.93,-415.403,224.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(2591.66,57.0649,224.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(2032.64,252.058,416.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(1664.07,314.551,416.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(1663.35,291.383,224.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(1320.5,251.85,416.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(2743.47,-3207.78,65.3052), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(3040.81,2016.1,133.507), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-1426.21,-3443.73,187.907), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(1665.04,536.081,640.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(1867.68,-138.64,600.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c4m1_milltown_a =
	[
		{ origin = Vector(-5801.77,7494.31,1009.75), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(383.878,3381.04,368.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-6356.01,7456.24,104.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-1547.58,6908.92,200.773), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(4152.82,1222.7,184.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-609.475,6187.33,296.031), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(1482.28,4149.37,435.32), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(3617.5,2161.08,368.155), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(3338.04,179.84,586.974), angles = "0 0 0", rotating = false, type = "large" }
	]
	c4m2_sugarmill_a =
	[
		{ origin = Vector(4260,-3671.71,406.515), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-719.081,-7283.16,441.979), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-921.116,-8917.79,295.392), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(2429.61,-5696.4,124.659), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(4315.07,-4528.41,97.6315), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-10.6827,-12670.2,113.272), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(3212.06,-3036.58,1164.28), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(2725.17,-4272.82,329.469), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(2581.79,-6098.72,100.829), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-345.992,-8559.92,624.281), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(-1507.19,-13161,1141.23), angles = "0 0 0", rotating = false, type = "large" }
	]
	c4m3_sugarmill_b =
	[
		{ origin = Vector(-925.624,-13530.8,432.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-53.2867,-6479.09,441.729), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(2434.5,-5315.34,194.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(2587.19,-6096.47,100.157), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(4114.54,-3217.8,406.265), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-1106.24,-8463.68,624.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(936.826,-6244.9,638.807), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(430.224,-4582.72,310.428), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(1741.85,-3929.74,737.02), angles = "0 0 0", rotating = false, type = "large" }
	]
	c4m4_milltown_b =
	[
		{ origin = Vector(3331.17,-847.188,586.974), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(665.963,2783.55,227.927), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(387.227,3489.37,336.38), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(4327.79,1780.41,363.761), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(673.109,4776.85,131.802), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-1486.32,7441.23,366.277), angles = "0 0 0", rotating = false, type = "large" }
	]
	c4m5_milltown_escape =
	[
		{ origin = Vector(-5868.91,8159.17,348.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-4641.69,7626.45,479.597), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(-6338.14,7169.93,104.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-5432.06,7068.65,100.536), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-5315.37,8567.07,584.038), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-7088.42,7698.42,113.924), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(-5815.44,6623.25,126.972), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-5801.8,7496.52,1009.75), angles = "0 0 0", rotating = false, type = "large" }
	]
	c5m1_waterfront =
	[
		{ origin = Vector(-335.683,64.8597,-52.3949), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-1177.66,-2398.49,144.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-2347.78,-553.647,-367.969), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-2720.07,-1612.63,-13.3263), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-1904.66,-1864.33,-71.9454), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-3068.68,-2336.01,-157.992), angles = "0 0 0", rotating = false, type = "medium" }
	]
	c5m2_park =
	[
		{ origin = Vector(-5054.91,-2216.52,-127.982), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-9802.61,-5213.03,-79.9688), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-8046.08,-6670.04,-247.969), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-7940.64,-6671.62,-247.969), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-7551.83,-416.524,-127.969), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-7166.92,-3491.44,44.5587), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-8789.87,-5193.33,89.7595), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-8113.72,-5774.25,485.766), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-6815.91,-8437.09,250.906), angles = "0 0 0", rotating = false, type = "large" }
	]
	c5m3_cemetery =
	[
		{ origin = Vector(6127.99,7700.52,208.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(5907.05,1012.93,155.243), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(2837.89,2634.4,176.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(4728.95,4570.35,131.378), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(4432.79,3202.69,154.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(6189.01,1337.86,-159.969), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(8768.77,-6591.84,756.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(7417.2,-8926.28,264.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c5m4_quarter =
	[
		{ origin = Vector(-2368.29,3616.46,64.0313), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-639.981,1607.46,224.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-569.655,1031.48,96.0313), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-3650.66,3888.94,384.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(29.7625,-1607.78,287.837), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-1193.22,1488.05,452.401), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-813.288,-2031.78,422.465), angles = "0 0 0", rotating = false, type = "large" }
	]
	c5m5_bridge =
	[
		{ origin = Vector(-6151.38,6420.53,765.699), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-1954.61,6463.8,852.885), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(2792.36,6336.46,790.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(2792.36,6450.22,790.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(2792.36,6211.1,790.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-7031.1,6298.27,456.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-12334.9,6552.2,453.565), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-3862.72,6225.25,897.47), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(2290.68,6425.58,790.031), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(14201,6326.86,790.031), angles = "0 90 0", rotating = false, type = "large" }
	]
	c6m1_riverbank =
	[
		{ origin = Vector(669.412,3116.3,640.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(1168,4459.88,510.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(2095.14,1641.12,352.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(4520.78,2670.4,555.536), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(4461.7,2397.54,224.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-4004.19,554.621,864.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-4223.99,1571.49,727.091), angles = "0 0 0", rotating = false, type = "large" }
	]
	c6m2_bedlam =
	[
		{ origin = Vector(2148.07,-1201.23,288.031), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(1750.27,3709.59,-275.002), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(1498.32,4839.88,-159.969), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-224.862,1354.13,28.093), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(1213.93,1966.51,336.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(998.689,2701.23,96.0313), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(1589.86,4615.77,32.0313), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(2868.05,5702.03,-1063.97), angles = "0 90 0", rotating = false, type = "large" }
		{ origin = Vector(5883.71,4207.33,-890.867), angles = "0 0 0", rotating = false, type = "large" }
	]
	c6m3_port =
	[
		{ origin = Vector(-2248.68,-646.404,320.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-1761.71,256.046,156.841), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(583.696,1765.25,160.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-1184.55,-952.523,0.03125), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-788.678,-952.523,0.03125), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-1379.67,-952.523,0.03125), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-374.426,-889.011,0.0760522), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(447.796,1824.08,160.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-901.026,2123.37,320.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(345.633,-358.962,184.031), angles = "0 0 0", rotating = false, type = "small" }
	]
	c7m1_docks =
	[
		{ origin = Vector(14063.8,2301.94,16.2378), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(10153.7,449.507,129.809), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(11410.8,-229.048,-63.9688), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(12001.8,-787.905,-35.1111), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(5208.64,552.395,382.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(4679.74,764.071,303.384), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(2516.95,-188.518,138.451), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(3404.88,1676.66,336.031), angles = "0 90 0", rotating = false, type = "large" }
	]
	c7m2_barge =
	[
		{ origin = Vector(9920.64,608.323,322.374), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-3827.21,671.672,342.031), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(2140.39,1436.81,132.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(5435.93,755.192,256.282), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(10062.7,2095.96,305.796), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-593.486,2559.1,756.694), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-2894.79,725.009,576.031), angles = "0 90 0", rotating = false, type = "large" }
	]
	c7m3_port =
	[
		{ origin = Vector(1052.7,2623.67,544.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-2137.34,-539.338,-95.9688), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(680.945,2047.86,160.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(583.284,1770.19,160.758), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-938.757,931.624,352.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(736.739,2264.6,640.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(1560.46,168.305,345.56), angles = "0 90 0", rotating = false, type = "large" }
	]
	c8m1_apartment =
	[
		{ origin = Vector(1811.17,1797.96,640.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(896.289,3030.33,637.176), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(2797.04,4191.32,15.8693), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(334.88,767.923,957.29), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(2624.62,2212.92,945.241), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(1976.26,3944.68,608.031), angles = "0 0 0", rotating = false, type = "large" }
		{ origin = Vector(3493.26,4004.85,1436.03), angles = "0 0 0", rotating = false, type = "tiny" }
	]
	c8m2_subway =
	[
		{ origin = Vector(2937.46,4155.97,-178.67), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(8467.56,3832.97,376.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(4526.2,3801.29,-287.969), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(2204.75,3968.89,-335.969), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(6775.15,2899.15,-178.67), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(7567.9,3417.3,424.031), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(8328.45,4596.1,1216.03), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(8805.87,5685.34,768.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c8m3_sewers =
	[
		{ origin = Vector(12631.2,5360.45,957.54), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(12712.9,6696.47,800.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(13137.6,7424.46,16.0313), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(11203.7,5091.59,712.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(11852.1,7923.79,276.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(13711,10120.7,-358.28), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(13068.6,10985.1,-191.969), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(13070.8,11448,-277.217), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(13788.6,11101.2,746.031), angles = "0 0 0", rotating = false, type = "large" }
		{ origin = Vector(13198.7,13927.2,5624.03), angles = "0 0 0", rotating = false, type = "large" }
	]
	c8m4_interior =
	[
		{ origin = Vector(12162.3,13317.7,42.4583), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(13687.1,14624.8,576.031), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(12718.3,14651.8,424.772), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(13695.3,13991.1,5641.54), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(12314.1,13388.4,152.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(13439.6,15006.3,624.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(14045.5,14862.1,5920.03), angles = "0 0 0", rotating = false, type = "large" }
	]
	c8m5_rooftop =
	[
		{ origin = Vector(7487.75,9322.88,6314.71), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(5986.62,7845.9,6210.03), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(7303.02,8911.17,6092.24), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(7048.08,9024.08,6096.03), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(6960.91,9464.52,5644.03), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(7022.35,7746.25,16.0313), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(7714.38,9340.58,5952.03), angles = "0 0 0", rotating = false, type = "large" }
	]
	c9m1_alleys =
	[
		{ origin = Vector(-8671.74,-9921.56,384.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(205.191,-6577.32,-45.685), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-2334.43,-8371.1,0.03125), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-7876.03,-10448,192.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-6194.29,-10207.2,348.124), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(-2692.39,-9362.13,362.682), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-1326.74,-3292.39,445.932), angles = "0 0 0", rotating = false, type = "large" }
	]
	c9m2_lots =
	[
		{ origin = Vector(1621.53,-1232.15,186.989), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(6858.532,6943.091,223.031), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(7559.25,6343.99,48.0313), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(7559.25,6680.94,48.0313), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(7535.98,6160.13,427.767), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(3568.14,-492.286,35.807), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(1761.86,218.214,45.0175), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(7844.706,6643.726,375.036), angles = "0 90 0", rotating = false, type = "large" }
	]
	c10m1_caves =
	[
		{ origin = Vector(-11659.9,-13385.5,554.054), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-12974.9,-6724.85,176.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-12244.6,-5599.09,-73.601), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-13089.6,-5241.27,-287.969), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(-12349.5,-9805.34,496.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-12974.9,-5860.65,176.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-10690.6,-4991.48,688.028), angles = "0 90 0", rotating = false, type = "large" }
	]
	c10m2_drainage =
	[
		{ origin = Vector(-11215.6,-8447.19,-273.116), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-8220.77,-8322.58,-452.1), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-9873.86,-7689.84,-376.396), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-10213.5,-8154.52,-162.483), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(-9872.4,-6733.67,-307.969), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-7846.5,-6987.36,-457.214), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-8826.14,-7633.1,953.457), angles = "0 0 0", rotating = false, type = "large" }
	]
	c10m3_ranchhouse =
	[
		{ origin = Vector(-9839.35,-6342.99,89.6114), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-4811.54,-1131.53,506.244), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-9440.1,-2750.96,-38.9688), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-6958,-1886.3,8.03125), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-8059.92,-5811.83,400.031), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(-12539.413,-6515.756,408.296), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-9092.79,-3946.72,356.642), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-5081.95,-1693.59,626.119), angles = "0 0 0", rotating = false, type = "large" }
	]
	c10m4_mainstreet =
	[
		{ origin = Vector(-3068.25,-57.6381,1039.03), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(2790.76,-1699.64,336.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-1375.73,-4670.05,-55.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-1425.11,-4670.65,-55.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(137.127,-1897.99,112.031), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(2756.35,-2412.78,336.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(1617.19,-4384.35,96.0313), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-673.274,-4583.52,176.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-1399.49,-4674.15,192.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c10m5_houseboat =
	[
		{ origin = Vector(2123.12,2634.09,326.222), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(4225.13,-5089.24,-170.896), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(2874.1,2413.57,-39.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(3065.88,2759.15,-39.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(3952.22,256.841,324.537), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(3854.66,4218.71,320.031), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(4264.85,-4686.83,231.018), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(2195.19,-4720.26,-35.1493), angles = "0 0 0", rotating = false, type = "large" }
	]
	c11m1_greenhouse =
	[
		{ origin = Vector(6418.19,-690.905,831.396), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(3427.550,1374.021,819.144), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(3518.21,2217.03,183.338), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(3370.21,731.601,554.228), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(2910.57,2108.9,416.535), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(4351.72,-300.349,1116.8), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(3394.22,-871.949,1029.47), angles = "0 0 0", rotating = false, type = "large" }
	]
	c11m2_offices =
	[
		{ origin = Vector(5488.19,4064.33,434.733), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(7419.72,3330.88,1212.23), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(7513.56,5654.95,16.0313), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(5839.04,3168.24,523.664), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(6272.13,1022.59,16.0313), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(5447.53,3558.41,303.983), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(6623.48,4828.47,600.482), angles = "0 0 0", rotating = false, type = "large" }
	]
	c11m3_garage =
	[
		{ origin = Vector(-3838.76,-3139.08,936.54), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-5872.75,-1719.78,512.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-3488.1,2854.93,32.0313), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-3647.99,2854.92,31.0313), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-3565.8,2854.83,32.0313), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-4952.03,-2623.2,352.031), angles = "0 90 0", rotating = false, type = "tiny" }
		{ origin = Vector(-6805.34,-1273.95,550.072), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-5239.47,124.543,1301.27), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-2881.81,3153.91,160.031), angles = "0 90 0", rotating = false, type = "large" }
	]
	c11m4_terminal =
	[
		{ origin = Vector(-93.3845,4457.05,144.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(98.4939,3620.52,16.0313), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(1163.08,4456.62,296.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(710.154,5631.1,296.031), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(541.737,3785.52,536.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(471.801,2959.14,348.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(2130.92,1586.85,448.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(2780.61,6941.86,313.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c11m5_runway =
	[
		{ origin = Vector(-6838.83,9651.14,568.031), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-5431.83,11812.2,62.8417), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-5418.15,10341.3,60.0313), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-6900.6,11531.3,-191.969), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-6891.96,10827.7,-191.969), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-5795.82,9010.43,176.974), angles = "0 0 0", rotating = false, type = "large" }
	]
	c12m1_hilltop =
	[
		{ origin = Vector(-8089.921,-15117.432,277.785), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-6446.82,-8719.63,1015.15), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-6553.63,-7570.19,384.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-6418.22,-7523.43,365.19), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-6367.87,-7414.88,377.974), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-10427.594,-13344.635,746.753), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-10980.1,-10901.5,938.975), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-8990.35,-8981.87,1062.03), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-7808.35,-9486.53,992.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c12m2_traintunnel =
	[
		{ origin = Vector(-8229.1,-7517.71,160.353), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-7719.68,-8901.63,304.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-4604.06,-8322.47,-63.5097), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-4175.46,-8323.85,-63.9688), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-3711.27,-8392.14,-63.1514), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-8740.09,-7214.79,200.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-8547.49,-8900.32,304.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-7563.7,-8612.52,826.025), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-4185.79,-8717.72,232.031), angles = "0 90 0", rotating = false, type = "large" }
	]
	c12m3_bridge =
	[
		{ origin = Vector(-1137.81,-10944.3,160.031), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(1827.29,-12229.5,484.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(1913.03,-12602.3,-31.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(2030.97,-12602.3,-31.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(1970.48,-12602.3,-31.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-952.414,-10439.4,72.0312), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(3342.01,-14299.5,169.088), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(4914.05,-13113.9,1141.33), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(5932.44,-13851.9,272.05), angles = "0 0 0", rotating = false, type = "large" }
	]
	c12m4_barn =
	[
		{ origin = Vector(9334.13,-9355.23,932.537), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(11022.1,-4584.55,324.481), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(10382,-2600.35,-63.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(10526,-2600.35,-63.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(10453.7,-2600.35,-63.9688), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(7488.13,-10687,897.565), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(10616.6,-7429.15,274.641), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(9688.78,-4243.98,722.381), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(10453.7,-1712.4,268.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c12m5_cornfield =
	[
		{ origin = Vector(10059.6,821.12,462.47), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(7138.16,270.204,596.031), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(8960.78,3370.82,201.741), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(9060.78,3370.82,204.697), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(9272.22,3547.45,961.057), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(8446.37,422.173,590.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(6823.95,1191.26,794.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(7186.65,2650.19,1034.12), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(7536.12,2649.97,1034.12), angles = "0 0 0", rotating = false, type = "large" }
	]
	c13m1_alpinecreek =
	[
		{ origin = Vector(-2778.1,1140.71,255.406), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(1200.27,1406.95,1601.38), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(992.23,183.899,585.485), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(1120.63,183.726,585.398), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-2876.72,2797.21,1273), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-2332,3247.16,976.031), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(869.342,2451.07,805.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(880.042,-464,476.011), angles = "0 90 0", rotating = false, type = "large" }
	]
	c13m2_southpinestream =
	[
		{ origin = Vector(7961.18,6390.28,585.92), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(6728,2925.41,1216.03), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(78.6595,8679.27,-404.969), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(8107.63,4172.91,649.555), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(5677.56,2211.83,1090), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(4905.09,2577.37,1120.03), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-356.712,4977.07,272.031), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-348.279,6151.25,302.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c13m3_memorialbridge =
	[
		{ origin = Vector(-2169.22,-4092.02,1758.03), angles = "0 90 0", rotating = true, type = "bh" }
		{ origin = Vector(-2169.54,-4092.02,2201.03), angles = "0 90 0", rotating = false, type = "item" }
		{ origin = Vector(1003.74,-4472.38,590.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-3844.47,-4095.34,896.031), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(6777.83,-4114.74,2201.03), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(3687.47,-4074.24,2201.03), angles = "0 90 0", rotating = false, type = "medium" }
		{ origin = Vector(3686.79,-4095.17,896.031), angles = "0 90 0", rotating = false, type = "large" }
	]
	c13m4_cutthroatcreek =
	[
		{ origin = Vector(-3872.6,-8143.45,723.031), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-772.295,3198.8,-101.969), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-1174.01,4688.39,180.031), angles = "0 0 0", rotating = false, type = "exp" }
		{ origin = Vector(-3622.38,-5926.86,623.746), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-3916.44,-3227.55,360.898), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-653.084,1568.25,18.0313), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-391.136,3752.91,88.0313), angles = "0 0 0", rotating = false, type = "large" }
	]
	c14m1_junkyard =
	[
		{ origin = Vector(-4227.94,-8819.65,103), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-2505.24,1649.99,182.772), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(-4450.59,2204.69,-58.7463), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-1669.74,-5730.32,-300.582), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(-2676.9,2190.81,-50.752), angles = "0 0 0", rotating = false, type = "small" }
		{ origin = Vector(-4814.390,2711.914,100.450), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-2407,7408.93,168.031), angles = "0 0 0", rotating = false, type = "large" }
	]
	c14m2_lighthouse =
	[
		{ origin = Vector(1319.83,344.463,830.588), angles = "0 0 0", rotating = true, type = "bh" }
		{ origin = Vector(-2479.24,5569.72,-101.291), angles = "0 0 0", rotating = false, type = "item" }
		{ origin = Vector(320.403,923.429,560.905), angles = "0 90 0", rotating = false, type = "exp" }
		{ origin = Vector(-4567.81,3575.97,1400.03), angles = "0 0 0", rotating = false, type = "tiny" }
		{ origin = Vector(479.378,946.381,696.031), angles = "0 90 0", rotating = false, type = "small" }
		{ origin = Vector(-981.672,2524.87,701.112), angles = "0 0 0", rotating = false, type = "medium" }
		{ origin = Vector(-4725,5825,-92.968), angles = "0 0 0", rotating = false, type = "large" }
	]
}




// Output count of mushroom statistics
// ----------------------------------------------------------------------------------------------------------------------------

::map_mushroom_stats <- {}

::outputMushroomData <- function(){
	
	map_mushroom_stats.clear()
	local count_total = 0
	local nl = "\n"
	
	foreach(mapname, mushroomArray in mushroomPositions){
		foreach(DS in mushroomArray){
			local type = DS.type
			if(!(mapname in map_mushroom_stats)){
				map_mushroom_stats[mapname] <- { bh = 0, exp = 0, item = 0, tiny = 0, small = 0, medium = 0, large = 0 }
			}
			map_mushroom_stats[mapname][type] += 1
			count_total += 1
		}
	}
	
	printl("- - - - Mushroom stats - - - -" + nl)
	//
	printl("Total count of placed mushrooms: " + count_total + " ( " + mushroomPositions[mapName].len() + " in this map )" + nl)
	//
	foreach(mapname, datatable in map_mushroom_stats){
		printl("================== " + mapname + " " + GetCharChain("=", 32 - mapname.len() ) + nl)
		printl("BunnyHop: " + datatable.bh + "  Explosive: " + datatable.exp + "  Item: " + datatable.item)
		printl("Tiny: " + datatable.tiny + "  Small: " + datatable.small + "  Medium: " + datatable.medium + "  Large: " + datatable.large + nl)
	}
}




// Returns a concatenated string of the passed string with length of num
// ----------------------------------------------------------------------------------------------------------------------------

::GetCharChain <- function(str, num){
	local txt = ""
	for(local i = 0; i < num; i++){
		txt += str
	}
	return txt
}




// Returns the slot the weapon belongs to
// ----------------------------------------------------------------------------------------------------------------------------

function getItemSlot(item){
	local className = item.GetClassname()
	
	local slot0 =
	[
		"weapon_grenade_launcher","weapon_rifle_m60",
		"weapon_rifle","weapon_rifle_desert","weapon_rifle_ak47",
		"weapon_rifle_sg552","weapon_smg_mp5",
		"weapon_shotgun_chrome","weapon_pumpshotgun",
		"weapon_shotgun_spas","weapon_autoshotgun",
		"weapon_smg","weapon_smg_silenced",
		"weapon_hunting_rifle","weapon_sniper_military",
		"weapon_sniper_scout","weapon_sniper_awp"
	]
	local slot1 = 
	[
		"weapon_melee","weapon_chainsaw",
		"weapon_pistol","weapon_pistol_magnum"
	]
	local slot2 =
	[
		"weapon_molotov","weapon_pipe_bomb","weapon_vomitjar"
	]
	local slot3 =
	[
		"weapon_first_aid_kit","weapon_defibrillator",
		"weapon_upgradepack_explosive","weapon_upgradepack_incendiary"
	]
	local slot4 =
	[
		"weapon_adrenaline","weapon_pain_pills"
	]
	local slot5 = 
	[
		"weapon_oxygentank","weapon_propanetank","weapon_gascan",
		"weapon_gnome","weapon_cola_bottles","weapon_fireworkcrate"
	]

	if(slot0.find(className) != null){
		return "slot0"
	}else if(slot1.find(className) != null){
		return "slot1"
	}else if(slot2.find(className) != null){
		return "slot2"
	}else if(slot3.find(className) != null){
		return "slot3"
	}else if(slot4.find(className) != null){
		return "slot4"
	}else if(slot5.find(className) != null){
		return "slot5"
	}else{
		return null
	}
}




// This will track timings of the survivor
// ----------------------------------------------------------------------------------------------------------------------------

::PlayerTimeData <- {}

function PlayerTimer(ent){

	if(!(ent in PlayerTimeData)){
		PlayerTimeData[ent] <- { timerActive = false, finished = false, startTime = Time(), endTime = Time(), time_best = 0, ticks = 0, seconds = 0 }
	}
	
	if(PlayerIsOnGround(ent)){
		if(ent in PlayerTimeData){
			if(!PlayerTimeData[ent].finished){
				if(PlayerTimeData[ent].ticks < 30){
					PlayerTimeData[ent].ticks += 1
				}else{
					PlayerTimeData[ent].ticks = 0
					PlayerTimeData[ent].seconds += 1
				}
			}
		}
	}
}




// Check if survivor reached the saferoom
// ----------------------------------------------------------------------------------------------------------------------------

function survivorSaferoomCheck(ent){
	if(!PlayerTimeData[ent].finished){
		if( ResponseCriteria.GetValue(ent, "incheckpoint" ) == "1" ){
			printFinalGroundTime(ent)
			ProcessSurvivorTime(ent)
		}
	}
}




// Iteration over all human player and pass them to different methods
// ----------------------------------------------------------------------------------------------------------------------------

function PlayerFunctions(){
	foreach(ent in GetHumanSurvivors()){
		if(ent.IsValid()){
			PlayerTimer(ent)
			survivorSaferoomCheck(ent)
		}
	}
}




// Returns true when player is on the ground
// ----------------------------------------------------------------------------------------------------------------------------

function PlayerIsOnGround(player){
	if(NetProps.GetPropInt(player, "m_fFlags") & 1){
		return true
	}
	return false
}




// Print a chat message in color x 
// ----------------------------------------------------------------------------------------------------------------------------

function toChat(color, message, sound){
	local player = Entities.FindByClassname(null, "player")
	switch(color)
	{
		case "white"	: color = "\x01" ; break
		case "blue"		: color = "\x03" ; break
		case "orange"	: color = "\x04" ; break
		case "green"	: color = "\x05" ; break
	}
	switch(sound)
	{
		case "reward"	: sound = "ui/littlereward.wav" ; break
		case "error"	: sound = "ui/beep_error01.wav" ; break
		case "click"	: sound = "ui/menu_click01.wav" ; break
	}
	ClientPrint(null, 5, color + message)
	if(sound != null){
		EmitAmbientSoundOn( sound, 1, 100, 100, player)
	}
}




// Will change the model of "tank_rock" to a log when it is a L4D1 map
// When "last chance mode" is active, the rock will start glowing
// ----------------------------------------------------------------------------------------------------------------------------

function tankrockListener(){
	local rock = null

	while(rock = Entities.FindByClassname(rock, "tank_rock")){
		if(rock.IsValid()){
			local scope = GetValidatedScriptScope(rock)
			
			if(survivorSet == 1){
				if(!("usesLog" in scope)){
					rock.SetModel("models/props_foliage/tree_trunk.mdl")
					scope["usesLog"] <- true
				}
			}
			
			// When last change mode is active we need to enable the glow on all new rocks
			if(last_chance_active){
				if(!("glowing" in scope)){
					NetProps.SetPropInt(rock, "m_Glow.m_iGlowType", 3)
					rock.GetScriptScope()["glowing"] <- true
				}
			}
		}
	}
}




// Returns the given color with changed intesity as string 
// ----------------------------------------------------------------------------------------------------------------------------

function getColorWithIntensity(color, intensity){	

	local values = split(color, " ")
	
	local rNew = values[0].tofloat()
	local gNew = values[1].tofloat()
	local bNew = values[2].tofloat()

	rNew	= (rNew / 100 * intensity).tointeger()
	gNew	= (gNew / 100 * intensity).tointeger()
	bNew	= (bNew / 100 * intensity).tointeger()

	return "" + rNew + " " + gNew + " " + bNew
}




// Is the current server a local one
// ----------------------------------------------------------------------------------------------------------------------------

function OnLocalServer(){
	if(GetListenServerHost() == null){
		return false
	}
	return true
}

Community Mutations

These mutations were made by the community, eventually added to the base game by Valve.

Community1 - Special Delivery


//-----------------------------------------------------
Msg("Activating Special Delivery\n");
Msg("Made by Rayman1103\n");

if ( !IsModelPrecached( "models/infected/smoker.mdl" ) )
	PrecacheModel( "models/infected/smoker.mdl" );
if ( !IsModelPrecached( "models/infected/smoker_l4d1.mdl" ) )
	PrecacheModel( "models/infected/smoker_l4d1.mdl" );
if ( !IsModelPrecached( "models/infected/boomer.mdl" ) )
	PrecacheModel( "models/infected/boomer.mdl" );
if ( !IsModelPrecached( "models/infected/boomer_l4d1.mdl" ) )
	PrecacheModel( "models/infected/boomer_l4d1.mdl" );
if ( !IsModelPrecached( "models/infected/boomette.mdl" ) )
	PrecacheModel( "models/infected/boomette.mdl" );
if ( !IsModelPrecached( "models/infected/hunter.mdl" ) )
	PrecacheModel( "models/infected/hunter.mdl" );
if ( !IsModelPrecached( "models/infected/hunter_l4d1.mdl" ) )
	PrecacheModel( "models/infected/hunter_l4d1.mdl" );
if ( !IsModelPrecached( "models/infected/limbs/exploded_boomette.mdl" ) )
{
	PrecacheModel( "models/infected/limbs/exploded_boomette.mdl" );
	::community1_no_female_boomers <- true;
}
if ( !IsModelPrecached( "models/infected/spitter.mdl" ) )
	PrecacheModel( "models/infected/spitter.mdl" );
if ( !IsModelPrecached( "models/infected/jockey.mdl" ) )
	PrecacheModel( "models/infected/jockey.mdl" );
if ( !IsModelPrecached( "models/infected/charger.mdl" ) )
	PrecacheModel( "models/infected/charger.mdl" );

MutationOptions <-
{
	ActiveChallenge = 1

	cm_CommonLimit = 0
	cm_DominatorLimit = 8
	cm_MaxSpecials = 8
	cm_ProhibitBosses = false
	cm_SpecialRespawnInterval = 0
	cm_AggressiveSpecials = false

	SpecialInitialSpawnDelayMin = 0
	SpecialInitialSpawnDelayMax = 5
	ShouldAllowSpecialsWithTank = true
	EscapeSpawnTanks = true
	MobMinSize = 0
	MobMaxSize = 0
	NoMobSpawns = true

	SmokerLimit = 2
	BoomerLimit = 2
	HunterLimit = 2
	SpitterLimit = 2
	JockeyLimit = 2
	ChargerLimit = 2

	// convert items that aren't useful
	weaponsToConvert =
	{
		weapon_pipe_bomb =	"weapon_molotov_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}
	
	DefaultItems =
	[
		"weapon_pistol_magnum",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}
}

MutationState <-
{
	SIModelsBase = [ [ "models/infected/smoker.mdl", "models/infected/smoker_l4d1.mdl" ],
					[ "models/infected/boomer.mdl", "models/infected/boomer_l4d1.mdl", "models/infected/boomette.mdl" ],
						[ "models/infected/hunter.mdl", "models/infected/hunter_l4d1.mdl" ],
							[ "models/infected/spitter.mdl" ],
								[ "models/infected/jockey.mdl" ],
									[ "models/infected/charger.mdl" ] ]
	SIModels = [ [ "models/infected/smoker.mdl", "models/infected/smoker_l4d1.mdl" ],
				[ "models/infected/boomer.mdl", "models/infected/boomer_l4d1.mdl", "models/infected/boomette.mdl" ],
					[ "models/infected/hunter.mdl", "models/infected/hunter_l4d1.mdl" ],
						[ "models/infected/spitter.mdl" ],
							[ "models/infected/jockey.mdl" ],
								[ "models/infected/charger.mdl" ] ]
	ModelCheck = [ false, false, false, false, false, false ]
	LastBoomerModel = ""
	BoomersChecked = 0
	LeftSafeAreaThink = false
}

function LeftSafeAreaThink()
{
	local player = null;
	while ( player = Entities.FindByClassname( player, "player" ) )
	{
		if ( ( !player.IsValid() ) || ( NetProps.GetPropInt( player, "m_iTeamNum" ) != 2 ) )
			continue;
		
		if ( ResponseCriteria.GetValue( player, "instartarea" ) == "0" )
		{
			SessionOptions.cm_MaxSpecials = 8;
			SessionState.LeftSafeAreaThink = false;
			break;
		}
		else
			continue;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	local spawner = null;
	while ( spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ) )
	{
		if ( spawner.IsValid() )
		{
			local population = NetProps.GetPropString( spawner, "m_szPopulation" );
			
			if ( population == "boomer" || population == "hunter" || population == "smoker" || population == "jockey"
				|| population == "charger" || population == "spitter" || population == "new_special" || population == "church"
					|| population == "tank" || population == "witch" || population == "witch_bride" || population == "river_docks_trap" )
				continue;
			else
				spawner.Kill();
		}
	}
	
	if ( Director.GetMapName() == "c1m1_hotel" )
		DirectorOptions.cm_TankLimit <- 0;
	else if ( Director.GetMapName() == "c5m5_bridge" || Director.GetMapName() == "c6m3_port" )
		DirectorOptions.cm_MaxSpecials = 0;
	else if ( Director.GetMapName() == "c7m1_docks" )
		DirectorOptions.cm_ProhibitBosses = true;
}

function OnGameEvent_player_left_safe_area( params )
{
	local player = GetPlayerFromUserID( params["userid"] );
	if ( !player )
		return;
	
	local instartarea = ResponseCriteria.GetValue( player, "instartarea" );
	if ( instartarea == "1" )
	{
		SessionOptions.cm_MaxSpecials = 0;
		SessionState.LeftSafeAreaThink = true;
	}
}

function OnGameEvent_triggered_car_alarm( params )
{
	if ( !Director.IsTankInPlay() )
	{
		DirectorOptions.cm_AggressiveSpecials = true;
		ZSpawn( { type = 8 } );
		DirectorOptions.cm_AggressiveSpecials = false;
	}
	
	StartAssault();
}

function OnGameEvent_finale_start( params )
{
	if ( Director.GetMapName() == "c6m3_port" )
		DirectorOptions.cm_MaxSpecials = 8;
}

function OnGameEvent_gauntlet_finale_start( params )
{
	if ( Director.GetMapName() == "c5m5_bridge" )
		DirectorOptions.cm_MaxSpecials = 8;
}

function OnGameEvent_player_spawn( params )
{
	local player = GetPlayerFromUserID( params["userid"] );
	
	if ( ( !player ) || ( player.IsSurvivor() ) )
		return;
	
	local zombieType = player.GetZombieType();
	if ( zombieType > 6 )
		return;
	
	local modelName = player.GetModelName();
	
	if ( !SessionState.ModelCheck[ zombieType - 1 ] )
	{
		if ( (zombieType == 2) && !("community1_no_female_boomers" in getroottable()) )
		{
			if ( SessionState.LastBoomerModel != modelName )
			{
				SessionState.LastBoomerModel = modelName;
				SessionState.BoomersChecked++;
			}
			if ( SessionState.BoomersChecked > 1 )
				SessionState.ModelCheck[ zombieType - 1 ] = true;
		}
		else
			SessionState.ModelCheck[ zombieType - 1 ] = true;
		
		if ( SessionState.SIModelsBase[zombieType - 1].find( modelName ) == null )
		{
			SessionState.SIModelsBase[zombieType - 1].append( modelName );
			SessionState.SIModels[zombieType - 1].append( modelName );
		}
	}
	
	if ( SessionState.SIModelsBase[zombieType - 1].len() == 1 )
		return;
	
	local zombieModels = SessionState.SIModels[zombieType - 1];
	if ( zombieModels.len() == 0 )
		SessionState.SIModels[zombieType - 1].extend( SessionState.SIModelsBase[zombieType - 1] );
	local foundModel = zombieModels.find( modelName );
	if ( foundModel != null )
	{
		zombieModels.remove( foundModel );
		return;
	}
	
	local randomElement = RandomInt( 0, zombieModels.len() - 1 );
	local randomModel = zombieModels[ randomElement ];
	zombieModels.remove( randomElement );
	
	player.SetModel( randomModel );
}

function Update()
{
	if ( SessionState.LeftSafeAreaThink )
		LeftSafeAreaThink();
	if ( Director.GetCommonInfectedCount() > 0 )
	{
		local infected = null;
		while ( infected = Entities.FindByClassname( infected, "infected" ) )
		{
			if ( infected.IsValid() )
				infected.Kill();
		}
	}
}

Community2 - Flu Season


Msg("Activating Community Mutation 2\n");

DirectorOptions <-
{
	ActiveChallenge = 1
	RelaxMinInterval = 5
	RelaxMaxInterval = 10
	cm_SpecialRespawnInterval = 8
	SpecialInitialSpawnDelayMin = 1
	SpecialInitialSpawnDelayMax = 5

	cm_MaxSpecials = 8
	BoomerLimit = 4
	SpitterLimit = 4
	SmokerLimit = 0
	HunterLimit = 0
	ChargerLimit = 0
	JockeyLimit = 0

	ProhibitBosses = 1 //tanks still spawn at finales though
	MegaMobSize = 0

	// convert items that aren't useful
	weaponsToConvert =
	{
		weapon_vomitjar = 	"weapon_molotov_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	local spawner = null;
	while ( spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ) )
	{
		if ( spawner.IsValid() )
		{
			local population = NetProps.GetPropString( spawner, "m_szPopulation" );
			
			if ( population == "boomer" || population == "spitter" || population == "church" || population == "river_docks_trap" )
				continue;
			else
				spawner.Kill();
		}
	}
}

Community3 - Riding My Survivor


Msg("Activating community mutation 3.\n");

DirectorOptions <-
{
	ActiveChallenge = 1
	
	cm_CommonLimit = 0
	
	BoomerLimit = 0
	ChargerLimit = 0
	HunterLimit = 0
	JockeyLimit = 4
	SmokerLimit = 0
	SpitterLimit = 0
	cm_MaxSpecials = 4
	
	cm_SpecialRespawnInterval = 20
	
	function ConvertZombieClass(id)
	{
		return 5;
	}
}

Community4 - Nightmare


//-----------------------------------------------------
Msg("Activating Nightmare\n");


DirectorOptions <-
{
	ActiveChallenge = 1

	cm_DominatorLimit = 8
	cm_MaxSpecials = 8
	cm_SpecialRespawnInterval = 30
	cm_AutoReviveFromSpecialIncap = 1
	cm_AllowPillConversion = 0
	cm_TankLimit = 4
	ProhibitBosses = 0

	BoomerLimit = 0
	SurvivorMaxIncapacitatedCount = 3
	SpecialInitialSpawnDelayMin = 5
	SpecialInitialSpawnDelayMax = 30
	TankHitDamageModifierCoop = 0.25	
}

Community5 - Death's Door


//-----------------------------------------------------
Msg("Activating Death's Door\n");
Msg("Made by Rayman1103\n");

MutationOptions <-
{
	cm_ShouldHurry = true
	cm_AllowPillConversion = false
	cm_AllowSurvivorRescue = false
	SurvivorMaxIncapacitatedCount = 0
	TempHealthDecayRate = 0.0

	function AllowFallenSurvivorItem( classname )
	{
		if ( classname == "weapon_first_aid_kit" )
			return false;

		return true;
	}

	weaponsToConvert =
	{
		weapon_first_aid_kit = "weapon_pain_pills_spawn"
		weapon_adrenaline = "weapon_pain_pills_spawn"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
			return weaponsToConvert[classname];

		return 0;
	}

	DefaultItems =
	[
		"weapon_pistol",
		"weapon_pistol",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
			return DefaultItems[idx];

		return 0;
	}
}

function OnGameEvent_round_start( params )
{
	Convars.SetValue( "pain_pills_decay_rate", 0.0 );

	for ( local player; player = Entities.FindByClassname( player, "player" ); ) // only works in restarts, which is desired
	{
		if ( player.IsSurvivor() && !IsPlayerABot( player ) )
			player.GetScriptScope().HeartbeatOn = false;
	}
}

function OnGameEvent_player_left_safe_area( params )
{
	SessionOptions.TempHealthDecayRate = 0.27;
}

function OnGameEvent_player_hurt( params )
{
	local player = GetPlayerFromUserID( params["userid"] );
	if ( !player || !player.IsSurvivor() || player.IsHangingFromLedge() )
		return;

	if ( NetProps.GetPropInt( player, "m_bIsOnThirdStrike" ) == 0 )
	{
		local health = player.GetHealth();
		if ( health > 0 )
		{
			if ( !IsPlayerABot( player ) )
			{
				local scope = player.GetScriptScope();
				if ( !scope.HeartbeatOn && health < player.GetMaxHealth() / 4 )
				{
					EmitSoundOnClient( "Player.Heartbeat", player );
					scope.HeartbeatOn = true;
				}
			}
			if ( health == 1 )
			{
				NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 1 );
				NetProps.SetPropInt( player, "m_isGoingToDie", 1 );
			}
		}
	}
}

function OnGameEvent_defibrillator_used( params )
{
	local player = GetPlayerFromUserID( params["subject"] );
	if ( !player )
		return;

	player.SetHealth( 1 );
	player.SetHealthBuffer( 99 );

	if ( !IsPlayerABot( player ) )
	{
		EmitSoundOnClient( "Player.Heartbeat", player );
		player.GetScriptScope().HeartbeatOn = true;
	}
	NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 1 );
	NetProps.SetPropInt( player, "m_isGoingToDie", 1 );
}

function OnGameEvent_heal_success( params )
{
	local player = GetPlayerFromUserID( params["subject"] );
	if ( !player )
		return;

	if ( !IsPlayerABot( player ) )
	{
		local scope = player.GetScriptScope();
		if ( scope.HeartbeatOn && player.GetHealth() >= player.GetMaxHealth() / 4 )
		{
			StopSoundOn( "Player.Heartbeat", player );
			scope.HeartbeatOn = false;
		}
	}
}

function CheckHealthAfterLedgeHang( userid )
{
	local player = GetPlayerFromUserID( userid );
	if ( !player )
		return;

	local health = player.GetHealth();

	if ( !IsPlayerABot( player ) )
	{
		local scope = player.GetScriptScope();
		if ( !scope.HeartbeatOn && health < player.GetMaxHealth() / 4 )
		{
			EmitSoundOnClient( "Player.Heartbeat", player );
			scope.HeartbeatOn = true;
		}
	}
	if ( health == 1 )
	{
		NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 1 );
		NetProps.SetPropInt( player, "m_isGoingToDie", 1 );
	}
}

function OnGameEvent_revive_success( params )
{
	local player = GetPlayerFromUserID( params["subject"] );
	if ( !params["ledge_hang"] || !player )
		return;

	if ( NetProps.GetPropInt( player, "m_bIsOnThirdStrike" ) == 0 )
		EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.CheckHealthAfterLedgeHang(" + params["subject"] + ")" );
}

function OnGameEvent_player_spawn( params )
{
	local player = GetPlayerFromUserID( params["userid"] );
	if ( !player || !player.IsSurvivor() )
		return;

	if ( !IsPlayerABot( player ) )
	{
		player.ValidateScriptScope();
		local scope = player.GetScriptScope();
		if ( !("HeartbeatOn" in scope) )
			scope.HeartbeatOn <- false;
	}
}

function OnGameEvent_player_death( params )
{
	if ( !("userid" in params) )
		return;

	local player = GetPlayerFromUserID( params["userid"] );
	if ( !player || !player.IsSurvivor() )
		return;

	if ( !IsPlayerABot( player ) )
	{
		local scope = player.GetScriptScope();
		if ( scope.HeartbeatOn )
		{
			StopSoundOn( "Player.Heartbeat", player );
			scope.HeartbeatOn = false;
		}
	}
}

function OnGameEvent_player_bot_replace( params )
{
	local player = GetPlayerFromUserID( params["player"] );
	if ( !player || !player.IsSurvivor() ) // in case an infected player uses jointeam 1
		return;

	local scope = player.GetScriptScope();
	if ( scope.HeartbeatOn )
	{
		StopSoundOn( "Player.Heartbeat", player );
		scope.HeartbeatOn = false;
	}
}

function OnGameEvent_bot_player_replace( params )
{
	local player = GetPlayerFromUserID( params["player"] );
	if ( !player )
		return;

	if ( !player.IsDead() ) // in case jointeam 2 or sb_takecontrol was used on a dead bot
	{
		if ( player.GetHealth() < player.GetMaxHealth() / 4 )
			player.GetScriptScope().HeartbeatOn = true; // unreliable if sb_takecontrol was used
		else
			StopSoundOn( "Player.Heartbeat", player );
	}
}

function OnGameEvent_player_complete_sacrifice( params )
{
	local player = GetPlayerFromUserID( params["userid"] );
	if ( !player )
		return;

	NetProps.SetPropInt( player, "m_takedamage", 0 );
	NetProps.SetPropInt( player, "m_isIncapacitated", 1 );
}

if ( !Director.IsSessionStartMap() )
{
	function PlayerSpawnDeadAfterTransition( userid )
	{
		local player = GetPlayerFromUserID( userid );
		if ( !player )
			return;

		player.SetHealth( 24 );
		player.SetHealthBuffer( 26 );

		if ( !IsPlayerABot( player ) )
		{
			EmitSoundOnClient( "Player.Heartbeat", player );
			player.GetScriptScope().HeartbeatOn = true;
		}
	}

	function PlayerSpawnAliveAfterTransition( userid )
	{
		local player = GetPlayerFromUserID( userid );
		if ( !player )
			return;

		local oldHealth = player.GetHealth();
		local maxHeal = player.GetMaxHealth() / 2;
		local healAmount = 0;

		if ( oldHealth < maxHeal )
		{
			healAmount = floor( (maxHeal - oldHealth) * 0.8 + 0.5 );
			player.SetHealth( oldHealth + healAmount );
			local bufferHealth = player.GetHealthBuffer() - healAmount;
			if ( bufferHealth < 0.0 )
				bufferHealth = 0.0;
			player.SetHealthBuffer( bufferHealth );
		}
		NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 0 );
		NetProps.SetPropInt( player, "m_isGoingToDie", 0 );
	}

	function OnGameEvent_player_transitioned( params )
	{
		local player = GetPlayerFromUserID( params["userid"] );
		if ( !player || !player.IsSurvivor() )
			return;

		if ( NetProps.GetPropInt( player, "m_lifeState" ) == 2 )
			EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.PlayerSpawnDeadAfterTransition(" + params["userid"] + ")", 0.03 );
		else
			EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.PlayerSpawnAliveAfterTransition(" + params["userid"] + ")" );
	}
}

Community6 - Confogl


// vim: set ts=4
// CompLite.nut (Confogl Mutation)
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// =============================================================================

IncludeScript("globals", this);

CompLite = InitializeCompLite();

// Don't need to do anything else if we're not first load
if(CompLite.Globals.GetCurrentRound() > 0)
{
	Msg("CompLite Starting Round "+CompLite.Globals.GetCurrentRound()+" on ");
	local mi = CompLite.Globals.MapInfo;
	if(mi.isIntro) Msg("an intro map.\n");
	else Msg("a non-intro map.\n");

	Msg("Found "+mi.saferoomPoints.len()+" saferoom points.\n");
	Msg("Map has a scavenge event? " + mi.hasScavengeEvent + "\n");
	Msg("MapName: "+mi.mapname+"\n");

	return;
}

Msg("Activating Mutation CompLite v3.6\n");

DirectorOptions.ActiveChallenge <- 1
DirectorOptions.cm_ProhibitBosses <- 0
DirectorOptions.cm_AllowPillConversion <- 0

// Name shortening references
local g_Timer = CompLite.Globals.Timer;
local g_FrameTimer = CompLite.Globals.FrameTimer;
local g_MapInfo = CompLite.Globals.MapInfo;
local g_GSC = CompLite.Globals.GSC;
local g_GSM = CompLite.Globals.GSM;
local g_MobResetti = CompLite.Globals.MobResetti;
local Modules = CompLite.Modules;

// Uncomment to add a debug event listener
//g_GSC.AddListener(Modules.MsgGSL());

g_GSC.AddListener(Modules.SpitterControl(Director, DirectorOptions, Entities));
g_GSC.AddListener(Modules.MobControl(g_MobResetti));


// Give out hunting rifles on non-intro maps.
// But limit them to 1 of each.
g_GSC.AddListener(Modules.HRControl(Entities, CompLite.Globals, Director));


g_GSC.AddListener(
	Modules.BasicItemSystems(
		// AllowWeaponSpawn Limits
		// 0: Always remove
		// >0: Keep the first n instances, delete others
		// <-1: Delete the first n instances, keep others.
		{
			weapon_defibrillator = 0
			weapon_grenade_launcher = 0
			weapon_upgradepack_incendiary = 0
			weapon_upgradepack_explosive = 0
			weapon_chainsaw = 0
			//weapon_molotov = 1
			//weapon_pipe_bomb = 2
			//weapon_vomitjar = 1
			weapon_propanetank = 0
			weapon_oxygentank = 0
			weapon_rifle_m60 = 0
			//weapon_first_aid_kit = -5
			upgrade_item = 0
		},
		// Conversion Rules
		{
		weapon_autoshotgun	  = "weapon_pumpshotgun_spawn"
		weapon_shotgun_spas	 = "weapon_shotgun_chrome_spawn"
		weapon_rifle			= "weapon_smg_spawn"
		weapon_rifle_desert	 = "weapon_smg_spawn"
		weapon_rifle_sg552	  = "weapon_smg_mp5_spawn"
		weapon_rifle_ak47	   = "weapon_smg_silenced_spawn"
		weapon_hunting_rifle	= "weapon_smg_silenced_spawn"
		weapon_sniper_military  = "weapon_shotgun_chrome_spawn"
		weapon_sniper_awp	   = "weapon_shotgun_chrome_spawn"
		weapon_sniper_scout	 = "weapon_pumpshotgun_spawn"
		weapon_first_aid_kit	= "weapon_pain_pills_spawn"
		weapon_molotov = "weapon_molotov_spawn"
		weapon_pipe_bomb = "weapon_pipe_bomb_spawn"
		weapon_vomitjar = "weapon_vomitjar_spawn"
		},
		// Default item list
		[
			"weapon_pain_pills",
			"weapon_pistol",
			"weapon_hunting_rifle"
		]
	)
);

// Enforce various item spawns to be single pickup.
g_GSC.AddListener(
	Modules.EntKVEnforcer(Entities,
		// classnames
		[
			"weapon_adrenaline_spawn",
			"weapon_pain_pills_spawn",
			"weapon_melee_spawn",
			"weapon_molotov_spawn",
			"weapon_vomitjar_spawn",
			"weapon_pipebomb_spawn"
		],
		// models
		[],
		// key to enforce
		"count",
		// value to set it to
		1
	)
);

// Entity tracking/removal/limiting
g_GSC.AddListener(
	Modules.ItemControl(Entities, 
	// Limit to value by classname
		{
			weapon_adrenaline_spawn = 1
			weapon_pain_pills_spawn = 4
			witch = 1
			func_playerinfected_clip = 0
			weapon_molotov_spawn = 1
			weapon_pipe_bomb_spawn = 1
			weapon_vomitjar_spawn = 1
		},
	// Limit to value by model name
		{
			["models/props_junk/propanecanister001a.mdl"] = 0,
			["models/props_equipment/oxygentank01.mdl"] = 0,
			["models/props_junk/explosive_box001.mdl"] = 1
		},
	// Remove these items from all saferooms
		[
			"weapon_adrenaline_spawn",
			"weapon_pain_pills_spawn",
			//"weapon_melee_spawn",
			"weapon_molotov_spawn",
			"weapon_pipe_bomb_spawn",
			"weapon_vomitjar_spawn"
		],
		g_MapInfo
	)
);

// Limit melee weapons to 4
g_GSC.AddListener(Modules.MeleeWeaponControl(Entities, 4));

// Remove all gascans on non-scavenge maps
g_GSC.AddListener(Modules.GasCanControl(Entities, g_MapInfo));


Msg("GSC/M/L Script run.\n");

Confogl Additional Scripts

These scripts are included in the main Confogl and globals scripts.

globals

// Call this once on script start to initialize CompLite's libraries and hook them up to ChallengeScript events.
// parentTable: The parent table where the CompLite library namespace should be created. This 
//		should be a table which will not be wiped on roundstart. getroottable() is a good idea, defaults to getroottable();
// customNameSpace: The custom name to use for the CompLite Libraries' namespace. Should be passed as a string. Defaults to "CompLite"
// ChallengeScript: Pass a reference to the ChallengeScript table. Defaults to ::DirectorScript.MapScript.ChallengeScript
function InitializeCompLite(parentTable = getroottable() , customNameSpace = "CompLite", ChallengeScript = ::DirectorScript.MapScript.ChallengeScript )
{
	if(customNameSpace in parentTable)
	{
		local CompLite = parentTable[customNameSpace];
		CompLite.Globals.IncrementRoundNumber();
		CompLite.Globals.GSM.Reset();

		if(CompLite.Globals.GetCurrentRound() == 1)
		{
			CompLite.Globals.MapInfo.IdentifyMap(Entities);
		}

		ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
		ChallengeScript.Update <- CompLite.ChallengeScript.Update;
		return CompLite;
	}
	
	local CompLite = parentTable[customNameSpace] <- {};
	
	IncludeScript("gamestate_model", CompLite);
	IncludeScript("globaltimers", CompLite);
	IncludeScript("utils", CompLite);
	IncludeScript("modules", CompLite);
	
	CompLite.ChallengeScript <- {
		CompLite = CompLite
		DirectorOptions = {
			CompLite = CompLite
			function AllowWeaponSpawn( classname ) 
			{ 
				return CompLite.Globals.GSM.OnAllowWeaponSpawn(classname);
			}
			function ConvertWeaponSpawn( classname ) 
			{ 
				return CompLite.Globals.GSM.OnConvertWeaponSpawn(classname);
			}
			function GetDefaultItem( idx ) 
			{
				return CompLite.Globals.GSM.OnGetDefaultItem(idx);
			}
			function ConvertZombieClass( id ) 
			{ 
				return CompLite.Globals.GSM.OnConvertZombieClass(id);
			}
		}

		function Update()
		{
			CompLite.Globals.Timer.Update();
			CompLite.Globals.FrameTimer.Update();
			CompLite.Globals.GSM.DoFrameUpdate();
		}
	}

	CompLite.Globals <- CompLiteGlobals(CompLite, Director, CompLite.ChallengeScript.DirectorOptions);

	ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
	ChallengeScript.Update <- CompLite.ChallengeScript.Update;

	CompLite.Globals.MapInfo.IdentifyMap(Entities);

	return CompLite;
}

class CompLiteGlobals {
	constructor(NameSpace, director, dopts)
	{
		Timer = NameSpace.Timers.GlobalSecondsTimer();
		FrameTimer = NameSpace.Timers.GlobalFrameTimer();
		MapInfo = NameSpace.Utils.MapInfo();
		GSC = NameSpace.GameState.GameStateController();
		GSM = NameSpace.GameState.GameStateModel(GSC, director);
		MobResetti = NameSpace.Utils.ZeroMobReset(director, dopts, FrameTimer);
	}

	function IncrementRoundNumber() { m_iRoundNumber++; }
	function GetCurrentRound() { return m_iRoundNumber; }
	//public
	Timer = null;
	FrameTimer = null;
	MapInfo = null;
	GSM = null;
	GSC = null;
	MobResetti = null;
	
	// private
	m_iRoundNumber = 0;
}
gamestate_model

// vim: set ts=4
// L4D2 GameState Model for Mutation VScripts
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// =============================================================================

// double include protection
if("GameState" in this) return;
GameState <- {
	ROUNDSTART_DELAY_INTERVAL = 2
};
IncludeScript("utils", this);

class GameState.GameStateModel
{
	constructor(controller, director)
	{
		m_controller = controller;
		m_pDirector = director;
	}

	function DoFrameUpdate()
	{
		if(m_bLastUpdateTankInPlay)
		{
			if(!m_pDirector.IsTankInPlay())
			{
				m_bLastUpdateTankInPlay = false;
				m_controller.TriggerTankLeavesPlay();
			}
		}
		else if(m_pDirector.IsTankInPlay())
		{
			m_bLastUpdateTankInPlay = true;
			m_controller.TriggerTankEntersPlay();
		}
		if(!m_bLastUpdateSafeAreaOpened && m_pDirector.HasAnySurvivorLeftSafeArea())
		{
			m_bLastUpdateSafeAreaOpened = true;
			m_controller.TriggerSafeAreaOpen();
		}
		if(!m_bRoundStarted && m_bHeardAWS && m_bHeardCWS && m_bHeardGDI 
			&& m_iRoundStartTime < Time()-m_roundstart_delay)
		{
			m_bRoundStarted = true;
			m_controller.TriggerRoundStart(GetCurrentRound());
		}
	}
	function OnAllowWeaponSpawn( classname )
	{
		m_bHeardAWS = true;
		m_iRoundStartTime = Time();
		return m_controller.TriggerAllowWeaponSpawn(classname);
	}
	function OnConvertWeaponSpawn( classname )
	{
		m_bHeardCWS = true;
		m_iRoundStartTime = Time();
		return m_controller.TriggerConvertWeaponSpawn(classname);
	}
	function OnGetDefaultItem( idx )
	{
		m_bHeardGDI = true;
		m_iRoundStartTime = Time();
		return m_controller.TriggerGetDefaultItem(idx);
	}
	function OnConvertZombieClass(id)
	{
		return m_controller.TriggerPCZSpawn(id);
	}

	function Reset()
	{
		m_bRoundStarted = false;
		m_bHeardAWS = false;
		m_bHeardCWS = false;
		m_bHeardGDI = false;
		m_iRoundStartTime = 0;
		m_bLastUpdateSafeAreaOpened = false;
	}

	// Check for various round-start even. ts before triggering OnRoundStart()
	m_bRoundStarted = false;
	m_bHeardAWS = false;
	m_bHeardCWS = false;
	m_bHeardGDI = false;
	m_iRoundStartTime = 0;

	m_bLastUpdateTankInPlay = false;
	m_bLastUpdateSafeAreaOpened = false;
	
	m_controller = null;
	m_pDirector = null;
	m_roundstart_delay = GameState.ROUNDSTART_DELAY_INTERVAL;
	static GetCurrentRound = Utils.GetCurrentRound;
};

class GameState.GameStateListener
{
	// Called on round start. These may be multiples of these triggered, unfortunately.
	function OnRoundStart(roundNumber) {}
	// Called when a player leaves saferoom or the saferoom timer counts down
	function OnSafeAreaOpened() {}
	// Called when tank spawns
	function OnTankEntersPlay() {}
	// Called when tank dies/leaves play
	function OnTankLeavesPlay() {}
	// Called when a player-controlled zombie is going to be spawned via ConvertZombieClass
	// This event will be chained--called on all Listeners with the modified id passed into successive calls.
	// id: SIClass id of the PCZ to be spawned
	// return another SIClass value to convert the PCZ spawn.
	function OnSpawnPCZ(id) {}
	// Called when a player-controlled zombie is going to be spawned via ConvertZombieClass
	// After conversions from OnSpawnPCZ have taken place
	// id: actual SIClass id to be spawned
	function OnSpawnedPCZ(id) {}
	// Called when DirectorOptions.GetDefaultItem() is called.
	// This event chain will notify all listeners, but only one return value will be used.
	// TODO: further abstraction to just have lists returned...
	// Should be at the beginning of the round normally.
	function OnGetDefaultItem(idx) {}
	// Called when DirectorOptions.AllowWeaponSpawn() is called. 
	// Should be at the beginning of the round normally, after conversions take place.
	// This event will stop being called on Listeners when one listener returns false.
	// classname: string classname of weapon to allow/disallow
	// return true to allow, false to disallow.
	function OnAllowWeaponSpawn(classname) {}
	// Called when DirectorOptions.ConvertWeaponSpawn is called
	// This event will be chained--called on all Listeners with the modified classname passed into successive calls.
	// classname: Classname of the weapon that would spawned
	// retun the classname that it should be converted to, or 0 for no conversion.
	function OnConvertWeaponSpawn(classname) {}
};

class GameState.GameStateController
{
	function AddListener(listener)
	{
		m_listeners.push(listener)
	}

	function TriggerRoundStart(roundNumber)
	{
		foreach(listener in m_listeners)
			listener.OnRoundStart(roundNumber);
	}
	function TriggerSafeAreaOpen()
	{
		foreach(listener in m_listeners)
			listener.OnSafeAreaOpened();
	}
	function TriggerTankEntersPlay()
	{
		foreach(listener in m_listeners)
			listener.OnTankEntersPlay();
	}
	function TriggerTankLeavesPlay()
	{
		foreach(listener in m_listeners)
			listener.OnTankLeavesPlay();
	}
	function TriggerPCZSpawn(id)
	{
		local retval = id;
		foreach(listener in m_listeners)
		{
			// Allow each listener to try to convert.
			// Not pretty in the long run but I'm okay with it.
			local ret = listener.OnSpawnPCZ(retval);
			if(ret != null) retval = ret;
		}

		// Simply notify everyone of the final value
		foreach(listener in m_listeners)
			listener.OnSpawnedPCZ(retval)
		return retval;
	}
	function TriggerAllowWeaponSpawn(classname)
	{
		foreach(listener in m_listeners)
		{
			// Cancel call chain once one listener returns false (says not to spawn it).
			if(listener.OnAllowWeaponSpawn(classname) == false) return false;
			// Interesting ! semantics
			//             null  false
			// !ret        true  true
			// ret==false  false true
			// ret==null   true  false
		}
		return true;
	}
	function TriggerConvertWeaponSpawn(classname)
	{
		local retcls = 0;
		foreach(listener in m_listeners)
		{
			local ret = listener.OnConvertWeaponSpawn(classname);
			if(ret != null && ret != 0) retcls = ret;
		}
		return retcls;
	}
	function TriggerGetDefaultItem(idx)
	{
		local retval = 0;
		foreach(listener in m_listeners)
		{
			local ret = listener.OnGetDefaultItem(idx);
			if(retval == 0 && ret != null) retval = ret;
		}
		return retval;
	}

	m_listeners = []
};
globaltimers

// vim: set ts=4
// Global Timers for L4D2 VScript Mutations
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// =============================================================================


/* Usage:
	Create an instance of one of these timers, e.g.
	g_Timer = GlobalTimer()
	and/or
	g_FrameTimer = GlobalFrameTimer()
	
	Then run the timer's Update() function in your global "update" function.
	e.g. 
	function Update()
	{
		g_Timer.Update();
		g_FrameTimer.Update();
	}
	
	To add a timer, extend TimerCallback to create a callback.
	e.g.
	class MyCallback extends TimerCallback
	{
		function OnTimerElapsed()
		{
			Msg("My Timer has elapsed!!!\n");
		}
	}
	
	Then register it to the global timer of your choice,
	
 */

// double include protection
if("Timers" in this) return;

Timers <- {};

class Timers.TimerCallback
{
	/* OnTimerElapsed()
	Executed once the timer is elapsed in a GlobalTimer
	 */
	function OnTimerElapsed() {}
};

class Timers.GlobalTimer
{
	constructor()
	{
		m_callbacks = array(0);
		m_cbtimes = array(0);
	}
	// Returns the current time in some format that supports arithmetic operations
	// Overload this in your final class
	function GetCurrentTime() { assert(null) }
	/* Update()
	Checks to see which timers have elapsed.
	Please run on global frame Update() function
	 */
	function Update()
	{
		while(m_cbtimes.len() && m_cbtimes[0] <= GetCurrentTime())
		{
			//Msg("Executing timer at "+GetCurrentTime()+" ("+Time()+") elapsed "+m_cbtimes[0]+"\n");
			local cb = m_callbacks[0];
			m_callbacks.remove(0);
			m_cbtimes.remove(0);
			cb.OnTimerElapsed();
		}
	}
	
	/* AddTimer(time,timer)
	Register a new timed callback.
	time: Time in seconds to wait before executing the timer callback.
	timer: TimerCallback to execute once the timer has elapsed.
	 */
	function AddTimer(time, timer)
	{
		local cbtime = GetCurrentTime() + time;
		//Msg("Adding time at "+GetCurrentTime()+" ("+Time()+") for "+cbtime+"\n");
		// Insert sorted Ascending by end timestamp (cbtime) into callbacks list
		local i = 0;
		// TODO: Binary search
		while(i < m_callbacks.len() && m_cbtimes[i] < cbtime) i++;
		if(i == m_callbacks.len())
		{
			m_callbacks.push(timer);
			m_cbtimes.push(cbtime);
		}
		else
		{
			m_callbacks.insert(i,timer);
			m_cbtimes.insert(i,cbtime);
		}
	}

	m_callbacks = [];
	m_cbtimes = [];
};

class Timers.GlobalSecondsTimer extends Timers.GlobalTimer
{
	// Returns the current time in seconds (internal use)
	function GetCurrentTime() { return Time(); }
};

class Timers.GlobalFrameTimer extends Timers.GlobalTimer
{
	// Returns the current time in frames (internal use)
	function GetCurrentTime()
	{
		return m_curFrame;
	}
	/* Update()
	Checks to see which timers have elapsed and executes callbacks.
	Please run on global frame Update() function
	 */
	function Update()
	{
		// TODO: Integer overflow after 180+ hours on the round?
		m_curFrame++;
		baseclass.Update();
	}
	m_curFrame = 0;
	// Need a name reference to base class
	static baseclass = Timers.GlobalTimer;
};
utils

// vim: set ts=4
// Utilities for L4D2 Vscript Mutations
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// =============================================================================


if("Utils" in this) return;
Utils <- {
	SIClass = {
		Smoker = 1
		Boomer = 2
		Hunter = 3
		Spitter = 4
		Jockey = 5
		Charger = 6
		Witch = 7
		Tank = 8
	}
	SIModels = [
		"", // null entry
		["models/infected/smoker.mdl"],
		["models/infected/boomer.mdl", "models/infected/boomette.mdl"],
		["models/infected/hunter.mdl"],
		["models/infected/spitter.mdl"],
		["models/infected/jockey.mdl"],
		["models/infected/charger.mdl"],
		["models/infected/witch.mdl", "models/infected/witch_bride.mdl"],
		["models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl"]
	]
	SurvivorModels = {
		coach = "models/survivors/survivor_coach.mdl"
		ellis = "models/survivors/survivor_mechanic.mdl"
		nick = "models/survivors/survivor_gambler.mdl"
		rochelle = "models/survivors/survivor_producer.mdl"
		louis = "models/survivors/survivor_manager.mdl"
		bill = "models/survivors/survivor_namvet.mdl"
		francis = "models/survivors/survivor_biker.mdl"
		zoey = "models/survivors/survivor_teenangst.mdl"
	}
	MeleeModels = [
		"models/weapons/melee/w_bat.mdl",
		"models/weapons/melee/w_chainsaw.mdl"
		"models/weapons/melee/w_cricket_bat.mdl",
		"models/weapons/melee/w_crowbar.mdl",
		"models/weapons/melee/w_didgeridoo.mdl",
		"models/weapons/melee/w_electric_guitar.mdl",
		"models/weapons/melee/w_fireaxe.mdl",
		"models/weapons/melee/w_frying_pan.mdl",
		"models/weapons/melee/w_golfclub.mdl",
		"models/weapons/melee/w_katana.mdl",
		"models/weapons/melee/w_machete.mdl",
		"models/weapons/melee/w_riotshield.mdl",
		"models/weapons/melee/w_tonfa.mdl"
	]
};
IncludeScript("globaltimers", this);

/* KeyReset
	Create a KeyReset to track the state of a key before you change its value, and
	reset it to the original value/state when you want to revert it.
	Can detect whether a key existed or not and will delete the key afterwards if it doesn't exists.
	
	e.g.
	myKeyReset = KeyReset(DirectorOptions, "JockeyLimit")
	
	then on some event...
	myKeyReset.set(0); // Set DirectorOptions.JockeyLimit to 0, storing the previous value/state
	
	and later...
	myKeyReset.unset(); // Reset DirectorOptions.JockeyLimit to whatever value it was before, or delete
	

 */

// Class that will detect the existence and old value of a key and store
// it for "identical" resetting at a later time.
// Assumes that while between Set() and Unset() calls no other entity will modify the
// value of this key.
class Utils.KeyReset
{
	constructor(owner, key)
	{
		m_owner = owner;
		m_key = key;
	}
	function set(val)
	{
		if(!m_bSet)
		{
			m_bExists = m_owner.rawin(m_key);
			if(m_bExists)
			{
				m_oldVal = m_owner.rawget(m_key);
			}
			m_bSet = true;
		}
		m_owner.rawset(m_key,val);
	}
	function unset()
	{
		if(!m_bSet) return;
		
		if(m_bExists)
		{
			m_owner.rawset(m_key,m_oldVal);
		}
		else
		{
			m_owner.rawdelete(m_key);
		}
		m_bSet = false;
	}
	m_owner = null;
	m_key = null;
	m_oldVal = null;
	m_bExists = false;
	m_bSet = false;
};


/* ZeroMobReset
	Class which handles resetting the mob timer without spawning CI.
	
	e.g.
	g_MobTimerCntl = ZeroMobReset(Director, DirectorOptions, g_FrameTimer);
	
	then later on some event
	g_MobTimerCntl.ZeroMobReset();
	

 */
// Can reset the mob spawn timer at any point without
// triggering an CI to spawn. Should not demolish any other state settings.
class Utils.ZeroMobReset extends Timers.TimerCallback
{
	// Initialize with Director, DirectorOptions, and a GlobalFrameTimer
	constructor(director, dopts, timer)
	{
		m_director = director;
		m_timer = timer;
		m_mobsizesetting = KeyReset(dopts, "MobSpawnSize");
	}
	/* ZeroMobReset()
	Resets the director's mob timer.
	Will trigger incoming horde music, but will not spawn any commons.
	 */
	function ZeroMobReset()
	{
		if(m_bResetInProgress) return;
		
		// set DirectorOptions.MobSpawnSize to 0 so the triggered
		// horde won't spawn CI
		m_mobsizesetting.set(0);
		m_director.ResetMobTimer();
		m_timer.AddTimer(1, this)
		m_bResetInProgress = true;
	}
	// Internal use only,
	// resets the mob size setting after the mob timer has been set
	function OnTimerElapsed()
	{
		m_mobsizesetting.unset();
		m_bResetInProgress = false;
	}
	m_bResetInProgress = false;
	m_director = null;
	m_timer = null;
	m_mobsizesetting = null;
	static KeyReset = Utils.KeyReset;
};

class Utils.Sphere {
	constructor(center, radius)
	{
		m_vecOrigin = center;
		m_flRadius = radius;
	}
	function GetOrigin()
	{
		return m_vecOrigin();
	}
	function GetRadius()
	{
		return m_flRadius;
	}
	// point: vector
	function ContainsPoint(point)
	{
		return (m_vecOrigin - point).Length() <= m_flRadius;
	}
	function ContainsEntity(entity)
	{
		return ContainsPoint(entity.GetOrigin());
	}
	m_vecOrigin = null;
	m_flRadius = null;
};

class Utils.MapInfo {
	function IdentifyMap(EntList)
	{
		isIntro = EntList.FindByName(null, "fade_intro") != null
			|| EntList.FindByName(null, "lcs_intro") != null;

		// also will become true in scavenge gamemode!
		hasScavengeEvent = EntList.FindByClassname(null, "point_prop_use_target") != null;

		saferoomPoints = [];

		if(isIntro)
		{
			local ent = EntList.FindByName(null, "survivorPos_intro_01");
			if(ent != null) saferoomPoints.push(ent.GetOrigin());
		}

		local ent = null;
		while((ent = EntList.FindByClassname(ent, "prop_door_rotating_checkpoint")) != null)
		{
			saferoomPoints.push(ent.GetOrigin());
		}

		if(IsMapC1M2(EntList)) mapname = "c1m2_streets";
		else mapname = "unknown";
	}
	function IsPointNearAnySaferoom(point, distance=2000.0)
	{
		// We actually check if any saferoom is near the point...
		local sphere = Sphere(point, distance);
		foreach(pt in saferoomPoints)
		{
			if(sphere.ContainsPoint(pt)) return true;
		}
		return false;
	}
	function IsEntityNearAnySaferoom(entity, distance=2000.0)
	{
		return IsPointNearAnySaferoom(entity.GetOrigin(), distance);
	}
	function IsMapC1M2(EntList)
	{
		// Identified by a entity with a given model at a given point
		local ent = EntList.FindByModel(null, "models/destruction_tanker/c1m2_cables_far.mdl");
		if(ent != null 
			&& (ent.GetOrigin() - Vector(-6856.0,-896.0,384.664)).Length() < 1.0) return true;
		return false;
	}
	isIntro = false
	isFinale = false
	hasScavengeEvent = false;
	saferoomPoints = null;
	mapname = null
	chapter = 0
	Sphere = Utils.Sphere;
};

class Utils.VectorClone {
	constructor(vec)
	{
		x=vec.x;
		y=vec.y;
		z=vec.z;
	}
	function ToVector()
	{
		return Vector(x,y,z);
	}
	x=0.0
	y=0.0
	z=0.0
};

class Utils.ItemInfo {
	constructor(ent)
	{
		m_vecOrigin = VectorClone(ent.GetOrigin());
		//m_vecForward = ent.GetForwardVector();
	}
	m_vecOrigin = null;
	//m_vecForward = null;
	static VectorClone = Utils.VectorClone;
};

Utils.KillEntity <- function (ent)
{
	DoEntFire("!self", "kill", "", 0, null, ent);
}

Utils.ArrayToTable <- function (arr)
{
	local tab = {};
	foreach(str in arr) tab[str] <- 0;
	return tab;
}

Utils.ArrayRemoveByValue <- function (arr, value)
{
	foreach(id,val in arr)
	{
		if(val == value)
		{
			arr.remove(id);
			break;
		}
	}
}

// return index on found
// return -1 on not found
Utils.ArraySearchByValue <- function (arr, value)
{
	foreach(id,val in arr)
	{
		if(val == value)
		{
			return id;
			break;
		}
	}
	return -1;
}

Utils.IsEntityInMoveHeirarchy <- function (moveChildEnt, moveParentCandidate)
{
	local curEnt = moveChildEnt;
	while(curEnt != null)
	{
		curEnt = curEnt.GetMoveParent();
		if(curEnt == moveParentCandidate) return true;
	}
	return false;
}

// TODO move/refactor...
Utils.GetCurrentRound <- function () 
{ 
	return ::CompLite.Globals.GetCurrentRound();
}
modules

// vim: set ts=4
// CompLite Mutation Modules
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// =============================================================================

if("Modules" in this) return;
Modules <- {};
IncludeScript("gamestate_model", this);
IncludeScript("utils", this);


class Modules.MsgGSL extends GameState.GameStateListener
{
	function OnRoundStart(roundNumber) { Msg("MsgGSL: OnRoundStart("+roundNumber+")\n"); }
	function OnSafeAreaOpened() { Msg("MsgGSL: OnSafeAreaOpened()\n"); }
	function OnTankEntersPlay() { Msg("MsgGSL: OnTankEntersPlay()\n"); }
	function OnTankLeavesPlay() { Msg("MsgGSL: OnTankLeavesPlay()\n"); }
	function OnSpawnPCZ(id) { Msg("MsgGSL: OnSpawnPCZ("+id+")\n"); }
	function OnSpawnedPCZ(id) { Msg("MsgGSL: OnSpawnedPCZ("+id+")\n"); }
	function OnGetDefaultItem(idx)
	{
		if(idx == 0) 
		{
			Msg("MsgGSL: OnGetDefaultItem(0) #"+m_defaultItemCnt+"\n");
			m_defaultItemCnt++;
		}
	}
	// Too much spam for these
	/*
	function OnAllowWeaponSpawn(classname) {}
	function OnConvertWeaponSpawn(classname) {}
	*/
	m_defaultItemCnt = 0;
};

class Modules.SpitterControl extends GameState.GameStateListener
{
	constructor(director, director_opts, entlist)
	{
		m_pDirector = director;
		m_pSpitterLimit = KeyReset(director_opts, "SpitterLimit");
		m_pEntities = entlist;
		// Initialize to default order...
		SpawnLastUsed = [1, 2, 3, 5, 6];
	}
	function OnTankEntersPlay()
	{
		m_pSpitterLimit.set(0);
	}
	function OnTankLeavesPlay()
	{
		m_pSpitterLimit.unset();
	}
	// Check if there is an instance of this SI on the map
	function IsGivenSIClassSpawned(id)
	{
		foreach(mdl in SIModels[id])
		{
			if(m_pEntities.FindByModel(null, mdl) != null)
			{
				return true;
			}
		}
		return false;
	}
	function OnSpawnPCZ(id)
	{
		// If a spitter is going to be spawned during tank,
		if(id == SIClass.Spitter && m_pDirector.IsTankInPlay())
		{
			foreach(si in SpawnLastUsed)
			{
				// Note: Player keeps SI model until they receive a new spawn
				// (while dead, they have the model of their last SI class)
				if(!IsGivenSIClassSpawned(si)) return si;
			}
			// default to hunter if we really can't pick another class...
			return SIClass.Hunter;
		}
		// Msg("Spawning SI Class "+newClass+".\n");
		return id;
	}
	function OnSpawnedPCZ(id)
	{
		// Mark that this SI to be spawned is most recently spawned now.
		if(id != SIClass.Spitter && id <= SIClass.Charger && id >= SIClass.Smoker)
		{
			// Low index = least recent
			// High index = most recent
			// Remove the other instance of this class in our array
			ArrayRemoveByValue(SpawnLastUsed, id);
			SpawnLastUsed.push(id);
		}
	}
	// List of last spawned time for each SI class
	SpawnLastUsed = null;
	// reference to director options
	m_pSpitterLimit = null;
	m_pDirector = null;
	m_pEntities = null;
	static KeyReset = Utils.KeyReset;
	static SIClass = Utils.SIClass;
	static SIModels = Utils.SIModels;
	static ArrayRemoveByValue = Utils.ArrayRemoveByValue;
};


class Modules.MobControl extends GameState.GameStateListener
{
	constructor(mobresetti)
	{
		//m_dopts = director_opts;
		m_resetti = mobresetti;
	}
	function OnSafeAreaOpened() 
	{
		m_resetti.ZeroMobReset();
	}
	// These functions created major problems....
	/*
	function OnTankEntersPlay()
	{
		m_oldMinTime = m_dopts.MobSpawnMinTime;
		m_oldMaxTime = m_dopts.MobSpawnMaxTime;

		m_dopts.MobSpawnMinTime = 99999;
		m_dopts.MobSpawnMaxTime = 99999;

		m_resetti.ZeroMobReset();
	}
	function OnTankLeavesPlay()
	{
		m_dopts.MobSpawnMinTime = m_oldMinTime;
		m_dopts.MobSpawnMaxTime = m_oldMaxTime;

		m_resetti.ZeroMobReset();
	} 
	m_oldMinTime = 0;
	m_oldMaxTime = 0; 
	m_dopts = null; */
	m_resetti = null;
};

class Modules.BasicItemSystems extends GameState.GameStateListener
{
	constructor(removalTable, convertTable, defaultItemList)
	{
		m_removalTable = removalTable;
		m_convertTable = convertTable;
		m_defaultItemList = defaultItemList;
	}
	function OnAllowWeaponSpawn(classname)
	{
		if ( classname in m_removalTable )
		{
			if(m_removalTable[classname] > 0)
			{
				//Msg("Found a "+classname+" to keep, "+m_removalTable[classname]+" remain.\n");
				m_removalTable[classname]--
			}
			else if (m_removalTable[classname] < -1)
			{
				//Msg("Killing just one "+classname+"\n");
				m_removalTable[classname]++
				return false;
			}
			else if (m_removalTable[classname] == 0)
			{
				//Msg("Removed "+classname+"\n")
				return false;
			}
		}
		return true;
	}
	function OnConvertWeaponSpawn(classname)
	{
		if ( classname in m_convertTable )
		{
			//Msg("Converting"+classname+" to "+convertTable[classname]+"\n")
			return m_convertTable[classname];
		}
		return 0;
	}
	function OnGetDefaultItem(idx)
	{
		if ( idx < m_defaultItemList.len())
		{
			return m_defaultItemList[idx];
		}
		return 0;
	}
	m_removalTable = null;
	m_convertTable = null;
	m_defaultItemList = null;
};

class Modules.EntKVEnforcer extends GameState.GameStateListener
{
	constructor(EntList, classes, models, key, value)
	{
		m_pEntities = EntList;
		m_classes = classes;
		m_models = models;
		m_key = key;
		switch(typeof value)
		{
			case "bool":
				m_value = value.tointeger();
				m_setFunc = "__KeyValueFromInt";
				break;
			case "float":
				m_value = value.tointeger();
				m_setFunc = "__KeyValueFromInt";
				break;
			case "integer":
				m_value = value;
				m_setFunc = "__KeyValueFromInt";
				break;
			case "string":
				m_value = value;
				m_setFunc = "__KeyValueFromString";
				break;
			case "instance":
				if(value.getclass() == ::Vector)
				{
					m_value = value;
					m_setFunc = "__KeyValueFromVector";
					break;
				}
			default:
				m_value = null;
				m_setFunc = null;
				// Unsupported type!!!
				throw "Unsupported type "+(typeof value)+" used with EntKVEnforcer!";
		}
	}
	function OnRoundStart(roundNumber)
	{
		local ent = null;

		while((ent = m_pEntities.Next(ent)) != null)
		{
			if(ent.GetClassname() in m_classes)
			{
				ent[m_setFuncName].call(ent, m_key, m_value);
			}
		}

		foreach(mdl in m_models)
		{
			ent = null;
			while((ent = m_pEntities.FindByModel(mdl)) != null)
			{
				ent[m_setFuncName].call(ent, m_key, m_value);
			}
		}
	}
	m_pEntities = null;
	m_classes = null;
	m_models = null;
	m_key = null;
	m_value = null;
	m_setFunc = null;
};

class Modules.ItemControl extends GameState.GameStateListener
{
	constructor(entlist, removalTable, modelRemovalTable, saferoomRemoveList, mapinfo)
	{
		m_entlist = entlist;
		m_removalTable = removalTable;
		m_modelRemovalTable = modelRemovalTable;
		m_saferoomRemoveList = ArrayToTable(saferoomRemoveList);
		m_pMapInfo = mapinfo;
	}
	function OnFirstRound()
	{
		local ent = m_entlist.First();
		local classname = "";
		local tItemEnts = {};
		local saferoomEnts = [];

		// Create an empty array for each item in our list.
		foreach(key,val in m_removalTable)
		{
			tItemEnts[key] <- [];
		}

		while(ent != null)
		{
			classname = ent.GetClassname()
			if(classname in m_saferoomRemoveList && m_pMapInfo.IsEntityNearAnySaferoom(ent, 1600.0))
			{
				// Make a list of items which are in saferooms that need to be removed
				// and don't track these entities for other removal.
				saferoomEnts.push(ent.weakref());
			}
			else if(classname in m_removalTable)
			{
				tItemEnts[classname].push(ent.weakref());
			}
			ent=m_entlist.Next(ent);
		}

		local tModelEnts = {};

		foreach(mdl,limit in m_modelRemovalTable)
		{
			local thisMdlEnts = tModelEnts[mdl] <- [];
			ent = null;
			while((ent = m_entlist.FindByModel(ent, mdl)) != null)
			{
				// Only use this entity if it's not one of the saferoom entities we're going to remove.
				thisMdlEnts.push(ent.weakref());
			}
		}

		// Remove all targeted saferoom items before doing roundstart removals
		foreach(entity in saferoomEnts) KillEntity(entity);
		local KilledEntList = saferoomEnts;

		m_firstRoundEnts = {}
		foreach(classname,instances in tItemEnts)
		{
			local cnt = m_removalTable[classname].tointeger();
			local saved_ents = m_firstRoundEnts[classname] <- [];
			// We need to choose certain items to save
			while( instances.len() > 0 && saved_ents.len() < cnt )
			{
				local saveIdx = RandomInt(0,instances.len()-1);
				// Track this entity's info for future rounds.
				saved_ents.push(ItemInfo(instances[saveIdx]));
				// Remove this entity from the kill list
				instances.remove(saveIdx);
			}
			Msg("Killing "+instances.len()+" "+classname+", leaving "+saved_ents.len()+" on the map.\n");
			foreach(inst in instances)
			{
				KillEntity(inst);
				KilledEntList.push(inst.weakref());
			}
		}

		m_firstRoundModelEnts = {}
		foreach(model,instances in tModelEnts)
		{
			// Don't use killed ents!
			foreach(deadEnt in KilledEntList) ArrayRemoveByValue(instances, deadEnt);
			local limit = m_modelRemovalTable[model].tointeger();
			local saved_ents = m_firstRoundModelEnts[model] <- [];
			while( instances.len() > 0 && saved_ents.len() < limit )
			{
				local saveIdx = RandomInt(0,instances.len()-1);
				// Track this entity's info for future rounds.
				saved_ents.push(ItemInfo(instances[saveIdx]));
				// Remove this entity from the kill list
				instances.remove(saveIdx);
			}
			Msg("Killing "+instances.len()+" "+model+", leaving "+saved_ents.len()+" on the map.\n");
			foreach(inst in instances)
			{
				KillEntity(inst);
			}
		}
	}
	function OnLaterRounds()
	{
		local ent = m_entlist.First();
		local classname = "";
		local tItemEnts = {};

		foreach(key,val in m_removalTable)
		{
			tItemEnts[key] <- [];
		}
		while(ent != null)
		{
			classname = ent.GetClassname()
			if(classname in m_removalTable)
			{
				tItemEnts[classname].push(ent.weakref());
			}
			ent=m_entlist.Next(ent);
		}

		local tModelEnts = {};
		foreach(mdl,limit in m_modelRemovalTable)
		{
			local thisMdlEnts = tModelEnts[mdl] <- [];
			ent = null;
			while((ent = m_entlist.FindByModel(ent, mdl)) != null)
			{
				// Only use this entity if it's not one of the saferoom entities we're going to remove.
				thisMdlEnts.push(ent.weakref());
			}
		}

		foreach(classname,entList in tItemEnts)
		{
			local firstItems = m_firstRoundEnts[classname];
			// count to keep alive
			local cnt = firstItems.len();
			if(cnt > entList.len())
			{
				Msg("Warning! Not enough "+classname+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
				cnt = entList.len();
			}

			for(local i = cnt; i < entList.len(); i++)
			{
				KillEntity(entList[i]);
			}

			for(local i = 0; i < cnt; i++)
			{
				local vec = VectorClone(firstItems[i].m_vecOrigin);

				// Hack. To avoid crashing the server by placing entities on top of each other here, 
				// we'll just offset the placement of each entity by 1 unit. 
				// Alternative solutions: 
				// 1. Check all tracked entity positions for conflicts and resolve through <insert algorithm here>
				// 2. Move all entities to offset by 1 unit, wait 1 frame, move entities to original (un-offset) position (same frame also crashes)
				// 3. Convert this code to kill/create entities instead of kill/shuffle entities (not possible atm)
				vec.z += 1.0;
				entList[i].SetOrigin(vec.ToVector());
				//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
			}
			Msg("Restored "+cnt+" "+classname+", out of "+entList.len()+" on the map.\n");
		}

		foreach(model,entList in tModelEnts)
		{
			local firstItems = m_firstRoundModelEnts[model];
			// count to keep alive
			local cnt = firstItems.len();
			if(cnt > entList.len())
			{
				Msg("Warning! Not enough "+model+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
				cnt = entList.len();
			}

			for(local i = cnt; i < entList.len(); i++)
			{
				KillEntity(entList[i]);
			}

			for(local i = 0; i < cnt; i++)
			{
				entList[i].SetOrigin(firstItems[i].m_vecOrigin.ToVector());
				//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
			}
			Msg("Restored "+cnt+" "+model+", out of "+entList.len()+" on the map.\n");
		}
	}
	function OnRoundStart(roundNumber)
	{
		Msg("ItemControl OnRoundStart()\n");
		// This will run multiple times per round in certain cases...
		// Notably, on natural map switch (transition) e.g. chapter 1 ends, start chapter 2.
		// Just make sure you don't screw up anything...
		if(roundNumber == 1)
		{
			OnFirstRound();
		}
		else
		{
			OnLaterRounds();
		}


	}
	// pointer to global Entity List
	m_entlist = null;
	// point to global mapinfo
	m_pMapInfo = null;
	// Table of entity classname, limit value pairs
	m_removalTable = null;
	m_modelRemovalTable = null;
	m_saferoomRemoveList = null;

	m_firstRoundEnts = null;
	m_firstRoundModelEnts = null;
	static ArrayToTable = Utils.ArrayToTable;
	static ArrayRemoveByValue = Utils.ArrayRemoveByValue;
	static ItemInfo = Utils.ItemInfo;
	static KillEntity = Utils.KillEntity;
	static VectorClone = Utils.VectorClone;
};

class Modules.MeleeWeaponControl extends GameState.GameStateListener {
	constructor(entlist, melee_limit)
	{
		m_pEntities = entlist;
		m_maxSpawns = melee_limit;
	}
	function EntListToItemInfoList(entlist)
	{
		local infolist = [];
		foreach (ent in entlist)
		{
			infolist.push(ItemInfo(ent));
		}
		return infolist;
	}
	function OnFirstRound()
	{
		m_firstRoundMelees = {};
		local meleeEnts = {}
		local totalCount = 0;

		// Enumerate all melee weapon spawns by model
		foreach(mdl in MeleeModels)
		{
			// prep table for later
			m_firstRoundMelees[mdl] <- []

			local spawnlist = []
			local ent = null;
			while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
			{
				spawnlist.push(ent.weakref());
				totalCount++;
			}
			meleeEnts[mdl] <- spawnlist;
		}

		if(totalCount < m_maxSpawns)
		{
			// There are less than m_maxSpawns melee weapons on the map,
			// so we record them all and we're done.
			foreach(mdl,spawnlist in meleeEnts)
			{
				m_firstRoundMelees[mdl] = EntListToItemInfoList(spawnlist);
			}
			Msg("Only "+totalCount+" melee weapons on the map to track.\n");
		}
		else
		{
			local savedCnt = 0;

			// Save m_maxSpawns of them
			while(savedCnt < m_maxSpawns)
			{
				local saveIdx = RandomInt(0,totalCount-1);

				// Iterate through the list until we've hit saveIdx melees.
				foreach(mdl, spawnlist in meleeEnts)
				{
					if(saveIdx < spawnlist.len())
					{
						// Save this item's spawn info.
						m_firstRoundMelees[mdl].push(ItemInfo(spawnlist[saveIdx]));
						spawnlist.remove(saveIdx);
						savedCnt++;
						totalCount--;
						break;
					}
					saveIdx -= spawnlist.len();
				}
			}

			// remove the remaining weapon spawns from the map.
			foreach(mdl, spawnlist in meleeEnts)
			{
				foreach(melee_ent in spawnlist)
				{
					KillEntity(melee_ent);
				}
			}
			Msg("Killing "+totalCount+" melee weapons and saving "+savedCnt+" out of "+(totalCount+savedCnt)+" on the map.\n");
		}

	}
	function OnOtherRounds()
	{
		local meleeEnts = {}
		local totalCount = 0;

		// Enumerate all melee weapon spawns by model
		foreach(mdl in MeleeModels)
		{
			local spawnlist = []
			local ent = null;
			while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
			{
				spawnlist.push(ent.weakref());
				totalCount++;
			}
			meleeEnts[mdl] <- spawnlist;
		}

		foreach(mdl,infolist in m_firstRoundMelees)
		{
			local restoreCnt = infolist.len();

			// Make sure this model isn't unset in this round's ent table
			if(!(mdl in meleeEnts))
			{
				Msg("Warning! No "+ mdl +" exist on R2 for restoring!\n");
				continue;
			}

			local thisMdlEnts = meleeEnts[mdl];

			// Check that this round's ent list is long enough to spawn last round's melees
			if(thisMdlEnts.len() < restoreCnt)
			{
				restoreCnt = thisMdlEnts.len();
				Msg("Warning! Not as many of melee weapon ("+ mdl +") available on R2! ("+restoreCnt+" < "+infolist.len()+"\n");
			}

			Msg("Restoring "+restoreCnt+" "+mdl+" out of "+thisMdlEnts.len()+"\n");

			// Move restoreCnt melees of this model to their spots from R1.
			for(local i = 0; i < restoreCnt; i++)
			{
				local ent = thisMdlEnts[0];
				thisMdlEnts.remove(0);
				ent.SetOrigin(infolist[i].m_vecOrigin.ToVector());
				//ent.SetForwardVector(infolist[i].m_vecForward.ToVector());
			}
		}

		// Delete the remaining melees from this round.
		foreach(mdl,spawnlist in meleeEnts)
		{
			foreach(ent in spawnlist)
			{
				KillEntity(ent);
			}
		}
	}
	function OnRoundStart(roundNumber)
	{
		if(roundNumber == 1)
		{
			OnFirstRound();
		}
		else
		{
			OnOtherRounds();
		}
	}
	m_pEntities = null;
	m_maxSpawns = null;

	m_firstRoundMelees = null;

	static MeleeModels = Utils.MeleeModels;
	static ItemInfo = Utils.ItemInfo;
	static KillEntity = Utils.KillEntity;
};

class Modules.HRControl extends GameState.GameStateListener //, extends TimerCallback (no MI support)
{
	constructor(entlist, globals, director)
	{
		m_pEntities = entlist;
		m_pGlobals = globals;
		m_pDirector = director;
	}
	function QueueCheck(time)
	{
		if(!m_bChecking)
		{
			m_pGlobals.Timer.AddTimer(time, this);
			m_bChecking = true;
		}
	}
	function OnRoundStart(roundNumber)
	{
		QueueCheck(1.0);
	}
	function OnTimerElapsed()
	{
		m_bChecking=false;
		if(!m_pDirector.HasAnySurvivorLeftSafeArea()) QueueCheck(5.0);

		local ent = null;
		local hrList = [];
		while((ent = m_pEntities.FindByClassname(ent, "weapon_hunting_rifle")) != null)
		{
			hrList.push(ent.weakref());
		}

		if(!m_pGlobals.MapInfo.isIntro)
		{
			if(hrList.len() <= 1) return;
			hrList.remove(RandomInt(0,hrList.len()-1));
		}

		// Delete the rest
		foreach(hr in hrList)
		{
			KillEntity(hr);
		}
	}
	m_pEntities = null;
	m_pTimer = null;
	m_pGlobals = null;
	m_pDirector = null;
	m_bChecking = false;
	static KillEntity = Utils.KillEntity;
};

class Modules.GasCanControl extends GameState.GameStateListener {
	constructor(entList, mapinfo)
	{
		m_pEntities = entList;
		m_pMapInfo = mapinfo;
	}
	function OnRoundStart(roundNumber)
	{
		// Don't demolish gascans if the map has a scavenge event!
		// Unless it's c1m2 because it uses cola yo.
		if(m_pMapInfo.hasScavengeEvent && m_pMapInfo.mapname != "c1m2_streets") return;

		local ent = null;
		local list = [];
		while((ent = m_pEntities.FindByModel(ent, "models/props_junk/gascan001a.mdl")) != null)
		{
			list.push(ent.weakref());
		}
		Msg("Killing "+list.len()+" gas cans from the map.\n");
		foreach(can in list)
		{
			KillEntity(can);
		}
	}
	m_pEntities = null;
	m_pMapInfo = null;
	static KillEntity = Utils.KillEntity;
}

EMS Mutations

These have been introduced with the L4D2 EMS Update

Dash


////////////////////////////////
// "Dash" is another extended mutation demo mode to show some of the capabilities available in script
//
// the players must run near a sequence of waypoints (well, ok, lampposts) in order
// As they reach each one, we light up the next one, and also summon a new panic wave from the next locale
// Designers use the Entity Placement tool to lay down a sequence of waypoints
// Waypoint naming:
// If you name your waypoints sequentially, e.g., waypoint_001, waypoint_002 flagpole waypoints will be used.
// See scripted_c5m2_park_dash.nut, c5m2_park_entities_dash.txt for an example.
//
// If you prefix your waypoints with "short_" e.g., short_waypoint_001, short_waypoint_002, then shorter
// waypoints will be used.
// See scripted_c5m4_quarter_dash.nut, c5m4_quarter_entities_dash.txt for an example.
//
// When survivors reach the final waypoint, we do a TANK wave, and they get a final time based on when they finish
// 
// The waypoint itself has a fairly big script to detect nearby players, and track which ones have reached it
//   as well as to trigger visual indications of progress, and "waypoint finished" calls back to this script
// And this script on startup has to sort the waypoints to make the in-order list

// printl(" Loading Dash mode")

MutationOptions <-
{   
	// since we are going to move the spawn point to "ahead" on the track as we hit waypoints
	PreferredMobDirection = SPAWN_NEAR_POSITION
	PreferredMobPositionRange = 600

	// we should change this to zero
	PreferredMobPosition = Vector( 0,0,0 )

	SpawnSetRule = SPAWN_POSITIONAL
	SpawnSetRadius = 2000
	SpawnSetPosition = Vector( -7150, -3647, -97 )

	WanderingZombieDensityModifier = 0 // get rid of wanderers

	cm_ShouldEscortHumanPlayers = 1
	cm_AggressiveSpecials = 1

	BoomerLimit  = 0
	ChargerLimit = 0
	HunterLimit  = 0
	JockeyLimit  = 0
	SpitterLimit = 0
	SmokerLimit  = 0
	MaxSpecials  = 0
	CommonLimit  = 20
	MegaMobSize	 = 20
	TankLimit    = 0
	WitchLimit	 = 0

	function EndScriptedMode()
	{
		local finished = true;
		foreach ( val in g_RoundState.WaypointList )
		{
			if ( val.GetScriptScope().active == true )
			{
				finished = false
				break;
			}
		}

		if ( finished )
		{
			// Finished the course
			return 0 // SCENARIO_RESTART
		}
		else
		{
			return 1 // SCENARIO_SURVIVORS_DEAD
		}
	}

	/*  Not using this method anymore
	function GetScoreboardFilename( endreason )
	{
		switch ( endreason )
		{
			case 1:
				return "Resource/UI/scriptedmodeplaceholderlost.res"
			case 0:
				return "Resource/UI/scriptedmodeplaceholderwon.res"
			default:
				return "Resource/UI/scriptedmodeplaceholder.res"
		}
	}
	*/

	// challenge mode experiment - make sure old CM type stuff works fine in new mutation code
	cm_HeadshotOnly = 0

	DefaultItems =
	[
		"weapon_rifle_m60",
		"weapon_pistol_magnum",
		"adrenaline",
		"pipe_bomb"
	]

	// Set characters up with default items
	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}	
}

// SessionState for this mode - mostly about the waypoint tracking/which on next/etc/etc
MutationState <-
{
	StartActive = true	
	CurrentWaypoint = 0
	FinalWaypoint = 0
	JustHitWaypoint = false
	YouWon = false
	FirstWaypointHit = false
	TimerWaypoint = 0   // for a soon to be written more flexible setup
	MobWaypoint = 0
	CommonIncrement = 0
}

// callback for waypoint generation - as we spawn each one we add it to the list
function SetWaypointCB( waypointObj, rarity )
{
	local waypointScr = waypointObj.GetScriptScope()
	waypointScr.myID <- g_ModeScript.MutationState.FinalWaypoint++

	
	// if we're using a custom list set the initial touch count on this waypoint
	if( "CustomWaypointList" in g_MapScript )
		waypointScr.initialTouchCount = g_MapScript.CustomWaypointList[ g_RoundState.WaypointList.len() ].startingTouchCount

	g_RoundState.WaypointList.append(waypointObj)
}

// the startbox needs a custom precache function if you are using it
function Precache()
{
	Startbox_Precache()
}

// called on startup - setup a few callbacks if they arent there
// and then go ahead and teleport players to start, spawn the startbox, activate first waypoint
function OnGameplayStart()
{
	CheckOrSetMapCallback( "MapOverrideOptions", @() false )
	CheckOrSetMapCallback( "MapGameplayStart", @() false )

	Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )

	//teleport players to the start point
	if (!TeleportPlayersToStartPoints( "gamemode_playerstart" ) )
		printl(" ** TeleportPlayersToStartPoints: Spawn point count or player count incorrect! Verify that there are 4 of each.")

	// create a start box
	if ( !SpawnStartBox( "startbox_origin" ) )
	{
		printl("Note: SpawnStartBox() called but there is no startbox_origin in map.")
	}

	// If the map script supplies a CustomWaypointList of points spawn the entities in that order
	if( "CustomWaypointList" in g_MapScript )
	{
		SpawnCustomWaypointList()
	}
	else
	{
		// Spawn waypoints in order
		SortAndSpawnWaypointList()
	}

	if (g_RoundState.WaypointList.len() > 0)
	{
		EntFire( g_RoundState.WaypointList[0].GetName(), "StartGlowing" )
		EntFire( g_RoundState.WaypointList[0].GetName(), "fireuser1" )
		g_RoundState.WaypointList[0].GetScriptScope().active = true
		SessionOptions.PreferredMobPosition = g_RoundState.WaypointList[0].GetOrigin()
	}

	SessionOptions.ScriptedStageType = STAGE_SETUP
	SessionOptions.ScriptedStageValue = 1000

	MapGameplayStart()
}

//=========================================================
// Uses the CustomWaypointList defined in the map script to
// spawn waypoints.  The info_item_position entities are
// collected in the order they appear in the list and then
// the waypoints are spawned in that order.
//=========================================================
function SpawnCustomWaypointList()
{
	// collect the info_item_position entities into a list
	local currentEnt  = null
	local tempWaypointList = []

	foreach( idx, val in CustomWaypointList )
	{
		currentEnt = Entities.FindByName( null, val.targetName )
		
		// store the waypoint if we found it, otherwise complain
		if( currentEnt )
		{
			tempWaypointList.append( currentEnt )
		}
		else
		{
			printl( " ** Waypoint Spawn ERROR**  I can't find the waypoint named: " + val.targetName )
		}
	}

	SpawnWaypointList( tempWaypointList )
}

//=========================================================
// Collect all the info_item_position entities that are named 
// "short_waypoint_*" OR "waypoint_*", put them into a list
// and sort it by suffix.  This will allow us to spawn all
// the waypoints in order so they can know their touch order
//=========================================================
function SortAndSpawnWaypointList()
{
	local currentWaypoint = Entities.FindByClassname( null, "info_item_position" )
	local tempWaypointList = []

	while( currentWaypoint )
	{
		local name = currentWaypoint.GetName()
		if( ( name.find( "waypoint_" ) == 0 ) || ( name.find( "short_waypoint_" ) == 0 ) )
		{
			tempWaypointList.append( currentWaypoint )
		}

		currentWaypoint = Entities.FindByClassname( currentWaypoint, "info_item_position" )
	}

	// sort the list by suffix	
	tempWaypointList.sort(@(a,b) a.GetName().slice(-4) <=> b.GetName().slice(-4) )

	SpawnWaypointList( tempWaypointList )
}

//=========================================================
// Spawn the provided list of waypoints on their positions
//=========================================================
function SpawnWaypointList( list )
{
	local waypointGroup = g_MapScript.GetEntityGroup( "DashWaypoint" )
	local shortWaypointGroup = g_MapScript.GetEntityGroup( "DashWaypointShort" )

	// set callbacks
	shortWaypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
	waypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB

	// spawn the waypoints	
	foreach( idx, ent in list )
	{
		// does our waypoint start with "short_"?  If so, it is a short waypoint! Spawn it.
		if( ent.GetName().find( "short_" ) == 0  )
		{
			SpawnSingleAt( shortWaypointGroup, ent.GetOrigin(), ent.GetAngles() )
		}
		else // assuming it must be a normal waypoint since it isn't "short_"
		{
			SpawnSingleAt( waypointGroup, ent.GetOrigin(), ent.GetAngles() )
		}
	}
}

function DashDisplayScores( )
{
	local final_time = HUDReadTimer(1)
	local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
	HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
	HUDPlace( HUD_FAR_RIGHT, 0.28, 0.50, 0.44, 0.06 )
	HUDPlace( HUD_FAR_LEFT,  0.28, 0.58, 0.44, 0.06 )
	HUDPlace( HUD_LEFT_BOT,  0.28, 0.66, 0.44, 0.06 )
	HUDPlace( HUD_RIGHT_BOT, 0.28, 0.74, 0.44, 0.06 )
	local ticker_str = score_strings.yourtime
	if ("finish" in score_strings)
		ticker_str = ticker_str + "\n" + score_strings.finish
	ticker_str = ticker_str + "\nBest Times So Far"
	Ticker_SetBlink( false )   // or true if you came in first? hmmm....
	Ticker_NewStr( ticker_str, 120 )
	foreach (idx, val in score_strings.topscores)
	{
		local hudfield = "score"+(idx+1)
		DashHUD.Fields[hudfield].dataval = val
		DashHUD.Fields[hudfield].flags = DashHUD.Fields[hudfield].flags & ~HUD_FLAG_NOTVISIBLE
	}
	Scoring_SaveTable( SessionState.MapName, SessionState.ModeName )
}

// this array holds the waypoints in order, up to SessionState.FinalWaypoint (though really, could just use len() for safety?
g_RoundState.WaypointList <- []

// called at basically each waypoint via ForceNextStage - or when a PANIC ends we just go to a delay
function GetNextStage()
{
	smDbgPrint("GetNextStage called")

	if ( SessionState.YouWon )
	{
		smDbgPrint("Really winningz, going to rezultz, yo!")
		SessionOptions.ScriptedStageType = STAGE_RESULTS
		SessionOptions.ScriptedStageValue = 10
		DashDisplayScores()
	}
	else if ( SessionState.JustHitWaypoint && SessionState.FinalWaypoint > 0 )
	{	
		if ( !SessionState.FirstWaypointHit ) // could be start line, or waypoint 0
		{  // hit firstone, kick things off
			smDbgPrint("Hit First Waypoint!")
			SessionState.FirstWaypointHit = true
			HUDManageTimers( 1 , TIMER_COUNTUP, 0)   // generalize this?

			// calculate the number of common infected to add each time a waypoint is completed
			SessionState.CommonIncrement = (100 - SessionOptions.CommonLimit) / SessionState.FinalWaypoint
		}  
		
		if( !MapOverrideOptions() )
		{
			// really, do we need this? or can we just always do panic here else a delay???
			local PercentageComplete = SessionState.CurrentWaypoint * 1.0 / SessionState.FinalWaypoint
		
			SessionState.JustHitWaypoint = false
			SessionOptions.ScriptedStageType = STAGE_PANIC
			SessionOptions.ScriptedStageValue = 1
			foreach (val in SpecialNames)
			{
				local limit = RandomInt(1,3) + PercentageComplete * 3
				SessionOptions[val + "Limit"] = limit
			}
			SessionOptions.MaxSpecials = RandomInt(2,6) + PercentageComplete * 3
			if (SessionOptions.CommonLimit < 100)
				SessionOptions.CommonLimit += SessionState.CommonIncrement

			if (SessionOptions.MegaMobSize < 50 )
				SessionOptions.MegaMobSize += SessionState.CommonIncrement

			if (SessionState.CurrentWaypoint == SessionState.FinalWaypoint - 1 )
			{
				Ticker_NewStr("One gate to go!")
				SessionOptions.TankLimit = 1 // how do i force a tank from script?
				SessionOptions.ScriptedStageType = STAGE_ESCAPE  // i guess i do an escape stage
			}
			else // display some help text on the ticker
			{
				switch( SessionState.CurrentWaypoint )
				{
					case 1:
						Ticker_NewStr( "Each time you activate a new waypoint more infected will attack.", 15 )
						break
					
					case 2:
						Ticker_NewStr( "You cleared the waypoint! Here come more infected!", 15 )
						break
				}
			}
		}
	}
	else
	{
		SessionOptions.ScriptedStageType = STAGE_DELAY
		SessionOptions.ScriptedStageValue = 1000
		SessionOptions.MaxSpecials = 0
	}

	smDbgPrint("Dash setting Mode " + SessionOptions.ScriptedStageType + " val " + SessionOptions.ScriptedStageValue)

	// we also want to positionally spawn at the mob target - though really i'd like to do some distance/scale stuff, too
	SessionOptions.SpawnSetPosition = SessionOptions.PreferredMobPosition
}

// because call DWD is where we active/deactive the waypoints, we can just treat the start line as secret pre-waypoint
function SurvivorLeftStartBox()
{
	SessionState.JustHitWaypoint = true   // and since the main loop never looks up into the array, that is o.k.
	Director.ForceNextStage()
}

// each time a waypoint is hit this is called to get us moving forward
// i.e. we need to activate the next waypoint, see if it is the final one, track counts, and so on
function DashWaypointDone( id )
{
	smDbgPrint("Dash Waypoint finished! " + id)

	// sanity check!
	if ( id != SessionState.CurrentWaypoint )
		printl("Hey! you think you are done with waypoint " + id + " but current is " + SessionState.CurrentWaypoint )

	EntFire( g_RoundState.WaypointList[id].GetName(), "StopGlowing" )
	g_RoundState.WaypointList[id].GetScriptScope().active = false
	// printl( "Deactivated waypoint " + id )
	local next_id = ++SessionState.CurrentWaypoint
	if ( next_id >= SessionState.FinalWaypoint )
	{
		// printl("Youz da Winner, yo!")
		SessionState.CurrentWaypoint--   // for safety
		SessionState.YouWon = true

		// stop the clock
		HUDManageTimers( 1, TIMER_STOP, 0 )
	}
	else
	{
		EntFire( g_RoundState.WaypointList[next_id].GetName(), "StartGlowing" )
		EntFire( g_RoundState.WaypointList[next_id].GetName(), "fireuser1" )
		//printl("Moving on to next waypoint " + next_id )
		g_RoundState.WaypointList[next_id].GetScriptScope().active = true
		SessionOptions.PreferredMobPosition = g_RoundState.WaypointList[next_id].GetOrigin()
	}
	SessionState.JustHitWaypoint = true
	Director.ForceNextStage()
}

// add Criteria for the new Response Rule speech criteria system
// i.e. this is to add data to the RR table the characters use to choose statements
function AddCriteria( criteriaTable )
{
	criteriaTable.PercentComplete <- SessionState.FinalWaypoint > 0 ? (SessionState.CurrentWaypoint*1.0)/SessionState.FinalWaypoint : 0
//	printl("Dash AddCriteria added " + criteriaTable.PercentComplete)
}

//-------------------------------------------------------
// Include all entity group interfaces needed for this mode
//-------------------------------------------------------
IncludeScript( "entitygroups/dash_waypoint_group" )
IncludeScript( "entitygroups/dash_waypoint_short_group" )

DashHUD <- {}

// HUD setup/control - our HUD is pretty simple in dash
function SetupModeHUD( )
{
	DashHUD =
	{
		Fields = 
		{
			waypoint = { slot = HUD_RIGHT_TOP, name = "waypoint", staticstring = "Waypoint: ", datafunc = @() SessionState.CurrentWaypoint },
			timer    = { slot = HUD_LEFT_TOP, name = "timer", staticstring = "Time: ", special = HUD_SPECIAL_TIMER1 },

			// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
			// we will move the Ticker down to be a "header" - then need 4 slots for top 4
			score1   = { slot = HUD_FAR_RIGHT, name = "score1", dataval = "Score1", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
			score2   = { slot = HUD_FAR_LEFT,  name = "score2", dataval = "Score2", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
			score3   = { slot = HUD_LEFT_BOT,  name = "score3", dataval = "Score3", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
			score4   = { slot = HUD_RIGHT_BOT, name = "score4", dataval = "Score4", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
		}
	}

	Ticker_AddToHud( DashHUD, "Hurry past the poles, go for a best time" )
	HUDSetLayout( DashHUD )
}

Holdout


///////////////////////////////////////////////////////////////////////////////
// The Holdout game mode! Brought to you by the new Mutation system!
// A Holdout Is...
//   Mostly a demo of the new mutation system, but a game mode as well
//   A Continuous cycle of attack/cooldown/attack/cooldown
//   in code, implemented as 3 phases
//        PANIC - spawn some enemies around the players
//        CLEAROUT - wait until the enemies are (mostly) all gone
//        DELAY - a cooldown time before the next wave of enemies
// There are several "special behaviors" as this happens
//   The Escape wave can happen at a specified wave #, or as a result of in game action (timer running down, for instance)
//   There can be special events that take over based on the current Stage #

// This is a very Structured mode - it uses stagetables and other "helpers" in the mutation system
// You dont need to use these in your own mutations, though you are of course welcome to

// as a map, you should go implement the following functions, returning a "stageTable"
// or null to ignore
//   DoMapEventCheck()
//   DoMapSetup()
//   GetMapEscapeStage()
//   IsMapSpecificStage()    // dont like this! potential out of sync/parallel variables
//   GetMapSpecificStage()   //    maybe IsMap sets a "stageSpecific" that GetMap can use?
//   GetAttackStage()
//   GetMapClearoutStage()
//   GetMapDelayStage()
///////////////////////////////////////////////////////////////////////////////

//---------------------------------------------------------
// This is the SessionState - any non-director data all your session code wants to be able to look at
// So we hold data about what wave, some UI controls, and info about managing warnings
MutationState <-
{
	HUDWaveInfo = true           // do you want the Wave # in middle of UI
	HUDRescueTimer = false       // or would you rather have the rescue timer (cant have both)
	HUDTickerText = "Holdout as long as you can"
	RescueStarted = false
	RawStageNum = -1
	ForcedEscapeStage = -1
	ScriptedStageWave = 0
	CooldownEndWarningTime = 8.5 // seconds before end of cooldown to play warning
	CooldownEndWarningChance = 15 // percent chance to play warning
	CooldownEndWarningFrequency = 0 // how many waves to wait before playing another warning
	LastWaveCooldownWarningPlayed = -1 // the last wave that the cooldown warning played on
}

//---------------------------------------------------------
// the DirectorOptions defaults for Holdout - though the maps will override many of these per wave and phase
MutationOptions <-
{
	PreferredMobDirection = SPAWN_NO_PREFERENCE
	SpawnSetRule = SPAWN_ANYWHERE
	JournalString = ""
	SpecialInfectedAssault = 0
	AllowWitchesInCheckpoints = 1
	AllowCrescendoEvents = 0
	EnforceFinaleNavSpawnRules = 0
	IgnoreNavThreatAreas = 1
	ZombieDiscardRange = 10000

	WanderingZombieDensityModifier = 0
	BoomerLimit  = 0
 	ChargerLimit = 0
 	HunterLimit  = 0
	JockeyLimit  = 0
	SpitterLimit = 0
	SmokerLimit  = 0
	MaxSpecials  = 0
	CommonLimit  = 20
	TankLimit    = 0
	MegaMobMaxSize = 100
	MegaMobMinSize = 75
	PanicWavePauseMax = 5
	PanicWavePauseMin = 1
	AddToSpawnTimer = 0
	ShouldAllowMobsWithTank = true
	ShouldAllowSpecialsWithTank = true
	ZombieSpawnRange = 800
	ZombieTankHealth = 6000 // SP default is 4000
	BileMobSize = 20
	EscapeSpawnTanks = true
	ZombieDontClear = 1
	MegaMobSize = 50       // i have no idea why this defaults to not between min and max

	cm_ShouldEscortHumanPlayers = 1
	cm_AggressiveSpecials = 1
}

//=========================================================
// Utilities and helpers
//=========================================================

const PHASE_PANIC = 1
const PHASE_CLEAR = 2
const PHASE_DELAY = 0

function StageNumToWaveBase( stagenum )
{
	return (stagenum + 2) / 3   // the +2 is because of our initial startup raw phases...
}

function WaveBaseToStageNum( wavebase, substage = PHASE_DELAY )
{
	return wavebase * 3 + substage
}

// Are players permitted to pick up this object by pressing their USE key?
// return true for yes, false for no.
function CanPickupObject( object )
{
	// mines
	if ( object.GetName().find( "mine_1_body" ) )
	{
		return true
	}
	
	// resource drops
	if ( object.GetName().find( "prop_resource" ) )
	{
		return true
	}

	// check the map script for a PickupObject function for extra qualified items
	// TODO: we should do this more generally!
	local canPickup = false
	if( "PickupObject" in g_MapScript )
	{
		canPickup = g_MapScript.PickupObject( object )
	}
	
	return canPickup
}

// Called from other systems when they want the next combat wave to be the escape
function StartEscapeWave()
{
	SessionState.ForcedEscapeStage =  ( ( SessionState.RawStageNum + 2 ) / 3) * 3 + 1
	// printl( "Prepping Escape Wave for Stage " + SessionState.ForcedEscapeStage + " from " + SessionState.RawStageNum )
}

// show the final score and timing
function HoldoutDisplayScores( )
{
	local final_time = Time() - SessionState.RealStartTime
	local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
	HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
	HUDPlace( HUD_FAR_RIGHT, 0.28, 0.50, 0.44, 0.06 )
	HUDPlace( HUD_FAR_LEFT,  0.28, 0.58, 0.44, 0.06 )
	HUDPlace( HUD_LEFT_BOT,  0.28, 0.66, 0.44, 0.06 )
	HUDPlace( HUD_RIGHT_BOT, 0.28, 0.74, 0.44, 0.06 )
	local ticker_str = score_strings.yourtime
	if ("finish" in score_strings)
		ticker_str = ticker_str + "\n" + score_strings.finish
	ticker_str = ticker_str + "\nBest Times So Far"
	Ticker_SetBlink( false )   // or true if you came in first?
	Ticker_NewStr( ticker_str, 120 )
	foreach (idx, val in score_strings.topscores)
	{
		local hudfield = "score"+(idx+1)
		HoldoutHUD.Fields[hudfield].dataval = val
		HoldoutHUD.Fields[hudfield].flags = HoldoutHUD.Fields[hudfield].flags & ~HUD_FLAG_NOTVISIBLE
	}
	Scoring_SaveTable( SessionState.MapName, SessionState.ModeName )
}

// if a chopper rescue, this is where we get called
function RescuedByCopter( )
{
	// for now
	HoldoutDisplayScores()
}

// @TODO - this is basically "dont shoot at mines" right? why is it here?
function BotQuery( queryflag, entity, defaultvalue )
{
	switch( queryflag )
	{
		case BOT_QUERY_NOTARGET:
		{
			local classname = entity.GetClassname()
			local targetname = entity.GetName()
			if ( targetname && targetname.find( "mine_1_body" ) )
			{
				return false;
			}
			return true;
		}
	}
	
	return defaultvalue;
}

//----------------------------------------------------
// Rather than checking every time to see if the map has defined each callback
// instead we are going to check for each callback on startup
//   if they arent there, we will insert a simple NOP lambda (if they are, we leave them)
// For what these callbacks do, check top of file where they are described
function OnGameplayStart()
{
	CheckOrSetMapCallback( "DoMapEventCheck", @() false )
	CheckOrSetMapCallback( "DoMapSetup", @() null)
	CheckOrSetMapCallback( "GetMapEscapeStage", @() null )
	CheckOrSetMapCallback( "IsMapSpecificStage", @() false )
	CheckOrSetMapCallback( "GetMapSpecificStage", @() null )
	CheckOrSetMapCallback( "GetAttackStage", @() null )
	CheckOrSetMapCallback( "GetMapClearoutStage", @() null )
	CheckOrSetMapCallback( "GetMapDelayStage", @() null )

	DoMapSetup()    // and then call the MapSpecific startup callback right away
}

//=========================================================
//=========================================================
function OnActivate()
{
	// @todo: put scoring back into holdout!
	Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )	

	// this is so we can do some non-wave-based thinking
	ScriptedMode_AddSlowPoll( HoldoutSlowPollUpdate )
}

//=========================================================
//=========================================================
function OnShutdown()
{
	ScriptedMode_RemoveSlowPoll( HoldoutSlowPollUpdate )
}

HoldoutHUD <- {}
//=========================================================
// since we have this mutation specific HUD but individual maps can use it differently
// it is a bit more complicated than a normal HUD Setup
// - use SessionState to tell the system which extra fields you want (Wave #, Timer, etc)
// - check and warn if you try to set conflicting fields
function SetupModeHUD()
{
	HoldoutHUD =
	{
		Fields = 
		{
			cooldown_time = { slot = HUD_LEFT_TOP, name = "cooldown", special = HUD_SPECIAL_COOLDOWN, flags = HUD_FLAG_COUNTDOWN_WARN | HUD_FLAG_BEEP },
			supply        = { slot = HUD_RIGHT_TOP, name = "supply", staticstring = "Supplies: ", datafunc = @() g_MapScript.Resources.CurrentCount },

			// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
			// we will move the Ticker down to be a "header" - then need 4 slots for top 4
			score1   = { slot = HUD_FAR_RIGHT, name = "score1", dataval = "Score1", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
			score2   = { slot = HUD_FAR_LEFT,  name = "score2", dataval = "Score2", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
			score3   = { slot = HUD_LEFT_BOT,  name = "score3", dataval = "Score3", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
			score4   = { slot = HUD_RIGHT_BOT, name = "score4", dataval = "Score4", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
		}
	}
	
	if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
	{   
		RescueTimer_Init( HoldoutHUD, HUD_MID_BOX, HUD_MID_BOT )
 		if (SessionState.HUDWaveInfo)
 			printl("Warning: Cannot have both Rescuetimer and Wave HUD elements at once");
 	}
 	else if ( "HUDWaveInfo" in SessionState && SessionState.HUDWaveInfo )
 	{
		HoldoutHUD.Fields.wave <- {slot = HUD_MID_TOP, name = "wave", staticstring = "Wave: ", datafunc = @() SessionState.ScriptedStageWave }
	}

	if( "HUDTickerTimeout" in SessionState )
	{
		TickerTimeout = SessionState.HUDTickerTimeout
	}
	
 	if ( "HUDTickerText" in SessionState )
 	{
 		if ( SessionState.HUDTickerText.len() > 0 )
 		{
 			Ticker_AddToHud( HoldoutHUD, SessionState.HUDTickerText )
 		}
 	}

	HUDSetLayout( HoldoutHUD );
}

//=========================================================
// The scripted mode calls this function each time the generator turns on. 
// The generator usually turns on because someone poured a can of fuel into it.
//=========================================================
function OnGeneratorStart()
{
	g_MapScript.RescueTimer_Start()
}

//=========================================================
// Counterpart to OnGeneratorStart(), this is called when the generator sputters and stops
//  usually because it has run out of fuel.
//=========================================================
function OnGeneratorStop()
{
	g_MapScript.RescueTimer_Stop()
}

//=========================================================
// this is the base "system maintenance" for holdout mode timers and UI
// It checks things like has rescue started, should we do a warning about cooldown about to end, etc
//=========================================================
function HoldoutSlowPollUpdate()
{
	RescueTimer_Tick()

	if ( !SessionState.RescueStarted )
	{
		if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
		{
			local seconds = g_MapScript.RescueTimer_Get()
		
			// start the rescue if the generator has run long enough
			if( seconds <= 0 )
			{
				SessionState.RescueStarted = true
				g_ModeScript.StartEscapeWave()
			}
		}
	}

	// CooldownEndWarningChance is the % chance that a random player will vocalize a warning
	// when a cooldown is close to ending
	if( "CooldownEndWarningChance" in SessionState )
	{
		local timeLeft = HUDReadTimer( HUD_SPECIAL_COOLDOWN )

		if ( ( timeLeft < SessionState.CooldownEndWarningTime ) && ( timeLeft > 0 ) && ( SessionState.LastWaveCooldownWarningPlayed + SessionState.CooldownEndWarningFrequency < SessionState.ScriptedStageWave ) )
		{
			SessionState.LastWaveCooldownWarningPlayed = SessionState.ScriptedStageWave

			// roll the dice...
			local chance = RandomInt(0, 100 )

			if( chance < SessionState.CooldownEndWarningChance )
			{
				// jackpot! collect the players into an array and select one at random to speak a line
				local playerEnt = null
				local playerArray = []

				while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
				{
					if (playerEnt.IsSurvivor() )
					{
						playerArray.append( playerEnt)
					}
				}

				local idx = RandomInt( 0, 3)

				if( idx < playerArray.len() )
				{
					// fire entity IO at "!activator" and pass the player ent as the activator
					EntFire( "!activator", "SpeakResponseConcept", "PlayerHurryUp", 0, playerArray[idx] )
				}
			}
		}
	}
}

function JournalFunc()
{
	DirectorOptions.JournalString = "{ resources = " + g_MapScript.Resources.CurrentCount
	if (SessionState.HUDRescueTimer)
		DirectorOptions.JournalString += ", rescue = " + RescueTimer_Get()
	if ("JournalMapFunc" in this)
		DirectorOptions.JournalString += JournalMapFunc()
	DirectorOptions.JournalString += " }"
}

//=========================================================
// A little digression of testing/demoing a "script based clearout"
//  You can just do C++ based clearout (to let the mob population clearout) of course
//  But from script, one can use a Poll function and GetInfectedStats to manage it yourself
//  See sm_utilities for the actual code/systems if you want to do something like it

// Whether to use the new Script based clearout or not - false here just means use the default c++ one 
::g_ScriptClearout <- true

// if you are going to use the new clearout - here is the table for determining it's behavior
holdout_ClearoutTable <-
{
	commons = 2
	specials = 0
	tanks = 0
	witches = 0
	plateautime = 5
	plateaucommons = 5
	plateauspecials = 1
	stopspecials = true
}

///////////////////////////////////////////////////////////////////////////////
//
// "Main Loop" of Holdout
// 
// GetNextStage gets called whenever the director finishes the last thing you told it to do
//   or if you do a ForceNextStage yourself
// In holdout, we use GetNextStage to manage the game, cycling through 3 types of Stage (as described at top)
// At each stage, we make some of our callbacks (that we checked and set up in OnGameplayStart)
// To determine what options we want for that stage - usually in the form of a "stagetable"
//   which is basically just a set of DirectorOptions parameters and a few extra fields
// And then we go and set all those fields in the director, set the stage type, and send the director off
//
///////////////////////////////////////////////////////////////////////////////

function GetNextStage()
{
	local use_stage = null

	local stageNum = ++SessionState.RawStageNum

	SessionState.ScriptedStageWave = ( stageNum + 2 ) / 3

	// first, special event management
	DoMapEventCheck()
	
	// fire game events as we begin and end cooldown states - initial map stage 0 is considered a cooldown state
	if( ( stageNum % 3 ) == PHASE_PANIC )
		FireScriptEvent( "on_cooldown_end", null )
	else if( ( ( stageNum % 3 ) == PHASE_DELAY ) || ( stageNum == 0 ) )
		FireScriptEvent( "on_cooldown_begin", null )

	// error check!
	if (SessionState.ForcedEscapeStage != -1 && stageNum > SessionState.ForcedEscapeStage)
		printl("Ummmmmm.... something has gone very wrong... we passed escape stage but are still picking next stages...");

	if ( stageNum == 1) // 1st real attack stage
	{
		SessionState.RealStartTime <- Time() 
		Director.ResetSpecialTimers()
	}

	//  now the generalized stage sequencing...
	if ( stageNum == 0 )
	{
		DirectorOptions.ScriptedStageType = STAGE_SETUP
		if ("HUDTickerText" in SessionState)
			Ticker_NewStr(SessionState.HUDTickerText)  // since the time it got set as start in HUD load is ages ago
	}
	else if ( stageNum == SessionState.ForcedEscapeStage )
	{
		use_stage = GetMapEscapeStage()
	}
	else if ( IsMapSpecificStage() )
		use_stage = GetMapSpecificStage()   // dont like this! potential out of sync/parallel variables
	else if ( ( stageNum % 3 ) == PHASE_PANIC )
	{
		use_stage = GetAttackStage()
		if (::g_ScriptClearout)
			ClearoutNotifyPanicStart( holdout_ClearoutTable )
	}
	else if ( ( stageNum % 3 ) == PHASE_CLEAR )
	{
		if (::g_ScriptClearout)
		{
			ClearoutStart( holdout_ClearoutTable )
			DirectorOptions.ScriptedStageType = STAGE_DELAY
			DirectorOptions.ScriptedStageValue = -1	// Infinite
		}
		else
			use_stage = GetMapClearoutStage()
	}
	else if ( ( stageNum % 3 ) == PHASE_DELAY )
	{
		use_stage = GetMapDelayStage()
	}

	// Put the special infected into assault mode - really want to do this sooner somehow? at maxspecials 0?
	switch ( DirectorOptions.ScriptedStageType )
	{
		case STAGE_CLEAROUT:
		case STAGE_DELAY:
		case STAGE_ESCAPE:
			DirectorOptions.SpecialInfectedAssault = 1
			break;
		default:
			DirectorOptions.SpecialInfectedAssault = 0
	}

	if ( use_stage != null )
	{
		if ( "stageDefaults" in g_MapScript )
			StageInfo_Execute( use_stage, stageDefaults )        // should use_stage just delegate?
		else
			StageInfo_Execute( use_stage )
		
		if ( "NextDelay" in use_stage )
			SessionState.NextDelayTime <- use_stage.NextDelay    // just overwrite, so we dont need to if check
		else
			SessionState.NextDelayTime <- 30                     // better default from somewhere?
	}

	smDbgPrint( "Setting type " + DirectorOptions.ScriptedStageType + " and val " + DirectorOptions.ScriptedStageValue )
}

ShootZones


ModeSpawns <-
[
	["ShootzoneTrigger", "shootzone_trigger_spawn_*", "shootzone_trigger_group", SPAWN_FLAGS.SPAWN],
	[ "ShootzoneSound", SPAWN_FLAGS.NOSPAWN ],
]


MutationState <-
{
	allPlayers = []
	playersInShootzone = []
	allShootzones = []
	enabledShootzones = []
	lastEnabledShootzoneID = -1
	FirstTime = true
}


MutationOptions <-
{
    CommonLimit = 30 // Maximum number of common zombies alive in the world at the same time
 	//MegaMobSize = 20 // Total number of common zombies in a mob. (never more than CommonLimit at one time)
 	//WanderingZombieDensityModifier = 0 // lets get rid of the wandering zombies
 	MaxSpecials  = 0 // removes all special infected from spawning
 	TankLimit    = 0 // removes all tanks from spawning
 	WitchLimit   = 0 // removes all witches from spawning
	//BoomerLimit  = 0
 	//ChargerLimit = 0
 	//HunterLimit  = 0
	//JockeyLimit  = 0
	//SpitterLimit = 0
	//SmokerLimit  = 0
}


ShootzonesHUD <-
{
	Fields = 
	{
		player1   = { slot = HUD_LEFT_TOP,  name = "player1", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
		player2   = { slot = HUD_LEFT_BOT,  name = "player2", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
		player3   = { slot = HUD_RIGHT_TOP, name = "player3", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
		player4   = { slot = HUD_RIGHT_BOT, name = "player4", dataval = "OUT", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
	}
}


function ShootzoneThinkSpawnCB( entity, rarity )
{
	printl( "Think Spawn!!!" )

	//entity.ValidateScriptScope()
	entity.GetScriptScope().ShootzoneThink <- function()
	{
		g_ModeScript.RecomputePlayersInShootzones()
	}
}


function OnGameplayStart()
{
	printl( "Starting SHOOTZONES!" )
	printl( "Max Flow Distance: " + GetMaxFlowDistance() )

	//Add all the players
	local playerEnt = null
	while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
	{
		if ( playerEnt.IsSurvivor() )
		{
			playerEnt.ValidateScriptScope()
			playerEnt.GetScriptScope().currentShootzone <- -1
			//playerEnt.GetScriptScope().currentShootzone = -1
			SessionState.allPlayers.append( playerEnt)
		}
	}

	//Initialize the HUD
	foreach ( index, playerEnt in SessionState.allPlayers )
	{
		local hudField = "player" + ( index + 1 )
		ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": OUT"
		ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
	}

	EnableShootzones()

	//ScriptedMode_AddUpdate( RecomputePlayersInShootzones.bindenv(this) )

	local shootzone_think =
	{
		function GetSpawnList()      { return [ EntityGroup.SpawnTables.shootzone_think ] }
		function GetEntityGroup()    { return EntityGroup }
		EntityGroup =
		{
			SpawnTables =
			{
				shootzone_think = 
				{
					initialSpawn = true
					SpawnInfo =
					{
						classname = "logic_script"
						angles = Vector( 0, 0, 90 )
						targetname = "shootzone_think"
						origin = Vector( 0, 0, 0 )
						vscripts = "NULL_SCRIPT_NAME"
						thinkfunction = "ShootzoneThink"
					}
				}
			} // SpawnTables
		} // EntityGroup
	}

	local shootzoneThinkGroup = shootzone_think.GetEntityGroup()
	shootzoneThinkGroup.SpawnTables[ "shootzone_think" ].PostPlaceCB <- ShootzoneThinkSpawnCB
	g_MapScript.SpawnSingleAt( shootzoneThinkGroup, Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )
	g_MapScript.SpawnSingleAt( g_MapScript.GetEntityGroup( "ShootzoneSound" ), Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )

}


function OnActivate()
{
}


function OnEntityGroupRegistered( name, group )
{
	if ( name == "ShootzoneTrigger" )
	{
		group.GetEntityGroup().SpawnTables[ "shootzone_script" ].PostPlaceCB <- ShootzoneScriptSpawnCB
	}
}


function ShootzoneScriptSpawnCB( entity, rarity )
{
	printl( "Script spawned: " + entity + ", name: " + entity.GetName() )

	local shootzoneScope = entity.GetScriptScope()

	//Set the id of the shootzone
	shootzoneScope.id = SessionState.allShootzones.len()
	printl( "Shootzone ID: " + shootzoneScope.id )
	
	//Flow distance for the shootzone
	shootzoneScope.flowDistance = GetFlowDistanceForPosition( shootzoneScope.origin )
	shootzoneScope.flowPercent = GetFlowPercentForPosition( shootzoneScope.origin, true )
	shootzoneScope.flowPercent1 = GetFlowPercentForPosition( shootzoneScope.origin, false )
	printl( "Flow Distance: " + shootzoneScope.flowDistance )
	printl( "Flow Percent: " + shootzoneScope.flowPercent )

	//Add the shootzone to the list of all the shootzones
	SessionState.allShootzones.append( entity )
	
}


// HUD setup/control - our HUD is pretty simple in dash
function SetupModeHUD( )
{
	HUDSetLayout( ShootzonesHUD )

	HUDPlace( HUD_LEFT_TOP, 0.01, 0.06, 0.2, 0.04 )
	HUDPlace( HUD_LEFT_BOT, 0.01, 0.11, 0.2, 0.04 )
	HUDPlace( HUD_RIGHT_TOP, 0.01, 0.16, 0.2, 0.04 )
	HUDPlace( HUD_RIGHT_BOT, 0.01, 0.21, 0.2, 0.04 )
}


function FindShootzoneClosestToPlayers()
{

}

function EnableShootzones()
{
	//Cycle through the shootzones and keep 3 enabled at all times
	if ( SessionState.allShootzones.len() < 3 )
	{
		foreach ( shootzone in SessionState.allShootzones )
		{
			shootzone.GetScriptScope().EnableShootzone()
			SessionState.enabledShootzones.append( shootzone )

			SessionState.lastEnabledShootzoneID = shootzone.GetScriptScope().id
		}
	}
	else
	{
		local index = SessionState.lastEnabledShootzoneID + 1
		while ( SessionState.enabledShootzones.len() < 3 )
		{
			if ( index >= SessionState.allShootzones.len() )
			{
				index = 0;
			}

			//Enable the next available shootzone in the list
			local currentShootzone = SessionState.allShootzones[index]
			if ( currentShootzone.GetScriptScope().isEnabled == false )
			{
				currentShootzone.GetScriptScope().EnableShootzone()
				SessionState.enabledShootzones.append( currentShootzone )

				SessionState.lastEnabledShootzoneID = currentShootzone.GetScriptScope().id
			}

			index++
		}
	}
}


function RecomputePlayersInShootzones()
{
//	if ( SessionState.FirstTime )
//	{
//		foreach ( shootzone in SessionState.allShootzones )
//		{
//			local shootzoneScope = shootzone.GetScriptScope()
//			DebugDrawText( shootzoneScope.origin, "Distance: " + shootzoneScope.flowDistance + "\nPercent: " + shootzoneScope.flowPercent + "%", false, 100000 )
//		}
//		
//		SessionState.FirstTime = false;
//	}

	//Keep track of players currently in a shootzone
	local prevShootzonePlayers = clone( SessionState.playersInShootzone )

	//Recompute which players are in an active shootzone
	SessionState.playersInShootzone.clear()

	foreach ( playerEnt in SessionState.allPlayers )
	{
		foreach ( shootzone in SessionState.enabledShootzones )
		{
			local shootzoneScope = shootzone.GetScriptScope()
			if ( shootzoneScope.isActive )
			{
				local playerToShootzone = playerEnt.GetOrigin() - shootzoneScope.origin
				local playerDistance = playerToShootzone.Length()

				if ( playerDistance < 150 )
				{
					playerEnt.GetScriptScope().currentShootzone = shootzoneScope.id
					SessionState.playersInShootzone.append( playerEnt )
					break;
				}
			}
		}	
	}

	//foreach ( shootzone in SessionState.allShootzones )
	//{
		//local shootzoneScope = shootzone.GetScriptScope()
		//local vecto = SessionState.allPlayers[0].GetOrigin() - shootzoneScope.origin
		//local dist = vecto.Length()
		//
		//if ( dist < 500 )
		//{
			//DebugDrawText( shootzoneScope.origin, "Distance: " + shootzoneScope.flowDistance + "\nPercent: " + shootzoneScope.flowPercent + "%\nPercent2: " + shootzoneScope.flowPercent1, false, 0.15 )
			//
			//DebugDrawLine( shootzoneScope.origin, SessionState.allPlayers[0].GetOrigin(), 255, 0, 0, false, 0.2 )
		//}
	//}
//
	foreach ( index, playerEnt in SessionState.allPlayers )
	{
		//This player just entered a shootzone
		if ( prevShootzonePlayers.find( playerEnt ) == null &&
			 SessionState.playersInShootzone.find( playerEnt ) != null )
		{
			//Start playing the sound for players that just entered the shootzone
			printl( playerEnt.GetPlayerName() + " entered shootzone " + playerEnt.GetScriptScope().currentShootzone )
			EmitSoundOnClient( "c2m4.BadMan1", playerEnt )
		}

		//This player just exited a shootzone
		if ( prevShootzonePlayers.find( playerEnt ) != null &&
			 SessionState.playersInShootzone.find( playerEnt ) == null )
		{
			//Stop playing the sound for players that just exited the shootzone
			printl( playerEnt.GetPlayerName() +" exited shootzone " + playerEnt.GetScriptScope().currentShootzone )

			playerEnt.GetScriptScope().currentShootzone = -1
			StopSoundOn( "c2m4.BadMan1", playerEnt )
		}

		local inOrOut = "OUT"
		foreach ( shootzonePlayer in SessionState.playersInShootzone )
		{
			if ( shootzonePlayer.GetPlayerName() == playerEnt.GetPlayerName() )
			{
				inOrOut = "IN"
				break;
			}
		}

		//Set the player HUD flags
		local hudField = "player" + ( index + 1 )
		ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": " + inOrOut
		ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
	}
}


function ShootzoneTimedOut( shootzoneID )
{
	foreach ( index, shootzone in SessionState.enabledShootzones )
	{
		if ( shootzone.GetScriptScope().id == shootzoneID )
		{
			SessionState.enabledShootzones.remove( index )
			break
		}
	}

	EnableShootzones()
}


function AllowTakeDamage( damageTable )
{
	foreach ( playerEnt in SessionState.allPlayers )
	{
		//If the attacker is a player
		if ( playerEnt == damageTable.Attacker )
		{
			//If the attacking player is in a shootzone then do damage
			foreach ( shootzonePlayer in SessionState.playersInShootzone )
			{
				if ( shootzonePlayer == damageTable.Attacker )
				{
					//printl( "Damage done by shootzone player: " + shootzonePlayer.GetPlayerName() )
					return true
				}
			}

			//Attacking player is not in a shootzone
			//printl( "Attacking player is not in shootzone: " + playerEnt.GetPlayerName() )
			return false
		}
	}

	//Attacker is not a player
	return true
}

GunBrain


//=========================================================
//=========================================================

GunStatsTable <- null

TARGET_INVALID <- -1
TARGET_SURVIVOR <- 9

MAX_TRACK_PLAYERS <- 10 // note - to change this we would need to update the server index to generate our graphs. There are only 10 graph servers so you can't go past the index 9.

// we need a barebones HUD for our ticker that shows up at the begining of the game
function SetupModeHUD( )
{
	ModeHUD <-
	{
		Fields =
		{
		}
	}
	Ticker_AddToHud( ModeHUD, "" )

	// load the ModeHUD table
	HUDSetLayout( ModeHUD )
}

/////////////////////////////////////////////////
// Mod Command interface
/////////////////////////////////////////////////

GB_HELP_TEXT <-
[
	"About GunBrain - Damage stats are stored on the server for each player. The motd points to an htm file that contains graphs of the stats.",
	"Type these commands into the chat window to perform the following actions.",
	"---",
	"gb_block <playername> - Remove stats for this player and prevent them from recording stats in the future. (useful for Bots)",
	"gb_allow <playername> - Allow a stats blocked player to record stats again.",
	"gb_update - Update the files on disk with the most recent version of the stats (this automatically happens at level end).",
	"gb_dump - Dump all the stats to the console.",
	"gb_reset - Start fresh collecting stats.",
	"gb_backup - creates gunbrainstats.bak with the current data",
	"gb_restore - loads data from gunbrainstats.bak",
	"gb_bot_block - Block all bots from recording stats",
]

function InterceptChat( str, srcEnt )
{
	if ( str.find("gb_help") != null )
	{
		foreach( idx, Line in GB_HELP_TEXT )
		{
			Say( null, Line, true )
		}
	}
	else if( srcEnt != null )
	{
		if ( str.find("gb_block ") )
		{
			local commandStart = str.find("gb_block ")
			local name = str.slice( commandStart + 9 )
			name = name.slice(0,-1)
			CommandAddBlockPlayer( name.toupper() )
			SessionState.GBStatsDirty = true
		}
		else if ( str.find("gb_allow ") )
		{
			local commandStart = str.find("gb_allow ")
			local name = str.slice( commandStart + 9 )
			name = name.slice(0,-1)
			CommandRemoveBlockPlayer( name.toupper() )
		}
		else if ( str.find("gb_update") )
		{
			SessionState.GBStatsDirty = true
		}
		else if ( str.find("gb_dump") )
		{
			CommandDumpData()
		}
		else if ( str.find("gb_reset") )
		{
			CommandResetData()
			SessionState.GBStatsDirty = true
		}
		else if ( str.find("gb_backup") )
		{
			CommandBackupData()
		}
		else if ( str.find("gb_restore") )
		{
			CommandRestoreData()
			SessionState.GBStatsDirty = true
		}
		else if ( str.find("gb_bot_block") )
		{
			CommandAddBlockPlayer( "ELLIS" )
			CommandAddBlockPlayer( "COACH" )
			CommandAddBlockPlayer( "NICK" )
			CommandAddBlockPlayer( "ROCHELLE" )
			CommandAddBlockPlayer( "FRANCIS" )
			CommandAddBlockPlayer( "LOUIS" )
			CommandAddBlockPlayer( "ZOEY" )
			CommandAddBlockPlayer( "BILL" )
		}		
	}
}

function IsPlayerBlocked( playerName )
{
	foreach( index, name in GunStatsTable.Blocked )
	{
		if( name == playerName )
		{
			return true
		}
	}
	return false
}

function CommandAddBlockPlayer( playerName )
{
	Say( null, "Blocking stats for " + playerName, true )

	if ( GunStatsTable.Blocked.len() >= 64 )
		return

	if ( IsPlayerBlocked( playerName ) )
	{
		return
	}
	
	
	GunStatsTable.Blocked.push( playerName )

	foreach( index, Player in GunStatsTable.Players )
	{
		if( playerName == Player.name )
		{
			Say( null, "Removing stats for " + playerName, true )
			GunStatsTable.Players.remove(index)
			return
		}
	}
}

function CommandRemoveBlockPlayer( playerName )
{
	Say( null, "Removing block for " + playerName, true )
	foreach( index, name in GunStatsTable.Blocked )
	{
		if( name == playerName )
		{
			GunStatsTable.Blocked.remove(index)
			return
		}
	}
}

function CommandUpdateData()
{
	CommitDamageData()
}

function CommandDumpData()
{
	printl( TableToString( GunStatsTable ) )
}

function CommandResetData()
{
	GunStatsTable.clear()
	VerifyStatsTableStorage()
	printl( TableToString( GunStatsTable ) )
}

function CommandBackupData()
{
	Say( null, "Backing up data", true )
	StringToFile( "GunBrainData.bak" , TableToString( GunStatsTable ) )
}

function CommandRestoreData()
{
	GunStatsTable.clear()

	local saved_data = FileToString( "GunBrainData.bak" )
	if (saved_data != null)
	{
		local compileDataFunc = compilestring( "local temp_table = " + saved_data + " return temp_table" )
		GunStatsTable = compileDataFunc()
	}
	else
	{
		GunStatsTable <- {}
	}

	VerifyStatsTableStorage()
}

/////////////////////////////////////////////////
// Startup
/////////////////////////////////////////////////

function OnGameplayStart()
{
	printl("Running GunBrain the enhanced gun stats mod")
}

function OnActivate()
{
	printl( " ** On Activate" )	

	SessionState.GBStatsDirty <- false
	ScriptedMode_AddUpdate( StatsUpdate )
	SessionState.GBStatsTick <- 0

	Ticker_SetBlink( true )
	Ticker_NewStr( "Welcome to GunBrain! Chat <gb_help> or open the MotD for available commands", 15 )
	
	PrepareStats()

	CommitDamageData()
}

function PrepareStats()
{
	local saved_data = FileToString( "GunBrainData" )
	if (saved_data != null)
	{
		local compileDataFunc = compilestring( "local temp_table = " + saved_data + " return temp_table" )
		GunStatsTable = compileDataFunc()
	}
	else
	{
		GunStatsTable <- {}
	}

	VerifyStatsTableStorage()

	foreach( index, Player in GunStatsTable.Players )
	{
		printl( "    " + index + " = " + Player.name );
	}
}

/////////////////////////////////////////////////
// Data Management
/////////////////////////////////////////////////

function StatsUpdate()
{
	SessionState.GBStatsTick++

	if( SessionState.GBStatsDirty || SessionState.GBStatsTick == 30 )
	{
		g_ModeScript.CommandUpdateData()

		SessionState.GBStatsDirty = false
		SessionState.GBStatsTick = 0

		Say( null, "Updating Stats", true )
	}
}

function VerifyStatsTableStorage()
{
	if ( !( "Players" in GunStatsTable ) )
	{
		printl("Adding new players list")
		GunStatsTable.Players <- []
	}
	if ( !( "Blocked" in GunStatsTable ) )
	{
		GunStatsTable.Blocked <- []
	}
}

function CommitDamageData()
{
	StringToFile( "GunBrainData" , TableToString( GunStatsTable ) )

	local damageImgString = "<p>"
	
	foreach( idx, Line in GB_HELP_TEXT )
	{
		damageImgString = damageImgString + Line + "<br>"
	}

	damageImgString = damageImgString + "</p>"
	
	damageImgString = damageImgString + CreateOutgoingDamageChartString() + CreateIncomingDamageChartString()
	foreach( index, Player in GunStatsTable.Players )
	{
		damageImgString = damageImgString + CreatePlayerAccuracyChartString( index )
	}
	foreach( index, Player in GunStatsTable.Players )
	{
		damageImgString = damageImgString + CreatePlayerDamageChartString( index )
	}
	StringToFile( "gunbrainstats.htm" , damageImgString )
	SendToServerConsole( "motdfile ems\\gunbrainstats.htm" )
	ReloadMOTD() 
}

function OnShutdown()
{
	CommitDamageData()
}

/////////////////////////////////////////////////
// Damage Event handling
/////////////////////////////////////////////////

function AllowTakeDamage( damageTable )
{	
	// check to see if this wasn't a weapon
	if( !( "GetClassname" in damageTable.Weapon ) )
	{
		// don't store shove damage
	}
	else
	{
		if( damageTable.Attacker.GetClassname() == "player" && damageTable.Attacker.IsSurvivor() )
		{
			local target_type = TARGET_INVALID

			if ( damageTable.Victim.GetClassname() == "infected" )
			{
				target_type = ZOMBIE_NORMAL
			}
			else if ( damageTable.Victim.GetClassname() == "player" )
			{
				if( damageTable.Victim.IsSurvivor() )
				{
					target_type = TARGET_SURVIVOR
				}
				else
				{
					target_type = damageTable.Victim.GetZombieType()
				}
			}
			else if ( damageTable.Victim.GetClassname() == "witch" )
			{
				target_type = ZOMBIE_WITCH
			}
			
			// remove WEAPON_ from the name
			local weapName = damageTable.Weapon.GetClassname()
			weapName = weapName.slice( 7 )

			AddHit( damageTable.Attacker.GetPlayerName().toupper(), weapName.toupper(), damageTable.Victim.GetHealth() > 0 ? damageTable.Victim.GetHealth() : 0, damageTable.DamageDone, damageTable.DamageType & DMG_HEADSHOT, target_type )
		}
		else
		{
			printl("bad target")
		}
	}
	return true
}

function OnGameEvent_weapon_fire( params )
{
	if ( "count" in params )
	{
		local attacker = GetPlayerFromUserID( params.userid )
		local weapon = params.weapon
		local shots = params.count

		AddShots( attacker.GetPlayerName().toupper(), weapon.toupper(), shots )
	}
}

function OnGameEvent_player_hurt( params )
{
	local victim = GetPlayerFromUserID( params.userid )
	if ( !victim.IsSurvivor() )
	{
		return
	}

	if( IsPlayerBlocked( victim.GetPlayerName().toupper() ) )
		return

	if( "attackerentid" in params )
	{
		local attacker = EntIndexToHScript( params.attackerentid )

		local target_type = TARGET_INVALID

		if( params.attackerentid == 0 && params.attacker == 0 )
		{
			// looks like an invalid attacker
		}
		else if ( attacker.GetClassname() == "infected" )
		{
			target_type = ZOMBIE_NORMAL
		}
		else if ( attacker.GetClassname() == "player" )
		{
			if( attacker.IsSurvivor() )
			{
				target_type = TARGET_SURVIVOR
			}
			else
			{
				target_type = attacker.GetZombieType()
			}
		}
		else if ( attacker.GetClassname() == "witch" )
		{
			target_type = ZOMBIE_WITCH
		}

		local player = FindOrAddPlayerData( victim.GetPlayerName().toupper() )
		if ( player != null )
		{
			AddIncomingDamage( player, params.health, params.dmg_health, target_type )
		}
	}
}

/////////////////////////////////////////////////
// Stat recording.
/////////////////////////////////////////////////

function AddHit( playerName, weaponName, victimHealth, damage, headShot, target_type )
{
	if( IsPlayerBlocked( playerName ) )
		return
	
//	printl( "playerName " + playerName + " weaponName " + weaponName + " victimHealth " + victimHealth + " damage " + damage + " headShot " + headShot )

	local Weapon = FindOrAddWeaponData( playerName, weaponName )

	if ( Weapon != null )
	{
		if( target_type != TARGET_INVALID )
		{
			AddWeaponHit( Weapon, victimHealth, damage, headShot, target_type )
		}

		local player = FindOrAddPlayerData( playerName )
		if ( player != null ) 
		{
			AddTypeDamage( player, victimHealth, damage, target_type )
		}
	}
}

function AddShots( playerName, weaponName, shots )
{
	if( IsPlayerBlocked( playerName ) )
		return

//	printl( "playerName " + playerName + " weaponName " + weaponName + " shots " + shots )

	local Weapon = FindOrAddWeaponData( playerName, weaponName )
	if ( Weapon  != null )
	{
		Weapon.shots += shots
	}
}

function CreateNewPlayerTable( playerName )
{
	local player = { name=playerName, WeaponData = [], TypeDamage = [], DamageTaken = [] }
	player.TypeDamage.resize(10,0)
	player.DamageTaken.resize(10,0)
	return player
}

function CreateNewWeaponTable( weaponName )
{
	local weapon = { name=weaponName, shots = 0, hits = 0, effectiveDamage = 0, overKill = 0, deadDamage = 0, headShots = 0, FFHits = 0 }
	return weapon
}

function FindOrAddPlayerData( playerName )
{
	local playerIdx = -1

	foreach( indexP, Player in GunStatsTable.Players )
	{
		if( playerName == Player.name )
		{
			playerIdx = indexP
			break
		}
	}	

	if ( playerIdx == -1 )
	{
		if ( GunStatsTable.Players.len() >= MAX_TRACK_PLAYERS )
		{
			return null
		}
		else
		{
			local player = CreateNewPlayerTable( playerName )
			playerIdx = GunStatsTable.Players.len()
			GunStatsTable.Players.push( player )
		}
	}

	return GunStatsTable.Players[playerIdx]
}

function FindOrAddWeaponData( playerName, weaponName )
{
	local foundPlayer = null
	local bFoundWeapon = false

	local weapIdx = -1
	local playerIdx = -1

	foreach( indexP, Player in GunStatsTable.Players )
	{
		if( playerName == Player.name )
		{
			playerIdx = indexP
			foreach( indexW, Weapon in Player.WeaponData )
			{	
				if( weaponName == Weapon.name )
				{
					return Weapon
				}
			}
			break
		}
	}	

	if ( playerIdx == -1 )
	{
		if ( GunStatsTable.Players.len() >= MAX_TRACK_PLAYERS )
		{
			return null
		}
		else
		{
			local player = CreateNewPlayerTable( playerName )
			playerIdx = GunStatsTable.Players.len()
			GunStatsTable.Players.push( player )
		}
	}

	local weapon = CreateNewWeaponTable( weaponName )
	GunStatsTable.Players[playerIdx].WeaponData.push( weapon )
	return weapon
}

function AddIncomingDamage( player, victimHealth, damage, target_type )
{
	if ( target_type == TARGET_INVALID )
		return

	local effectiveDamage = damage
	if ( damage > victimHealth )
	{
		effectiveDamage = victimHealth
	}

	player.DamageTaken[target_type] += effectiveDamage
}

function AddTypeDamage( player, victimHealth, damage, target_type )
{
	if ( target_type == TARGET_INVALID )
		return

	local effectiveDamage = damage
	if ( damage > victimHealth )
	{
		effectiveDamage = victimHealth
	}

	player.TypeDamage[target_type] += effectiveDamage
}

function AddWeaponHit( weaponData, victimHealth, damage, headShot, target_type )
{
	if ( target_type == TARGET_SURVIVOR )
	{
		weaponData.FFHits++
		return
	}

	if ( weaponData.name == "MELEE" || weaponData.name == "CHAINSAW" )
	{
		weaponData.shots++
	}

//	printl( "victimHealth " + victimHealth + " damage " + damage + " headShot " + headShot + " target_type " + target_type )

	if ( damage < 0 )
		damage = 0
	
	local effectiveDamage = damage
	local overKillDamage = 0
	local deadDamage = 0

	if ( victimHealth <= 0 )
	{
		effectiveDamage = 0
		overKillDamage = 0
		deadDamage = damage
	}
	if ( damage > victimHealth )
	{
		effectiveDamage = victimHealth
		overKillDamage = damage - victimHealth
	}

	weaponData.hits++
	weaponData.effectiveDamage += effectiveDamage
	weaponData.overKill += overKillDamage
	weaponData.deadDamage += deadDamage

	if ( headShot > 0 )
	{
		weaponData.headShots++
	}

//	printl( "hits " + weaponData.hits + "  effectiveDamage " + weaponData.effectiveDamage + "  overKill " + weaponData.overKill + "  headShots " + weaponData.headShots + "  shots " + weaponData.shots )
}


/////////////////////////////////////////////////
// Chart URL building
/////////////////////////////////////////////////

function ComputeChartHeight( desiredHeight )
{
	local height = desiredHeight

	if( height < 220 )
	{
		height = 220
	}
	if( height > 500 )
	{
		height = 500
	}
	return height
}
function CreateOutgoingDamageChartString()
{
	local chart_url = "<img src="
	chart_url = chart_url + "\""
	chart_url = chart_url + "http://"
	chart_url = chart_url + 8
	chart_url = chart_url + ".chart.apis.google.com/chart"

	local target_names = ["Common","Smoker","Boomer","Hunter","Spitter","Jockey","Charger","Witch","Tank","Survivor"]

	{
		local line = "?chxl=1:"
		foreach( index, player in GunStatsTable.Players )
		{
			// player names are backwards so go in revers order
			line = line + "|" + GunStatsTable.Players[ (GunStatsTable.Players.len() - 1) - index ].name
		}
		chart_url = chart_url + line
	}
	
	chart_url = chart_url + "&chxr=0,0,100"
	chart_url = chart_url + "&chxt=x,y"
	chart_url = chart_url + "&chbh=a"
	chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 30*( 1 + GunStatsTable.Players.len() ) ) ) )
	chart_url = chart_url + "&cht=bhs"
	chart_url = chart_url + "&chco=D1C304,43D948,B6D943,0E8F17,6CA0E0,04C7D1,0E04D1,D104B2,8A04D1,DE2E12"
	chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000"

	local maxDamage = 100
	foreach( indexP, player in GunStatsTable.Players )
	{
		local playerDamageTotal = 0 
		foreach( index_target, damage in player.TypeDamage )
		{
			playerDamageTotal += damage
		}
		if( playerDamageTotal > maxDamage )
		{
			maxDamage = playerDamageTotal
		}
	}

	local damageScalar = 1000.0/maxDamage

	{		
		local line = "&chd=t"
		foreach( index_target, target in target_names )
		{
			if( index_target == 0 ) {	line = line + ":" } else { line = line + "|" }

			foreach( indexP, player in GunStatsTable.Players )
			{
				if( indexP == 0 ) {	line = line + "" } else { line = line + "," }

				local num = damageScalar*player.TypeDamage[index_target]
				line = line + "" + floor( num )
			}
		}
		chart_url = chart_url + line
	}

	{
		local line = "&chdl="
		foreach( index, name in target_names )
		{
			if( index != 0 ) { line = line + "|" }
			line = line + name
		}
		chart_url = chart_url + line
	}
	chart_url = chart_url + "&chtt=" + "Outgoing Player damage by Target"
	chart_url = chart_url + "\"" + "\\><br><br><br><br>"

	return chart_url
}

function CreateIncomingDamageChartString()
{
	local chart_url = "<img src="
	chart_url = chart_url + "\""
	chart_url = chart_url + "http://"
	chart_url = chart_url + 9
	chart_url = chart_url + ".chart.apis.google.com/chart"

	local target_names = ["Common","Smoker","Boomer","Hunter","Spitter","Jockey","Charger","Witch","Tank","Survivor"]

	{
		local line = "?chxl=1:"
		foreach( index, player in GunStatsTable.Players )
		{
			// player names are backwards so go in revers order
			line = line + "|" + GunStatsTable.Players[ (GunStatsTable.Players.len() - 1) - index ].name
		}
		chart_url = chart_url + line
	}

	chart_url = chart_url + "&chxr=0,0,100"
	chart_url = chart_url + "&chxt=x,y"
	chart_url = chart_url + "&chbh=a"
	chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 30*( 1 + GunStatsTable.Players.len() ) ) ) )
	chart_url = chart_url + "&cht=bhs"
	chart_url = chart_url + "&chco=D1C304,43D948,B6D943,0E8F17,6CA0E0,04C7D1,0E04D1,D104B2,8A04D1,DE2E12"
	chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000,0,1000"

	local maxDamage = 100
	foreach( indexP, player in GunStatsTable.Players )
	{
		local playerDamageTotal = 0 
		foreach( index_target, damage in player.DamageTaken )
		{
			playerDamageTotal += damage
		}
		if( playerDamageTotal > maxDamage )
		{
			maxDamage = playerDamageTotal
		}
	}

	local damageScalar = 1000.0/maxDamage

	{		
		local line = "&chd=t"
		foreach( index_target, target in target_names )
		{
			if( index_target == 0 ) {	line = line + ":" } else { line = line + "|" }

			foreach( indexP, player in GunStatsTable.Players )
			{
				if( indexP == 0 ) {	line = line + "" } else { line = line + "," }

				local num = damageScalar*player.DamageTaken[index_target]
				line = line + "" + floor( num )
			}
		}
		chart_url = chart_url + line
	}

	{
		local line = "&chdl="
		foreach( index, name in target_names )
		{
			if( index != 0 ) { line = line + "|" }
			line = line + name
		}
		chart_url = chart_url + line
	}
	chart_url = chart_url + "&chtt=" + "Incoming Player damage by Source"
	chart_url = chart_url + "\"" + "\\><br><br><br><br>"

	return chart_url
}

function CreatePlayerAccuracyChartString( player_index )
{
	local chart_url = "<img src="
	chart_url = chart_url + "\""
	chart_url = chart_url + "http://"
	chart_url = chart_url + player_index
	chart_url = chart_url + ".chart.apis.google.com/chart"

	local Player = GunStatsTable.Players[player_index]
	{
		local line = "?chxl=1:"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			// weapon names are listed backwards so store them such
			line = line + "|" + Player.WeaponData[ (Player.WeaponData.len() - 1) - indexW ].name
		}
		chart_url = chart_url + line
	}

	chart_url = chart_url + "&chxr=0,0,100"
	chart_url = chart_url + "&chxt=x,y"
	chart_url = chart_url + "&chbh=a"
	chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 36*( 1.6 + GunStatsTable.Players[player_index].WeaponData.len() ) )) )
	chart_url = chart_url + "&cht=bhs"
	chart_url = chart_url + "&chco=3D7930,A2C180,DCBA80,DC5030"
	chart_url = chart_url + "&chds=0,1000,0,1000,0,1000,0,1000"

	{		
		local line = "&chd=t"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			if( indexW == 0 ) {	line = line + ":" } else { line = line + "," }

			local num = ( Weapon.headShots / (0.001*(Weapon.shots + 1) ) )
			line = line + "" + floor( num )
		}
		line = line + "|"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			if( indexW != 0 ) { line = line + "," }

			local num = ( (Weapon.hits - Weapon.headShots) / (0.001*(Weapon.shots + 0.01) ) )
			line = line + "" + floor( num )
		}
		line = line + "|"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			if( indexW != 0 ) { line = line + "," }

			local num = ( ( Weapon.shots - ( Weapon.hits + Weapon.FFHits ) ) / (0.001*(Weapon.shots + 0.01) ) )
			line = line + "" + floor( num )
		}
		line = line + "|"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			if( indexW != 0 ) { line = line + "," }

			local num = ( Weapon.FFHits / (0.001*(Weapon.shots + 0.01) ) )
			line = line + "" + floor( num )
		}
		chart_url = chart_url + line
	}

	chart_url = chart_url + "&chdl=Headshot|Non-Headshot Hit|Miss|Friendly Fire"
	chart_url = chart_url + "&chtt=" + Player.name + " Shot Hit Type"
	chart_url = chart_url + "\"" + "\\><br><br><br><br>"

	return chart_url
}

function CreatePlayerDamageChartString( player_index )
{
	local chart_url = "<img src="
	chart_url = chart_url + "\""
	chart_url = chart_url + "http://"
	chart_url = chart_url + player_index
	chart_url = chart_url + ".chart.apis.google.com/chart"

	local Player = GunStatsTable.Players[player_index]
	{
		local line = "?chxl=1:"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			// weapon names are listed backwards so store them such
			line = line + "|" + Player.WeaponData[ (Player.WeaponData.len() - 1) - indexW ].name
		}
		chart_url = chart_url + line
	}
	
	chart_url = chart_url + "&chxr=0,0,75"
	chart_url = chart_url + "&chxt=x,y"
	chart_url = chart_url + "&chbh=a"
	chart_url = chart_url + "&chs=600x" + ComputeChartHeight( floor(( 36*( 1.6 + GunStatsTable.Players[player_index].WeaponData.len() ) )) )
	chart_url = chart_url + "&cht=bhs"
	chart_url = chart_url + "&chco=1B84E0,43C5CC,A4B3B0"
	chart_url = chart_url + "&chds=0,75,0,75,0,75"

	{		
		local line = "&chd=t"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			if( indexW == 0 )
			{
				line = line + ":"
			}
			else
			{
				line = line + ","
			}
			line = line + ( Weapon.effectiveDamage / (Weapon.shots + 1) )
		}
		line = line + "|"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			if( indexW != 0 )
			{
				line = line + ","
			}
			line = line + ( Weapon.overKill / (Weapon.shots + 1) )
		}
		line = line + "|"
		foreach( indexW, Weapon in Player.WeaponData )
		{
			if( indexW != 0 )
			{
				line = line + ","
			}
			line = line + ( Weapon.deadDamage / (Weapon.shots + 1) )
		}
		chart_url = chart_url + line
	}

	chart_url = chart_url + "&chdl=Effective+Damage|Overkill+Damage|Dead Target+Damage"
	chart_url = chart_url + "&chtt=" + Player.name + " Damage+Breakdown(per pellet)"
	chart_url = chart_url + "\"" + "\\><br><br><br><br>"

	return chart_url
}


/////////////////////////////////////////////////
// Table manipulation
/////////////////////////////////////////////////

// These two helper functions can call themselves and each other so if want either you would need both.
function TableToString( table )  
{
	local table_string = "{\n"
		
	foreach (idx, key in table)
	{
		if ( typeof(key) == "table" )
		{
			table_string = table_string + idx + "=\n" + TableToString( key ) + ",\n"
		}
		else if ( typeof(key) == "array" )
		{
			table_string = table_string + idx + "=\n" + ArrayToString( key ) + ",\n"
		}
		else if ( typeof(key) == "string" )
		{
			table_string = table_string + idx + "=\"" + key + "\",\n"
		}
		else
		{
			table_string = table_string + idx + "=" + key + ",\n"
		}
	}
	return table_string	+ "}"
}

function ArrayToString( array )
{
	local array_string = "[\n"
		
	foreach (idx, key in array)
	{
		if ( typeof(key) == "table" )
		{
			array_string = array_string + TableToString( key ) + ",\n"
		}
		else if ( typeof(key) == "array" )
		{
			array_string = array_string + ArrayToString( key ) + ",\n"
		}
		else if ( typeof(key) == "string" )
		{
			array_string = array_string + "\"" + key + "\",\n"
		}
		else
		{
			array_string = array_string + key + ",\n"
		}
	}
	return array_string	+ "]"
}

TankRun


//-----------------------------------------------------
Msg("Activating Tank Run\n");

if ( !IsModelPrecached( "models/infected/hulk.mdl" ) )
	PrecacheModel( "models/infected/hulk.mdl" );
if ( !IsModelPrecached( "models/infected/hulk_dlc3.mdl" ) )
	PrecacheModel( "models/infected/hulk_dlc3.mdl" );
if ( !IsModelPrecached( "models/infected/hulk_l4d1.mdl" ) )
	PrecacheModel( "models/infected/hulk_l4d1.mdl" );

MutationOptions <-
{
	cm_TankRun = true
	cm_ShouldHurry = true
	cm_InfiniteFuel = true
	cm_AllowPillConversion = false
	cm_CommonLimit = 0
	cm_DominatorLimit = 0
	cm_MaxSpecials = 0
	cm_ProhibitBosses = true
	cm_AggressiveSpecials = true

	BoomerLimit = 0
	SmokerLimit = 0
	HunterLimit = 0
	ChargerLimit = 0
	SpitterLimit = 0
	JockeyLimit = 0
	cm_WitchLimit = 0
	cm_TankLimit = 8

	MobMinSize = 0
	MobMaxSize = 0
	NoMobSpawns = true
	EscapeSpawnTanks = false

	// convert items that aren't useful
	weaponsToConvert =
	{
		ammo = "upgrade_laser_sight"
	}

	function ConvertWeaponSpawn( classname )
	{
		if ( classname in weaponsToConvert )
		{
			return weaponsToConvert[classname];
		}
		return 0;
	}

	DefaultItems =
	[
		"weapon_pistol_magnum",
	]

	function GetDefaultItem( idx )
	{
		if ( idx < DefaultItems.len() )
		{
			return DefaultItems[idx];
		}
		return 0;
	}
}

MutationState <-
{
	TankModelsBase = [ "models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl", "models/infected/hulk_l4d1.mdl" ]
	TankModels = [ "models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl", "models/infected/hulk_l4d1.mdl" ]
	ModelCheck = false
	FinaleStarted = false
	TriggerRescue = false
	RescueDelay = 600
	LastAlarmTankTime = 0
	LastSpawnTime = 0
	SpawnInterval = 20
	DoubleTanks = false
	TankBiled = {}
	TanksDisabled = false
	TankHealth = 4000
	BileHurtTankThink = false
	SpawnTankThink = false
	TriggerRescueThink = false
	LeftSafeAreaThink = false
	CheckPrimaryWeaponThink = false
	FinaleType = -1
}

function GetNumTanks()
{
	local infStats = {};
	GetInfectedStats( infStats );
	return infStats.Tanks;
}

if ( IsMissionFinalMap() )
{
	local triggerFinale = Entities.FindByClassname( null, "trigger_finale" );
	if ( triggerFinale )
	{
		MutationState.FinaleType = NetProps.GetPropInt( triggerFinale, "m_type" );
		if ( NetProps.GetPropInt( triggerFinale, "m_bIsSacrificeFinale" ) )
		{
			function OnGameEvent_generator_started( params )
			{
				if ( !SessionState.FinaleStarted )
					return;

				HUDManageTimers( 0, TIMER_COUNTDOWN, HUDReadTimer( 0 ) - 30 );
				if ( GetNumTanks() < SessionOptions.cm_TankLimit )
					ZSpawn( { type = 8 } );
			}
		}
	}

	TankRunHUD <- {};
	function SetupModeHUD()
	{
		TankRunHUD =
		{
			Fields =
			{
				rescue_time = { slot = HUD_MID_TOP, name = "rescue", special = HUD_SPECIAL_TIMER0, flags = HUD_FLAG_COUNTDOWN_WARN | HUD_FLAG_BEEP | HUD_FLAG_ALIGN_CENTER | HUD_FLAG_NOTVISIBLE },
			}
		}
		HUDSetLayout( TankRunHUD );
	}

	if ( MutationState.FinaleType != 4 )
	{
		function GetNextStage()
		{
			if ( SessionState.TriggerRescue )
			{
				SessionOptions.ScriptedStageType = STAGE_ESCAPE;
				return;
			}
			if ( SessionState.FinaleStarted )
			{
				SessionOptions.ScriptedStageType = STAGE_DELAY;
				SessionOptions.ScriptedStageValue = -1;
			}
		}
	}

	function OnGameEvent_finale_start( params )
	{
		if ( SessionState.MapName == "c6m3_port" )
		{
			SessionOptions.cm_TankLimit = 8;
			SessionState.TanksDisabled = false;
			SessionState.SpawnTankThink = true;
		}

		if ( SessionState.FinaleType == 4 )
			return;

		HUDManageTimers( 0, TIMER_COUNTDOWN, SessionState.RescueDelay );
		TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags & ~HUD_FLAG_NOTVISIBLE;

		SessionState.DoubleTanks = true;
		SessionState.SpawnInterval = 40;
		SessionState.FinaleStarted = true;
		SessionState.TriggerRescueThink = true;
	}

	function OnGameEvent_gauntlet_finale_start( params )
	{
		if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c13m4_cutthroatcreek" )
		{
			SessionOptions.cm_TankLimit = 8;
			SessionState.TanksDisabled = false;
			SessionState.SpawnTankThink = true;
		}
	}

	function OnGameEvent_finale_vehicle_leaving( params )
	{
		SessionState.SpawnTankThink = false;
	}
}

function AllowTakeDamage( damageTable )
{
	if ( !damageTable.Attacker || !damageTable.Victim || !damageTable.Inflictor )
		return true;

	if ( damageTable.Victim.IsPlayer() && damageTable.Attacker.IsPlayer() )
	{
		if ( damageTable.Attacker.IsSurvivor() && damageTable.Victim.GetZombieType() == 8 )
		{
			if ( damageTable.Inflictor.GetClassname() == "pipe_bomb_projectile" )
				damageTable.DamageDone = 500;

			if ( damageTable.Weapon )
			{
				local weaponClass = damageTable.Weapon.GetClassname();
				if ( weaponClass == "weapon_pistol" )
					damageTable.DamageDone = (damageTable.DamageDone * 1.25);
				else if ( weaponClass == "weapon_melee" )
					damageTable.DamageDone = (damageTable.DamageDone * 1.334);
			}
		}
	}

	return true;
}

function TriggerRescueThink()
{
	if ( HUDReadTimer( 0 ) <= 0 )
	{
		SessionState.TriggerRescue = true;
		SessionState.TriggerRescueThink = false;
		Director.ForceNextStage();

		TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags | HUD_FLAG_NOTVISIBLE;
		HUDManageTimers( 0, TIMER_DISABLE, 0 );
	}
}

function SpawnTankThink()
{
	if ( SessionOptions.cm_TankLimit == 0 )
		return;

	if ( (GetNumTanks() < SessionOptions.cm_TankLimit) && ((Time() - SessionState.LastSpawnTime) >= SessionState.SpawnInterval || SessionState.LastSpawnTime == 0) )
	{
		if ( ZSpawn( { type = 8 } ) )
		{
			if ( SessionState.DoubleTanks )
				ZSpawn( { type = 8 } );
			SessionState.LastSpawnTime = Time();
		}
	}
}

function LeftSafeAreaThink()
{
	for ( local player; player = Entities.FindByClassname( player, "player" ); )
	{
		if ( NetProps.GetPropInt( player, "m_iTeamNum" ) != 2 )
			continue;

		if ( ResponseCriteria.GetValue( player, "instartarea" ) == "0" )
		{
			SessionState.LeftSafeAreaThink = false;
			SessionState.SpawnTankThink = true;
			break;
		}
	}
}

function BileHurtTankThink()
{
	foreach( tank, survivor in SessionState.TankBiled )
	{
		tank.TakeDamage( 100, 0, survivor );
	}
}

function CheckDifficultyForTankHealth( difficulty )
{
	local health = [2000, 3000, 4000, 5000];
	SessionState.TankHealth = health[difficulty];
}

if ( Director.IsFirstMapInScenario() )
{
	function CheckPrimaryWeaponThink()
	{
		local startArea = null;
		local startPos = null;
		for ( local survivorSpawn; survivorSpawn = Entities.FindByClassname( survivorSpawn, "info_survivor_position" ); )
		{
			local area = NavMesh.GetNearestNavArea( survivorSpawn.GetOrigin(), 100, false, false );
			if ( (area) && (area.HasSpawnAttributes( 128 ) || area.HasSpawnAttributes( 2048 )) )
			{
				startArea = area;
				startPos = survivorSpawn.GetOrigin();
				break;
			}
		}

		if ( !startPos )
			return;

		for ( local weapon; weapon = Entities.FindByClassnameWithin( weapon, "weapon_*", startPos, 1200 ); )
		{
			if ( weapon.GetOwnerEntity() )
				continue;

			local weaponName = weapon.GetClassname();
			local primaryNames = [ "smg", "rifle", "shotgun", "sniper", "grenade" ];
			if ( weaponName == "weapon_spawn" )
			{
				weaponName = NetProps.GetPropString( weapon, "m_iszWeaponToSpawn" );
				if ( weaponName.find( "pistol" ) != null )
					continue;
				primaryNames.append( "any" );
			}

			foreach( name in primaryNames )
			{
				if ( weaponName.find( name ) != null )
					return;
			}
		}

		local itemNames = [ "weapon_pistol_spawn", "weapon_pistol_magnum_spawn", "weapon_spawn", "weapon_melee_spawn" ];
		foreach( name in itemNames )
		{
			local item = Entities.FindByClassnameNearest( name, startPos, 600 );
			if ( item )
			{
				local nearestArea = NavMesh.GetNearestNavArea( item.GetOrigin(), 100, false, false );
				if ( nearestArea )
					startArea = nearestArea;
				break;
			}
		}

		local w = [ "any_smg", "tier1_shotgun" ];
		for ( local i = 0; i < 2; i++ )
			SpawnEntityFromTable( "weapon_spawn", { spawn_without_director = 1, weapon_selection = w[i], count = 5, spawnflags = 3, origin = (startArea.FindRandomSpot() + Vector(0,0,50)), angles = Vector(RandomInt(0,90),RandomInt(0,90),90) } );
	}

	function OnGameplayStart()
	{
		SessionState.CheckPrimaryWeaponThink = true;
	}
}

function OnGameEvent_round_start_post_nav( params )
{
	for ( local spawner; spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ); )
	{
		local population = NetProps.GetPropString( spawner, "m_szPopulation" );

		if ( population == "tank" || population == "river_docks_trap" )
			continue;
		else
			spawner.Kill();
	}
	for ( local ammo; ammo = Entities.FindByModel( ammo, "models/props/terror/ammo_stack.mdl" ); )
		ammo.Kill();

	if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c6m3_port" || SessionState.MapName == "c13m4_cutthroatcreek" )
	{
		SessionOptions.cm_TankLimit = 0;
		SessionState.TanksDisabled = true;
	}

	CheckDifficultyForTankHealth( GetDifficulty() );
	EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.TankRunThink()", 1.0 );
}

function OnGameEvent_difficulty_changed( params )
{
	CheckDifficultyForTankHealth( params["newDifficulty"] );
}

function OnGameEvent_player_left_safe_area( params )
{
	if ( SessionState.TanksDisabled )
		return;

	local player = GetPlayerFromUserID( params["userid"] );
	if ( !player )
	{
		SessionState.SpawnTankThink = true;
		return;
	}

	if ( ResponseCriteria.GetValue( player, "instartarea" ) == "1" )
		SessionState.LeftSafeAreaThink = true;
	else
		SessionState.SpawnTankThink = true;
}

function OnGameEvent_player_disconnect( params )
{
	local player = GetPlayerFromUserID( params["userid"] );
	if ( !player )
		return;

	if ( player.GetZombieType() == 8 && player in SessionState.TankBiled )
	{
		SessionState.TankBiled.rawdelete( player );
		if ( SessionState.TankBiled.len() == 0 )
			SessionState.BileHurtTankThink = false;
	}
}

function OnGameEvent_mission_lost( params )
{
	SessionState.SpawnTankThink = false;
}

function OnGameEvent_player_now_it( params )
{
	local attacker = GetPlayerFromUserID( params["attacker"] );
	local victim = GetPlayerFromUserID( params["userid"] );

	if ( !attacker || !victim )
		return;

	if ( attacker.IsSurvivor() && victim.GetZombieType() == 8 )
	{
		if ( victim in SessionState.TankBiled )
			return;

		victim.SetFriction( 2.0 );
		SessionState.TankBiled.rawset( victim, attacker );
		if ( SessionState.TankBiled.len() == 1 )
			SessionState.BileHurtTankThink = true;
	}
}

function OnGameEvent_player_no_longer_it( params )
{
	local victim = GetPlayerFromUserID( params["userid"] );

	if ( !victim )
		return;

	if ( victim.GetZombieType() == 8 && victim in SessionState.TankBiled )
	{
		victim.SetFriction( 1.0 );
		SessionState.TankBiled.rawdelete( victim );
		if ( SessionState.TankBiled.len() == 0 )
			SessionState.BileHurtTankThink = false;
	}
}

function OnGameEvent_triggered_car_alarm( params )
{
	if ( (GetNumTanks() < SessionOptions.cm_TankLimit) && ((Time() - SessionState.LastAlarmTankTime) >= SessionState.SpawnInterval || SessionState.LastAlarmTankTime == 0) )
	{
		if ( ZSpawn( { type = 8 } ) )
			SessionState.LastAlarmTankTime = Time();
	}
}

function OnGameEvent_tank_spawn( params )
{
	local tank = GetPlayerFromUserID( params["userid"] );
	if ( !tank )
		return;

	tank.SetMaxHealth( SessionState.TankHealth );
	tank.SetHealth( SessionState.TankHealth );
	local modelName = tank.GetModelName();

	if ( !SessionState.ModelCheck )
	{
		SessionState.ModelCheck = true;

		if ( SessionState.TankModelsBase.find( modelName ) == null )
		{
			SessionState.TankModelsBase.append( modelName );
			SessionState.TankModels.append( modelName );
		}
	}

	local tankModels = SessionState.TankModels;
	if ( tankModels.len() == 0 )
		SessionState.TankModels.extend( SessionState.TankModelsBase );
	local foundModel = tankModels.find( modelName );
	if ( foundModel != null )
	{
		tankModels.remove( foundModel );
		return;
	}

	local randomElement = RandomInt( 0, tankModels.len() - 1 );
	local randomModel = tankModels[ randomElement ];
	tankModels.remove( randomElement );

	tank.SetModel( randomModel );
}

function OnGameEvent_tank_killed( params )
{
	local tank = GetPlayerFromUserID( params["userid"] );

	if ( tank in SessionState.TankBiled )
	{
		SessionState.TankBiled.rawdelete( tank );
		if ( SessionState.TankBiled.len() == 0 )
			SessionState.BileHurtTankThink = false;
	}

	if ( SessionState.FinaleStarted )
		HUDManageTimers( 0, TIMER_COUNTDOWN, HUDReadTimer( 0 ) - 10 );
}

function TankRunThink()
{
	if ( SessionState.LeftSafeAreaThink )
		LeftSafeAreaThink();
	if ( SessionState.SpawnTankThink )
		SpawnTankThink();
	if ( SessionState.TriggerRescueThink )
		TriggerRescueThink();
	if ( SessionState.BileHurtTankThink )
		BileHurtTankThink();
	if ( SessionState.CheckPrimaryWeaponThink )
	{
		CheckPrimaryWeaponThink();
		SessionState.CheckPrimaryWeaponThink = false;
	}
	if ( Director.GetCommonInfectedCount() > 0 )
	{
		for ( local infected; infected = Entities.FindByClassname( infected, "infected" ); )
			infected.Kill();
	}
	EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.TankRunThink()", 1.0 );
}

See also

Intros

Documentations

Miscelleanous