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

From Valve Developer Community
Jump to navigation Jump to search
m (→‎Decrypted L4D2 Mutation Scripts: Clarified that not all scripts were decrypted in the initial batch)
m (Tank Run's ID is "TankRun", not "Tank Run". Source: "map c1m1_hotel tankrun")
 
(30 intermediate revisions by 6 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==
===L4d1===
===L4d1===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
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>}}


===L4d1coop - L4D1 Co-op===
===L4d1coop - L4D1 Co-op===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
Line 231: Line 345:


IncludeScript( "l4d1" )
IncludeScript( "l4d1" )
</source>
</source>}}


===L4d1vs - L4D1 Versus===
===L4d1vs - L4D1 Versus===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
Line 241: Line 355:


IncludeScript( "l4d1" )
IncludeScript( "l4d1" )
</source>
</source>}}


===L4d1survival - L4D1 Survival===
===L4d1survival - L4D1 Survival===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//============ Copyright (c) Valve Corporation, All rights reserved. ==========
//
//
Line 251: Line 365:


IncludeScript( "l4d1" )
IncludeScript( "l4d1" )
</source>
</source>}}


===Mutation1 - Last Man On Earth===
===Mutation1 - Last Man on Earth===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 1\n");
Msg("Activating Mutation 1\n");
Line 369: Line 483:
}
}
}
}
</source>
</source>}}


===Mutation2 - Headshot!===
===Mutation2 - Headshot!===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 2\n");
Msg("Activating Mutation 2\n");
Line 383: Line 497:
cm_HeadshotOnly = 1
cm_HeadshotOnly = 1
}
}
</source>
</source>}}


===Mutation3 - Bleed Out===
===Mutation3 - Bleed Out===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 3\n");
Msg("Activating Mutation 3\n");
Line 445: Line 559:
DirectorOptions.RecalculateHealthDecay();
DirectorOptions.RecalculateHealthDecay();
}
}
</source>
</source>}}


===Mutation4 - Hard Eight===
===Mutation4 - Hard Eight===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 4\n");
Msg("Activating Mutation 4\n");
Line 463: Line 577:
DominatorLimit = 8
DominatorLimit = 8
}
}
</source>
</source>}}


===Mutation5 - Four Swordsmen===
===Mutation5 - Four Swordsmen===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 5\n");
Msg("Activating Mutation 5\n");
Line 612: Line 726:
}
}
}
}
</source>
</source>}}
 
===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.
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
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
}
</source>}}


===Mutation7 - Chainsaw Massacre===
===Mutation7 - Chainsaw Massacre===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 7\n");
Msg("Activating Mutation 7\n");
Line 696: Line 830:
EntFire( wep + "_spawn", "Kill" );
EntFire( wep + "_spawn", "Kill" );
}
}
</source>
</source>}}


===Mutation8 - Ironman===
===Mutation8 - Iron Man===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 8\n");
Msg("Activating Mutation 8\n");
Line 724: Line 858:
}
}
}
}
</source>
</source>}}


===Mutation9 - Last Gnome On Earth===
===Mutation9 - Last Gnome On Earth===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 9\n");
Msg("Activating Mutation 9\n");
Line 739: Line 873:
cm_ShouldHurry = 1
cm_ShouldHurry = 1
}
}
</source>
</source>}}


===Mutation10 - Room For One===
===Mutation10 - Room For One===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 10\n");
Msg("Activating Mutation 10\n");
Line 753: Line 887:
cm_FirstManOut = 1
cm_FirstManOut = 1
}
}
</source>
</source>}}


===Mutation11 - Healthpackalypse!===
===Mutation11 - Healthpackalypse!===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 11\n");
Msg("Activating Mutation 11\n");
Line 781: Line 915:
}
}
}
}
</source>
</source>}}


===Mutation12 - Realism Versus===
===Mutation12 - Realism Versus===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 12\n");
Msg("Activating Mutation 12\n");
Line 828: Line 962:
}
}
}
}
</source>
</source>}}


===Mutation13 - Follow the Liter===
===Mutation13 - Follow the Liter===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 13\n");
Msg("Activating Mutation 13\n");
Line 844: Line 978:
ScavengeScoreBonusTime = 30
ScavengeScoreBonusTime = 30
}
}
</source>
</source>}}


===Mutation14 - Gib Fest===
===Mutation14 - Gib Fest===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 14\n");
Msg("Activating Mutation 14\n");
Line 922: Line 1,056:
EntFire( wep + "_spawn", "Kill" );
EntFire( wep + "_spawn", "Kill" );
}
}
</source>
</source>}}


===Mutation15 - Versus Survival===
===Mutation15 - Versus Survival===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
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 958: Line 1,091:
function OnGameEvent_survival_round_start( params )
function OnGameEvent_survival_round_start( params )
{
{
EntFire( "info_director", "PanicEvent" );
local validStartNames = { func_button_timed = "OnTimeUp", func_button = "OnPressed", script_func_button = "OnPressed", trigger_finale = "UseStart", prop_door_rotating = "OnOpen" };
local function GetSurvivalStartEntity()
if ( SessionState.MapName == "c1m2_steets" )
EntFire( "store_doors", "Open" );
else if ( SessionState.MapName == "c7m1_docks" )
{
{
EntFire( "tankdoorin", "Open" );
local validIO = { logic_relay = { input = "Trigger", output = "OnTrigger" }, logic_timer = { input = "Enable", output = "OnTimer" } };
EntFire( "tankdoorin_button", "Kill" );
local function CheckOutputs( entity, outputName )
EntFire( "tank_sound_timer", "Disable" );
{
EntFire( "doorsound", "PlaySound" );
local ent = entity;
EntFire( "tank_fog", "Enable" );
local output = outputName;
EntFire( "tank_fog", "Disable", "", 0.5 );
while ( ent )
EntFire( "big_splash", "Start" );
{
EntFire( "big_splash", "Stop", "", 2 );
local target = null;
EntFire( "coop_tank", "Trigger" );
local targetOutput = "";
EntFire( "radio_game_event", "Kill" );
local nElements = EntityOutputs.GetNumElements( ent, output );
EntFire( "tank_door_clip", "Kill" );
for ( local i = 0; i < nElements; i++ )
EntFire( "director", "EnableTankFrustration" );
{
EntFire( "battlefield_cleared", "UnblockNav", "", 60 );
local outputs = {};
EntFire( "tank_car_camera_clip", "Kill" );
EntityOutputs.GetOutputTable( ent, output, outputs, i );
}
local outputTarget = Entities.FindByName( null, outputs.target );
else if ( SessionState.MapName == "c11m5_runway" )
if ( !outputTarget )
EntFire( "planecrash_trigger", "Trigger", "", 16 );
continue;
else if ( SessionState.MapName == "c12m2_traintunnel" )
EntFire( "emergency_door", "Open" );
}
</source>


===Mutation16 - Hunting Party===
if ( outputTarget.GetClassname() == "info_director" && outputs.input.find( "PanicEvent" ) != null )
<source lang=cpp>
return outputTarget;
//-----------------------------------------------------
else
Msg("Activating Mutation 16\n");
{
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;


DirectorOptions <-
local target = CheckOutputs( ent, output );
{
if ( (!target) || (target.GetClassname() != "info_director") )
ActiveChallenge = 1
continue;


cm_SpecialSlotCountdownTime = 15
return ent;
cm_SpecialRespawnInterval = 1
}
cm_MaxSpecials = 4
}
return null;
}


HunterLimit = 4
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" );
}
</source>}}
 
===Mutation16 - Hunting Party===
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
Msg("Activating Mutation 16\n");
 
 
DirectorOptions <-
{
ActiveChallenge = 1
 
cm_SpecialSlotCountdownTime = 15
cm_SpecialRespawnInterval = 1
cm_MaxSpecials = 4
 
HunterLimit = 4
BoomerLimit = 0
BoomerLimit = 0
SmokerLimit = 0
SmokerLimit = 0
Line 1,009: Line 1,266:
DominatorLimit = 4
DominatorLimit = 4
}
}
</source>
</source>}}


===Mutation17 - Lone Gunman===
===Mutation17 - Lone Gunman===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 17\n");
Msg("Activating Mutation 17\n");
Line 1,090: Line 1,347:
EntFire( wep + "_spawn", "Kill" );
EntFire( wep + "_spawn", "Kill" );
}
}
</source>
</source>}}


===Mutation18 - Bleed Out Versus===
===Mutation18 - Bleed Out Versus===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 18\n");
Msg("Activating Mutation 18\n");
Line 1,135: Line 1,392:
DirectorOptions.RecalculateHealthDecay();
DirectorOptions.RecalculateHealthDecay();
}
}
</source>
</source>}}


===Mutation19 - Taaannnkk!===
===Mutation19 - Taaannnk!!===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Mutation 19\n");
Msg("Activating Mutation 19\n");


DirectorOptions <-
DirectorOptions <-
Line 1,148: 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,162: Line 1,418:
{
{
return 8;
return 8;
}
}


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


===Mutation20 - Healing Gnome===
function OnGameEvent_round_start_post_nav( params )
<source lang=cpp>
//-----------------------------------------------------
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;


function AllowWeaponSpawn( classname )
if ( outputs.input == "Unlock" || outputs.input == "Enable" || outputs.input == "SetValueTest" || outputs.input == "MoveToFloor" )
{
return outputTarget;
if ( classname in weaponsToRemove )
else
{
{
return false;
if ( outputTarget.GetClassname() == "logic_relay" && outputs.input == "Trigger" )
{
target = outputTarget;
targetOutput = "OnTrigger";
}
}
}
ent = target;
output = targetOutput;
}
}
return true;
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;


// Challenge vars
if ( target.GetClassname() == "logic_relay" )
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
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;
}
}


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


===Rocketdude===
local validOutputs = { func_button_timed = "OnTimeUp", func_button = "OnPressed", script_func_button = "OnPressed", prop_door_rotating = "OnOpen" };
Technically, Rocketdude was made by community members for the "The Last Stand" update, but listed as official because it is an official mutation.
if ( !target.GetClassname() in validOutputs )
<source lang=cpp>
continue;
//****************************************************************************************
// //
// rocketDude.nut (mainfile) //
// //
//****************************************************************************************


Msg("Activating RocketDude By ReneTM \n")
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" );
}


::rocketdude_version <- "v1.7.7 build: 17:07:07 Feb 07 2021"
local bRemoveClips = false;
::mapName <- Director.GetMapName().tolower()
switch( SessionState.MapName )
::survivorSet <- Director.GetSurvivorSet()
{
local grenadeData = {}
// 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" );
}
</source>}}


 
===Mutation20 - Healing Gnome===
// Different scripts 2 include
{{ExpandBox|<source lang=js>
// ----------------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------
 
Msg("Activating Mutation 20\n");
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
DirectorOptions <-
// ----------------------------------------------------------------------------------------------------------------------------
{
ActiveChallenge = 1


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


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


 
// Challenge vars
 
cm_TempHealthOnly = 1
 
cm_AllowPillConversion = 0
// Creates a bullet time when the previous one is atleast 32 seconds ago.
cm_HealingGnome = 1
// When this condition is met bullet time will occur with a probability of 3%
// ----------------------------------------------------------------------------------------------------------------------------
TempHealthDecayRate = 0.001
 
function RecalculateHealthDecay()
local lastBulletTime = Time()
{
local lastDiceTime = Time()
if ( Director.HasAnySurvivorLeftSafeArea() )
 
{
::GLOBALS <-
TempHealthDecayRate = 0.27 // pain_pills_decay_rate default
{
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(){
function Update()
SaveTable("GLOBAL_SAVINGS", GLOBALS)
{
DirectorOptions.RecalculateHealthDecay();
}
}
</source>}}


function restoreGlobals(){
===RocketDude===
RestoreTable("GLOBAL_SAVINGS", GLOBALS)
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")


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




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


function grenadeExplodeEvent( impact ){
IncludeScript("rocketdude/rd_debug")
foreach( player in getSurvivorsInRange( impact ) ){
IncludeScript("rocketdude/rd_utils")
if(player.IsValid()){
IncludeScript("rocketdude/rd_melee_getter")
doRocketJump(impact, player)
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")








// Returns an array of players within the blast radius
// Precache models and sounds
// ----------------------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------------------


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


precacheRocketDudeModels()


precacheSounds()


// 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)
}
}
}
}




// 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()


// Get's fired 'OnGameplayStart' (usually after every single loadingscreen)
::GLOBALS <-
// ----------------------------------------------------------------------------------------------------------------------------
{
allowBulletTime = false
}


function OnGameplayStart(){
function bulletTime(){
if(GLOBALS.allowBulletTime){
/* CREATE MUSHROOMS */
if(Time() > lastBulletTime + 32){
if(IsValveMap()){
if(Time() >= lastDiceTime + 2){
spawnMushrooms()
if(rollDice(3)){
lastBulletTime = Time()
DoEntFire( "!self", "Start", "", 0, timeScaler, timeScaler )
DoEntFire( "!self", "Stop", "", 1, timeScaler, timeScaler )
}
lastDiceTime = Time()
}
}
}
}
}
/* SPAWN MAP SIDED MUSHROOMS */
 
spawnMapSidedMushrooms()
function saveGlobals(){
SaveTable("GLOBAL_SAVINGS", GLOBALS)
/* SET PLAYERS MAX HEALTH AND CURRENT HEALTH TO 200 */
}
setPlayersHealth()
 
function restoreGlobals(){
/* GENERAL SETTINGS */
RestoreTable("GLOBAL_SAVINGS", GLOBALS)
checkCvars()
}


/* CREATE BULLET TIME */
createBulletTimerEntity()


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


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


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


::killFixEntities <- function(){
function grenadeExplodeEvent( impact ){
if(!fixEntitiesRemoved){
foreach( player in getSurvivorsInRange( impact ) ){
EntFire( "anv_mapfixes*", "Kill" )
if(player.IsValid()){
EntFire( "env_player_blocker", "Kill" )
doRocketJump(impact, player)
EntFire( "rene_relay", "Trigger" )
}
fixEntitiesRemoved = true
}
}
}
}
Line 1,414: Line 1,856:




// Called for every player within the detonation radius, it will launch the player in the calculated direction
// Returns an array of players within the blast radius
// ----------------------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------------------


function doRocketJump(detonationPos, player){
function getSurvivorsInRange(pos){
 
local player = null
local hitSurface = getSurfaceValue(detonationPos, player)
while(player = Entities.FindByClassnameWithin(player, "player", pos, 160)){
local ignoreDistance = 160
if(!player.IsDead()){
local playerEyes = player.EyePosition()
if(!player.IsIncapacitated()){
local finalVector = null
yield player
}
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)
}
}
}
}
Line 1,453: Line 1,873:




// 1. Save current origin of projectiles
// Compares current position of a projectile with the previous one. Force it to explode when it got stuck on prop_dynamic
// 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))
function dynamicPropCheck(grenade){
local grenadeGlowColor = GetColorInt(Vector(255,16,16))
if(grenade in grenadeData){
 
if(grenade.IsValid()){
function ProjectileGenerator(){
if((grenadeData[grenade] - grenade.GetOrigin()).Length() < 1){
local proj = null
NetProps.SetPropInt(grenade, "m_takedamage", 1)
while(proj = Entities.FindByClassname(proj, "grenade_launcher_projectile")){
grenade.TakeDamage(1337, 2, null)
yield proj
}
}
}
}
}
}


function GrenadeListener(){
 
local ent = null
 
foreach(ent in ProjectileGenerator()){
 
dynamicPropCheck(ent)
// Get's fired 'OnGameplayStart' (usually after every single loadingscreen)
if(!IsPlayerABot(NetProps.GetPropEntity(ent, "m_hThrower"))){
// ----------------------------------------------------------------------------------------------------------------------------
grenadeData[ent] <- ent.GetOrigin()
 
}
function OnGameplayStart(){
/* CREATE MUSHROOMS */
if(IsValveMap()){
spawnMushrooms()
}
/* SPAWN MAP SIDED MUSHROOMS */
spawnMapSidedMushrooms()
local scope = GetValidatedScriptScope(ent)
/* SET PLAYERS MAX HEALTH AND CURRENT HEALTH TO 200 */
setPlayersHealth()
// Projectile model and color
/* GENERAL SETTINGS */
if(!("modelChanged" in scope)){
checkCvars()
if(NetProps.GetPropEntity(ent, "m_hThrower") in bunnyPlayers){
 
ent.SetModel("models/w_models/weapons/w_rd_grenade_scale_x4_burn.mdl")
/* CREATE BULLET TIME */
}else{
createBulletTimerEntity()
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)){
/* KILL ALL DEATH CAMS */
scope["creationTimestamp"] <- Time()
removeDeathFallCameras()
}
// Projectile Glow
/* CREATE THINK TIMER */
if(Time() > scope["creationTimestamp"] + 0.12){
createThinkTimer()
if(!("glowEnabled" in scope)){
 
if(NetProps.GetPropEntity(ent, "m_hThrower") in bunnyPlayers){
/* RESTORE SETTINGS LIKE BULLET TIME 1/0 */
NetProps.SetPropInt(ent, "m_Glow.m_glowColorOverride", grenadeGlowColor)
restoreGlobals()
NetProps.SetPropInt(ent, "m_Glow.m_nGlowRangeMin", 32)
}
NetProps.SetPropInt(ent, "m_Glow.m_nGlowRange", 8192)
 
NetProps.SetPropInt(ent, "m_Glow.m_iGlowType", 3)
::fixEntitiesRemoved <- false
NetProps.SetPropInt(ent, "m_Glow.m_bFlashing", 1)
 
scope["glowEnabled"] <- true
::killFixEntities <- function(){
}
if(!fixEntitiesRemoved){
}
EntFire( "anv_mapfixes*", "Kill" )
}
EntFire( "env_player_blocker", "Kill" )
EntFire( "rene_relay", "Trigger" )
// Let rockets travel in a straight line like in TF2
fixEntitiesRemoved = true
// 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)
}
}
}
}
}
Line 1,535: Line 1,936:




// Disable player´s ledge hang, set his max health to 200 and disable fall damage crack
// Called for every player within the detonation radius, it will launch the player in the calculated direction
// ----------------------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------------------


local PlayerSettingsCheckTime = Time()
function doRocketJump(detonationPos, player){
function PlayerSettings(){
 
if(Time() > PlayerSettingsCheckTime + 4){
local hitSurface = getSurfaceValue(detonationPos, player)
foreach(player in GetSurvivors()){
local ignoreDistance = 160
DoEntFire("!self", "DisableLedgeHang", "", 0.0, player, player)
local playerEyes = player.EyePosition()
DoEntFire("!self", "ignorefalldamagewithoutreset", "99999", 0.0, player, player)
local finalVector = null
//
if(NetProps.GetPropInt(player, "m_iMaxHealth") != 200){
local detonationDistance = (detonationPos - playerEyes).Length()
NetProps.SetPropInt(player, "m_iMaxHealth", 200)
local playerIsMidAir = NetProps.GetPropInt(player, "m_hGroundEntity") & 1
}
local midAirFactor = 0
PlayerSettingsCheckTime = Time()
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)
}
}
}
}




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


 
 
 
// 1. Save current origin of projectiles
 
// 2. Check if projectile is stuck to prop_dynamic
// While the player´s primary and secondary weapon still have infinite ammo we want to remove used items from player inventory
// 3. Projectiles from bots are ignored ( just in case )
// Since players are able to receive throwables from mushrooms we need to check if any survivor is standing right on a mushroom
// 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
// ----------------------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------------------


function projectileListener(){
local grenadeColor = GetColorInt(Vector(64,64,64))
local projectiles = [ "vomitjar_projectile", "molotov_projectile", "pipe_bomb_projectile" ]
local grenadeGlowColor = GetColorInt(Vector(255,16,16))
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){
function ProjectileGenerator(){
DoEntFire("!self", "TouchTest", "", 0.03, trigger, trigger)
local proj = null
}
while(proj = Entities.FindByClassname(proj, "grenade_launcher_projectile")){
}
yield proj
}
}
}
}
}
}


 
function GrenadeListener(){
 
local ent = null
 
foreach(ent in ProjectileGenerator()){
// Check if mushrooms can be reactivated
dynamicPropCheck(ent)
// ----------------------------------------------------------------------------------------------------------------------------
if(!IsPlayerABot(NetProps.GetPropEntity(ent, "m_hThrower"))){
 
grenadeData[ent] <- ent.GetOrigin()
function updateMushroomTrigger(){
}
foreach(trigger in medkit_triggers){
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
}
}
}
local scope = GetValidatedScriptScope(trigger)
// 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"])
}
}


if(Time() >= scope.usetime + scope.restoreTime){
// Check if saved instances are still valid. Fire "grenadeExplodeEvent" when this is not the case anymore
if(scope.usable == false){
foreach(grenade, origin in grenadeData){
scope.usetime = Time() - scope.restoreTime
if(!grenade.IsValid()){
setMedVisibility(1, scope.model)
grenadeExplodeEvent(origin)
scope.usable = true
grenadeData.rawdelete(grenade)
DoEntFire("!self", "TouchTest", "", 0, trigger, trigger)
}
}
}
}
}
Line 1,622: Line 2,057:




// Hold space for auto-bhop ( if player used bhop mushroom )
// Disable player´s ledge hang, set his max health to 200 and disable fall damage crack
// ----------------------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------------------


::bunnyPlayers <- {}
local PlayerSettingsCheckTime = Time()
 
function PlayerSettings(){
function autobhop(){
if(Time() > PlayerSettingsCheckTime + 4){
foreach(player,ent in bunnyPlayers){
foreach(player in GetSurvivors()){
if(ent.IsValid()){
DoEntFire("!self", "DisableLedgeHang", "", 0.0, player, player)
if(!(NetProps.GetPropInt(ent, "m_fFlags") & 1) && NetProps.GetPropInt(ent, "movetype") == 2){
DoEntFire("!self", "ignorefalldamagewithoutreset", "99999", 0.0, player, player)
if(ent.GetButtonMask() & 2){
//
ent.OverrideFriction(0.033, 0)
if(NetProps.GetPropInt(player, "m_iMaxHealth") != 200){
}
NetProps.SetPropInt(player, "m_iMaxHealth", 200)
NetProps.SetPropInt(ent, "m_afButtonDisabled", NetProps.GetPropInt(ent, "m_afButtonDisabled") | 2)
}else{
NetProps.SetPropInt(ent, "m_afButtonDisabled", NetProps.GetPropInt(ent, "m_afButtonDisabled") & ~2)
}
}
}else{
PlayerSettingsCheckTime = Time()
bunnyPlayers.rawdelete(player)
}
}
}
}
Line 1,645: Line 2,076:




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)
}
}
}
}
}
}








// Get's fired every tick from a timer
// Check if mushrooms can be reactivated
// ----------------------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------------------


function Think(){
function updateMushroomTrigger(){
checkCvars()
foreach(trigger in medkit_triggers){
GrenadeListener()
PlayerSettings()
local scope = GetValidatedScriptScope(trigger)
projectileListener()
 
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()
updateMushroomTrigger()
autobhop()
autobhop()
Line 1,666: Line 2,188:
safeRoomTimer()
safeRoomTimer()
}
}
</source>
</source>}}


====Rocketdude Additional Scripts====
====RocketDude Additional Scripts====
These scripts are all included in the main Rocket dude script.
These scripts are all included in the main RocketDude script.
=====rd_custom_map_support=====
=====rd_custom_map_support=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 1,750: Line 2,272:
}
}
}
}
</source>
</source>}}


=====rd_damage_controll=====
=====rd_damage_controll=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 1,861: Line 2,383:
return true
return true
}
}
</source>
</source>}}


=====rd_debug=====
=====rd_debug=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 1,898: Line 2,420:
}
}
}
}
</source>
</source>}}


=====rd_decals=====
=====rd_decals=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 2,018: Line 2,540:


}
}
</source>
</source>}}


=====rd_detonation_analysis=====
=====rd_detonation_analysis=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 2,068: Line 2,590:
}
}
}
}
</source>
</source>}}


=====rd_director=====
=====rd_director=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 2,147: Line 2,669:
}
}
}
}
</source>
</source>}}


=====rd_events=====
=====rd_events=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 2,728: Line 3,250:


__CollectEventCallbacks(this, "OnGameEvent_", "GameEventCallbacks", RegisterScriptGameEventListener)
__CollectEventCallbacks(this, "OnGameEvent_", "GameEventCallbacks", RegisterScriptGameEventListener)
</source>
</source>}}


=====rd_hud_controller=====
=====rd_hud_controller=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 2,904: Line 3,426:


HUDSetLayout(RD_HUD)
HUDSetLayout(RD_HUD)
</source>
</source>}}
   
   
=====rd_last_chance=====
=====rd_last_chance=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 3,150: Line 3,672:
}
}
}
}
</source>
</source>}}


=====rd_map_specifics=====
=====rd_map_specifics=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 3,369: 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 3,457: Line 3,980:
})
})
}
}
</source>
</source>}}


=====rd_meds=====
=====rd_meds=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 3,900: Line 4,423:


}
}
</source>
</source>}}


=====rd_melee_getter=====
=====rd_melee_getter=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 4,012: Line 4,535:
}
}
}
}
</source>
</source>}}


=====rd_mode_description=====
=====rd_mode_description=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 4,093: Line 4,616:
ClientPrint(ent, 5, GREEN + "Check the console for information!")
ClientPrint(ent, 5, GREEN + "Check the console for information!")
}
}
</source>
</source>}}


=====rd_saferoom_timer=====
=====rd_saferoom_timer=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 4,378: Line 4,901:
//insafespot
//insafespot
//incheckpoint
//incheckpoint
</source>
</source>}}


=====rd_speedrun_mode=====
=====rd_speedrun_mode=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 4,590: Line 5,113:
}
}
}
}
</source>
</source>}}


=====rd_utils=====
=====rd_utils=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
//****************************************************************************************
//****************************************************************************************
// //
// //
Line 4,705: 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,714: 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,720: 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 5,934: Line 6,459:
return true
return true
}
}
</source>
</source>}}


==Community Mutations==
==Community Mutations==
These mutations were made by the community, eventually added to the base game by valve.
These mutations were made by the community, eventually added to the base game by Valve.
===Community1 - Special Delivery===
===Community1 - Special Delivery===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Special Delivery\n");
Msg("Activating Special Delivery\n");
Line 6,198: Line 6,723:
}
}
}
}
</source>
</source>}}


===Community2 -Flu Season===
===Community2 - Flu Season===
<source lang=cpp>
{{ExpandBox|<source lang=js>
Msg("Activating Community Mutation 2\n");
Msg("Activating Community Mutation 2\n");


Line 6,256: Line 6,781:
}
}
}
}
</source>
</source>}}


===Community3 - Riding My Survivor===
===Community3 - Riding My Survivor===
<source lang=cpp>
{{ExpandBox|<source lang=js>
Msg("Activating community mutation 3.\n");
Msg("Activating community mutation 3.\n");


Line 6,283: Line 6,808:
}
}
}
}
</source>
</source>}}


===Community4 - Nightmare===
===Community4 - Nightmare===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Nightmare\n");
Msg("Activating Nightmare\n");
Line 6,309: Line 6,834:
TankHitDamageModifierCoop = 0.25
TankHitDamageModifierCoop = 0.25
}
}
</source>
</source>}}


===Community5 - Death's Door===
===Community5 - Death's Door===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Death's Door\n");
Msg("Activating Death's Door\n");
Msg("Made by Rayman1103\n");
Msg("Made by Rayman1103\n");


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


weaponsToConvert =
function AllowFallenSurvivorItem( classname )
{
{
weapon_first_aid_kit = "weapon_pain_pills_spawn"
if ( classname == "weapon_first_aid_kit" )
weapon_adrenaline = "weapon_pain_pills_spawn"
return false;
 
return true;
}
 
weaponsToConvert =
{
weapon_first_aid_kit = "weapon_pain_pills_spawn"
weapon_adrenaline = "weapon_pain_pills_spawn"
}
}


Line 6,336: Line 6,867:
{
{
if ( classname in weaponsToConvert )
if ( classname in weaponsToConvert )
{
return weaponsToConvert[classname];
return weaponsToConvert[classname];
}
 
return 0;
return 0;
}
}
Line 6,351: 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 )
<source lang=cpp>
{
// 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 )
 
{
CompLite = InitializeCompLite();
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 );
}
}
}
}


// Don't need to do anything else if we're not first load
function OnGameEvent_defibrillator_used( params )
if(CompLite.Globals.GetCurrentRound() > 0)
{
{
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");
player.SetHealth( 1 );
Msg("Map has a scavenge event? " + mi.hasScavengeEvent + "\n");
player.SetHealthBuffer( 99 );
Msg("MapName: "+mi.mapname+"\n");


return;
if ( !IsPlayerABot( player ) )
{
EmitSoundOnClient( "Player.Heartbeat", player );
player.GetScriptScope().HeartbeatOn = true;
}
NetProps.SetPropInt( player, "m_bIsOnThirdStrike", 1 );
NetProps.SetPropInt( player, "m_isGoingToDie", 1 );
}
}


Msg("Activating Mutation CompLite v3.6\n");
function OnGameEvent_heal_success( params )
{
local player = GetPlayerFromUserID( params["subject"] );
if ( !player )
return;


DirectorOptions.ActiveChallenge <- 1
if ( !IsPlayerABot( player ) )
DirectorOptions.cm_ProhibitBosses <- 0
{
DirectorOptions.cm_AllowPillConversion <- 0
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 );
}
}


// Name shortening references
function OnGameEvent_revive_success( params )
local g_Timer = CompLite.Globals.Timer;
{
local g_FrameTimer = CompLite.Globals.FrameTimer;
local player = GetPlayerFromUserID( params["subject"] );
local g_MapInfo = CompLite.Globals.MapInfo;
if ( !params["ledge_hang"] || !player )
local g_GSC = CompLite.Globals.GSC;
return;
local g_GSM = CompLite.Globals.GSM;
local g_MobResetti = CompLite.Globals.MobResetti;
local Modules = CompLite.Modules;


// Uncomment to add a debug event listener
if ( NetProps.GetPropInt( player, "m_bIsOnThirdStrike" ) == 0 )
//g_GSC.AddListener(Modules.MsgGSL());
EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.CheckHealthAfterLedgeHang(" + params["subject"] + ")" );
}


g_GSC.AddListener(Modules.SpitterControl(Director, DirectorOptions, Entities));
function OnGameEvent_player_spawn( params )
g_GSC.AddListener(Modules.MobControl(g_MobResetti));
{
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;
}
}


// Give out hunting rifles on non-intro maps.
function OnGameEvent_player_death( params )
// But limit them to 1 of each.
{
g_GSC.AddListener(Modules.HRControl(Entities, CompLite.Globals, Director));
if ( !("userid" in params) )
return;


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


g_GSC.AddListener(
if ( !IsPlayerABot( player ) )
Modules.BasicItemSystems(
{
// AllowWeaponSpawn Limits
local scope = player.GetScriptScope();
// 0: Always remove
if ( scope.HeartbeatOn )
// >0: Keep the first n instances, delete others
// <-1: Delete the first n instances, keep others.
{
{
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
//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.
function OnGameEvent_player_bot_replace( params )
g_GSC.AddListener(
{
Modules.EntKVEnforcer(Entities,
local player = GetPlayerFromUserID( params["player"] );
// classnames
if ( !player || !player.IsSurvivor() ) // in case an infected player uses jointeam 1
[
return;
"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
local scope = player.GetScriptScope();
g_GSC.AddListener(
if ( scope.HeartbeatOn )
Modules.ItemControl(Entities,
{
// Limit to value by classname
StopSoundOn( "Player.Heartbeat", player );
{
scope.HeartbeatOn = false;
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
function OnGameEvent_bot_player_replace( params )
g_GSC.AddListener(Modules.MeleeWeaponControl(Entities, 4));
{
local player = GetPlayerFromUserID( params["player"] );
if ( !player )
return;


// Remove all gascans on non-scavenge maps
if ( !player.IsDead() ) // in case jointeam 2 or sb_takecontrol was used on a dead bot
g_GSC.AddListener(Modules.GasCanControl(Entities, g_MapInfo));
{
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;


Msg("GSC/M/L Script run.\n");
NetProps.SetPropInt( player, "m_takedamage", 0 );
</source>
NetProps.SetPropInt( player, "m_isIncapacitated", 1 );
 
}
====Confogl Additional Scripts====
These scripts are included in the main Confogl and <code>globals</code> scripts.


=====globals=====
if ( !Director.IsSessionStartMap() )
<source lang=cpp>
// 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)
function PlayerSpawnDeadAfterTransition( userid )
{
{
local CompLite = parentTable[customNameSpace];
local player = GetPlayerFromUserID( userid );
CompLite.Globals.IncrementRoundNumber();
if ( !player )
CompLite.Globals.GSM.Reset();
return;
 
player.SetHealth( 24 );
player.SetHealthBuffer( 26 );


if(CompLite.Globals.GetCurrentRound() == 1)
if ( !IsPlayerABot( player ) )
{
{
CompLite.Globals.MapInfo.IdentifyMap(Entities);
EmitSoundOnClient( "Player.Heartbeat", player );
player.GetScriptScope().HeartbeatOn = true;
}
}
}


ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
function PlayerSpawnAliveAfterTransition( userid )
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
{
return CompLite;
local player = GetPlayerFromUserID( userid );
}
if ( !player )
return;
local CompLite = parentTable[customNameSpace] <- {};
 
local oldHealth = player.GetHealth();
IncludeScript("gamestate_model", CompLite);
local maxHeal = player.GetMaxHealth() / 2;
IncludeScript("globaltimers", CompLite);
local healAmount = 0;
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()
if ( oldHealth < maxHeal )
{
{
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;
 
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"] + ")" );
}
}
</source>}}


ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
===Community6 - Confogl===
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
{{ExpandBox|<source lang=js>
// vim: set ts=4
// CompLite.nut (Confogl Mutation)
// Copyright (C) 2012 ProdigySim
// All rights reserved.
// =============================================================================


CompLite.Globals.MapInfo.IdentifyMap(Entities);
IncludeScript("globals", this);


return CompLite;
CompLite = InitializeCompLite();
}


class CompLiteGlobals {
// Don't need to do anything else if we're not first load
constructor(NameSpace, director, dopts)
if(CompLite.Globals.GetCurrentRound() > 0)
{
{
Timer = NameSpace.Timers.GlobalSecondsTimer();
Msg("CompLite Starting Round "+CompLite.Globals.GetCurrentRound()+" on ");
FrameTimer = NameSpace.Timers.GlobalFrameTimer();
local mi = CompLite.Globals.MapInfo;
MapInfo = NameSpace.Utils.MapInfo();
if(mi.isIntro) Msg("an intro map.\n");
GSC = NameSpace.GameState.GameStateController();
else Msg("a non-intro map.\n");
GSM = NameSpace.GameState.GameStateModel(GSC, director);
 
MobResetti = NameSpace.Utils.ZeroMobReset(director, dopts, FrameTimer);
Msg("Found "+mi.saferoomPoints.len()+" saferoom points.\n");
}
Msg("Map has a scavenge event? " + mi.hasScavengeEvent + "\n");
Msg("MapName: "+mi.mapname+"\n");


function IncrementRoundNumber() { m_iRoundNumber++; }
return;
function GetCurrentRound() { return m_iRoundNumber; }
//public
Timer = null;
FrameTimer = null;
MapInfo = null;
GSM = null;
GSC = null;
MobResetti = null;
// private
m_iRoundNumber = 0;
}
}
</source>


=====gamestate_model=====
Msg("Activating Mutation CompLite v3.6\n");
<source lang=cpp>
 
// vim: set ts=4
DirectorOptions.ActiveChallenge <- 1
// L4D2 GameState Model for Mutation VScripts
DirectorOptions.cm_ProhibitBosses <- 0
// Copyright (C) 2012 ProdigySim
DirectorOptions.cm_AllowPillConversion <- 0
// All rights reserved.
// =============================================================================


// double include protection
// Name shortening references
if("GameState" in this) return;
local g_Timer = CompLite.Globals.Timer;
GameState <- {
local g_FrameTimer = CompLite.Globals.FrameTimer;
ROUNDSTART_DELAY_INTERVAL = 2
local g_MapInfo = CompLite.Globals.MapInfo;
};
local g_GSC = CompLite.Globals.GSC;
IncludeScript("utils", this);
local g_GSM = CompLite.Globals.GSM;
local g_MobResetti = CompLite.Globals.MobResetti;
local Modules = CompLite.Modules;


class GameState.GameStateModel
// Uncomment to add a debug event listener
{
//g_GSC.AddListener(Modules.MsgGSL());
constructor(controller, director)
{
m_controller = controller;
m_pDirector = director;
}


function DoFrameUpdate()
g_GSC.AddListener(Modules.SpitterControl(Director, DirectorOptions, Entities));
{
g_GSC.AddListener(Modules.MobControl(g_MobResetti));
if(m_bLastUpdateTankInPlay)
 
 
// 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.
{
{
if(!m_pDirector.IsTankInPlay())
weapon_defibrillator = 0
{
weapon_grenade_launcher = 0
m_bLastUpdateTankInPlay = false;
weapon_upgradepack_incendiary = 0
m_controller.TriggerTankLeavesPlay();
weapon_upgradepack_explosive = 0
}
weapon_chainsaw = 0
}
//weapon_molotov = 1
else if(m_pDirector.IsTankInPlay())
//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
{
{
m_bLastUpdateTankInPlay = true;
weapon_autoshotgun   = "weapon_pumpshotgun_spawn"
m_controller.TriggerTankEntersPlay();
weapon_shotgun_spas = "weapon_shotgun_chrome_spawn"
}
weapon_rifle = "weapon_smg_spawn"
if(!m_bLastUpdateSafeAreaOpened && m_pDirector.HasAnySurvivorLeftSafeArea())
weapon_rifle_desert = "weapon_smg_spawn"
{
weapon_rifle_sg552   = "weapon_smg_mp5_spawn"
m_bLastUpdateSafeAreaOpened = true;
weapon_rifle_ak47   = "weapon_smg_silenced_spawn"
m_controller.TriggerSafeAreaOpen();
weapon_hunting_rifle = "weapon_smg_silenced_spawn"
}
weapon_sniper_military  = "weapon_shotgun_chrome_spawn"
if(!m_bRoundStarted && m_bHeardAWS && m_bHeardCWS && m_bHeardGDI
weapon_sniper_awp   = "weapon_shotgun_chrome_spawn"
&& m_iRoundStartTime < Time()-m_roundstart_delay)
weapon_sniper_scout = "weapon_pumpshotgun_spawn"
{
weapon_first_aid_kit = "weapon_pain_pills_spawn"
m_bRoundStarted = true;
weapon_molotov = "weapon_molotov_spawn"
m_controller.TriggerRoundStart(GetCurrentRound());
weapon_pipe_bomb = "weapon_pipe_bomb_spawn"
}
weapon_vomitjar = "weapon_vomitjar_spawn"
}
},
function OnAllowWeaponSpawn( classname )
// Default item list
{
[
m_bHeardAWS = true;
"weapon_pain_pills",
m_iRoundStartTime = Time();
"weapon_pistol",
return m_controller.TriggerAllowWeaponSpawn(classname);
"weapon_hunting_rifle"
}
]
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()
// Enforce various item spawns to be single pickup.
{
g_GSC.AddListener(
m_bRoundStarted = false;
Modules.EntKVEnforcer(Entities,
m_bHeardAWS = false;
// classnames
m_bHeardCWS = false;
[
m_bHeardGDI = false;
"weapon_adrenaline_spawn",
m_iRoundStartTime = 0;
"weapon_pain_pills_spawn",
m_bLastUpdateSafeAreaOpened = false;
"weapon_melee_spawn",
}
"weapon_molotov_spawn",
"weapon_vomitjar_spawn",
"weapon_pipebomb_spawn"
],
// models
[],
// key to enforce
"count",
// value to set it to
1
)
);


// Check for various round-start even. ts before triggering OnRoundStart()
// Entity tracking/removal/limiting
m_bRoundStarted = false;
g_GSC.AddListener(
m_bHeardAWS = false;
Modules.ItemControl(Entities,
m_bHeardCWS = false;
// Limit to value by classname
m_bHeardGDI = false;
{
m_iRoundStartTime = 0;
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");
</source>}}


m_bLastUpdateTankInPlay = false;
====Confogl Additional Scripts====
m_bLastUpdateSafeAreaOpened = false;
These scripts are included in the main Confogl and <code>globals</code> scripts.
m_controller = null;
m_pDirector = null;
m_roundstart_delay = GameState.ROUNDSTART_DELAY_INTERVAL;
static GetCurrentRound = Utils.GetCurrentRound;
};


class GameState.GameStateListener
=====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 )
{
{
// Called on round start. These may be multiples of these triggered, unfortunately.
if(customNameSpace in parentTable)
function OnRoundStart(roundNumber) {}
{
// Called when a player leaves saferoom or the saferoom timer counts down
local CompLite = parentTable[customNameSpace];
function OnSafeAreaOpened() {}
CompLite.Globals.IncrementRoundNumber();
// Called when tank spawns
CompLite.Globals.GSM.Reset();
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
if(CompLite.Globals.GetCurrentRound() == 1)
{
{
function AddListener(listener)
CompLite.Globals.MapInfo.IdentifyMap(Entities);
{
}
m_listeners.push(listener)
}


function TriggerRoundStart(roundNumber)
ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
{
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
foreach(listener in m_listeners)
return CompLite;
listener.OnRoundStart(roundNumber);
}
}
function TriggerSafeAreaOpen()
{
local CompLite = parentTable[customNameSpace] <- {};
foreach(listener in m_listeners)
listener.OnSafeAreaOpened();
IncludeScript("gamestate_model", CompLite);
}
IncludeScript("globaltimers", CompLite);
function TriggerTankEntersPlay()
IncludeScript("utils", CompLite);
{
IncludeScript("modules", CompLite);
foreach(listener in m_listeners)
listener.OnTankEntersPlay();
CompLite.ChallengeScript <- {
}
CompLite = CompLite
function TriggerTankLeavesPlay()
DirectorOptions = {
{
CompLite = CompLite
foreach(listener in m_listeners)
function AllowWeaponSpawn( classname )  
listener.OnTankLeavesPlay();
{
}
return CompLite.Globals.GSM.OnAllowWeaponSpawn(classname);
function TriggerPCZSpawn(id)
}
{
function ConvertWeaponSpawn( classname )  
local retval = id;
{  
foreach(listener in m_listeners)
return CompLite.Globals.GSM.OnConvertWeaponSpawn(classname);
{
}
// Allow each listener to try to convert.
function GetDefaultItem( idx )  
// Not pretty in the long run but I'm okay with it.
{
local ret = listener.OnSpawnPCZ(retval);
return CompLite.Globals.GSM.OnGetDefaultItem(idx);
if(ret != null) retval = ret;
}
function ConvertZombieClass( id )  
{  
return CompLite.Globals.GSM.OnConvertZombieClass(id);
}
}
}


// Simply notify everyone of the final value
function Update()
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).
CompLite.Globals.Timer.Update();
if(listener.OnAllowWeaponSpawn(classname) == false) return false;
CompLite.Globals.FrameTimer.Update();
// Interesting ! semantics
CompLite.Globals.GSM.DoFrameUpdate();
//            null  false
// !ret        true  true
// ret==false  false true
// ret==null  true  false
}
}
return true;
}
}
function TriggerConvertWeaponSpawn(classname)
 
{
CompLite.Globals <- CompLiteGlobals(CompLite, Director, CompLite.ChallengeScript.DirectorOptions);
local retcls = 0;
 
foreach(listener in m_listeners)
ChallengeScript.DirectorOptions <- CompLite.ChallengeScript.DirectorOptions;
{
ChallengeScript.Update <- CompLite.ChallengeScript.Update;
local ret = listener.OnConvertWeaponSpawn(classname);
 
if(ret != null && ret != 0) retcls = ret;
CompLite.Globals.MapInfo.IdentifyMap(Entities);
}
 
return retcls;
return CompLite;
}
}
function TriggerGetDefaultItem(idx)
 
class CompLiteGlobals {
constructor(NameSpace, director, dopts)
{
{
local retval = 0;
Timer = NameSpace.Timers.GlobalSecondsTimer();
foreach(listener in m_listeners)
FrameTimer = NameSpace.Timers.GlobalFrameTimer();
{
MapInfo = NameSpace.Utils.MapInfo();
local ret = listener.OnGetDefaultItem(idx);
GSC = NameSpace.GameState.GameStateController();
if(retval == 0 && ret != null) retval = ret;
GSM = NameSpace.GameState.GameStateModel(GSC, director);
}
MobResetti = NameSpace.Utils.ZeroMobReset(director, dopts, FrameTimer);
return retval;
}
}


m_listeners = []
function IncrementRoundNumber() { m_iRoundNumber++; }
};
function GetCurrentRound() { return m_iRoundNumber; }
</source>
//public
Timer = null;
FrameTimer = null;
MapInfo = null;
GSM = null;
GSC = null;
MobResetti = null;
// private
m_iRoundNumber = 0;
}
</source>}}


=====globaltimers=====
=====gamestate_model=====
<source lang=cpp>
{{ExpandBox|<source lang=js>
// vim: set ts=4
// vim: set ts=4
// Global Timers 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);


/* Usage:
class GameState.GameStateModel
Create an instance of one of these timers, e.g.
{
g_Timer = GlobalTimer()
constructor(controller, director)
and/or
{
g_FrameTimer = GlobalFrameTimer()
m_controller = controller;
m_pDirector = director;
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.
function DoFrameUpdate()
e.g.
class MyCallback extends TimerCallback
{
{
function OnTimerElapsed()
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)
{
{
Msg("My Timer has elapsed!!!\n");
m_bRoundStarted = true;
m_controller.TriggerRoundStart(GetCurrentRound());
}
}
}
}
function OnAllowWeaponSpawn( classname )
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_bHeardAWS = true;
m_cbtimes = array(0);
m_iRoundStartTime = Time();
return m_controller.TriggerAllowWeaponSpawn(classname);
}
function OnConvertWeaponSpawn( classname )
{
m_bHeardCWS = true;
m_iRoundStartTime = Time();
return m_controller.TriggerConvertWeaponSpawn(classname);
}
}
// Returns the current time in some format that supports arithmetic operations
function OnGetDefaultItem( idx )
// 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())
m_bHeardGDI = true;
{
m_iRoundStartTime = Time();
//Msg("Executing timer at "+GetCurrentTime()+" ("+Time()+") elapsed "+m_cbtimes[0]+"\n");
return m_controller.TriggerGetDefaultItem(idx);
local cb = m_callbacks[0];
m_callbacks.remove(0);
m_cbtimes.remove(0);
cb.OnTimerElapsed();
}
}
}
function OnConvertZombieClass(id)
/* 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;
return m_controller.TriggerPCZSpawn(id);
//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 = [];
function Reset()
m_cbtimes = [];
{
};
m_bRoundStarted = false;
m_bHeardAWS = false;
m_bHeardCWS = false;
m_bHeardGDI = false;
m_iRoundStartTime = 0;
m_bLastUpdateSafeAreaOpened = false;
}


class Timers.GlobalSecondsTimer extends Timers.GlobalTimer
// 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
{
{
// Returns the current time in seconds (internal use)
// Called on round start. These may be multiples of these triggered, unfortunately.
function GetCurrentTime() { return Time(); }
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 Timers.GlobalFrameTimer extends Timers.GlobalTimer
class GameState.GameStateController
{
{
// Returns the current time in frames (internal use)
function AddListener(listener)
function GetCurrentTime()
{
{
return m_curFrame;
m_listeners.push(listener)
}
}
/* Update()
 
Checks to see which timers have elapsed and executes callbacks.
function TriggerRoundStart(roundNumber)
Please run on global frame Update() function
{
*/
foreach(listener in m_listeners)
function Update()
listener.OnRoundStart(roundNumber);
}
function TriggerSafeAreaOpen()
{
foreach(listener in m_listeners)
listener.OnSafeAreaOpened();
}
function TriggerTankEntersPlay()
{
foreach(listener in m_listeners)
listener.OnTankEntersPlay();
}
function TriggerTankLeavesPlay()
{
{
// TODO: Integer overflow after 180+ hours on the round?
foreach(listener in m_listeners)
m_curFrame++;
listener.OnTankLeavesPlay();
baseclass.Update();
}
}
m_curFrame = 0;
function TriggerPCZSpawn(id)
// Need a name reference to base class
{
static baseclass = Timers.GlobalTimer;
local retval = id;
};
foreach(listener in m_listeners)
</source>
{
// 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;
}


=====utils=====
// Simply notify everyone of the final value
<source lang=cpp>
foreach(listener in m_listeners)
// vim: set ts=4
listener.OnSpawnedPCZ(retval)
// Utilities for L4D2 Vscript Mutations
return retval;
// 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 = [
function TriggerAllowWeaponSpawn(classname)
"", // null entry
{
["models/infected/smoker.mdl"],
foreach(listener in m_listeners)
["models/infected/boomer.mdl", "models/infected/boomette.mdl"],
{
["models/infected/hunter.mdl"],
// Cancel call chain once one listener returns false (says not to spawn it).
["models/infected/spitter.mdl"],
if(listener.OnAllowWeaponSpawn(classname) == false) return false;
["models/infected/jockey.mdl"],
// Interesting ! semantics
["models/infected/charger.mdl"],
//             null  false
["models/infected/witch.mdl", "models/infected/witch_bride.mdl"],
// !ret        true  true
["models/infected/hulk.mdl", "models/infected/hulk_dlc3.mdl"]
// ret==false  false true
]
// ret==null  true  false
SurvivorModels = {
}
coach = "models/survivors/survivor_coach.mdl"
return true;
ellis = "models/survivors/survivor_mechanic.mdl"
}
nick = "models/survivors/survivor_gambler.mdl"
function TriggerConvertWeaponSpawn(classname)
rochelle = "models/survivors/survivor_producer.mdl"
{
louis = "models/survivors/survivor_manager.mdl"
local retcls = 0;
bill = "models/survivors/survivor_namvet.mdl"
foreach(listener in m_listeners)
francis = "models/survivors/survivor_biker.mdl"
{
zoey = "models/survivors/survivor_teenangst.mdl"
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;
}
}
MeleeModels = [
 
"models/weapons/melee/w_bat.mdl",
m_listeners = []
"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);
</source>}}


/* KeyReset
=====globaltimers=====
Create a KeyReset to track the state of a key before you change its value, and
{{ExpandBox|<source lang=js>
reset it to the original value/state when you want to revert it.
// vim: set ts=4
Can detect whether a key existed or not and will delete the key afterwards if it doesn't exists.
// 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.
e.g.
myKeyReset = KeyReset(DirectorOptions, "JockeyLimit")
class MyCallback extends TimerCallback
{
function OnTimerElapsed()
{
Msg("My Timer has elapsed!!!\n");
}
}
then on some event...
Then register it to the global timer of your choice,
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
*/
// 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 that will detect the existence and old value of a key and store
class Timers.GlobalTimer
// 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)
constructor()
{
{
m_owner = owner;
m_callbacks = array(0);
m_key = key;
m_cbtimes = array(0);
}
}
function set(val)
// 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()
{
{
if(!m_bSet)
while(m_cbtimes.len() && m_cbtimes[0] <= GetCurrentTime())
{
{
m_bExists = m_owner.rawin(m_key);
//Msg("Executing timer at "+GetCurrentTime()+" ("+Time()+") elapsed "+m_cbtimes[0]+"\n");
if(m_bExists)
local cb = m_callbacks[0];
{
m_callbacks.remove(0);
m_oldVal = m_owner.rawget(m_key);
m_cbtimes.remove(0);
}
cb.OnTimerElapsed();
m_bSet = true;
}
}
m_owner.rawset(m_key,val);
}
}
function unset()
/* 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)
{
{
if(!m_bSet) return;
local cbtime = GetCurrentTime() + time;
//Msg("Adding time at "+GetCurrentTime()+" ("+Time()+") for "+cbtime+"\n");
if(m_bExists)
// 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_owner.rawset(m_key,m_oldVal);
m_callbacks.push(timer);
m_cbtimes.push(cbtime);
}
}
else
else
{
{
m_owner.rawdelete(m_key);
m_callbacks.insert(i,timer);
m_cbtimes.insert(i,cbtime);
}
}
m_bSet = false;
}
}
m_owner = null;
 
m_key = null;
m_callbacks = [];
m_oldVal = null;
m_cbtimes = [];
m_bExists = false;
m_bSet = false;
};
};


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


/* ZeroMobReset
class Timers.GlobalFrameTimer extends Timers.GlobalTimer
Class which handles resetting the mob timer without spawning CI.
{
// Returns the current time in frames (internal use)
e.g.
function GetCurrentTime()
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;
return m_curFrame;
m_timer = timer;
m_mobsizesetting = KeyReset(dopts, "MobSpawnSize");
}
}
/* ZeroMobReset()
/* Update()
Resets the director's mob timer.
Checks to see which timers have elapsed and executes callbacks.
Will trigger incoming horde music, but will not spawn any commons.
Please run on global frame Update() function
*/
*/
function ZeroMobReset()
function Update()
{
{
if(m_bResetInProgress) return;
// TODO: Integer overflow after 180+ hours on the round?
m_curFrame++;
// set DirectorOptions.MobSpawnSize to 0 so the triggered
baseclass.Update();
// horde won't spawn CI
m_mobsizesetting.set(0);
m_director.ResetMobTimer();
m_timer.AddTimer(1, this)
m_bResetInProgress = true;
}
}
// Internal use only,
m_curFrame = 0;
// resets the mob size setting after the mob timer has been set
// Need a name reference to base class
function OnTimerElapsed()
static baseclass = Timers.GlobalTimer;
{
m_mobsizesetting.unset();
m_bResetInProgress = false;
}
m_bResetInProgress = false;
m_director = null;
m_timer = null;
m_mobsizesetting = null;
static KeyReset = Utils.KeyReset;
};
};
</source>}}


class Utils.Sphere {
=====utils=====
constructor(center, radius)
{{ExpandBox|<source lang=js>
{
// vim: set ts=4
m_vecOrigin = center;
// Utilities for L4D2 Vscript Mutations
m_flRadius = radius;
// Copyright (C) 2012 ProdigySim
}
// All rights reserved.
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!
if("Utils" in this) return;
hasScavengeEvent = EntList.FindByClassname(null, "point_prop_use_target") != null;
Utils <- {
 
SIClass = {
saferoomPoints = [];
Smoker = 1
 
Boomer = 2
if(isIntro)
Hunter = 3
{
Spitter = 4
local ent = EntList.FindByName(null, "survivorPos_intro_01");
Jockey = 5
if(ent != null) saferoomPoints.push(ent.GetOrigin());
Charger = 6
}
Witch = 7
 
Tank = 8
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)
SIModels = [
{
"", // null entry
// We actually check if any saferoom is near the point...
["models/infected/smoker.mdl"],
local sphere = Sphere(point, distance);
["models/infected/boomer.mdl", "models/infected/boomette.mdl"],
foreach(pt in saferoomPoints)
["models/infected/hunter.mdl"],
{
["models/infected/spitter.mdl"],
if(sphere.ContainsPoint(pt)) return true;
["models/infected/jockey.mdl"],
}
["models/infected/charger.mdl"],
return false;
["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"
}
}
function IsEntityNearAnySaferoom(entity, distance=2000.0)
MeleeModels = [
{
"models/weapons/melee/w_bat.mdl",
return IsPointNearAnySaferoom(entity.GetOrigin(), distance);
"models/weapons/melee/w_chainsaw.mdl"
}
"models/weapons/melee/w_cricket_bat.mdl",
function IsMapC1M2(EntList)
"models/weapons/melee/w_crowbar.mdl",
{
"models/weapons/melee/w_didgeridoo.mdl",
// Identified by a entity with a given model at a given point
"models/weapons/melee/w_electric_guitar.mdl",
local ent = EntList.FindByModel(null, "models/destruction_tanker/c1m2_cables_far.mdl");
"models/weapons/melee/w_fireaxe.mdl",
if(ent != null
"models/weapons/melee/w_frying_pan.mdl",
&& (ent.GetOrigin() - Vector(-6856.0,-896.0,384.664)).Length() < 1.0) return true;
"models/weapons/melee/w_golfclub.mdl",
return false;
"models/weapons/melee/w_katana.mdl",
}
"models/weapons/melee/w_machete.mdl",
isIntro = false
"models/weapons/melee/w_riotshield.mdl",
isFinale = false
"models/weapons/melee/w_tonfa.mdl"
hasScavengeEvent = false;
]
saferoomPoints = null;
mapname = null
chapter = 0
Sphere = Utils.Sphere;
};
};
IncludeScript("globaltimers", this);


class Utils.VectorClone {
/* KeyReset
constructor(vec)
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.
x=vec.x;
Can detect whether a key existed or not and will delete the key afterwards if it doesn't exists.
y=vec.y;
z=vec.z;
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 ToVector()
function set(val)
{
{
return Vector(x,y,z);
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);
}
}
x=0.0
function unset()
y=0.0
z=0.0
};
 
class Utils.ItemInfo {
constructor(ent)
{
{
m_vecOrigin = VectorClone(ent.GetOrigin());
if(!m_bSet) return;
//m_vecForward = ent.GetForwardVector();
if(m_bExists)
{
m_owner.rawset(m_key,m_oldVal);
}
else
{
m_owner.rawdelete(m_key);
}
m_bSet = false;
}
}
m_vecOrigin = null;
m_owner = null;
//m_vecForward = null;
m_key = null;
static VectorClone = Utils.VectorClone;
m_oldVal = null;
};
m_bExists = false;
 
m_bSet = false;
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)
/* ZeroMobReset
{
Class which handles resetting the mob timer without spawning CI.
foreach(id,val in arr)
{
e.g.
if(val == value)
g_MobTimerCntl = ZeroMobReset(Director, DirectorOptions, g_FrameTimer);
{
arr.remove(id);
then later on some event
break;
g_MobTimerCntl.ZeroMobReset();
}
}
}


// return index on found
*/
// return -1 on not found
// Can reset the mob spawn timer at any point without
Utils.ArraySearchByValue <- function (arr, value)
// triggering an CI to spawn. Should not demolish any other state settings.
class Utils.ZeroMobReset extends Timers.TimerCallback
{
{
foreach(id,val in arr)
// Initialize with Director, DirectorOptions, and a GlobalFrameTimer
constructor(director, dopts, timer)
{
{
if(val == value)
m_director = director;
{
m_timer = timer;
return id;
m_mobsizesetting = KeyReset(dopts, "MobSpawnSize");
break;
}
}
}
return -1;
/* ZeroMobReset()
}
Resets the director's mob timer.
 
Will trigger incoming horde music, but will not spawn any commons.
Utils.IsEntityInMoveHeirarchy <- function (moveChildEnt, moveParentCandidate)
*/
{
function ZeroMobReset()
local curEnt = moveChildEnt;
while(curEnt != null)
{
{
curEnt = curEnt.GetMoveParent();
if(m_bResetInProgress) return;
if(curEnt == moveParentCandidate) return true;
// 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;
}
}
return false;
// Internal use only,
}
// resets the mob size setting after the mob timer has been set
 
function OnTimerElapsed()
// TODO move/refactor...
Utils.GetCurrentRound <- function ()
{
return ::CompLite.Globals.GetCurrentRound();
}
</source>
 
=====modules=====
<source lang=cpp>
// 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)  
m_mobsizesetting.unset();
{
m_bResetInProgress = false;
Msg("MsgGSL: OnGetDefaultItem(0) #"+m_defaultItemCnt+"\n");
m_defaultItemCnt++;
}
}
}
// Too much spam for these
m_bResetInProgress = false;
/*
m_director = null;
function OnAllowWeaponSpawn(classname) {}
m_timer = null;
function OnConvertWeaponSpawn(classname) {}
m_mobsizesetting = null;
*/
static KeyReset = Utils.KeyReset;
m_defaultItemCnt = 0;
};
};


class Modules.SpitterControl extends GameState.GameStateListener
class Utils.Sphere {
{
constructor(center, radius)
constructor(director, director_opts, entlist)
{
{
m_pDirector = director;
m_vecOrigin = center;
m_pSpitterLimit = KeyReset(director_opts, "SpitterLimit");
m_flRadius = radius;
m_pEntities = entlist;
// Initialize to default order...
SpawnLastUsed = [1, 2, 3, 5, 6];
}
}
function OnTankEntersPlay()
function GetOrigin()
{
{
m_pSpitterLimit.set(0);
return m_vecOrigin();
}
}
function OnTankLeavesPlay()
function GetRadius()
{
{
m_pSpitterLimit.unset();
return m_flRadius;
}
}
// Check if there is an instance of this SI on the map
// point: vector
function IsGivenSIClassSpawned(id)
function ContainsPoint(point)
{
{
foreach(mdl in SIModels[id])
return (m_vecOrigin - point).Length() <= m_flRadius;
{
if(m_pEntities.FindByModel(null, mdl) != null)
{
return true;
}
}
return false;
}
}
function OnSpawnPCZ(id)
function ContainsEntity(entity)
{
{
// If a spitter is going to be spawned during tank,
return ContainsPoint(entity.GetOrigin());
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)
m_vecOrigin = null;
m_flRadius = null;
};
 
class Utils.MapInfo {
function IdentifyMap(EntList)
{
{
// Mark that this SI to be spawned is most recently spawned now.
isIntro = EntList.FindByName(null, "fade_intro") != null
if(id != SIClass.Spitter && id <= SIClass.Charger && id >= SIClass.Smoker)
|| 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)
{
{
// Low index = least recent
local ent = EntList.FindByName(null, "survivorPos_intro_01");
// High index = most recent
if(ent != null) saferoomPoints.push(ent.GetOrigin());
// 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;
};


local ent = null;
while((ent = EntList.FindByClassname(ent, "prop_door_rotating_checkpoint")) != null)
{
saferoomPoints.push(ent.GetOrigin());
}


class Modules.MobControl extends GameState.GameStateListener
if(IsMapC1M2(EntList)) mapname = "c1m2_streets";
{
else mapname = "unknown";
constructor(mobresetti)
}
function IsPointNearAnySaferoom(point, distance=2000.0)
{
{
//m_dopts = director_opts;
// We actually check if any saferoom is near the point...
m_resetti = mobresetti;
local sphere = Sphere(point, distance);
foreach(pt in saferoomPoints)
{
if(sphere.ContainsPoint(pt)) return true;
}
return false;
}
}
function OnSafeAreaOpened()  
function IsEntityNearAnySaferoom(entity, distance=2000.0)
{
{
m_resetti.ZeroMobReset();
return IsPointNearAnySaferoom(entity.GetOrigin(), distance);
}
}
// These functions created major problems....
function IsMapC1M2(EntList)
/*
function OnTankEntersPlay()
{
{
m_oldMinTime = m_dopts.MobSpawnMinTime;
// Identified by a entity with a given model at a given point
m_oldMaxTime = m_dopts.MobSpawnMaxTime;
local ent = EntList.FindByModel(null, "models/destruction_tanker/c1m2_cables_far.mdl");
 
if(ent != null
m_dopts.MobSpawnMinTime = 99999;
&& (ent.GetOrigin() - Vector(-6856.0,-896.0,384.664)).Length() < 1.0) return true;
m_dopts.MobSpawnMaxTime = 99999;
return false;
 
m_resetti.ZeroMobReset();
}
}
function OnTankLeavesPlay()
isIntro = false
{
isFinale = false
m_dopts.MobSpawnMinTime = m_oldMinTime;
hasScavengeEvent = false;
m_dopts.MobSpawnMaxTime = m_oldMaxTime;
saferoomPoints = null;
 
mapname = null
m_resetti.ZeroMobReset();
chapter = 0
}
Sphere = Utils.Sphere;
m_oldMinTime = 0;
m_oldMaxTime = 0;
m_dopts = null; */
m_resetti = null;
};
};


class Modules.BasicItemSystems extends GameState.GameStateListener
class Utils.VectorClone {
{
constructor(vec)
constructor(removalTable, convertTable, defaultItemList)
{
{
m_removalTable = removalTable;
x=vec.x;
m_convertTable = convertTable;
y=vec.y;
m_defaultItemList = defaultItemList;
z=vec.z;
}
}
function OnAllowWeaponSpawn(classname)
function ToVector()
{
{
if ( classname in m_removalTable )
return Vector(x,y,z);
{
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)
x=0.0
y=0.0
z=0.0
};
 
class Utils.ItemInfo {
constructor(ent)
{
{
if ( classname in m_convertTable )
m_vecOrigin = VectorClone(ent.GetOrigin());
{
//m_vecForward = ent.GetForwardVector();
//Msg("Converting"+classname+" to "+convertTable[classname]+"\n")
return m_convertTable[classname];
}
return 0;
}
}
function OnGetDefaultItem(idx)
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 ( idx < m_defaultItemList.len())
if(val == value)
{
{
return m_defaultItemList[idx];
arr.remove(id);
break;
}
}
return 0;
}
}
m_removalTable = null;
}
m_convertTable = null;
m_defaultItemList = null;
};


class Modules.EntKVEnforcer extends GameState.GameStateListener
// return index on found
// return -1 on not found
Utils.ArraySearchByValue <- function (arr, value)
{
{
constructor(EntList, classes, models, key, value)
foreach(id,val in arr)
{
{
m_pEntities = EntList;
if(val == value)
m_classes = classes;
m_models = models;
m_key = key;
switch(typeof value)
{
{
case "bool":
return id;
m_value = value.tointeger();
break;
m_setFunc = "__KeyValueFromInt";
}
break;
}
case "float":
return -1;
m_value = value.tointeger();
}
m_setFunc = "__KeyValueFromInt";
 
break;
Utils.IsEntityInMoveHeirarchy <- function (moveChildEnt, moveParentCandidate)
case "integer":
{
m_value = value;
local curEnt = moveChildEnt;
m_setFunc = "__KeyValueFromInt";
while(curEnt != null)
break;
{
case "string":
curEnt = curEnt.GetMoveParent();
m_value = value;
if(curEnt == moveParentCandidate) return true;
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)
return false;
{
}
local ent = null;
 
// TODO move/refactor...
Utils.GetCurrentRound <- function ()  
{
return ::CompLite.Globals.GetCurrentRound();
}
</source>}}
 
=====modules=====
{{ExpandBox|<source lang=js>
// 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);


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)
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)  
{
{
ent = null;
Msg("MsgGSL: OnGetDefaultItem(0) #"+m_defaultItemCnt+"\n");
while((ent = m_pEntities.FindByModel(mdl)) != null)
m_defaultItemCnt++;
{
ent[m_setFuncName].call(ent, m_key, m_value);
}
}
}
}
}
m_pEntities = null;
// Too much spam for these
m_classes = null;
/*
m_models = null;
function OnAllowWeaponSpawn(classname) {}
m_key = null;
function OnConvertWeaponSpawn(classname) {}
m_value = null;
*/
m_setFunc = null;
m_defaultItemCnt = 0;
};
};


class Modules.ItemControl extends GameState.GameStateListener
class Modules.SpitterControl extends GameState.GameStateListener
{
{
constructor(entlist, removalTable, modelRemovalTable, saferoomRemoveList, mapinfo)
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_entlist = entlist;
m_pSpitterLimit.unset();
m_removalTable = removalTable;
m_modelRemovalTable = modelRemovalTable;
m_saferoomRemoveList = ArrayToTable(saferoomRemoveList);
m_pMapInfo = mapinfo;
}
}
function OnFirstRound()
// Check if there is an instance of this SI on the map
function IsGivenSIClassSpawned(id)
{
{
local ent = m_entlist.First();
foreach(mdl in SIModels[id])
local classname = "";
local tItemEnts = {};
local saferoomEnts = [];
 
// Create an empty array for each item in our list.
foreach(key,val in m_removalTable)
{
{
tItemEnts[key] <- [];
if(m_pEntities.FindByModel(null, mdl) != null)
{
return true;
}
}
}
 
return false;
while(ent != null)
}
function OnSpawnPCZ(id)
{
// If a spitter is going to be spawned during tank,
if(id == SIClass.Spitter && m_pDirector.IsTankInPlay())
{
{
classname = ent.GetClassname()
foreach(si in SpawnLastUsed)
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
// Note: Player keeps SI model until they receive a new spawn
// and don't track these entities for other removal.
// (while dead, they have the model of their last SI class)
saferoomEnts.push(ent.weakref());
if(!IsGivenSIClassSpawned(si)) return si;
}
}
else if(classname in m_removalTable)
// default to hunter if we really can't pick another class...
{
return SIClass.Hunter;
tItemEnts[classname].push(ent.weakref());
}
ent=m_entlist.Next(ent);
}
}
 
// Msg("Spawning SI Class "+newClass+".\n");
local tModelEnts = {};
return id;
 
}
foreach(mdl,limit in m_modelRemovalTable)
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)
{
{
local thisMdlEnts = tModelEnts[mdl] <- [];
// Low index = least recent
ent = null;
// High index = most recent
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
// Remove the other instance of this class in our array
{
ArrayRemoveByValue(SpawnLastUsed, id);
// Only use this entity if it's not one of the saferoom entities we're going to remove.
SpawnLastUsed.push(id);
thisMdlEnts.push(ent.weakref());
}
}
}
}
// 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;
};


// Remove all targeted saferoom items before doing roundstart removals
foreach(entity in saferoomEnts) KillEntity(entity);
local KilledEntList = saferoomEnts;


m_firstRoundEnts = {}
class Modules.MobControl extends GameState.GameStateListener
foreach(classname,instances in tItemEnts)
{
{
constructor(mobresetti)
local cnt = m_removalTable[classname].tointeger();
{
local saved_ents = m_firstRoundEnts[classname] <- [];
//m_dopts = director_opts;
// We need to choose certain items to save
m_resetti = mobresetti;
while( instances.len() > 0 && saved_ents.len() < cnt )
}
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)
{
{
local saveIdx = RandomInt(0,instances.len()-1);
//Msg("Killing just one "+classname+"\n");
// Track this entity's info for future rounds.
m_removalTable[classname]++
saved_ents.push(ItemInfo(instances[saveIdx]));
return false;
// Remove this entity from the kill list
instances.remove(saveIdx);
}
}
Msg("Killing "+instances.len()+" "+classname+", leaving "+saved_ents.len()+" on the map.\n");
else if (m_removalTable[classname] == 0)
foreach(inst in instances)
{
{
KillEntity(inst);
//Msg("Removed "+classname+"\n")
KilledEntList.push(inst.weakref());
return false;
}
}
}
}
 
return true;
m_firstRoundModelEnts = {}
}
foreach(model,instances in tModelEnts)
function OnConvertWeaponSpawn(classname)
{
if ( classname in m_convertTable )
{
{
// Don't use killed ents!
//Msg("Converting"+classname+" to "+convertTable[classname]+"\n")
foreach(deadEnt in KilledEntList) ArrayRemoveByValue(instances, deadEnt);
return m_convertTable[classname];
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);
}
}
}
return 0;
}
}
function OnLaterRounds()
function OnGetDefaultItem(idx)
{
{
local ent = m_entlist.First();
if ( idx < m_defaultItemList.len())
local classname = "";
local tItemEnts = {};
 
foreach(key,val in m_removalTable)
{
{
tItemEnts[key] <- [];
return m_defaultItemList[idx];
}
while(ent != null)
{
classname = ent.GetClassname()
if(classname in m_removalTable)
{
tItemEnts[classname].push(ent.weakref());
}
ent=m_entlist.Next(ent);
}
}
return 0;
}
m_removalTable = null;
m_convertTable = null;
m_defaultItemList = null;
};


local tModelEnts = {};
class Modules.EntKVEnforcer extends GameState.GameStateListener
foreach(mdl,limit in m_modelRemovalTable)
{
constructor(EntList, classes, models, key, value)
{
m_pEntities = EntList;
m_classes = classes;
m_models = models;
m_key = key;
switch(typeof value)
{
{
local thisMdlEnts = tModelEnts[mdl] <- [];
case "bool":
ent = null;
m_value = value.tointeger();
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
m_setFunc = "__KeyValueFromInt";
{
break;
// Only use this entity if it's not one of the saferoom entities we're going to remove.
case "float":
thisMdlEnts.push(ent.weakref());
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;


foreach(classname,entList in tItemEnts)
while((ent = m_pEntities.Next(ent)) != null)
{
{
local firstItems = m_firstRoundEnts[classname];
if(ent.GetClassname() in m_classes)
// 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");
ent[m_setFuncName].call(ent, m_key, m_value);
cnt = entList.len();
}
}
}


for(local i = cnt; i < entList.len(); i++)
foreach(mdl in m_models)
{
ent = null;
while((ent = m_pEntities.FindByModel(mdl)) != null)
{
{
KillEntity(entList[i]);
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;
};


for(local i = 0; i < cnt; i++)
class Modules.ItemControl extends GameState.GameStateListener
{
{
local vec = VectorClone(firstItems[i].m_vecOrigin);
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 = [];


// Hack. To avoid crashing the server by placing entities on top of each other here,
// Create an empty array for each item in our list.
// we'll just offset the placement of each entity by 1 unit.  
foreach(key,val in m_removalTable)
// Alternative solutions:
{
// 1. Check all tracked entity positions for conflicts and resolve through <insert algorithm here>
tItemEnts[key] <- [];
// 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)
while(ent != null)
{
{
local firstItems = m_firstRoundModelEnts[model];
classname = ent.GetClassname()
// count to keep alive
if(classname in m_saferoomRemoveList && m_pMapInfo.IsEntityNearAnySaferoom(ent, 1600.0))
local cnt = firstItems.len();
if(cnt > entList.len())
{
{
Msg("Warning! Not enough "+model+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
// Make a list of items which are in saferooms that need to be removed
cnt = entList.len();
// and don't track these entities for other removal.
saferoomEnts.push(ent.weakref());
}
}
 
else if(classname in m_removalTable)
for(local i = cnt; i < entList.len(); i++)
{
{
KillEntity(entList[i]);
tItemEnts[classname].push(ent.weakref());
}
}
ent=m_entlist.Next(ent);
}
local tModelEnts = {};


for(local i = 0; i < cnt; i++)
foreach(mdl,limit in m_modelRemovalTable)
{
local thisMdlEnts = tModelEnts[mdl] <- [];
ent = null;
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
{
{
entList[i].SetOrigin(firstItems[i].m_vecOrigin.ToVector());
// Only use this entity if it's not one of the saferoom entities we're going to remove.
//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
thisMdlEnts.push(ent.weakref());
}
}
Msg("Restored "+cnt+" "+model+", out of "+entList.len()+" on the map.\n");
}
}
}
 
function OnRoundStart(roundNumber)
// Remove all targeted saferoom items before doing roundstart removals
{
foreach(entity in saferoomEnts) KillEntity(entity);
Msg("ItemControl OnRoundStart()\n");
local KilledEntList = saferoomEnts;
// This will run multiple times per round in certain cases...
 
// Notably, on natural map switch (transition) e.g. chapter 1 ends, start chapter 2.
m_firstRoundEnts = {}
// Just make sure you don't screw up anything...
foreach(classname,instances in tItemEnts)
if(roundNumber == 1)
{
{
OnFirstRound();
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());
}
}
}
else
 
m_firstRoundModelEnts = {}
foreach(model,instances in tModelEnts)
{
{
OnLaterRounds();
// 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)
}
// 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));
tItemEnts[key] <- [];
}
}
return infolist;
while(ent != null)
}
function OnFirstRound()
{
m_firstRoundMelees = {};
local meleeEnts = {}
local totalCount = 0;
 
// Enumerate all melee weapon spawns by model
foreach(mdl in MeleeModels)
{
{
// prep table for later
classname = ent.GetClassname()
m_firstRoundMelees[mdl] <- []
if(classname in m_removalTable)
 
local spawnlist = []
local ent = null;
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
{
{
spawnlist.push(ent.weakref());
tItemEnts[classname].push(ent.weakref());
totalCount++;
}
}
meleeEnts[mdl] <- spawnlist;
ent=m_entlist.Next(ent);
}
}


if(totalCount < m_maxSpawns)
local tModelEnts = {};
foreach(mdl,limit in m_modelRemovalTable)
{
{
// There are less than m_maxSpawns melee weapons on the map,
local thisMdlEnts = tModelEnts[mdl] <- [];
// so we record them all and we're done.
ent = null;
foreach(mdl,spawnlist in meleeEnts)
while((ent = m_entlist.FindByModel(ent, mdl)) != null)
{
{
m_firstRoundMelees[mdl] = EntListToItemInfoList(spawnlist);
// Only use this entity if it's not one of the saferoom entities we're going to remove.
thisMdlEnts.push(ent.weakref());
}
}
Msg("Only "+totalCount+" melee weapons on the map to track.\n");
}
}
else
 
foreach(classname,entList in tItemEnts)
{
{
local savedCnt = 0;
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();
}


// Save m_maxSpawns of them
for(local i = cnt; i < entList.len(); i++)
while(savedCnt < m_maxSpawns)
{
{
local saveIdx = RandomInt(0,totalCount-1);
KillEntity(entList[i]);
 
// 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.
for(local i = 0; i < cnt; i++)
foreach(mdl, spawnlist in meleeEnts)
{
{
foreach(melee_ent in spawnlist)
local vec = VectorClone(firstItems[i].m_vecOrigin);
{
 
KillEntity(melee_ent);
// 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("Killing "+totalCount+" melee weapons and saving "+savedCnt+" out of "+(totalCount+savedCnt)+" on the map.\n");
Msg("Restored "+cnt+" "+classname+", out of "+entList.len()+" on the map.\n");
}
}


}
foreach(model,entList in tModelEnts)
function OnOtherRounds()
{
local meleeEnts = {}
local totalCount = 0;
 
// Enumerate all melee weapon spawns by model
foreach(mdl in MeleeModels)
{
{
local spawnlist = []
local firstItems = m_firstRoundModelEnts[model];
local ent = null;
// count to keep alive
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
local cnt = firstItems.len();
if(cnt > entList.len())
{
{
spawnlist.push(ent.weakref());
Msg("Warning! Not enough "+model+" spawned this round to match R1! ("+entList.len()+" < "+cnt+")\n");
totalCount++;
cnt = entList.len();
}
}
meleeEnts[mdl] <- spawnlist;
}


foreach(mdl,infolist in m_firstRoundMelees)
for(local i = cnt; i < entList.len(); i++)
{
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");
KillEntity(entList[i]);
continue;
}
}


local thisMdlEnts = meleeEnts[mdl];
for(local i = 0; i < cnt; i++)
 
// Check that this round's ent list is long enough to spawn last round's melees
if(thisMdlEnts.len() < restoreCnt)
{
{
restoreCnt = thisMdlEnts.len();
entList[i].SetOrigin(firstItems[i].m_vecOrigin.ToVector());
Msg("Warning! Not as many of melee weapon ("+ mdl +") available on R2! ("+restoreCnt+" < "+infolist.len()+"\n");
//entList[i].SetForwardVector(firstItems[i].m_vecForward.ToVector());
}
 
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);
}
}
Msg("Restored "+cnt+" "+model+", out of "+entList.len()+" on the map.\n");
}
}
}
}
function OnRoundStart(roundNumber)
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)
if(roundNumber == 1)
{
{
Line 7,975: Line 8,558:
else
else
{
{
OnOtherRounds();
OnLaterRounds();
}
}
}
m_pEntities = null;
m_maxSpawns = null;


m_firstRoundMelees = null;


static MeleeModels = Utils.MeleeModels;
}
static ItemInfo = Utils.ItemInfo;
// pointer to global Entity List
static KillEntity = Utils.KillEntity;
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.HRControl extends GameState.GameStateListener //, extends TimerCallback (no MI support)
class Modules.MeleeWeaponControl extends GameState.GameStateListener {
{
constructor(entlist, melee_limit)
constructor(entlist, globals, director)
{
{
m_pEntities = entlist;
m_pEntities = entlist;
m_pGlobals = globals;
m_maxSpawns = melee_limit;
m_pDirector = director;
}
}
function QueueCheck(time)
function EntListToItemInfoList(entlist)
{
{
if(!m_bChecking)
local infolist = [];
foreach (ent in entlist)
{
{
m_pGlobals.Timer.AddTimer(time, this);
infolist.push(ItemInfo(ent));
m_bChecking = true;
}
}
return infolist;
}
}
function OnRoundStart(roundNumber)
function OnFirstRound()
{
{
QueueCheck(1.0);
m_firstRoundMelees = {};
}
local meleeEnts = {}
function OnTimerElapsed()
local totalCount = 0;
{
m_bChecking=false;
if(!m_pDirector.HasAnySurvivorLeftSafeArea()) QueueCheck(5.0);


local ent = null;
// Enumerate all melee weapon spawns by model
local hrList = [];
foreach(mdl in MeleeModels)
while((ent = m_pEntities.FindByClassname(ent, "weapon_hunting_rifle")) != null)
{
{
hrList.push(ent.weakref());
// prep table for later
}
m_firstRoundMelees[mdl] <- []


if(!m_pGlobals.MapInfo.isIntro)
local spawnlist = []
{
local ent = null;
if(hrList.len() <= 1) return;
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
hrList.remove(RandomInt(0,hrList.len()-1));
{
spawnlist.push(ent.weakref());
totalCount++;
}
meleeEnts[mdl] <- spawnlist;
}
}


// Delete the rest
if(totalCount < m_maxSpawns)
foreach(hr in hrList)
{
{
KillEntity(hr);
// 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");
}
}
}
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)
function OnOtherRounds()
{
{
// Don't demolish gascans if the map has a scavenge event!
local meleeEnts = {}
// Unless it's c1m2 because it uses cola yo.
local totalCount = 0;
if(m_pMapInfo.hasScavengeEvent && m_pMapInfo.mapname != "c1m2_streets") return;


local ent = null;
// Enumerate all melee weapon spawns by model
local list = [];
foreach(mdl in MeleeModels)
while((ent = m_pEntities.FindByModel(ent, "models/props_junk/gascan001a.mdl")) != null)
{
{
list.push(ent.weakref());
local spawnlist = []
local ent = null;
while((ent = m_pEntities.FindByModel(ent, mdl)) != null)
{
spawnlist.push(ent.weakref());
totalCount++;
}
meleeEnts[mdl] <- spawnlist;
}
}
Msg("Killing "+list.len()+" gas cans from the map.\n");
 
foreach(can in list)
foreach(mdl,infolist in m_firstRoundMelees)
{
{
KillEntity(can);
local restoreCnt = infolist.len();
}
}
m_pEntities = null;
m_pMapInfo = null;
static KillEntity = Utils.KillEntity;
}
</source>


==EMS Mutations==
// Make sure this model isn't unset in this round's ent table
These have been introduced with the [[L4D2 EMS]] Update
if(!(mdl in meleeEnts))
===Dash===
{
<source lang=cpp>
Msg("Warning! No "+ mdl +" exist on R2 for restoring!\n");
////////////////////////////////
continue;
// "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")
local thisMdlEnts = meleeEnts[mdl];


MutationOptions <-
// Check that this round's ent list is long enough to spawn last round's melees
{  
if(thisMdlEnts.len() < restoreCnt)
// since we are going to move the spawn point to "ahead" on the track as we hit waypoints
{
PreferredMobDirection = SPAWN_NEAR_POSITION
restoreCnt = thisMdlEnts.len();
PreferredMobPositionRange = 600
Msg("Warning! Not as many of melee weapon ("+ mdl +") available on R2! ("+restoreCnt+" < "+infolist.len()+"\n");
}


// we should change this to zero
Msg("Restoring "+restoreCnt+" "+mdl+" out of "+thisMdlEnts.len()+"\n");
PreferredMobPosition = Vector( 0,0,0 )


SpawnSetRule = SPAWN_POSITIONAL
// Move restoreCnt melees of this model to their spots from R1.
SpawnSetRadius = 2000
for(local i = 0; i < restoreCnt; i++)
SpawnSetPosition = Vector( -7150, -3647, -97 )
{
local ent = thisMdlEnts[0];
thisMdlEnts.remove(0);
ent.SetOrigin(infolist[i].m_vecOrigin.ToVector());
//ent.SetForwardVector(infolist[i].m_vecForward.ToVector());
}
}


WanderingZombieDensityModifier = 0 // get rid of wanderers
// Delete the remaining melees from this round.
 
foreach(mdl,spawnlist in meleeEnts)
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 )
foreach(ent in spawnlist)
{
{
finished = false
KillEntity(ent);
break;
}
}
}
}
 
}
if ( finished )
function OnRoundStart(roundNumber)
{
if(roundNumber == 1)
{
{
// Finished the course
OnFirstRound();
return 0 // SCENARIO_RESTART
}
}
else
else
{
{
return 1 // SCENARIO_SURVIVORS_DEAD
OnOtherRounds();
}
}
}
}
m_pEntities = null;
m_maxSpawns = null;
m_firstRoundMelees = null;


/*  Not using this method anymore
static MeleeModels = Utils.MeleeModels;
function GetScoreboardFilename( endreason )
static ItemInfo = Utils.ItemInfo;
{
static KillEntity = Utils.KillEntity;
switch ( endreason )
};
 
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)
{
{
case 1:
m_pGlobals.Timer.AddTimer(time, this);
return "Resource/UI/scriptedmodeplaceholderlost.res"
m_bChecking = true;
case 0:
return "Resource/UI/scriptedmodeplaceholderwon.res"
default:
return "Resource/UI/scriptedmodeplaceholder.res"
}
}
}
}
*/
function OnRoundStart(roundNumber)
{
QueueCheck(1.0);
}
function OnTimerElapsed()
{
m_bChecking=false;
if(!m_pDirector.HasAnySurvivorLeftSafeArea()) QueueCheck(5.0);


// challenge mode experiment - make sure old CM type stuff works fine in new mutation code
local ent = null;
cm_HeadshotOnly = 0
local hrList = [];
while((ent = m_pEntities.FindByClassname(ent, "weapon_hunting_rifle")) != null)
{
hrList.push(ent.weakref());
}


DefaultItems =
if(!m_pGlobals.MapInfo.isIntro)
[
{
"weapon_rifle_m60",
if(hrList.len() <= 1) return;
"weapon_pistol_magnum",
hrList.remove(RandomInt(0,hrList.len()-1));
"adrenaline",
}
"pipe_bomb"
]


// Set characters up with default items
// Delete the rest
function GetDefaultItem( idx )
foreach(hr in hrList)
{
if ( idx < DefaultItems.len() )
{
{
return DefaultItems[idx];
KillEntity(hr);
}
}
return 0;
}
}
m_pEntities = null;
}
m_pTimer = null;
 
m_pGlobals = null;
// SessionState for this mode - mostly about the waypoint tracking/which on next/etc/etc
m_pDirector = null;
MutationState <-
m_bChecking = false;
{
static KillEntity = Utils.KillEntity;
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
class Modules.GasCanControl extends GameState.GameStateListener {
function SetWaypointCB( waypointObj, rarity )
constructor(entList, mapinfo)
{
{
local waypointScr = waypointObj.GetScriptScope()
m_pEntities = entList;
waypointScr.myID <- g_ModeScript.MutationState.FinalWaypoint++
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;
// if we're using a custom list set the initial touch count on this waypoint
local list = [];
if( "CustomWaypointList" in g_MapScript )
while((ent = m_pEntities.FindByModel(ent, "models/props_junk/gascan001a.mdl")) != null)
waypointScr.initialTouchCount = g_MapScript.CustomWaypointList[ g_RoundState.WaypointList.len() ].startingTouchCount
{
 
list.push(ent.weakref());
g_RoundState.WaypointList.append(waypointObj)
}
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;
}
}
</source>}}


// the startbox needs a custom precache function if you are using it
==EMS Mutations==
function Precache()
These have been introduced with the [[L4D2 EMS]] Update
{
===Dash===
Startbox_Precache()
{{ExpandBox|<source lang=js>
}
////////////////////////////////
 
// "Dash" is another extended mutation demo mode to show some of the capabilities available in script
// 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
// the players must run near a sequence of waypoints (well, ok, lampposts) in order
function OnGameplayStart()
// 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
CheckOrSetMapCallback( "MapOverrideOptions", @() false )
// Waypoint naming:
CheckOrSetMapCallback( "MapGameplayStart", @() false )
// 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


Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )
// printl(" Loading Dash mode")


//teleport players to the start point
MutationOptions <-
if (!TeleportPlayersToStartPoints( "gamemode_playerstart" ) )
printl(" ** TeleportPlayersToStartPoints: Spawn point count or player count incorrect! Verify that there are 4 of each.")
// since we are going to move the spawn point to "ahead" on the track as we hit waypoints
PreferredMobDirection = SPAWN_NEAR_POSITION
PreferredMobPositionRange = 600


// create a start box
// we should change this to zero
if ( !SpawnStartBox( "startbox_origin" ) )
PreferredMobPosition = Vector( 0,0,0 )
{
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
SpawnSetRule = SPAWN_POSITIONAL
if( "CustomWaypointList" in g_MapScript )
SpawnSetRadius = 2000
{
SpawnSetPosition = Vector( -7150, -3647, -97 )
SpawnCustomWaypointList()
}
else
{
// Spawn waypoints in order
SortAndSpawnWaypointList()
}


if (g_RoundState.WaypointList.len() > 0)
WanderingZombieDensityModifier = 0 // get rid of wanderers
{
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
cm_ShouldEscortHumanPlayers = 1
SessionOptions.ScriptedStageValue = 1000
cm_AggressiveSpecials = 1


MapGameplayStart()
BoomerLimit  = 0
}
ChargerLimit = 0
HunterLimit  = 0
JockeyLimit  = 0
SpitterLimit = 0
SmokerLimit  = 0
MaxSpecials  = 0
CommonLimit  = 20
MegaMobSize = 20
TankLimit    = 0
WitchLimit = 0


//=========================================================
function EndScriptedMode()
// Uses the CustomWaypointList defined in the map script to
{
// spawn waypoints. The info_item_position entities are
local finished = true;
// collected in the order they appear in the list and then
foreach ( val in g_RoundState.WaypointList )
// the waypoints are spawned in that order.
{
//=========================================================
if ( val.GetScriptScope().active == true )
function SpawnCustomWaypointList()
{
{
finished = false
// collect the info_item_position entities into a list
break;
local currentEnt  = null
}
local tempWaypointList = []
}


foreach( idx, val in CustomWaypointList )
if ( finished )
{
currentEnt = Entities.FindByName( null, val.targetName )
// store the waypoint if we found it, otherwise complain
if( currentEnt )
{
{
tempWaypointList.append( currentEnt )
// Finished the course
return 0 // SCENARIO_RESTART
}
}
else
else
{
{
printl( " ** Waypoint Spawn ERROR**  I can't find the waypoint named: " + val.targetName )
return 1 // SCENARIO_SURVIVORS_DEAD
}
}
}
}


SpawnWaypointList( tempWaypointList )
/*  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
// Collect all the info_item_position entities that are named
cm_HeadshotOnly = 0
// "short_waypoint_*" OR "waypoint_*", put them into a list
 
// and sort it by suffix.  This will allow us to spawn all
DefaultItems =
// the waypoints in order so they can know their touch order
[
//=========================================================
"weapon_rifle_m60",
function SortAndSpawnWaypointList()
"weapon_pistol_magnum",
{
"adrenaline",
local currentWaypoint = Entities.FindByClassname( null, "info_item_position" )
"pipe_bomb"
local tempWaypointList = []
]


while( currentWaypoint )
// Set characters up with default items
function GetDefaultItem( idx )
{
{
local name = currentWaypoint.GetName()
if ( idx < DefaultItems.len() )
if( ( name.find( "waypoint_" ) == 0 ) || ( name.find( "short_waypoint_" ) == 0 ) )
{
{
tempWaypointList.append( currentWaypoint )
return DefaultItems[idx];
}
}
return 0;
}
}


currentWaypoint = Entities.FindByClassname( currentWaypoint, "info_item_position" )
// 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++


// sort the list by suffix
tempWaypointList.sort(@(a,b) a.GetName().slice(-4) <=> b.GetName().slice(-4) )
// 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


SpawnWaypointList( tempWaypointList )
g_RoundState.WaypointList.append(waypointObj)
}
}


//=========================================================
// the startbox needs a custom precache function if you are using it
// Spawn the provided list of waypoints on their positions
function Precache()
//=========================================================
function SpawnWaypointList( list )
{
{
local waypointGroup = g_MapScript.GetEntityGroup( "DashWaypoint" )
Startbox_Precache()
local shortWaypointGroup = g_MapScript.GetEntityGroup( "DashWaypointShort" )
}
 
// 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 )


// set callbacks
//teleport players to the start point
shortWaypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
if (!TeleportPlayersToStartPoints( "gamemode_playerstart" ) )
waypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
printl(" ** TeleportPlayersToStartPoints: Spawn point count or player count incorrect! Verify that there are 4 of each.")


// spawn the waypoints
// create a start box
foreach( idx, ent in list )
if ( !SpawnStartBox( "startbox_origin" ) )
{
{
// does our waypoint start with "short_"?  If so, it is a short waypoint! Spawn it.
printl("Note: SpawnStartBox() called but there is no startbox_origin in map.")
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( )
// If the map script supplies a CustomWaypointList of points spawn the entities in that order
{
if( "CustomWaypointList" in g_MapScript )
local final_time = HUDReadTimer(1)
{
local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
SpawnCustomWaypointList()
HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
}
HUDPlace( HUD_FAR_RIGHT, 0.28, 0.50, 0.44, 0.06 )
else
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)
// Spawn waypoints in order
DashHUD.Fields[hudfield].dataval = val
SortAndSpawnWaypointList()
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?
if (g_RoundState.WaypointList.len() > 0)
g_RoundState.WaypointList <- []
{
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()
}


// called at basically each waypoint via ForceNextStage - or when a PANIC ends we just go to a delay
//=========================================================
function GetNextStage()
// 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()
{
{
smDbgPrint("GetNextStage called")
// collect the info_item_position entities into a list
local currentEnt  = null
local tempWaypointList = []


if ( SessionState.YouWon )
foreach( idx, val in CustomWaypointList )
{
{
smDbgPrint("Really winningz, going to rezultz, yo!")
currentEnt = Entities.FindByName( null, val.targetName )
SessionOptions.ScriptedStageType = STAGE_RESULTS
SessionOptions.ScriptedStageValue = 10
// store the waypoint if we found it, otherwise complain
DashDisplayScores()
if( currentEnt )
{
tempWaypointList.append( currentEnt )
}
else
{
printl( " ** Waypoint Spawn ERROR**  I can't find the waypoint named: " + val.targetName )
}
}
}
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
SpawnWaypointList( tempWaypointList )
SessionState.CommonIncrement = (100 - SessionOptions.CommonLimit) / SessionState.FinalWaypoint
}
}
 
//=========================================================
if( !MapOverrideOptions() )
// Collect all the info_item_position entities that are named
{
// "short_waypoint_*" OR "waypoint_*", put them into a list
// really, do we need this? or can we just always do panic here else a delay???
// and sort it by suffix. This will allow us to spawn all
local PercentageComplete = SessionState.CurrentWaypoint * 1.0 / SessionState.FinalWaypoint
// the waypoints in order so they can know their touch order
//=========================================================
SessionState.JustHitWaypoint = false
function SortAndSpawnWaypointList()
SessionOptions.ScriptedStageType = STAGE_PANIC
{
SessionOptions.ScriptedStageValue = 1
local currentWaypoint = Entities.FindByClassname( null, "info_item_position" )
foreach (val in SpecialNames)
local tempWaypointList = []
{
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 )
while( currentWaypoint )
SessionOptions.MegaMobSize += SessionState.CommonIncrement
{
local name = currentWaypoint.GetName()
if( ( name.find( "waypoint_" ) == 0 ) || ( name.find( "short_waypoint_" ) == 0 ) )
{
tempWaypointList.append( currentWaypoint )
}


if (SessionState.CurrentWaypoint == SessionState.FinalWaypoint - 1 )
currentWaypoint = Entities.FindByClassname( currentWaypoint, "info_item_position" )
{
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)
// sort the list by suffix
tempWaypointList.sort(@(a,b) a.GetName().slice(-4) <=> b.GetName().slice(-4) )


// we also want to positionally spawn at the mob target - though really i'd like to do some distance/scale stuff, too
SpawnWaypointList( tempWaypointList )
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()
// Spawn the provided list of waypoints on their positions
//=========================================================
function SpawnWaypointList( list )
{
{
SessionState.JustHitWaypoint = true  // and since the main loop never looks up into the array, that is o.k.
local waypointGroup = g_MapScript.GetEntityGroup( "DashWaypoint" )
Director.ForceNextStage()
local shortWaypointGroup = g_MapScript.GetEntityGroup( "DashWaypointShort" )
}


// each time a waypoint is hit this is called to get us moving forward
// set callbacks
// i.e. we need to activate the next waypoint, see if it is the final one, track counts, and so on
shortWaypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
function DashWaypointDone( id )
waypointGroup.SpawnTables[ "waypoint" ].PostPlaceCB <- SetWaypointCB
{
smDbgPrint("Dash Waypoint finished! " + id)


// sanity check!
// spawn the waypoints
if ( id != SessionState.CurrentWaypoint )
foreach( idx, ent in list )
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!")
// does our waypoint start with "short_"?  If so, it is a short waypoint! Spawn it.
SessionState.CurrentWaypoint--  // for safety
if( ent.GetName().find( "short_" ) == 0 )
SessionState.YouWon = true
{
 
SpawnSingleAt( shortWaypointGroup, ent.GetOrigin(), ent.GetAngles() )
// stop the clock
}
HUDManageTimers( 1, TIMER_STOP, 0 )
else // assuming it must be a normal waypoint since it isn't "short_"
}
{
else
SpawnSingleAt( waypointGroup, ent.GetOrigin(), ent.GetAngles() )
{
}
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
function DashDisplayScores( )
// 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
local final_time = HUDReadTimer(1)
// printl("Dash AddCriteria added " + criteriaTable.PercentComplete)
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 )
// Include all entity group interfaces needed for this mode
HUDPlace( HUD_LEFT_BOT,  0.28, 0.66, 0.44, 0.06 )
//-------------------------------------------------------
HUDPlace( HUD_RIGHT_BOT, 0.28, 0.74, 0.44, 0.06 )
IncludeScript( "entitygroups/dash_waypoint_group" )
local ticker_str = score_strings.yourtime
IncludeScript( "entitygroups/dash_waypoint_short_group" )
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 )
}


DashHUD <- {}
// this array holds the waypoints in order, up to SessionState.FinalWaypoint (though really, could just use len() for safety?
g_RoundState.WaypointList <- []


// HUD setup/control - our HUD is pretty simple in dash
// called at basically each waypoint via ForceNextStage - or when a PANIC ends we just go to a delay
function SetupModeHUD( )
function GetNextStage()
{
{
DashHUD =
smDbgPrint("GetNextStage called")
 
if ( SessionState.YouWon )
{
{
Fields =
smDbgPrint("Really winningz, going to rezultz, yo!")
{
SessionOptions.ScriptedStageType = STAGE_RESULTS
waypoint = { slot = HUD_RIGHT_TOP, name = "waypoint", staticstring = "Waypoint: ", datafunc = @() SessionState.CurrentWaypoint },
SessionOptions.ScriptedStageValue = 10
timer    = { slot = HUD_LEFT_TOP, name = "timer", staticstring = "Time: ", special = HUD_SPECIAL_TIMER1 },
DashDisplayScores()
 
// 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 },
}
}
}
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?


Ticker_AddToHud( DashHUD, "Hurry past the poles, go for a best time" )
// calculate the number of common infected to add each time a waypoint is completed
HUDSetLayout( DashHUD )
SessionState.CommonIncrement = (100 - SessionOptions.CommonLimit) / SessionState.FinalWaypoint
}
}
</source>
 
if( !MapOverrideOptions() )
===Holdout===
{
<source lang=cpp>
// really, do we need this? or can we just always do panic here else a delay???
///////////////////////////////////////////////////////////////////////////////
local PercentageComplete = SessionState.CurrentWaypoint * 1.0 / SessionState.FinalWaypoint
// The Holdout game mode! Brought to you by the new Mutation system!
// A Holdout Is...
SessionState.JustHitWaypoint = false
//  Mostly a demo of the new mutation system, but a game mode as well
SessionOptions.ScriptedStageType = STAGE_PANIC
//  A Continuous cycle of attack/cooldown/attack/cooldown
SessionOptions.ScriptedStageValue = 1
//  in code, implemented as 3 phases
foreach (val in SpecialNames)
//        PANIC - spawn some enemies around the players
{
//        CLEAROUT - wait until the enemies are (mostly) all gone
local limit = RandomInt(1,3) + PercentageComplete * 3
//        DELAY - a cooldown time before the next wave of enemies
SessionOptions[val + "Limit"] = limit
// 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)
SessionOptions.MaxSpecials = RandomInt(2,6) + PercentageComplete * 3
//  There can be special events that take over based on the current Stage #
if (SessionOptions.CommonLimit < 100)
SessionOptions.CommonLimit += SessionState.CommonIncrement


// This is a very Structured mode - it uses stagetables and other "helpers" in the mutation system
if (SessionOptions.MegaMobSize < 50 )
// You dont need to use these in your own mutations, though you are of course welcome to
SessionOptions.MegaMobSize += SessionState.CommonIncrement


// as a map, you should go implement the following functions, returning a "stageTable"
if (SessionState.CurrentWaypoint == SessionState.FinalWaypoint - 1 )
// or null to ignore
{
//  DoMapEventCheck()
Ticker_NewStr("One gate to go!")
//  DoMapSetup()
SessionOptions.TankLimit = 1 // how do i force a tank from script?
//  GetMapEscapeStage()
SessionOptions.ScriptedStageType = STAGE_ESCAPE  // i guess i do an escape stage
//   IsMapSpecificStage()    // dont like this! potential out of sync/parallel variables
}
//  GetMapSpecificStage()  //    maybe IsMap sets a "stageSpecific" that GetMap can use?
else // display some help text on the ticker
//   GetAttackStage()
{
//   GetMapClearoutStage()
switch( SessionState.CurrentWaypoint )
//  GetMapDelayStage()
{
///////////////////////////////////////////////////////////////////////////////
case 1:
 
Ticker_NewStr( "Each time you activate a new waypoint more infected will attack.", 15 )
//---------------------------------------------------------
break
// 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
case 2:
MutationState <-
Ticker_NewStr( "You cleared the waypoint! Here come more infected!", 15 )
{
break
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
else
ForcedEscapeStage = -1
{
ScriptedStageWave = 0
SessionOptions.ScriptedStageType = STAGE_DELAY
CooldownEndWarningTime = 8.5 // seconds before end of cooldown to play warning
SessionOptions.ScriptedStageValue = 1000
CooldownEndWarningChance = 15 // percent chance to play warning
SessionOptions.MaxSpecials = 0
CooldownEndWarningFrequency = 0 // how many waves to wait before playing another warning
}
LastWaveCooldownWarningPlayed = -1 // the last wave that the cooldown warning played on
 
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
// the DirectorOptions defaults for Holdout - though the maps will override many of these per wave and phase
// i.e. we need to activate the next waypoint, see if it is the final one, track counts, and so on
MutationOptions <-
function DashWaypointDone( id )
{
{
PreferredMobDirection = SPAWN_NO_PREFERENCE
smDbgPrint("Dash Waypoint finished! " + id)
SpawnSetRule = SPAWN_ANYWHERE
 
JournalString = ""
// sanity check!
SpecialInfectedAssault = 0
if ( id != SessionState.CurrentWaypoint )
AllowWitchesInCheckpoints = 1
printl("Hey! you think you are done with waypoint " + id + " but current is " + SessionState.CurrentWaypoint )
AllowCrescendoEvents = 0
EnforceFinaleNavSpawnRules = 0
IgnoreNavThreatAreas = 1
ZombieDiscardRange = 10000


WanderingZombieDensityModifier = 0
EntFire( g_RoundState.WaypointList[id].GetName(), "StopGlowing" )
BoomerLimit  = 0
g_RoundState.WaypointList[id].GetScriptScope().active = false
ChargerLimit = 0
// printl( "Deactivated waypoint " + id )
HunterLimit  = 0
local next_id = ++SessionState.CurrentWaypoint
JockeyLimit  = 0
if ( next_id >= SessionState.FinalWaypoint )
SpitterLimit = 0
{
SmokerLimit  = 0
// printl("Youz da Winner, yo!")
MaxSpecials  = 0
SessionState.CurrentWaypoint--  // for safety
CommonLimit  = 20
SessionState.YouWon = true
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
// stop the clock
cm_AggressiveSpecials = 1
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
// Utilities and helpers
// i.e. this is to add data to the RR table the characters use to choose statements
//=========================================================
function AddCriteria( criteriaTable )
 
const PHASE_PANIC = 1
const PHASE_CLEAR = 2
const PHASE_DELAY = 0
 
function StageNumToWaveBase( stagenum )
{
{
return (stagenum + 2) / // the +2 is because of our initial startup raw phases...
criteriaTable.PercentComplete <- SessionState.FinalWaypoint > 0 ? (SessionState.CurrentWaypoint*1.0)/SessionState.FinalWaypoint : 0
// printl("Dash AddCriteria added " + criteriaTable.PercentComplete)
}
}


function WaveBaseToStageNum( wavebase, substage = PHASE_DELAY )
//-------------------------------------------------------
{
// Include all entity group interfaces needed for this mode
return wavebase * 3 + substage
//-------------------------------------------------------
}
IncludeScript( "entitygroups/dash_waypoint_group" )
IncludeScript( "entitygroups/dash_waypoint_short_group" )
 
DashHUD <- {}


// Are players permitted to pick up this object by pressing their USE key?
// HUD setup/control - our HUD is pretty simple in dash
// return true for yes, false for no.
function SetupModeHUD( )
function CanPickupObject( object )
{
{
// mines
DashHUD =
if ( object.GetName().find( "mine_1_body" ) )
{
{
return true
Fields =
}
{
waypoint = { slot = HUD_RIGHT_TOP, name = "waypoint", staticstring = "Waypoint: ", datafunc = @() SessionState.CurrentWaypoint },
// resource drops
timer    = { slot = HUD_LEFT_TOP, name = "timer", staticstring = "Time: ", special = HUD_SPECIAL_TIMER1 },
if ( object.GetName().find( "prop_resource" ) )
 
{
// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
return true
// 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 },
}
}
}


// check the map script for a PickupObject function for extra qualified items
Ticker_AddToHud( DashHUD, "Hurry past the poles, go for a best time" )
// TODO: we should do this more generally!
HUDSetLayout( DashHUD )
local canPickup = false
if( "PickupObject" in g_MapScript )
{
canPickup = g_MapScript.PickupObject( object )
}
return canPickup
}
}
</source>}}


// Called from other systems when they want the next combat wave to be the escape
===Holdout===
function StartEscapeWave()
{{ExpandBox|<source lang=js>
{
///////////////////////////////////////////////////////////////////////////////
SessionState.ForcedEscapeStage =  ( ( SessionState.RawStageNum + 2 ) / 3) * 3 + 1
// The Holdout game mode! Brought to you by the new Mutation system!
// printl( "Prepping Escape Wave for Stage " + SessionState.ForcedEscapeStage + " from " + SessionState.RawStageNum )
// 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 #


// show the final score and timing
// This is a very Structured mode - it uses stagetables and other "helpers" in the mutation system
function HoldoutDisplayScores( )
// You dont need to use these in your own mutations, though you are of course welcome to
{
 
local final_time = Time() - SessionState.RealStartTime
// as a map, you should go implement the following functions, returning a "stageTable"
local score_strings = Scoring_AddScoreAndBuildStrings( Scoring_MakeName(), final_time )
// or null to ignore
HUDPlace( HUD_TICKER, 0.32, 0.27, 0.36, 0.20 )
//  DoMapEventCheck()
HUDPlace( HUD_FAR_RIGHT, 0.28, 0.50, 0.44, 0.06 )
//  DoMapSetup()
HUDPlace( HUD_FAR_LEFT,  0.28, 0.58, 0.44, 0.06 )
//  GetMapEscapeStage()
HUDPlace( HUD_LEFT_BOT,  0.28, 0.66, 0.44, 0.06 )
//  IsMapSpecificStage()   // dont like this! potential out of sync/parallel variables
HUDPlace( HUD_RIGHT_BOT, 0.28, 0.74, 0.44, 0.06 )
//  GetMapSpecificStage()  //   maybe IsMap sets a "stageSpecific" that GetMap can use?
local ticker_str = score_strings.yourtime
//  GetAttackStage()
if ("finish" in score_strings)
//  GetMapClearoutStage()
ticker_str = ticker_str + "\n" + score_strings.finish
//  GetMapDelayStage()
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( )
// 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 <-
{
{
// for now
HUDWaveInfo = true          // do you want the Wave # in middle of UI
HoldoutDisplayScores()
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
}
}


// @TODO - this is basically "dont shoot at mines" right? why is it here?
//---------------------------------------------------------
function BotQuery( queryflag, entity, defaultvalue )
// the DirectorOptions defaults for Holdout - though the maps will override many of these per wave and phase
MutationOptions <-
{
{
switch( queryflag )
PreferredMobDirection = SPAWN_NO_PREFERENCE
{
SpawnSetRule = SPAWN_ANYWHERE
case BOT_QUERY_NOTARGET:
JournalString = ""
{
SpecialInfectedAssault = 0
local classname = entity.GetClassname()
AllowWitchesInCheckpoints = 1
local targetname = entity.GetName()
AllowCrescendoEvents = 0
if ( targetname && targetname.find( "mine_1_body" ) )
EnforceFinaleNavSpawnRules = 0
{
IgnoreNavThreatAreas = 1
return false;
ZombieDiscardRange = 10000
}
return true;
}
}
return defaultvalue;
}


//----------------------------------------------------
WanderingZombieDensityModifier = 0
// Rather than checking every time to see if the map has defined each callback
BoomerLimit  = 0
// instead we are going to check for each callback on startup
ChargerLimit = 0
//  if they arent there, we will insert a simple NOP lambda (if they are, we leave them)
HunterLimit  = 0
// For what these callbacks do, check top of file where they are described
JockeyLimit  = 0
function OnGameplayStart()
SpitterLimit = 0
{
SmokerLimit  = 0
CheckOrSetMapCallback( "DoMapEventCheck", @() false )
MaxSpecials  = 0
CheckOrSetMapCallback( "DoMapSetup", @() null)
CommonLimit  = 20
CheckOrSetMapCallback( "GetMapEscapeStage", @() null )
TankLimit    = 0
CheckOrSetMapCallback( "IsMapSpecificStage", @() false )
MegaMobMaxSize = 100
CheckOrSetMapCallback( "GetMapSpecificStage", @() null )
MegaMobMinSize = 75
CheckOrSetMapCallback( "GetAttackStage", @() null )
PanicWavePauseMax = 5
CheckOrSetMapCallback( "GetMapClearoutStage", @() null )
PanicWavePauseMin = 1
CheckOrSetMapCallback( "GetMapDelayStage", @() null )
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


DoMapSetup()    // and then call the MapSpecific startup callback right away
cm_ShouldEscortHumanPlayers = 1
cm_AggressiveSpecials = 1
}
}


//=========================================================
//=========================================================
// Utilities and helpers
//=========================================================
//=========================================================
function OnActivate()
 
const PHASE_PANIC = 1
const PHASE_CLEAR = 2
const PHASE_DELAY = 0
 
function StageNumToWaveBase( stagenum )
{
{
// @todo: put scoring back into holdout!
return (stagenum + 2) / 3  // the +2 is because of our initial startup raw phases...
Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )
 
// this is so we can do some non-wave-based thinking
ScriptedMode_AddSlowPoll( HoldoutSlowPollUpdate )
}
}


//=========================================================
function WaveBaseToStageNum( wavebase, substage = PHASE_DELAY )
//=========================================================
function OnShutdown()
{
{
ScriptedMode_RemoveSlowPoll( HoldoutSlowPollUpdate )
return wavebase * 3 + substage
}
}


HoldoutHUD <- {}
// Are players permitted to pick up this object by pressing their USE key?
//=========================================================
// return true for yes, false for no.
// since we have this mutation specific HUD but individual maps can use it differently
function CanPickupObject( object )
// 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 =
// mines
if ( object.GetName().find( "mine_1_body" ) )
{
{
Fields =
return true
{
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 )
// resource drops
if ( object.GetName().find( "prop_resource" ) )
RescueTimer_Init( HoldoutHUD, HUD_MID_BOX, HUD_MID_BOT )
{
if (SessionState.HUDWaveInfo)
return true
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 )
// 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 )
{
{
TickerTimeout = SessionState.HUDTickerTimeout
canPickup = g_MapScript.PickupObject( object )
}
}
if ( "HUDTickerText" in SessionState )
return canPickup
{
}
if ( SessionState.HUDTickerText.len() > 0 )
{
Ticker_AddToHud( HoldoutHUD, SessionState.HUDTickerText )
}
}


HUDSetLayout( HoldoutHUD );
// 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
// The scripted mode calls this function each time the generator turns on.
function HoldoutDisplayScores( )
// The generator usually turns on because someone poured a can of fuel into it.
//=========================================================
function OnGeneratorStart()
{
{
g_MapScript.RescueTimer_Start()
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
// Counterpart to OnGeneratorStart(), this is called when the generator sputters and stops
function RescuedByCopter( )
//  usually because it has run out of fuel.
//=========================================================
function OnGeneratorStop()
{
{
g_MapScript.RescueTimer_Stop()
// for now
HoldoutDisplayScores()
}
}


//=========================================================
// @TODO - this is basically "dont shoot at mines" right? why is it here?
// this is the base "system maintenance" for holdout mode timers and UI
function BotQuery( queryflag, entity, defaultvalue )
// It checks things like has rescue started, should we do a warning about cooldown about to end, etc
//=========================================================
function HoldoutSlowPollUpdate()
{
{
RescueTimer_Tick()
switch( queryflag )
 
if ( !SessionState.RescueStarted )
{
{
if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
case BOT_QUERY_NOTARGET:
{
{
local seconds = g_MapScript.RescueTimer_Get()
local classname = entity.GetClassname()
local targetname = entity.GetName()
// start the rescue if the generator has run long enough
if ( targetname && targetname.find( "mine_1_body" ) )
if( seconds <= 0 )
{
{
SessionState.RescueStarted = true
return false;
g_ModeScript.StartEscapeWave()
}
}
return true;
}
}
}
}
return defaultvalue;
}


// CooldownEndWarningChance is the % chance that a random player will vocalize a warning
//----------------------------------------------------
// when a cooldown is close to ending
// Rather than checking every time to see if the map has defined each callback
if( "CooldownEndWarningChance" in SessionState )
// 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)
local timeLeft = HUDReadTimer( HUD_SPECIAL_COOLDOWN )
// 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 )


if ( ( timeLeft < SessionState.CooldownEndWarningTime ) && ( timeLeft > 0 ) && ( SessionState.LastWaveCooldownWarningPlayed + SessionState.CooldownEndWarningFrequency < SessionState.ScriptedStageWave ) )
DoMapSetup()   // and then call the MapSpecific startup callback right away
{
}
SessionState.LastWaveCooldownWarningPlayed = SessionState.ScriptedStageWave


// roll the dice...
//=========================================================
local chance = RandomInt(0, 100 )
//=========================================================
function OnActivate()
{
// @todo: put scoring back into holdout!
Scoring_LoadTable( SessionState.MapName, SessionState.ModeName )


if( chance < SessionState.CooldownEndWarningChance )
// this is so we can do some non-wave-based thinking
{
ScriptedMode_AddSlowPoll( HoldoutSlowPollUpdate )
// 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()
//=========================================================
//=========================================================
function OnShutdown()
{
{
DirectorOptions.JournalString = "{ resources = " + g_MapScript.Resources.CurrentCount
ScriptedMode_RemoveSlowPoll( HoldoutSlowPollUpdate )
if (SessionState.HUDRescueTimer)
}
DirectorOptions.JournalString += ", rescue = " + RescueTimer_Get()
if ("JournalMapFunc" in this)
DirectorOptions.JournalString += JournalMapFunc()
DirectorOptions.JournalString += " }"
}


HoldoutHUD <- {}
//=========================================================
//=========================================================
// A little digression of testing/demoing a "script based clearout"
// since we have this mutation specific HUD but individual maps can use it differently
// You can just do C++ based clearout (to let the mob population clearout) of course
// it is a bit more complicated than a normal HUD Setup
// But from script, one can use a Poll function and GetInfectedStats to manage it yourself
// - use SessionState to tell the system which extra fields you want (Wave #, Timer, etc)
//  See sm_utilities for the actual code/systems if you want to do something like it
// - 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 },


// Whether to use the new Script based clearout or not - false here just means use the default c++ one
// this is kinda a lie! we are really going to move these w/HUDPlace for displaying final scores!
::g_ScriptClearout <- true
// 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 },
// if you are going to use the new clearout - here is the table for determining it's behavior
score2  = { slot = HUD_FAR_LEFT,  name = "score2", dataval = "Score2", flags = HUD_FLAG_NOTVISIBLE | HUD_FLAG_ALIGN_LEFT },
holdout_ClearoutTable <-
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 },
commons = 2
}
specials = 0
}
tanks = 0
witches = 0
if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
plateautime = 5
plateaucommons = 5
RescueTimer_Init( HoldoutHUD, HUD_MID_BOX, HUD_MID_BOT )
plateauspecials = 1
if (SessionState.HUDWaveInfo)
stopspecials = true
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 )
//
{
// "Main Loop" of Holdout
TickerTimeout = SessionState.HUDTickerTimeout
//
}
// GetNextStage gets called whenever the director finishes the last thing you told it to do
//  or if you do a ForceNextStage yourself
if ( "HUDTickerText" in SessionState )
// 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)
if ( SessionState.HUDTickerText.len() > 0 )
// 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
Ticker_AddToHud( HoldoutHUD, SessionState.HUDTickerText )
// And then we go and set all those fields in the director, set the stage type, and send the director off
}
//
}
///////////////////////////////////////////////////////////////////////////////
 
HUDSetLayout( HoldoutHUD );
}


function GetNextStage()
//=========================================================
// 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()
{
{
local use_stage = null
g_MapScript.RescueTimer_Start()
}


local stageNum = ++SessionState.RawStageNum
//=========================================================
// 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()
}


SessionState.ScriptedStageWave = ( stageNum + 2 ) / 3
//=========================================================
// 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()


// first, special event management
if ( !SessionState.RescueStarted )
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()  
if ( "HUDRescueTimer" in SessionState && SessionState.HUDRescueTimer )
Director.ResetSpecialTimers()
{
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()
}
}
}
}


// now the generalized stage sequencing...
// CooldownEndWarningChance is the % chance that a random player will vocalize a warning
if ( stageNum == 0 )
// when a cooldown is close to ending
if( "CooldownEndWarningChance" in SessionState )
{
{
DirectorOptions.ScriptedStageType = STAGE_SETUP
local timeLeft = HUDReadTimer( HUD_SPECIAL_COOLDOWN )
if ("HUDTickerText" in SessionState)
 
Ticker_NewStr(SessionState.HUDTickerText) // since the time it got set as start in HUD load is ages ago
if ( ( timeLeft < SessionState.CooldownEndWarningTime ) && ( timeLeft > 0 ) && ( SessionState.LastWaveCooldownWarningPlayed + SessionState.CooldownEndWarningFrequency < SessionState.ScriptedStageWave ) )
}
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 )
SessionState.LastWaveCooldownWarningPlayed = SessionState.ScriptedStageWave
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?
// roll the dice...
switch ( DirectorOptions.ScriptedStageType )
local chance = RandomInt(0, 100 )
{
case STAGE_CLEAROUT:
case STAGE_DELAY:
case STAGE_ESCAPE:
DirectorOptions.SpecialInfectedAssault = 1
break;
default:
DirectorOptions.SpecialInfectedAssault = 0
}


if ( use_stage != null )
if( chance < SessionState.CooldownEndWarningChance )
{
{
if ( "stageDefaults" in g_MapScript )
// jackpot! collect the players into an array and select one at random to speak a line
StageInfo_Execute( use_stage, stageDefaults )        // should use_stage just delegate?
local playerEnt = null
else
local playerArray = []
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 )
while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
}
{
</source>
if (playerEnt.IsSurvivor() )
{
playerArray.append( playerEnt)
}
}


===Shootzones===
local idx = RandomInt( 0, 3)
<source lang=cpp>
ModeSpawns <-
[
["ShootzoneTrigger", "shootzone_trigger_spawn_*", "shootzone_trigger_group", SPAWN_FLAGS.SPAWN],
[ "ShootzoneSound", SPAWN_FLAGS.NOSPAWN ],
]


if( idx < playerArray.len() )
{
// fire entity IO at "!activator" and pass the player ent as the activator
EntFire( "!activator", "SpeakResponseConcept", "PlayerHurryUp", 0, playerArray[idx] )
}
}
}
}
}


MutationState <-
function JournalFunc()
{
{
allPlayers = []
DirectorOptions.JournalString = "{ resources = " + g_MapScript.Resources.CurrentCount
playersInShootzone = []
if (SessionState.HUDRescueTimer)
allShootzones = []
DirectorOptions.JournalString += ", rescue = " + RescueTimer_Get()
enabledShootzones = []
if ("JournalMapFunc" in this)
lastEnabledShootzoneID = -1
DirectorOptions.JournalString += JournalMapFunc()
FirstTime = true
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


MutationOptions <-
// if you are going to use the new clearout - here is the table for determining it's behavior
holdout_ClearoutTable <-
{
{
    CommonLimit = 30 // Maximum number of common zombies alive in the world at the same time
commons = 2
//MegaMobSize = 20 // Total number of common zombies in a mob. (never more than CommonLimit at one time)
specials = 0
//WanderingZombieDensityModifier = 0 // lets get rid of the wandering zombies
tanks = 0
MaxSpecials  = 0 // removes all special infected from spawning
witches = 0
TankLimit    = 0 // removes all tanks from spawning
plateautime = 5
WitchLimit  = 0 // removes all witches from spawning
plateaucommons = 5
//BoomerLimit  = 0
plateauspecials = 1
//ChargerLimit = 0
stopspecials = true
//HunterLimit  = 0
//JockeyLimit  = 0
//SpitterLimit = 0
//SmokerLimit  = 0
}
}


///////////////////////////////////////////////////////////////////////////////
//
// "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
//
///////////////////////////////////////////////////////////////////////////////


ShootzonesHUD <-
function GetNextStage()
{
{
Fields =  
local use_stage = null
{
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 },
}
}


local stageNum = ++SessionState.RawStageNum


function ShootzoneThinkSpawnCB( entity, rarity )
SessionState.ScriptedStageWave = ( stageNum + 2 ) / 3
{
printl( "Think Spawn!!!" )


//entity.ValidateScriptScope()
// first, special event management
entity.GetScriptScope().ShootzoneThink <- function()
DoMapEventCheck()
{
g_ModeScript.RecomputePlayersInShootzones()
// 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...");


function OnGameplayStart()
if ( stageNum == 1) // 1st real attack stage
{
{
printl( "Starting SHOOTZONES!" )
SessionState.RealStartTime <- Time()  
printl( "Max Flow Distance: " + GetMaxFlowDistance() )
Director.ResetSpecialTimers()
}


//Add all the players
// now the generalized stage sequencing...
local playerEnt = null
if ( stageNum == 0 )
while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
{
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 ( playerEnt.IsSurvivor() )
if (::g_ScriptClearout)
{
{
playerEnt.ValidateScriptScope()
ClearoutStart( holdout_ClearoutTable )
playerEnt.GetScriptScope().currentShootzone <- -1
DirectorOptions.ScriptedStageType = STAGE_DELAY
//playerEnt.GetScriptScope().currentShootzone = -1
DirectorOptions.ScriptedStageValue = -1 // Infinite
SessionState.allPlayers.append( playerEnt)
}
}
else
use_stage = GetMapClearoutStage()
}
}
 
else if ( ( stageNum % 3 ) == PHASE_DELAY )
//Initialize the HUD
foreach ( index, playerEnt in SessionState.allPlayers )
{
{
local hudField = "player" + ( index + 1 )
use_stage = GetMapDelayStage()
ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": OUT"
ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
}
}


EnableShootzones()
// Put the special infected into assault mode - really want to do this sooner somehow? at maxspecials 0?
 
switch ( DirectorOptions.ScriptedStageType )
//ScriptedMode_AddUpdate( RecomputePlayersInShootzones.bindenv(this) )
 
local shootzone_think =
{
{
function GetSpawnList()      { return [ EntityGroup.SpawnTables.shootzone_think ] }
case STAGE_CLEAROUT:
function GetEntityGroup()    { return EntityGroup }
case STAGE_DELAY:
EntityGroup =
case STAGE_ESCAPE:
{
DirectorOptions.SpecialInfectedAssault = 1
SpawnTables =
break;
{
default:
shootzone_think =
DirectorOptions.SpecialInfectedAssault = 0
{
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()
if ( use_stage != null )
shootzoneThinkGroup.SpawnTables[ "shootzone_think" ].PostPlaceCB <- ShootzoneThinkSpawnCB
{
g_MapScript.SpawnSingleAt( shootzoneThinkGroup, Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )
if ( "stageDefaults" in g_MapScript )
g_MapScript.SpawnSingleAt( g_MapScript.GetEntityGroup( "ShootzoneSound" ), Vector( 0, 0, 0 ) , QAngle( 0, 0, 0 ) )
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 )
}
}
</source>}}


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




function OnEntityGroupRegistered( name, group )
MutationState <-
{
{
if ( name == "ShootzoneTrigger" )
allPlayers = []
{
playersInShootzone = []
group.GetEntityGroup().SpawnTables[ "shootzone_script" ].PostPlaceCB <- ShootzoneScriptSpawnCB
allShootzones = []
}
enabledShootzones = []
lastEnabledShootzoneID = -1
FirstTime = true
}
}




function ShootzoneScriptSpawnCB( entity, rarity )
MutationOptions <-
{
{
printl( "Script spawned: " + entity + ", name: " + entity.GetName() )
    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)
local shootzoneScope = entity.GetScriptScope()
//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
}


//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
ShootzonesHUD <-
SessionState.allShootzones.append( entity )
{
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 },
}
}
}




// HUD setup/control - our HUD is pretty simple in dash
function ShootzoneThinkSpawnCB( entity, rarity )
function SetupModeHUD( )
{
{
HUDSetLayout( ShootzonesHUD )
printl( "Think Spawn!!!" )


HUDPlace( HUD_LEFT_TOP, 0.01, 0.06, 0.2, 0.04 )
//entity.ValidateScriptScope()
HUDPlace( HUD_LEFT_BOT, 0.01, 0.11, 0.2, 0.04 )
entity.GetScriptScope().ShootzoneThink <- function()
HUDPlace( HUD_RIGHT_TOP, 0.01, 0.16, 0.2, 0.04 )
{
HUDPlace( HUD_RIGHT_BOT, 0.01, 0.21, 0.2, 0.04 )
g_ModeScript.RecomputePlayersInShootzones()
}
}
}




function FindShootzoneClosestToPlayers()
function OnGameplayStart()
{
{
printl( "Starting SHOOTZONES!" )
printl( "Max Flow Distance: " + GetMaxFlowDistance() )


}
//Add all the players
 
local playerEnt = null
function EnableShootzones()
while ( playerEnt = Entities.FindByClassname( playerEnt, "player" ) )
{
//Cycle through the shootzones and keep 3 enabled at all times
if ( SessionState.allShootzones.len() < 3 )
{
{
foreach ( shootzone in SessionState.allShootzones )
if ( playerEnt.IsSurvivor() )
{
{
shootzone.GetScriptScope().EnableShootzone()
playerEnt.ValidateScriptScope()
SessionState.enabledShootzones.append( shootzone )
playerEnt.GetScriptScope().currentShootzone <- -1
//playerEnt.GetScriptScope().currentShootzone = -1
SessionState.allPlayers.append( playerEnt)
}
}


SessionState.lastEnabledShootzoneID = shootzone.GetScriptScope().id
//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
}
}
else
 
EnableShootzones()
 
//ScriptedMode_AddUpdate( RecomputePlayersInShootzones.bindenv(this) )
 
local shootzone_think =
{
{
local index = SessionState.lastEnabledShootzoneID + 1
function GetSpawnList()      { return [ EntityGroup.SpawnTables.shootzone_think ] }
while ( SessionState.enabledShootzones.len() < 3 )
function GetEntityGroup()   { return EntityGroup }
EntityGroup =
{
{
if ( index >= SessionState.allShootzones.len() )
SpawnTables =
{
{
index = 0;
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
}


//Enable the next available shootzone in the list
local shootzoneThinkGroup = shootzone_think.GetEntityGroup()
local currentShootzone = SessionState.allShootzones[index]
shootzoneThinkGroup.SpawnTables[ "shootzone_think" ].PostPlaceCB <- ShootzoneThinkSpawnCB
if ( currentShootzone.GetScriptScope().isEnabled == 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 ) )
currentShootzone.GetScriptScope().EnableShootzone()
SessionState.enabledShootzones.append( currentShootzone )
 
SessionState.lastEnabledShootzoneID = currentShootzone.GetScriptScope().id
}


index++
}
}
}
}




function RecomputePlayersInShootzones()
function OnActivate()
{
{
// 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
function OnEntityGroupRegistered( name, group )
local prevShootzonePlayers = clone( SessionState.playersInShootzone )
{
if ( name == "ShootzoneTrigger" )
{
group.GetEntityGroup().SpawnTables[ "shootzone_script" ].PostPlaceCB <- ShootzoneScriptSpawnCB
}
}


//Recompute which players are in an active shootzone
SessionState.playersInShootzone.clear()


foreach ( playerEnt in SessionState.allPlayers )
function ShootzoneScriptSpawnCB( entity, rarity )
{
{
foreach ( shootzone in SessionState.enabledShootzones )
printl( "Script spawned: " + entity + ", name: " + entity.GetName() )
{
local shootzoneScope = shootzone.GetScriptScope()
if ( shootzoneScope.isActive )
{
local playerToShootzone = playerEnt.GetOrigin() - shootzoneScope.origin
local playerDistance = playerToShootzone.Length()


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


//foreach ( shootzone in SessionState.allShootzones )
//Set the id of the shootzone
//{
shootzoneScope.id = SessionState.allShootzones.len()
//local shootzoneScope = shootzone.GetScriptScope()
printl( "Shootzone ID: " + shootzoneScope.id )
//local vecto = SessionState.allPlayers[0].GetOrigin() - shootzoneScope.origin
//local dist = vecto.Length()
//Flow distance for the shootzone
//
shootzoneScope.flowDistance = GetFlowDistanceForPosition( shootzoneScope.origin )
//if ( dist < 500 )
shootzoneScope.flowPercent = GetFlowPercentForPosition( shootzoneScope.origin, true )
//{
shootzoneScope.flowPercent1 = GetFlowPercentForPosition( shootzoneScope.origin, false )
//DebugDrawText( shootzoneScope.origin, "Distance: " + shootzoneScope.flowDistance + "\nPercent: " + shootzoneScope.flowPercent + "%\nPercent2: " + shootzoneScope.flowPercent1, false, 0.15 )
printl( "Flow Distance: " + shootzoneScope.flowDistance )
//
printl( "Flow Percent: " + shootzoneScope.flowPercent )
//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
//Add the shootzone to the list of all the shootzones
if ( prevShootzonePlayers.find( playerEnt ) != null &&
SessionState.allShootzones.append( entity )
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"
// HUD setup/control - our HUD is pretty simple in dash
foreach ( shootzonePlayer in SessionState.playersInShootzone )
function SetupModeHUD( )
{
{
if ( shootzonePlayer.GetPlayerName() == playerEnt.GetPlayerName() )
HUDSetLayout( ShootzonesHUD )
{
inOrOut = "IN"
break;
}
}


//Set the player HUD flags
HUDPlace( HUD_LEFT_TOP, 0.01, 0.06, 0.2, 0.04 )
local hudField = "player" + ( index + 1 )
HUDPlace( HUD_LEFT_BOT, 0.01, 0.11, 0.2, 0.04 )
ShootzonesHUD.Fields[hudField].dataval = SessionState.allPlayers[index].GetPlayerName() + ": " + inOrOut
HUDPlace( HUD_RIGHT_TOP, 0.01, 0.16, 0.2, 0.04 )
ShootzonesHUD.Fields[hudField].flags = ShootzonesHUD.Fields[hudField].flags & ~HUD_FLAG_NOTVISIBLE
HUDPlace( HUD_RIGHT_BOT, 0.01, 0.21, 0.2, 0.04 )
}
}
}




function ShootzoneTimedOut( shootzoneID )
function FindShootzoneClosestToPlayers()
{
{
foreach ( index, shootzone in SessionState.enabledShootzones )
{
if ( shootzone.GetScriptScope().id == shootzoneID )
{
SessionState.enabledShootzones.remove( index )
break
}
}


EnableShootzones()
}
}


 
function EnableShootzones()
function AllowTakeDamage( damageTable )
{
{
foreach ( playerEnt in SessionState.allPlayers )
//Cycle through the shootzones and keep 3 enabled at all times
if ( SessionState.allShootzones.len() < 3 )
{
{
//If the attacker is a player
foreach ( shootzone in SessionState.allShootzones )
if ( playerEnt == damageTable.Attacker )
{
shootzone.GetScriptScope().EnableShootzone()
SessionState.enabledShootzones.append( shootzone )
 
SessionState.lastEnabledShootzoneID = shootzone.GetScriptScope().id
}
}
else
{
local index = SessionState.lastEnabledShootzoneID + 1
while ( SessionState.enabledShootzones.len() < 3 )
{
{
//If the attacking player is in a shootzone then do damage
if ( index >= SessionState.allShootzones.len() )
foreach ( shootzonePlayer in SessionState.playersInShootzone )
{
index = 0;
}
 
//Enable the next available shootzone in the list
local currentShootzone = SessionState.allShootzones[index]
if ( currentShootzone.GetScriptScope().isEnabled == false )
{
{
if ( shootzonePlayer == damageTable.Attacker )
currentShootzone.GetScriptScope().EnableShootzone()
{
SessionState.enabledShootzones.append( currentShootzone )
//printl( "Damage done by shootzone player: " + shootzonePlayer.GetPlayerName() )
 
return true
SessionState.lastEnabledShootzoneID = currentShootzone.GetScriptScope().id
}
}
}


//Attacking player is not in a shootzone
index++
//printl( "Attacking player is not in shootzone: " + playerEnt.GetPlayerName() )
return false
}
}
}
}
//Attacker is not a player
return true
}
}
</source>


===Gunbrain===
<source lang=cpp>
//=========================================================
//=========================================================


GunStatsTable <- null
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;
// }


TARGET_INVALID <- -1
//Keep track of players currently in a shootzone
TARGET_SURVIVOR <- 9
local prevShootzonePlayers = clone( SessionState.playersInShootzone )


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.
//Recompute which players are in an active shootzone
SessionState.playersInShootzone.clear()


// we need a barebones HUD for our ticker that shows up at the begining of the game
foreach ( playerEnt in SessionState.allPlayers )
function SetupModeHUD( )
{
ModeHUD <-
{
{
Fields =
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 )
}
}
}
Ticker_AddToHud( ModeHUD, "" )


// load the ModeHUD table
//This player just exited a shootzone
HUDSetLayout( ModeHUD )
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
// Mod Command interface
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
}
}


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 )
function ShootzoneTimedOut( shootzoneID )
{
{
if ( str.find("gb_help") != null )
foreach ( index, shootzone in SessionState.enabledShootzones )
{
{
foreach( idx, Line in GB_HELP_TEXT )
if ( shootzone.GetScriptScope().id == shootzoneID )
{
{
Say( null, Line, true )
SessionState.enabledShootzones.remove( index )
break
}
}
}
}
else if( srcEnt != null )
 
EnableShootzones()
}
 
 
function AllowTakeDamage( damageTable )
{
foreach ( playerEnt in SessionState.allPlayers )
{
{
if ( str.find("gb_block ") )
//If the attacker is a player
if ( playerEnt == damageTable.Attacker )
{
{
local commandStart = str.find("gb_block ")
//If the attacking player is in a shootzone then do damage
local name = str.slice( commandStart + 9 )
foreach ( shootzonePlayer in SessionState.playersInShootzone )
name = name.slice(0,-1)
{
CommandAddBlockPlayer( name.toupper() )
if ( shootzonePlayer == damageTable.Attacker )
SessionState.GBStatsDirty = true
{
//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
}
}
else if ( str.find("gb_allow ") )
}
{
 
local commandStart = str.find("gb_allow ")
//Attacker is not a player
local name = str.slice( commandStart + 9 )
return true
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" )
}
}
}
}
</source>}}
===GunBrain===
{{ExpandBox|<source lang=js>
//=========================================================
//=========================================================
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.


function IsPlayerBlocked( playerName )
// we need a barebones HUD for our ticker that shows up at the begining of the game
function SetupModeHUD( )
{
{
foreach( index, name in GunStatsTable.Blocked )
ModeHUD <-
{
{
if( name == playerName )
Fields =
{
{
return true
}
}
}
}
return false
Ticker_AddToHud( ModeHUD, "" )
}


function CommandAddBlockPlayer( playerName )
// load the ModeHUD table
{
HUDSetLayout( ModeHUD )
Say( null, "Blocking stats for " + playerName, true )
}


if ( GunStatsTable.Blocked.len() >= 64 )
/////////////////////////////////////////////////
return
// Mod Command interface
/////////////////////////////////////////////////


if ( IsPlayerBlocked( playerName ) )
GB_HELP_TEXT <-
{
[
return
"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)",
GunStatsTable.Blocked.push( playerName )
"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",
]


foreach( index, Player in GunStatsTable.Players )
function InterceptChat( str, srcEnt )
{
if ( str.find("gb_help") != null )
{
{
if( playerName == Player.name )
foreach( idx, Line in GB_HELP_TEXT )
{
{
Say( null, "Removing stats for " + playerName, true )
Say( null, Line, true )
GunStatsTable.Players.remove(index)
return
}
}
}
}
}
else if( srcEnt != null )
 
function CommandRemoveBlockPlayer( playerName )
{
Say( null, "Removing block for " + playerName, true )
foreach( index, name in GunStatsTable.Blocked )
{
{
if( name == playerName )
if ( str.find("gb_block ") )
{
{
GunStatsTable.Blocked.remove(index)
local commandStart = str.find("gb_block ")
return
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 ")
function CommandUpdateData()
local name = str.slice( commandStart + 9 )
{
name = name.slice(0,-1)
CommitDamageData()
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 CommandDumpData()
function IsPlayerBlocked( playerName )
{
{
printl( TableToString( GunStatsTable ) )
foreach( index, name in GunStatsTable.Blocked )
{
if( name == playerName )
{
return true
}
}
return false
}
}


function CommandResetData()
function CommandAddBlockPlayer( playerName )
{
{
GunStatsTable.clear()
Say( null, "Blocking stats for " + playerName, true )
VerifyStatsTableStorage()
printl( TableToString( GunStatsTable ) )
}


function CommandBackupData()
if ( GunStatsTable.Blocked.len() >= 64 )
{
return
Say( null, "Backing up data", true )
 
StringToFile( "GunBrainData.bak" , TableToString( GunStatsTable ) )
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()
function CommandRestoreData()
Line 10,381: Line 11,138:
return array_string + "]"
return array_string + "]"
}
}
</source>
</source>}}


===Tankrun===
===TankRun===
<source lang=cpp>
{{ExpandBox|<source lang=js>
//-----------------------------------------------------
//-----------------------------------------------------
Msg("Activating Tank Run\n");
Msg("Activating Tank Run\n");
Line 10,397: Line 11,154:
MutationOptions <-
MutationOptions <-
{
{
cm_ShouldHurry = 1
cm_TankRun = true
cm_InfiniteFuel = 1
cm_ShouldHurry = true
cm_InfiniteFuel = true
cm_AllowPillConversion = false
cm_CommonLimit = 0
cm_CommonLimit = 0
cm_DominatorLimit = 0
cm_DominatorLimit = 0
Line 10,404: Line 11,163:
cm_ProhibitBosses = true
cm_ProhibitBosses = true
cm_AggressiveSpecials = true
cm_AggressiveSpecials = true
 
BoomerLimit = 0
BoomerLimit = 0
SmokerLimit = 0
SmokerLimit = 0
Line 10,413: Line 11,172:
cm_WitchLimit = 0
cm_WitchLimit = 0
cm_TankLimit = 8
cm_TankLimit = 8
 
MobMinSize = 0
MobMinSize = 0
MobMaxSize = 0
MobMaxSize = 0
Line 10,422: Line 11,181:
weaponsToConvert =
weaponsToConvert =
{
{
weapon_defibrillator = "weapon_first_aid_kit_spawn"
ammo = "upgrade_laser_sight"
ammo = "upgrade_laser_sight"
}
}
Line 10,456: Line 11,214:
ModelCheck = false
ModelCheck = false
FinaleStarted = false
FinaleStarted = false
FinaleStartTime = 0
TriggerRescue = false
TriggerRescue = false
RescueDelay = 600
RescueDelay = 600
LastAlarmTankTime = 0
LastSpawnTime = 0
LastSpawnTime = 0
SpawnInterval = 20
SpawnInterval = 20
DoubleTanks = false
DoubleTanks = false
TankBiled = {}
TankBiled = {}
TanksAlive = 0
TanksDisabled = false
TanksDisabled = false
TankHealth = 4000
TankHealth = 4000
Line 10,470: Line 11,227:
TriggerRescueThink = false
TriggerRescueThink = false
LeftSafeAreaThink = false
LeftSafeAreaThink = false
CheckPrimaryWeaponThink = false
FinaleType = -1
FinaleType = -1
}
}


local triggerFinale = Entities.FindByClassname( null, "trigger_finale" );
function GetNumTanks()
if ( triggerFinale )
{
MutationState.FinaleType = NetProps.GetPropInt( triggerFinale, "m_type" );
local infStats = {};
GetInfectedStats( infStats );
return infStats.Tanks;
}


if ( MutationState.FinaleType != 4 )
if ( IsMissionFinalMap() )
{
{
function GetNextStage()
local triggerFinale = Entities.FindByClassname( null, "trigger_finale" );
if ( triggerFinale )
{
{
if ( SessionState.TriggerRescue )
MutationState.FinaleType = NetProps.GetPropInt( triggerFinale, "m_type" );
if ( NetProps.GetPropInt( triggerFinale, "m_bIsSacrificeFinale" ) )
{
{
SessionOptions.ScriptedStageType = STAGE_ESCAPE;
function OnGameEvent_generator_started( params )
TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags | HUD_FLAG_NOTVISIBLE;
{
return;
if ( !SessionState.FinaleStarted )
return;
 
HUDManageTimers( 0, TIMER_COUNTDOWN, HUDReadTimer( 0 ) - 30 );
if ( GetNumTanks() < SessionOptions.cm_TankLimit )
ZSpawn( { type = 8 } );
}
}
}
if ( SessionState.FinaleStarted )
}
 
TankRunHUD <- {};
function SetupModeHUD()
{
TankRunHUD =
{
{
SessionOptions.ScriptedStageType = STAGE_DELAY;
Fields =
SessionOptions.ScriptedStageValue = -1;
{
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;
}
}
}
}
Line 10,499: Line 11,329:
if ( !damageTable.Attacker || !damageTable.Victim || !damageTable.Inflictor )
if ( !damageTable.Attacker || !damageTable.Victim || !damageTable.Inflictor )
return true;
return true;
 
if ( damageTable.Attacker.IsPlayer() && damageTable.Victim.IsPlayer() )
if ( damageTable.Victim.IsPlayer() && damageTable.Attacker.IsPlayer() )
{
{
if ( damageTable.Attacker.IsSurvivor() && damageTable.Victim.GetZombieType() == 8 )
if ( damageTable.Attacker.IsSurvivor() && damageTable.Victim.GetZombieType() == 8 )
Line 10,506: Line 11,336:
if ( damageTable.Inflictor.GetClassname() == "pipe_bomb_projectile" )
if ( damageTable.Inflictor.GetClassname() == "pipe_bomb_projectile" )
damageTable.DamageDone = 500;
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;
return true;
}
}
Line 10,514: Line 11,353:
function TriggerRescueThink()
function TriggerRescueThink()
{
{
if ( (Time() - SessionState.FinaleStartTime) >= SessionState.RescueDelay )
if ( HUDReadTimer( 0 ) <= 0 )
{
{
SessionState.TriggerRescue = true;
SessionState.TriggerRescue = true;
SessionState.TriggerRescueThink = false;
Director.ForceNextStage();
Director.ForceNextStage();
SessionState.TriggerRescueThink = false;
 
TankRunHUD.Fields.rescue_time.flags = TankRunHUD.Fields.rescue_time.flags | HUD_FLAG_NOTVISIBLE;
HUDManageTimers( 0, TIMER_DISABLE, 0 );
}
}
}
}
Line 10,524: Line 11,366:
function SpawnTankThink()
function SpawnTankThink()
{
{
if ( (SessionState.TanksAlive < 8) && ((Time() - SessionState.LastSpawnTime) >= SessionState.SpawnInterval || SessionState.LastSpawnTime == 0) )
if ( SessionOptions.cm_TankLimit == 0 )
return;
 
if ( (GetNumTanks() < SessionOptions.cm_TankLimit) && ((Time() - SessionState.LastSpawnTime) >= SessionState.SpawnInterval || SessionState.LastSpawnTime == 0) )
{
{
local success = ZSpawn( { type = 8 } );
if ( ZSpawn( { type = 8 } ) )
if ( success )
{
{
if ( SessionState.DoubleTanks )
if ( SessionState.DoubleTanks )
Line 10,538: Line 11,382:
function LeftSafeAreaThink()
function LeftSafeAreaThink()
{
{
local player = null;
for ( local player; player = Entities.FindByClassname( player, "player" ); )
while ( player = Entities.FindByClassname( player, "player" ) )
{
{
if ( ( !player.IsValid() ) || ( NetProps.GetPropInt( player, "m_iTeamNum" ) != 2 ) )
if ( NetProps.GetPropInt( player, "m_iTeamNum" ) != 2 )
continue;
continue;
 
if ( ResponseCriteria.GetValue( player, "instartarea" ) == "0" )
if ( ResponseCriteria.GetValue( player, "instartarea" ) == "0" )
{
{
Line 10,550: Line 11,393:
break;
break;
}
}
else
continue;
}
}
}
}
Line 10,565: Line 11,406:
function CheckDifficultyForTankHealth( difficulty )
function CheckDifficultyForTankHealth( difficulty )
{
{
local health = 0;
local health = [2000, 3000, 4000, 5000];
if ( difficulty == "easy" )
SessionState.TankHealth = health[difficulty];
health = 2000;
else if ( difficulty == "normal" )
health = 3000;
else if ( difficulty == "hard" )
health = 4000;
else if ( difficulty == "impossible" )
health = 5000;
if ( SessionState.MapName == "c1m1_hotel" )
SessionState.TankHealth = (health / 5);
else
SessionState.TankHealth = health;
}
}


function OnGameEvent_round_start_post_nav( params )
if ( Director.IsFirstMapInScenario() )
{
{
local spawner = null;
function CheckPrimaryWeaponThink()
while ( spawner = Entities.FindByClassname( spawner, "info_zombie_spawn" ) )
{
{
if ( spawner.IsValid() )
local startArea = null;
local startPos = null;
for ( local survivorSpawn; survivorSpawn = Entities.FindByClassname( survivorSpawn, "info_survivor_position" ); )
{
{
local population = NetProps.GetPropString( spawner, "m_szPopulation" );
local area = NavMesh.GetNearestNavArea( survivorSpawn.GetOrigin(), 100, false, false );
if ( (area) && (area.HasSpawnAttributes( 128 ) || area.HasSpawnAttributes( 2048 )) )
if ( population == "tank" || population == "river_docks_trap" )
{
startArea = area;
startPos = survivorSpawn.GetOrigin();
break;
}
}
 
if ( !startPos )
return;
 
for ( local weapon; weapon = Entities.FindByClassnameWithin( weapon, "weapon_*", startPos, 1200 ); )
{
if ( weapon.GetOwnerEntity() )
continue;
continue;
else
 
spawner.Kill();
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();
}
}
local ammo = null;
for ( local ammo; ammo = Entities.FindByModel( ammo, "models/props/terror/ammo_stack.mdl" ); )
while ( ammo = Entities.FindByModel( ammo, "models/props/terror/ammo_stack.mdl" ) )
ammo.Kill();
ammo.Kill();
 
if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c6m3_port" || SessionState.MapName == "c13m4_cutthroatcreek" )
if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c6m3_port" || SessionState.MapName == "c13m4_cutthroatcreek" )
{
{
Line 10,605: Line 11,495:
SessionState.TanksDisabled = true;
SessionState.TanksDisabled = true;
}
}
 
CheckDifficultyForTankHealth( Convars.GetStr( "z_difficulty" ).tolower() );
CheckDifficultyForTankHealth( GetDifficulty() );
EntFire( "worldspawn", "RunScriptCode", "g_ModeScript.TankRunThink()", 1.0 );
}
}


function OnGameEvent_difficulty_changed( params )
function OnGameEvent_difficulty_changed( params )
{
{
CheckDifficultyForTankHealth( params["strDifficulty"].tolower() );
CheckDifficultyForTankHealth( params["newDifficulty"] );
}
}


Line 10,618: Line 11,509:
if ( SessionState.TanksDisabled )
if ( SessionState.TanksDisabled )
return;
return;
 
local player = GetPlayerFromUserID( params["userid"] );
local player = GetPlayerFromUserID( params["userid"] );
if ( !player )
if ( !player )
Line 10,625: Line 11,516:
return;
return;
}
}
 
local instartarea = ResponseCriteria.GetValue( player, "instartarea" );
if ( ResponseCriteria.GetValue( player, "instartarea" ) == "1" )
if ( instartarea == "1" )
SessionState.LeftSafeAreaThink = true;
SessionState.LeftSafeAreaThink = true;
else
else
Line 10,633: Line 11,523:
}
}


function OnGameEvent_finale_start( params )
function OnGameEvent_player_disconnect( params )
{
{
if ( SessionState.MapName == "c6m3_port" )
local player = GetPlayerFromUserID( params["userid"] );
{
if ( !player )
SessionOptions.cm_TankLimit = 8;
SessionState.TanksDisabled = false;
SessionState.SpawnTankThink = true;
}
if ( SessionState.FinaleType == 4 )
return;
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 )
if ( player.GetZombieType() == 8 && player in SessionState.TankBiled )
{
if ( SessionState.MapName == "c5m5_bridge" || SessionState.MapName == "c13m4_cutthroatcreek" )
{
{
SessionOptions.cm_TankLimit = 8;
SessionState.TankBiled.rawdelete( player );
SessionState.TanksDisabled = false;
if ( SessionState.TankBiled.len() == 0 )
SessionState.SpawnTankThink = true;
SessionState.BileHurtTankThink = false;
}
}
}
function OnGameEvent_finale_vehicle_leaving( params )
{
SessionState.SpawnTankThink = false;
}
}


Line 10,679: Line 11,546:
local attacker = GetPlayerFromUserID( params["attacker"] );
local attacker = GetPlayerFromUserID( params["attacker"] );
local victim = GetPlayerFromUserID( params["userid"] );
local victim = GetPlayerFromUserID( params["userid"] );
 
if ( !attacker || !victim )
if ( !attacker || !victim )
return;
return;
 
if ( attacker.IsSurvivor() && victim.GetZombieType() == 8 )
if ( attacker.IsSurvivor() && victim.GetZombieType() == 8 )
{
{
if ( victim in SessionState.TankBiled )
if ( victim in SessionState.TankBiled )
return;
return;
 
victim.OverrideFriction( Convars.GetFloat( "vomitjar_duration_infected_bot" ), 2.0 );
victim.SetFriction( 2.0 );
SessionState.TankBiled.rawset( victim, attacker );
SessionState.TankBiled.rawset( victim, attacker );
if ( SessionState.TankBiled.len() == 1 )
if ( SessionState.TankBiled.len() == 1 )
Line 10,698: Line 11,565:
{
{
local victim = GetPlayerFromUserID( params["userid"] );
local victim = GetPlayerFromUserID( params["userid"] );
 
if ( !victim )
if ( !victim )
return;
return;
 
if ( victim.GetZombieType() == 8 )
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 ( victim in SessionState.TankBiled )
if ( ZSpawn( { type = 8 } ) )
{
SessionState.LastAlarmTankTime = Time();
SessionState.TankBiled.rawdelete( victim );
if ( SessionState.TankBiled.len() == 0 )
SessionState.BileHurtTankThink = false;
}
}
}
}
}
Line 10,718: Line 11,592:
if ( !tank )
if ( !tank )
return;
return;
 
SessionState.TanksAlive++;
tank.SetMaxHealth( SessionState.TankHealth );
tank.SetMaxHealth( SessionState.TankHealth );
tank.SetHealth( SessionState.TankHealth );
tank.SetHealth( SessionState.TankHealth );
local modelName = tank.GetModelName();
local modelName = tank.GetModelName();
 
if ( !SessionState.ModelCheck )
if ( !SessionState.ModelCheck )
{
{
SessionState.ModelCheck = true;
SessionState.ModelCheck = true;
 
if ( SessionState.TankModelsBase.find( modelName ) == null )
if ( SessionState.TankModelsBase.find( modelName ) == null )
{
{
SessionState.TankModelsBase.append( modelName );
SessionState.TankModelsBase.append( modelName );
SessionState.TankModels.append( modelName );
SessionState.TankModels.append( modelName );
}
}
}
}
 
local tankModels = SessionState.TankModels;
local tankModels = SessionState.TankModels;
if ( tankModels.len() == 0 )
if ( tankModels.len() == 0 )
SessionState.TankModels.extend( SessionState.TankModelsBase );
SessionState.TankModels.extend( SessionState.TankModelsBase );
local foundModel = tankModels.find( modelName );
local foundModel = tankModels.find( modelName );
if ( foundModel != null )
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>}}
 


== See also ==
== See also ==
Line 10,818: Line 11,674:
*[[L4D2 Vscript Examples]]
*[[L4D2 Vscript Examples]]
*[[L4D2_Director_Scripts|L4D2 Director Scripts]]
*[[L4D2_Director_Scripts|L4D2 Director Scripts]]
*[[List of L4D2 Script Functions|L4D2 Script Functions]]
*[[Left 4 Dead 2/Script Functions|L4D2 Script Functions]]
*[[L4D2_Level_Design/Boss_Prohibition|L4D2 Level Design/Boss Prohibition]]
*[[L4D2_Level_Design/Boss_Prohibition|L4D2 Level Design/Boss Prohibition]]
*[[L4D2_Level_Design/Custom_Finale|Custom Finales]] ''Contains all original finale scripts as reference.''
*[[L4D2_Level_Design/Custom_Finale|Custom Finales]] ''Contains all original finale scripts as reference.''
Line 10,828: 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