#using scripts\codescripts\struct;

#using scripts\shared\aat_shared;
#using scripts\shared\ai_puppeteer_shared;
#using scripts\shared\archetype_shared\archetype_shared;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\demo_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\hud_util_shared;
#using scripts\shared\laststand_shared;
#using scripts\shared\lui_shared;
#using scripts\shared\math_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\visionset_mgr_shared;
#using scripts\zm\gametypes\_globallogic;
#using scripts\zm\gametypes\_globallogic_vehicle;

#using scripts\zm\_bb;

#using scripts\shared\ai\systems\gib;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	   	                                                                                                                                                                                                                                  	                                                                                                                                                                                                                                                                                                                                                                                                                                                   
                                                                                                                                                                                                          	     	                                                                                   
                                                                                                            	  	  	              	   	                                                                                                                                       	                                                                                                                                                            	                                                                                                                                                                

#using scripts\zm\gametypes\_weapons;
#using scripts\zm\gametypes\_zm_gametype;
#using scripts\zm\gametypes\_globallogic_spawn;
#using scripts\zm\gametypes\_globallogic_player;

#using scripts\zm\_util;
#using scripts\zm\_zm_attackables;
#using scripts\zm\_zm_audio;
#using scripts\zm\_zm_bgb;
#using scripts\zm\_zm_bgb_token;
#using scripts\zm\_zm_blockers;
#using scripts\zm\_zm_bot;
#using scripts\zm\_zm_daily_challenges;
#using scripts\zm\_zm_equipment;
#using scripts\zm\_zm_ffotd;
#using scripts\zm\_zm_game_module;
//#using scripts\zm\_zm_hero_weapon;
#using scripts\zm\_zm_laststand;
#using scripts\zm\_zm_melee_weapon;
#using scripts\zm\_zm_perks;
#using scripts\zm\_zm_pers_upgrades;
#using scripts\zm\_zm_pers_upgrades_functions;
#using scripts\zm\_zm_pers_upgrades_system;
#using scripts\zm\_zm_placeable_mine;
#using scripts\zm\_zm_player;
#using scripts\zm\_zm_powerups;
#using scripts\zm\_zm_score;
#using scripts\zm\_zm_spawner;
#using scripts\zm\_zm_stats;
#using scripts\zm\_zm_timer;
#using scripts\zm\_zm_unitrigger;
#using scripts\zm\_zm_utility;
#using scripts\zm\_zm_weapons;
#using scripts\zm\_zm_zonemgr;
#using scripts\shared\ai_shared;

// AATs
                              
#using scripts\zm\aats\_zm_aat_blast_furnace;
#using scripts\zm\aats\_zm_aat_dead_wire;
#using scripts\zm\aats\_zm_aat_fire_works;
#using scripts\zm\aats\_zm_aat_thunder_wall;
#using scripts\zm\aats\_zm_aat_turned;

// BGBs
#using scripts\zm\bgbs\_zm_bgb_aftertaste;
#using scripts\zm\bgbs\_zm_bgb_alchemical_antithesis;
#using scripts\zm\bgbs\_zm_bgb_always_done_swiftly;
#using scripts\zm\bgbs\_zm_bgb_anywhere_but_here;
#using scripts\zm\bgbs\_zm_bgb_armamental_accomplishment;
#using scripts\zm\bgbs\_zm_bgb_arms_grace;
#using scripts\zm\bgbs\_zm_bgb_arsenal_accelerator;
#using scripts\zm\bgbs\_zm_bgb_burned_out;
#using scripts\zm\bgbs\_zm_bgb_cache_back;
#using scripts\zm\bgbs\_zm_bgb_coagulant;
#using scripts\zm\bgbs\_zm_bgb_danger_closest;
#using scripts\zm\bgbs\_zm_bgb_dead_of_nuclear_winter;
#using scripts\zm\bgbs\_zm_bgb_ephemeral_enhancement;
#using scripts\zm\bgbs\_zm_bgb_firing_on_all_cylinders;
#using scripts\zm\bgbs\_zm_bgb_im_feelin_lucky;
#using scripts\zm\bgbs\_zm_bgb_immolation_liquidation;
#using scripts\zm\bgbs\_zm_bgb_impatient;
#using scripts\zm\bgbs\_zm_bgb_in_plain_sight;
#using scripts\zm\bgbs\_zm_bgb_kill_joy;
#using scripts\zm\bgbs\_zm_bgb_killing_time;
#using scripts\zm\bgbs\_zm_bgb_licensed_contractor;
#using scripts\zm\bgbs\_zm_bgb_lucky_crit;
#using scripts\zm\bgbs\_zm_bgb_now_you_see_me;
#using scripts\zm\bgbs\_zm_bgb_on_the_house;
#using scripts\zm\bgbs\_zm_bgb_perkaholic;
#using scripts\zm\bgbs\_zm_bgb_phoenix_up;
#using scripts\zm\bgbs\_zm_bgb_pop_shocks;
#using scripts\zm\bgbs\_zm_bgb_respin_cycle;
#using scripts\zm\bgbs\_zm_bgb_stock_option;
#using scripts\zm\bgbs\_zm_bgb_sword_flay;
#using scripts\zm\bgbs\_zm_bgb_unquenchable;
#using scripts\zm\bgbs\_zm_bgb_wall_power;
#using scripts\zm\bgbs\_zm_bgb_whos_keeping_score;

// DLC1 BGBS
#using scripts\zm\bgbs\_zm_bgb_crawl_space;
#using scripts\zm\bgbs\_zm_bgb_fatal_contraption;
#using scripts\zm\bgbs\_zm_bgb_head_drama;
#using scripts\zm\bgbs\_zm_bgb_undead_man_walking;

// DLC2 BGBS
#using scripts\zm\bgbs\_zm_bgb_fear_in_headlights;
#using scripts\zm\bgbs\_zm_bgb_secret_shopper;
#using scripts\zm\bgbs\_zm_bgb_temporal_gift;
#using scripts\zm\bgbs\_zm_bgb_unbearable;

// DLC3 BGBS
#using scripts\zm\bgbs\_zm_bgb_crate_power;
#using scripts\zm\bgbs\_zm_bgb_disorderly_combat;
#using scripts\zm\bgbs\_zm_bgb_projectile_vomiting;
#using scripts\zm\bgbs\_zm_bgb_shopping_free;
#using scripts\zm\bgbs\_zm_bgb_slaughter_slide;

// DLC4 BGBS
#using scripts\zm\bgbs\_zm_bgb_bullet_boost;
#using scripts\zm\bgbs\_zm_bgb_mind_blown;
#using scripts\zm\bgbs\_zm_bgb_near_death_experience;
#using scripts\zm\bgbs\_zm_bgb_newtonian_negation;
#using scripts\zm\bgbs\_zm_bgb_profit_sharing;
#using scripts\zm\bgbs\_zm_bgb_round_robbin;
#using scripts\zm\bgbs\_zm_bgb_self_medication;

// DLC5 BGBS
#using scripts\zm\bgbs\_zm_bgb_board_games;
#using scripts\zm\bgbs\_zm_bgb_board_to_death;
#using scripts\zm\bgbs\_zm_bgb_extra_credit;
#using scripts\zm\bgbs\_zm_bgb_eye_candy;
#using scripts\zm\bgbs\_zm_bgb_flavor_hexed;
#using scripts\zm\bgbs\_zm_bgb_idle_eyes;
#using scripts\zm\bgbs\_zm_bgb_power_vacuum;
#using scripts\zm\bgbs\_zm_bgb_reign_drops;
#using scripts\zm\bgbs\_zm_bgb_soda_fountain;
#using scripts\zm\bgbs\_zm_bgb_tone_death;

// DLC5 BGB REQUIRED POWERUPS
#using scripts\zm\_zm_powerup_bonus_points_player;

#using scripts\zm\craftables\_zm_craftables;

#using scripts\shared\ai\zombie_death;
#using scripts\shared\ai\zombie_utility;

                                                                                                           	                                      	                	                       	              	              	                          	                                                                                                                                                                                                          
  
                                                                                       	                                
       
                                                                                                                                                                                              	                                                                                                                                                                                                                                                                                                                                                                                                                  	                                                                                              	                                                                                                                                                                                                                                                                                                                                                                                                                                                                              	                                                                                                                                                                                                                                                                                                                                                       	                                                  

#precache( "material", "hud_chalk_1" );
#precache( "material", "hud_chalk_2" );
#precache( "material", "hud_chalk_3" );
#precache( "material", "hud_chalk_4" );
#precache( "material", "hud_chalk_5" );

#precache( "material", "zom_icon_community_pot" );
#precache( "material", "zom_icon_community_pot_strip" );

#precache( "material","zom_icon_player_life");

#precache( "string", "ZOMBIE_WEAPONCOSTAMMO" );
#precache( "string", "ZOMBIE_ROUND" );
#precache( "string", "SCRIPT_PLUS" );
#precache( "string", "ZOMBIE_GAME_OVER" );
#precache( "string", "ZOMBIE_SURVIVED_ROUND" );
#precache( "string", "ZOMBIE_SURVIVED_ROUNDS" );
#precache( "string", "ZOMBIE_SURVIVED_NOMANS" );
#precache( "string", "ZOMBIE_EXTRA_LIFE" );
#precache( "string", "ZOMBIE_UNDEFINED" );
#precache( "triggerstring", "ZOMBIE_ELECTRIC_SWITCH" );
#precache( "triggerstring", "ZOMBIE_NEED_POWER" );
// Random Treasure Chest
#precache( "string", "ZOMBIE_RANDOM_WEAPON_COST" );
// Weapons
#precache( "string", "ZOMBIE_WEAPON_COSTONLYFILL_500" );
#precache( "triggerstring", "ZOMBIE_WEAPON_COSTONLYFILL_2000" );
#precache( "triggerstring", "ZOMBIE_WEAPONAMMOONLY_250" );
#precache( "triggerstring", "ZOMBIE_WEAPONCOSTAMMO_UPGRADE_500_250" );
// Barrier Pieces
#precache( "string", "ZOMBIE_BUTTON_BUY_BACK_BARRIER_10" );
#precache( "string", "ZOMBIE_BUTTON_BUY_BACK_BARRIER_20" );
#precache( "string", "ZOMBIE_BUTTON_BUY_BACK_BARRIER_50" );
#precache( "string", "ZOMBIE_BUTTON_BUY_BACK_BARRIER_100" );
// REWARD Barrier Pieces
#precache( "string", "ZOMBIE_BUTTON_REWARD_BARRIER" );
// Debris
#precache( "string", "ZOMBIE_BUTTON_BUY_CLEAR_DEBRIS_COST" );
// Doors
#precache( "string", "ZOMBIE_BUTTON_BUY_OPEN_DOOR_COST" );
#precache( "string", "ZOMBIE_BUTTON_BUY_CLOSE_DOOR" );

#precache( "triggerstring", "ZOMBIE_BUTTON_REWARD_BARRIER" );

#precache( "fx", "_t6/maps/zombie/fx_zombie_bar_break" );
#precache( "fx", "_t6/maps/zombie/fx_zombie_bar_break_lite" );
#precache( "fx", "_t6/maps/zombie/fx_fog_zombie_amb" );
#precache( "fx", "zombie/fx_weapon_box_open_glow_zmb" );
#precache( "fx", "zombie/fx_weapon_box_closed_glow_zmb" );
#precache( "fx", "zombie/fx_glow_eye_orange" );
#precache( "fx", "zombie/fx_bul_flesh_head_fatal_zmb" );
#precache( "fx", "zombie/fx_bul_flesh_head_nochunks_zmb" );
#precache( "fx", "zombie/fx_bul_flesh_neck_spurt_zmb" );
#precache( "fx", "_t6/maps/zombie/fx_zombie_tesla_neck_spurt" );
#precache( "fx", "zombie/fx_blood_torso_explo_zmb" );
#precache( "fx", "zombie/fx_blood_torso_explo_lg_zmb" );
#precache( "fx", "zombie/fx_spawn_dirt_hand_burst_zmb" );
#precache( "fx", "zombie/fx_spawn_dirt_body_billowing_zmb" );
#precache( "fx", "zombie/fx_spawn_dirt_body_dustfalling_zmb" );	
#precache( "fx", "zombie/fx_fire_torso_zmb" );
#precache( "fx", "_t6/explosions/fx_default_explosion" );
#precache( "fx", "_t6/maps/zombie/fx_zmb_tanzit_upgrade" );

#precache( "menu", "StartMenu_Main" );
#precache( "menu", "InitialBlack" );

#precache( "eventstring", "force_scoreboard" );
#precache( "eventstring", "open_ingame_menu" );
#precache( "eventstring", "play_promo_anim" );

#precache( "string", "ZOMBIE_REVIVING_SOLO", "ZOMBIE_PLAYER_NAME_0" );
#precache( "string", "ZOMBIE_REVIVING_SOLO", "ZOMBIE_PLAYER_NAME_1" );
#precache( "string", "ZOMBIE_REVIVING_SOLO", "ZOMBIE_PLAYER_NAME_2" );
#precache( "string", "ZOMBIE_REVIVING_SOLO", "ZOMBIE_PLAYER_NAME_3" );

#namespace zm;

function autoexec ignore_systems()
{
	//shutdown unwanted systems - doing it in an autoexec is the only clean way to do it
	system::ignore("gadget_clone");
	system::ignore("gadget_armor");
	system::ignore("gadget_heat_wave");
	system::ignore("gadget_resurrect");
	system::ignore("gadget_shock_field");
	system::ignore("gadget_active_camo");
	system::ignore("gadget_mrpukey");
	system::ignore("gadget_misdirection");
	system::ignore("gadget_smokescreen");
	system::ignore("gadget_firefly_swarm");
	system::ignore("gadget_immolation");
	system::ignore("gadget_forced_malfunction");
	system::ignore("gadget_sensory_overload");
	system::ignore("gadget_rapid_strike");
	system::ignore("gadget_unstoppable_force");
	system::ignore("gadget_overdrive");
	system::ignore("gadget_concussive_wave");
	system::ignore("gadget_ravage_core");
	system::ignore("gadget_es_strike");
	system::ignore("gadget_cacophany");
	system::ignore("gadget_iff_override");
	system::ignore("gadget_security_breach");
	system::ignore("gadget_surge");
	system::ignore("gadget_exo_breakdown");
	system::ignore("gadget_servo_shortout");
	system::ignore("gadget_system_overload");
	system::ignore("gadget_cleanse");
	system::ignore("gadget_flashback");
	system::ignore("gadget_combat_efficiency");
	system::ignore("gadget_other");
	system::ignore("gadget_camo");
	system::ignore("gadget_vision_pulse");
	system::ignore("gadget_speed_burst");
	system::ignore("gadget_thief");
	system::ignore("replay_gun");
	system::ignore("spike_charge_siegebot");
	system::ignore("siegebot");
	system::ignore("amws");
	
}



function autoexec __init__sytem__() {     system::register("zm",&__init__,undefined,undefined);    }
	
function __init__()
{
	if(!isdefined(level.zombie_vars))level.zombie_vars=[];
}

function init()
{	
	//New movement disabled for Zombies
	SetDvar( "doublejump_enabled", 0 );
	SetDvar( "juke_enabled", 0 );
	SetDvar( "playerEnergy_enabled", 0 );
	SetDvar( "wallrun_enabled", 0 );
	SetDvar( "sprintLeap_enabled", 0 );
	SetDvar( "traverse_mode", 2 );
	SetDvar( "weaponrest_enabled", 0 );
	
	//we dont care about CP movie skipping logic in ZM
	SetDvar( "ui_allowDisplayContinue", true );

	if ( !IsDefined( level.killstreakWeapons ) )
	{
		level.killstreakWeapons = [];
	}

	level.weaponNone = GetWeapon( "none" );
	level.weaponNull = GetWeapon( "weapon_null" );
	level.weaponBaseMelee = GetWeapon( "knife" );
	level.weaponBaseMeleeHeld = GetWeapon( "knife_held" );
	level.weaponBallisticKnife = GetWeapon( "knife_ballistic" );
	if(!isdefined(level.weaponRiotshield))level.weaponRiotshield=GetWeapon( "riotshield" );
	level.weaponReviveTool = GetWeapon( "syrette" );
	level.weaponZMDeathThroe = GetWeapon( "death_throe" );
	level.weaponZMFists = GetWeapon( "zombie_fists" );

	if(!isdefined(level.giveCustomLoadout))level.giveCustomLoadout=&zm_weapons::give_start_weapons;
	
	level.projectiles_should_ignore_world_pause = true;
	
	level.player_out_of_playable_area_monitor = true;	
	level.player_too_many_weapons_monitor = true;
	level.player_too_many_weapons_monitor_func = &player_too_many_weapons_monitor;

	level.player_too_many_players_check = true;
	level.player_too_many_players_check_func = &player_too_many_players_check;
	
	level._use_choke_weapon_hints = 1;
	level._use_choke_blockers = 1;

	level.speed_change_round = 15;
	
	level.passed_introscreen = false;
		
	if(!isdefined(level.custom_ai_type))
	{
		level.custom_ai_type = [];
	}
	
	level.custom_ai_spawn_check_funcs = [];

	// put things you'd like to be able to turn off in here above this line
	level thread zm_ffotd::main_start();
	
	level.zombiemode = true;
	level.reviveFeature = false;
	level.swimmingFeature = false;
	level.calc_closest_player_using_paths = false;
	level.zombie_melee_in_water = true;
	level.put_timed_out_zombies_back_in_queue = true;
	level.use_alternate_poi_positioning = true;
	level.zmb_laugh_alias = "zmb_laugh_child";
	level.sndAnnouncerIsRich = true;

	level.scr_zm_ui_gametype = GetDvarString( "ui_gametype" );
	level.scr_zm_ui_gametype_group = "";//GetDvarString( "ui_zm_gamemodegroup" );
	level.scr_zm_map_start_location = "";//GetDvarString( "ui_zm_mapstartlocation" );

	level.curr_gametype_affects_rank = false;
	gametype = toLower( GetDvarString( "g_gametype" ) );
	if ( "zclassic" == gametype || "zstandard" == gametype )
	{
		level.curr_gametype_affects_rank = true;
	}

	level.grenade_multiattack_bookmark_count = 1;
	demo::initActorBookmarkParams( 3, 6000, 6000 );
		
	// set up any gameplay modes
/*	level.GAME_MODULE_CLASSIC_INDEX = 0;
	zm_game_module::register_game_module(level.GAME_MODULE_CLASSIC_INDEX,"classic",undefined,undefined);	
	zm_game_module::set_current_game_module(level.scr_zm_game_module); */
	
	// Allowing custom round_spawn_failsafe function to be set here.
	if(	!isdefined(	level._zombies_round_spawn_failsafe	)	)
	{
		level._zombies_round_spawn_failsafe = &zombie_utility::round_spawn_failsafe;
	}

	level.func_get_zombie_spawn_delay = &get_zombie_spawn_delay;
	level.func_get_delay_between_rounds = &get_delay_between_rounds;

	level.zombie_visionset = "zombie_neutral";

	level.wait_and_revive = false;

	if(GetDvarString("anim_intro") == "1")
	{
		level.zombie_anim_intro = 1;
	}
	else
	{
		level.zombie_anim_intro = 0;
	}

	precache_models();
	
	precache_zombie_leaderboards();

	level._ZOMBIE_GIB_PIECE_INDEX_ALL = 0;
	level._ZOMBIE_GIB_PIECE_INDEX_RIGHT_ARM = 1;
	level._ZOMBIE_GIB_PIECE_INDEX_LEFT_ARM = 2;
	level._ZOMBIE_GIB_PIECE_INDEX_RIGHT_LEG = 3;
	level._ZOMBIE_GIB_PIECE_INDEX_LEFT_LEG = 4;
	level._ZOMBIE_GIB_PIECE_INDEX_HEAD = 5;
	level._ZOMBIE_GIB_PIECE_INDEX_GUTS = 6;
	level._ZOMBIE_GIB_PIECE_INDEX_HAT = 7;

	//Limit zombie to 24 max, must have for network purposes
	if ( !isdefined( level.zombie_ai_limit ) )
	{
		level.zombie_ai_limit = 24;
	}	
	if ( !isdefined( level.zombie_actor_limit ) )
	{
		level.zombie_actor_limit = 31;
	}
	
	init_flags();
	init_dvars();
	init_strings();
	init_levelvars();
	init_sounds();
	init_shellshocks();
	init_client_field_callback_funcs();
	
	zm_utility::register_offhand_weapons_for_level_defaults();

	level thread drive_client_connected_notifies();
	
	// load map defaults

	//Systems
	zm_craftables::init();	
	zm_perks::init();
	zm_powerups::init();
	zm_spawner::init();
	zm_weapons::init();
	
	//Grab all static poi in the map
	level.zombie_poi_array = getEntArray( "zombie_poi", "script_noteworthy" );

	
	init_function_overrides();
	
	// ww: init the pistols in the game so last stand has the importance order
	level thread last_stand_pistol_rank_init();
	
	level thread post_all_players_connected();
	
	level start_zm_dash_counter_watchers();
	
	zm_utility::init_utility();

	util::registerClientSys( "lsm" );

	// STATS_TODO: will check if the following is needed later
	initializeStatTracking();	
	
	if ( GetPlayers().size <= 1 )
	{
		incrementCounter( "global_solo_games", 1 );
	}
	else if( ( isdefined( level.systemLink ) && level.systemLink ) )
	{
		incrementCounter( "global_systemlink_games", 1 );
	}
	else if ( GetDvarInt( "splitscreen_playerCount" ) == GetPlayers().size )
	{
		incrementCounter( "global_splitscreen_games", 1 );
	}
	else // coop game
	{
		incrementCounter( "global_coop_games", 1 );
	}
	
	callback::on_connect( &zm_on_player_connect);

	//self LUINotifyEvent( &"score_event", 3, label, score, rampageBonus );

	// Initialize persistent upgrades
	//zm_pers_upgrades::pers_upgrade_init();
	
	zm_utility::set_demo_intermission_point();

	level thread zm_ffotd::main_end();
	level thread zm_utility::track_players_intersection_tracker();
	level thread onAllPlayersReady();
	level thread startUnitriggers();
	
	level thread confuse_third_party_trainers();
	
	callback::on_spawned( &zm_on_player_spawned );
	printHashIDs();
}

function post_main()
{
	level thread init_custom_ai_type();	
}

function cheat_enabled( val )
{
	if ( GetDvarInt( "zombie_cheat" ) >= val )
	{
/#
		return true;
#/

		if ( IsProfileBuild() )
		{
			return true;
		}
	}

	return false;
}

function confuse_third_party_trainers()
{
	level flag::wait_till_any( array( "start_zombie_round_logic", "start_encounters_match_logic" ) );
	while(1)
	{
		temp_var = get_round_number(); 
		level.round_number = undefined; 
		temp_var2 = temp_var; 
		switch( RandomInt(5) )
		{
			case 0:
				temp_var3 = temp_var; 
			case 1:
				temp_var4 = temp_var; 
			case 2:
				temp_var5 = temp_var; 
			case 3:
				temp_var6 = temp_var; 
			case 4:
				temp_var7 = temp_var; 
		}
		level.round_number = temp_var; 
		temp_var = undefined; 
		temp_var1 = undefined; 
		temp_var2 = undefined; 
		temp_var3 = undefined; 
		temp_var4 = undefined; 
		temp_var5 = undefined; 
		temp_var6 = undefined; 
		temp_var7 = undefined; 
		
		
		{wait(.05);}; 
	}
}

function set_round_number( new_round ) 
{
	if ( new_round > 255 )
		new_round = 255; 
	world.roundnumber = new_round ^ 115; 
}

function get_round_number() 
{
	return world.roundnumber ^ 115; 
}


function startUnitriggers()
{
	level flag::wait_till_any( array( "start_zombie_round_logic", "start_encounters_match_logic" ) );
	level thread zm_unitrigger::main();
}

function drive_client_connected_notifies()
{
	while(1)
	{
		level waittill("connected", player);
		player demo::reset_actor_bookmark_kill_times();
		player callback::callback( #"on_player_connect" );
	}
}

function fade_out_intro_screen_zm( hold_black_time, fade_out_time, destroyed_afterwards )
{
	lui::screen_fade_out( 0, undefined );

	if( IsDefined( hold_black_time ) )
	{
		wait hold_black_time;
	}
	else
	{
		wait 0.2;
	}

	if( !IsDefined( fade_out_time ) )
	{
		fade_out_time = 1.5;
	}
	
	array::thread_all(GetPlayers(),	&initialBlackEnd);
	level clientfield::set( "sndZMBFadeIn", 1 );
	lui::screen_fade_in( fade_out_time, undefined );
	
	//level notify("fade_introblack");

	wait 1.6;

	level.passed_introscreen = true;

	players = GetPlayers();
	for(i = 0; i < players.size; i++)
	{
		if( IsDefined( level.customHudReveal ) )
		{
			players[i] thread [[level.customHudReveal]]();
		}
		else
		{
			players[i] ShowHudAndPlayPromo();
		}
		
		if(!( isdefined( level.host_ended_game ) && level.host_ended_game ))
		{
			if (isdefined(level.player_movement_suppressed))
			{
				players[i] FreezeControls(level.player_movement_suppressed);
				/# println(" Unfreeze controls 4"); #/
				
			}
			else
			{
				if(!( isdefined( players[i].hostMigrationControlsFrozen ) && players[i].hostMigrationControlsFrozen ))
				{
					players[i] FreezeControls(false);
					
					/# println(" Unfreeze controls 5"); #/
				}
			}
		}
	}
	// level notify("fade_in_complete");
	
	level flag::set("initial_blackscreen_passed");
	level clientfield::set("gameplay_started", 1);
}

function ShowHudAndPlayPromo()
{
	self setClientUIVisibilityFlag( "hud_visible", 1 );
	self setClientUIVisibilityFlag( "weapon_hud_visible", 1 );
	
	if( !( isdefined( self.seen_promo_anim ) && self.seen_promo_anim ) && SessionModeIsOnlineGame() )
	{
		self LUINotifyEvent( &"play_promo_anim", 0 );
		self.seen_promo_anim = true;
	}
}

function onAllPlayersReady()
{
	
	timeOut = GetTime() + 5000;	// 5 second time out.
	
	while(IsLoadingCinematicPlaying() || (GetNumExpectedPlayers() == 0  && (GetTime() < timeOut)))
	{
		wait(0.1);
	}
	
	/#	println( "ZM >> player_count_expected=" + GetNumExpectedPlayers());		#/
	player_count_actual = 0;
	while( (GetNumConnectedPlayers() < GetNumExpectedPlayers()) || (player_count_actual != GetNumExpectedPlayers()) )
	{
		players = GetPlayers();
		player_count_actual = 0;
		for( i = 0; i < players.size; i++ )
		{
			players[i] FreezeControls( true );
			if( players[i].sessionstate == "playing" )
			{
				player_count_actual++;
			} 
		}
	
		/#	println( "ZM >> Num Connected =" + GetNumConnectedPlayers() + " Expected : " + GetNumExpectedPlayers());	#/
		wait( 0.1 );	
	}

	SetInitialPlayersConnected();

	level flag::set( "all_players_connected" );
	SetDvar( "all_players_are_connected", "1" );

	/#	println( "ZM >> We have all players - START ZOMBIE LOGIC" );	#/


	
	//Check to see if we should spawn some bots to help
	if ( (1 == GetNumConnectedPlayers()) && (GetDvarInt( "scr_zm_enable_bots" )==1) )
	{
		level thread add_bots();
		level flag::set("initial_players_connected");
	}
	else
	{
		players = GetPlayers();
		if ( players.size == 1 )
		{
			level flag::set( "solo_game" );
			level.solo_lives_given = 0;
			foreach ( player in players )
			{
				player.lives = 0;
			}
			level zm::set_default_laststand_pistol( true );
		}

		level flag::set("initial_players_connected");

		array::thread_all(GetPlayers(),	&initialBlack);
		
		while ( !AreTexturesLoaded() )
		{
			{wait(.05);};
		}

		if ( isdefined(level.added_initial_streamer_blackscreen) )
		{
			wait(level.added_initial_streamer_blackscreen); 
		}
		
		//level flag::set( "start_zombie_round_logic" );
		thread start_zombie_logic_in_x_sec( 3.0 );
	}

	set_intermission_point();
	
	n_black_screen = 5.0;
	level thread fade_out_intro_screen_zm( n_black_screen, 1.5, true );
	wait n_black_screen;

	// Reset the start time
	level.n_gameplay_start_time = GetTime();
	clientfield::set( "game_start_time", level.n_gameplay_start_time );
}

function initialBlack()
{
	self CloseMenu( "InitialBlack" );	
	self OpenMenu( "InitialBlack" );	
}

function initialBlackEnd()
{
	self CloseMenu( "InitialBlack" );	
//	self clientfield::set_to_player( "sndLevelStartSnapOff", 1 );
}



function start_zombie_logic_in_x_sec( time_to_wait )
{
	wait( time_to_wait );
	level flag::set( "start_zombie_round_logic" );
}


function getAllOtherPlayers()
{
	aliveplayers = [];

	// Make a list of fully connected, non-spectating, alive players
	for(i = 0; i < level.players.size; i++)
	{
		if ( !isdefined( level.players[i] ) )
			continue;
		player = level.players[i];
		
		if ( player.sessionstate != "playing" || player == self )
			continue;

		aliveplayers[aliveplayers.size] = player;
	}
	return aliveplayers;
}

function updatePlayerNum( player )
{
	if(!Isdefined(player.playernum))
	{
		if(player.team == "allies")
		{
			player.playernum = zm_utility::get_game_var("_team1_num");
			zm_utility::set_game_var("_team1_num", player.playernum + 1);
		}	
		else
		{
			player.playernum = zm_utility::get_game_var("_team2_num");
			zm_utility::set_game_var("_team2_num", player.playernum + 1);
		}	
	}	
}

function getFreeSpawnpoint(spawnpoints, player)
{
	// There are no valid spawnpoints in the map
	if(!isdefined(spawnpoints))
	{
/#
		iprintlnbold( "ZM >> No free spawn points in map" );
#/
		return undefined;
	}

	// only should happen on initial spawn.
	
	if(!isdefined(game["spawns_randomized"]))
	{
		game["spawns_randomized"] = true;
		
		spawnpoints = array::randomize(spawnpoints);
		
		random_chance = randomint(100);
		if(random_chance > 50)
		{
			zm_utility::set_game_var("side_selection", 1);
		}
		else
		{
			zm_utility::set_game_var("side_selection", 2);
		}
	}	
	
	side_selection = zm_utility::get_game_var("side_selection");
	
	// used in vs. games where you switch sides on next round.
	if( zm_utility::get_game_var("switchedsides"))
	{
		if(side_selection == 2)
		{
			side_selection = 1;
		}
		else if(side_selection == 1)
		{
			side_selection = 2;
		}
	}
	
	if(IsDefined(player) && IsDefined(player.team))
	{
		i = 0; 
		while(IsDefined(spawnpoints) && i < spawnpoints.size)
		{
			If(side_selection == 1)
			{
				if(player.team != "allies" && (IsDefined(spawnpoints[i].script_int) && spawnpoints[i].script_int == 1))
				{
					ArrayRemoveValue(spawnpoints, spawnpoints[i]);
					i=0;
				}
				else if(player.team == "allies" && (IsDefined(spawnpoints[i].script_int) && spawnpoints[i].script_int == 2))
				{
					ArrayRemoveValue(spawnpoints, spawnpoints[i]);
					i=0;
				}
				else 
				{
					i++;
				}
			}	
			else
			{
				if(player.team == "allies" && (IsDefined(spawnpoints[i].script_int) && spawnpoints[i].script_int == 1))
				{
					ArrayRemoveValue(spawnpoints, spawnpoints[i]);
					i=0;
				}
				else if(player.team != "allies" && (IsDefined(spawnpoints[i].script_int) && spawnpoints[i].script_int == 2))
				{
					ArrayRemoveValue(spawnpoints, spawnpoints[i]);
					i=0;
				}
				else 
				{
					i++;
				}
			}		
		}	
	}	

	updatePlayerNum( player );

	for( j = 0; j < spawnpoints.size; j++ )
	{
		if(!IsDefined(spawnpoints[j].en_num))
		{
			for( m = 0; m < spawnpoints.size; m++ )
			{
				spawnpoints[m].en_num = m;
			}	
		}	

		if(	spawnpoints[j].en_num == player.playernum)
		{
			return spawnpoints[j];
		}	
	}

	return spawnpoints[0];
}


function delete_in_createfx()
{
	exterior_goals =struct::get_array( "exterior_goal", "targetname" );
	for( i = 0; i < exterior_goals.size; i++ )
	{
		if( !IsDefined( exterior_goals[i].target ) ) // If the exterior_goal entity has no targets defined then return
		{
			continue;
		}
		targets = GetEntArray( exterior_goals[i].target, "targetname" ); // Grab all the pieces that are targeted by the exterior_goal

		for( j = 0; j < targets.size; j++ ) // count total targets of exterior_goal
		{
			targets[j] zm_utility::self_delete();
		}
	}

	if(isdefined(level.level_createfx_callback_thread))
	{
		level thread [[level.level_createfx_callback_thread]]();
	}
}



function add_bots()
{
	//Wait for the host!
	host = util::GetHostPlayer();
	while ( !IsDefined( host ) )
	{
		{wait(.05);};
		host = util::GetHostPlayer();
	}
	
	wait( 4.0 );
	
	//Then spawn bots
	zbot_spawn();
	SetDvar("bot_AllowMovement", "1");
	SetDvar("bot_PressAttackBtn", "1");
	SetDvar("bot_PressMeleeBtn", "1");
	
	//Wait until bots are spawned
	while( GetPlayers().size<2 )
	{
		{wait(.05);};
	}
	
	//Unfreeze all players
	players = GetPlayers();
	for( i = 0; i < players.size; i++ )
	{
		players[i] FreezeControls( false );
		/# println(" Unfreeze controls 6"); #/
		
	}

	level.numberBotsAdded = 1;
	level flag::set( "start_zombie_round_logic" );
}

function zbot_spawn()
{
	player = util::GetHostPlayer();
	
	//spawnPoints =struct::get_array( "initial_spawn_points", "targetname" ); 
	//spawnPoint =  getFreeSpawnpoint( spawnPoints, player );
	
	bot = AddTestClient();	
	if ( !IsDefined( bot ) )
	{
	/#	println( "Could not add test client" );	#/
		return;
	}
			
	
	spawnPoint = bot zm_gametype::onFindValidSpawnPoint();
	
	bot.pers["isBot"] = true;
	bot.equipment_enabled = false;
	yaw = spawnPoint.angles[1];
	
	return bot;
}

function post_all_players_connected()
{
	level thread end_game();
	
	level flag::wait_till( "start_zombie_round_logic" );
	
	zm_utility::increment_zm_dash_counter( "start_per_game", 1 );
	zm_utility::increment_zm_dash_counter( "start_per_player", level.players.size );	
	zm_utility::upload_zm_dash_counters();
	
	level.dash_counter_start_player_count = level.players.size;
/#
	println( "sessions: mapname=", level.script, " gametype zom isserver 1 player_count=", GetPlayers().size );
#/
	// Start the Zombie MODE!
	level thread round_end_monitor();
	
	if(!level.zombie_anim_intro)
	{
		if(isDefined(level._round_start_func))
		{
			level thread [[level._round_start_func]]();
		}
	}

	level thread players_playing();

	DisableGrenadeSuicide();
	level.startInvulnerableTime = GetDvarInt( "player_deathInvulnerableTime" );
}

function start_zm_dash_counter_watchers()
{	
	level thread first_consumables_used_watcher();
	level thread players_reached_rounds_counter_watcher();
}

function first_consumables_used_watcher()
{
	level flag::init( "first_consumables_used" );
	
	level flag::wait_till( "first_consumables_used" );
	
	zm_utility::increment_zm_dash_counter( "first_consumables_used", 1 );
	zm_utility::upload_zm_dash_counters();
}

function players_reached_rounds_counter_watcher()
{
	while( true )
	{
		level waittill( "start_of_round" );
		
		if( !IsDefined( level.dash_counter_round_reached_5 ) && level.round_number >= 5 )
		{
			level.dash_counter_round_reached_5 = true;
			zm_utility::increment_zm_dash_counter( "reached_5", 1 );
		}
		
		if( !IsDefined( level.dash_counter_round_reached_10 ) && level.round_number >= 10 )
		{
			level.dash_counter_round_reached_10 = true;
			zm_utility::increment_zm_dash_counter( "reached_10", 1 );
			return;
		}
	}
}

function init_custom_ai_type()
{
	//wait( 0.1 );
//	waittillframeend;

	if( isDefined( level.custom_ai_type ) )
	{
		for( i = 0; i < level.custom_ai_type.size; i++ )
		{
			[[ level.custom_ai_type[i] ]]();
		}
	}
}
	
function zombiemode_melee_miss()
{
	if( isDefined( self.enemy.curr_pay_turret ) )
	{
		self.enemy doDamage( GetDvarInt( "ai_meleeDamage" ), self.origin, self, self, "none", "melee" );
	}
}

/*------------------------------------
function chrisp - adding vo to track players ammo
------------------------------------*/
function player_track_ammo_count()
{
	self notify( "stop_ammo_tracking" );
	self endon( "disconnect" );
	self endon( "stop_ammo_tracking" );
	
	ammoLowCount = 0;
	ammoOutCount = 0;

	while ( 1 )
	{
		wait( .5 );
        weapon = self getcurrentweapon();
        
		if ( weapon == level.weaponNone || weapon.skiplowammovox )
		{
			continue;
		}
		
		if( weapon.type == "grenade" )
		{
			continue;
		}

		if ( self GetAmmoCount( weapon ) > 5 || self laststand::player_is_in_laststand() )
		{
			ammoOutCount = 0;
			ammoLowCount = 0;
			continue;
		}		
		
		if ( self GetAmmoCount( weapon ) > 0 )
		{
			if ( ammoLowCount < 1 )
			{
				self zm_audio::create_and_play_dialog( "general", "ammo_low" );
				ammoLowCount++;
			}
		}
		else
		{
			if ( ammoOutCount < 1 )
			{
				wait ( .5 );
				
				if( (self getcurrentweapon() ) !== weapon ) //This prevents "ammo out" vo from playing in the case that we swap a weapon from the box.
				{
					continue;
				}
				
				self zm_audio::create_and_play_dialog( "general", "ammo_out" );
				ammoOutCount++;
			}
		}
		wait( 20 );	
	}	
}

/*------------------------------------
function audio plays when more than 1 player connects
------------------------------------*/
function spawn_vo()
{
	//not sure if we need this
	wait(1);
	
	players = GetPlayers();
	
	//just pick a random player for now and play some vo 
	if(players.size > 1)
	{
		player = array::random(players);
		index = zm_utility::get_player_index(player);
		player thread spawn_vo_player(index,players.size);
	}

}

function spawn_vo_player(index,num)
{
	sound = "plr_" + index + "_vox_" + num +"play";
	self PlaySoundWithNotify(sound, "sound_done");			
	self waittill("sound_done");
}

function precache_models()
{
	if ( isDefined( level.precacheCustomCharacters ) )
	{
		self [[ level.precacheCustomCharacters ]]();
	}
}

function init_shellshocks()
{
	level.player_killed_shellshock = "zombie_death";
}

function init_strings()
{
	zm_utility::add_zombie_hint( "undefined", &"ZOMBIE_UNDEFINED" );

	// Random Treasure Chest
	zm_utility::add_zombie_hint( "default_treasure_chest", &"ZOMBIE_RANDOM_WEAPON_COST" );

	// Barrier Pieces
	zm_utility::add_zombie_hint( "default_buy_barrier_piece_10", &"ZOMBIE_BUTTON_BUY_BACK_BARRIER_10" );
	zm_utility::add_zombie_hint( "default_buy_barrier_piece_20", &"ZOMBIE_BUTTON_BUY_BACK_BARRIER_20" );
	zm_utility::add_zombie_hint( "default_buy_barrier_piece_50", &"ZOMBIE_BUTTON_BUY_BACK_BARRIER_50" );
	zm_utility::add_zombie_hint( "default_buy_barrier_piece_100", &"ZOMBIE_BUTTON_BUY_BACK_BARRIER_100" );

	// REWARD Barrier Pieces
	zm_utility::add_zombie_hint( "default_reward_barrier_piece", &"ZOMBIE_BUTTON_REWARD_BARRIER" );
	
	// Areas
	zm_utility::add_zombie_hint( "default_buy_area", &"ZOMBIE_BUTTON_BUY_OPEN_AREA_COST" );
}

function init_sounds()
{
	zm_utility::add_sound( "end_of_round", "mus_zmb_round_over" );
	zm_utility::add_sound( "end_of_game", "mus_zmb_game_over" ); //Had to remove this and add a music state switch so that we can add other musical elements.
	zm_utility::add_sound( "chalk_one_up", "mus_zmb_chalk" );
	zm_utility::add_sound( "purchase", "zmb_cha_ching" );
	zm_utility::add_sound( "no_purchase", "zmb_no_cha_ching" );

	// Zombification
	// TODO need to vary these up
	zm_utility::add_sound( "playerzombie_usebutton_sound", "zmb_zombie_vocals_attack" );
	zm_utility::add_sound( "playerzombie_attackbutton_sound", "zmb_zombie_vocals_attack" );
	zm_utility::add_sound( "playerzombie_adsbutton_sound", "zmb_zombie_vocals_attack" );

	// Head gib
	zm_utility::add_sound( "zombie_head_gib", "zmb_zombie_head_gib" );

	// Blockers
	zm_utility::add_sound( "rebuild_barrier_piece", "zmb_repair_boards" );
	zm_utility::add_sound( "rebuild_barrier_metal_piece", "zmb_metal_repair" );
	zm_utility::add_sound( "rebuild_barrier_hover", "zmb_boards_float" );
	zm_utility::add_sound( "debris_hover_loop", "zmb_couch_loop" );
	zm_utility::add_sound( "break_barrier_piece", "zmb_break_boards" );
	zm_utility::add_sound( "grab_metal_bar", "zmb_bar_pull" );
	zm_utility::add_sound( "break_metal_bar", "zmb_bar_break" );
	zm_utility::add_sound( "drop_metal_bar", "zmb_bar_drop" );
	zm_utility::add_sound("blocker_end_move", "zmb_board_slam");
	zm_utility::add_sound( "barrier_rebuild_slam", "zmb_board_slam" );
	zm_utility::add_sound( "bar_rebuild_slam", "zmb_bar_repair" );
	zm_utility::add_sound( "zmb_rock_fix", "zmb_break_rock_barrier_fix" );
	zm_utility::add_sound( "zmb_vent_fix", "evt_vent_slat_repair" );
	zm_utility::add_sound ("zmb_barrier_debris_move", "zmb_barrier_debris_move");

	// Doors
	zm_utility::add_sound( "door_slide_open", "zmb_door_slide_open" );
	zm_utility::add_sound( "door_rotate_open", "zmb_door_slide_open" );

	// Debris
	zm_utility::add_sound( "debris_move", "zmb_weap_wall" );

	// Random Weapon Chest
	zm_utility::add_sound( "open_chest", "zmb_lid_open" );
	zm_utility::add_sound( "music_chest", "zmb_music_box" );
	zm_utility::add_sound( "close_chest", "zmb_lid_close" );

	// Weapons on walls
	zm_utility::add_sound( "weapon_show", "zmb_weap_wall" );
	
	zm_utility::add_sound( "break_stone", "evt_break_stone" );
}

function init_levelvars()
{
	// Variables
	// used to a check in last stand for players to become zombies
	level.is_zombie_level				= true; 
	level.default_laststandpistol 		= GetWeapon( "pistol_standard" );
	level.default_solo_laststandpistol	= GetWeapon( "pistol_standard_upgraded" );
	level.super_ee_weapon				= GetWeapon( "pistol_burst" );
	level.laststandpistol				= level.default_laststandpistol;		// so we dont get the uber colt when we're knocked out
	level.start_weapon					= level.default_laststandpistol;
	level.first_round					= true;	
	level.start_round					= GetGametypeSetting( "startRound" );
	level.round_number					= level.start_round;
	level.enable_magic					= GetGametypeSetting( "magic" );
	level.headshots_only				= GetGametypeSetting( "headshotsonly" );	
	level.player_starting_points 		= level.round_number * 500;
	level.round_start_time				= 0;
	level.pro_tips_start_time			= 0;
	level.intermission					= false;
	level.dog_intermission				= false;
	level.zombie_total					= 0;									// Total number of zombies left to spawn
	level.zombie_respawns				= 0;									// Total number of zombies that need to be respawned due to cleanup
	level.total_zombies_killed			= 0;
	level.hudelem_count					= 0;
	level.zm_loc_types					= [];
	level.zm_loc_types[ "zombie_location" ] = [];			// List of normal zombie spawners (other types will be added in the zone manager)
	
	level.zm_variant_type_max						= [];
	level.zm_variant_type_max[ "walk" ]				= [];
	level.zm_variant_type_max[ "run" ]				= [];
	level.zm_variant_type_max[ "sprint" ] 			= [];
	level.zm_variant_type_max[ "super_sprint" ] 	= [];
	level.zm_variant_type_max[ "walk" ][ "down" ]	= 14;
	level.zm_variant_type_max[ "walk" ][ "up" ] 	= 16;
	level.zm_variant_type_max[ "run" ][ "down" ]	= 13;
	level.zm_variant_type_max[ "run" ][ "up" ]		= 12;
	level.zm_variant_type_max[ "sprint" ][ "down" ]	= 9;
	level.zm_variant_type_max[ "sprint" ][ "up" ]	= 8;
	level.zm_variant_type_max[ "super_sprint" ][ "down" ] = 1;
	level.zm_variant_type_max[ "super_sprint" ][ "up" ]	= 1;
	level.zm_variant_type_max[ "burned" ][ "down" ] = 1;
	level.zm_variant_type_max[ "burned" ][ "up" ] = 1;
	level.zm_variant_type_max[ "jump_pad_super_sprint" ][ "down" ] = 1;
	level.zm_variant_type_max[ "jump_pad_super_sprint" ][ "up" ] = 1;

	level.current_zombie_array = [];
	level.current_zombie_count = 0;
	level.zombie_total_subtract = 0;
	level.destructible_callbacks = [];
	
	foreach( team in level.teams )
	{
		if(!isdefined(level.zombie_vars[ team ]))level.zombie_vars[ team ]=[];
	}

	difficulty = 1;
	column = int(difficulty) + 1;

	//#######################################################################
	//	zombie_utility::set_zombie_var( identifier, 					value,	float,	column );

	// AI
	zombie_utility::set_zombie_var( "zombie_health_increase", 			100,	false,	column );	//	cumulatively add this to the zombies' starting health each round (up to round 10)
	zombie_utility::set_zombie_var( "zombie_health_increase_multiplier",0.1, 	true,	column );	//	after round 10 multiply the zombies' starting health by this amount
	zombie_utility::set_zombie_var( "zombie_health_start", 				150,	false,	column );	//	starting health of a zombie at round 1
	zombie_utility::set_zombie_var( "zombie_spawn_delay", 				2.0,	true,	column );	// Time to wait between spawning zombies.  This is modified based on the round number.
	zombie_utility::set_zombie_var( "zombie_new_runner_interval", 		 10,	false,	column );	//	Interval between changing walkers who are too far away into runners 
	zombie_utility::set_zombie_var( "zombie_move_speed_multiplier", 	  4,	false,	column );	//	Multiply by the round number to give the base speed value.  0-40 = walk, 41-70 = run, 71+ = sprint
	zombie_utility::set_zombie_var( "zombie_move_speed_multiplier_easy",  2,	false,	column );	//	Multiply by the round number to give the base speed value.  0-40 = walk, 41-70 = run, 71+ = sprint

	zombie_utility::set_zombie_var( "zombie_max_ai", 					24,		false,	column );	//	Base number of zombies per player (modified by round #)
	zombie_utility::set_zombie_var( "zombie_ai_per_player", 			6,		false,	column );	//	additional zombie modifier for each player in the game
	zombie_utility::set_zombie_var( "below_world_check", 				-1000 );					//	Check height to see if a zombie has fallen through the world.

	// Round	
	zombie_utility::set_zombie_var( "spectators_respawn", 				true );		// Respawn in the spectators in between rounds
	zombie_utility::set_zombie_var( "zombie_use_failsafe", 				true );		// Will slowly kill zombies who are stuck
	zombie_utility::set_zombie_var( "zombie_between_round_time", 		10 );		// How long to pause after the round ends
	zombie_utility::set_zombie_var( "zombie_intermission_time", 		15 );		// Length of time to show the end of game stats
	zombie_utility::set_zombie_var( "game_start_delay", 				0,		false,	column );	// How much time to give people a break before starting spawning

	// Life and death
	zombie_utility::set_zombie_var( "player_base_health", 				100 );		// Base health of a player

	zombie_utility::set_zombie_var( "penalty_no_revive", 				0.10, 	true,	column );	// Percentage of money you lose if you let a teammate die
	zombie_utility::set_zombie_var( "penalty_died",						0.0, 	true,	column );	// Percentage of money lost if you die
	zombie_utility::set_zombie_var( "penalty_downed", 					0.05, 	true,	column );	// Percentage of money lost if you go down // ww: told to remove downed point loss

	zombie_utility::set_zombie_var( "zombie_score_kill_4player", 		50 );		// Individual Points for a zombie kill in a 4 player game
	zombie_utility::set_zombie_var( "zombie_score_kill_3player",		50 );		// Individual Points for a zombie kill in a 3 player game
	zombie_utility::set_zombie_var( "zombie_score_kill_2player",		50 );		// Individual Points for a zombie kill in a 2 player game
	zombie_utility::set_zombie_var( "zombie_score_kill_1player",		50 );		// Individual Points for a zombie kill in a 1 player game

	zombie_utility::set_zombie_var( "zombie_score_damage_normal",		10 );		// points gained for a hit with a non-automatic weapon
	zombie_utility::set_zombie_var( "zombie_score_damage_light",		10 );		// points gained for a hit with an automatic weapon

	zombie_utility::set_zombie_var( "zombie_score_bonus_melee", 		80 );		// Bonus points for a melee kill
	zombie_utility::set_zombie_var( "zombie_score_bonus_head", 			50 );		// Bonus points for a head shot kill
	zombie_utility::set_zombie_var( "zombie_score_bonus_neck", 			20 );		// Bonus points for a neck shot kill
	zombie_utility::set_zombie_var( "zombie_score_bonus_torso", 		10 );		// Bonus points for a torso shot kill
	zombie_utility::set_zombie_var( "zombie_score_bonus_burn", 			10 );		// Bonus points for a burn kill

	zombie_utility::set_zombie_var( "zombie_flame_dmg_point_delay",		500 );	

	zombie_utility::set_zombie_var( "zombify_player", 					false );	// Default to not zombify the player till further support

	if ( IsSplitScreen() )
	{
		zombie_utility::set_zombie_var( "zombie_timer_offset", 			280 );	// hud offsets
	}
	
	level thread init_player_levelvars();

	level.gamedifficulty = GetGametypeSetting( "zmDifficulty" );

	if( level.gamedifficulty == 0 ) //easy
	{
		level.zombie_move_speed			= level.round_number * level.zombie_vars["zombie_move_speed_multiplier_easy"]; 
	}
	else	//normal
	{
		level.zombie_move_speed			= level.round_number * level.zombie_vars["zombie_move_speed_multiplier"]; 
	}

	//Make sure we only have walker zombies in round 1
	if ( level.round_number == 1 )
	{
		level.zombie_move_speed = 1;
	}

	level.speed_change_max = 0;
	level.speed_change_num = 0;

	set_round_number( level.round_number );
}

function init_player_levelvars()
{
	level flag::wait_till( "start_zombie_round_logic" );
	
	difficulty = 1;
	column = int(difficulty) + 1;	
	
	for(i = 0; i < 8; i ++)
	{
		points = 500;
		
		if(i > 3)
		{
			points = 3000;	// Dont change the behavior of grief.
		}
		
		points = zombie_utility::set_zombie_var( ("zombie_score_start_"+ (i+1) +"p"), points, false, column );
	}
}

function init_dvars()
{
//t6.5todo: move these dvars out of script, dangerous to leave them here like this

	if( GetDvarString( "zombie_debug" ) == "" )
	{
		SetDvar( "zombie_debug", "0" );
	}

	if( GetDvarString( "scr_zm_enable_bots" ) == "" )
	{
		SetDvar( "scr_zm_enable_bots", "0" );
	}

	if( GetDvarString( "zombie_cheat" ) == "" )
	{
		SetDvar( "zombie_cheat", "0" );
	}

	if ( GetDvarString("zombiemode_debug_zombie_count") == "" ) 
	{
		SetDvar("zombiemode_debug_zombie_count", "0");
	}

	if ( level.script != "zombie_cod5_prototype" )
	{
		SetDvar( "magic_chest_movable", "1" );
	}

	SetDvar( "revive_trigger_radius", "75" ); 

	SetDvar( "scr_deleteexplosivesonspawn", "0" );
}


function init_function_overrides()
{
	level.callbackPlayerDamage 		= &Callback_PlayerDamage;
	level.overridePlayerDamage		= &player_damage_override; //_cheat;
	level.callbackPlayerKilled		= &player_killed_override;
	
	level.playerlaststand_func		= &player_laststand;
	level.callbackPlayerLastStand	= &Callback_PlayerLastStand;
	
	level.prevent_player_damage		= &player_prevent_damage;

	level.callbackActorKilled		= &actor_killed_override;
	level.callbackActorDamage		= &actor_damage_override_wrapper;
	
	level.callbackVehicleDamage 	= &vehicle_damage_override;
	level.callbackVehicleKilled	    = &globallogic_vehicle::Callback_VehicleKilled;
	level.callbackVehicleRadiusDamage = &globallogic_vehicle::Callback_VehicleRadiusDamage;
	
	level.custom_introscreen		= &zombie_intro_screen; 
	level.custom_intermission		= &player_intermission; 

	level.global_damage_func		= &zm_spawner::zombie_damage; 
	level.global_damage_func_ads	= &zm_spawner::zombie_damage_ads;
	
	level.reset_clientdvars			= &onPlayerConnect_clientDvars;
	
	level.zombie_last_stand 		= &last_stand_pistol_swap;
	level.zombie_last_stand_pistol_memory = &last_stand_save_pistol_ammo;
	level.zombie_last_stand_ammo_return		= &last_stand_restore_pistol_ammo;

	level.player_becomes_zombie		= &zombify_player; 	

	level.validate_enemy_path_length = &zm_utility::default_validate_enemy_path_length;
}

function Callback_PlayerLastStand( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration )
{
	self endon( "disconnect" );
	//self Callback("on_player_last_stand");
	zm_laststand::PlayerLastStand( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ); 
}

function CodeCallback_DestructibleEvent( event, param1, param2, param3 )
{
	if( event == "broken" )
	{
		notify_type = param1;
		attacker = param2;
		weapon = param3;

		if ( IsDefined( level.destructible_callbacks[ notify_type ] ) )
		{
			self thread [[level.destructible_callbacks[ notify_type ]]]( notify_type, attacker );
		}

		self notify( event, notify_type, attacker );
	}
	else if( event == "breakafter" )
	{
		piece = param1;
		time = param2;
		damage = param3;
		self thread breakAfter( time, damage, piece );
	}
}

function breakAfter( time, damage, piece )
{
	self notify( "breakafter" );
	self endon( "breakafter" );
	
	wait time;
	
	// this does not work in mp.  DoDamage does not take a piece for mp.
	self dodamage( damage, self.origin, undefined, /*piece*/undefined );
}

function Callback_PlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal )
{	
	startedInLastStand = 0;
	if ( isPlayer(self)  )
	{
		startedInLastStand = self laststand::player_is_in_laststand();
	}
	
	/# println( "ZM Callback_PlayerDamage"+iDamage+"\n"); #/
	if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && (eAttacker.sessionteam == self.sessionteam) && !eAttacker HasPerk( "specialty_playeriszombie" ) && !( isdefined( self.is_zombie ) && self.is_zombie ) )
	{
		self process_friendly_fire_callbacks( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex );
		if ( self != eAttacker )
		{
			//one player shouldn't damage another player, grenades, airstrikes called in by another player
		/#	println("Exiting - players can't hurt each other.");	#/
			
			
			return;
		}
		else if ( sMeansOfDeath != "MOD_GRENADE_SPLASH"
				&& sMeansOfDeath != "MOD_GRENADE"
				&& sMeansOfDeath != "MOD_EXPLOSIVE"
				&& sMeansOfDeath != "MOD_PROJECTILE"
				&& sMeansOfDeath != "MOD_PROJECTILE_SPLASH"
				&& sMeansOfDeath != "MOD_BURNED"
				&& sMeansOfDeath != "MOD_SUICIDE" )
		{
		/#	println("Exiting - damage type verbotten.");	#/
			//player should be able to damage they're selves with grenades and stuff
			//otherwise don't damage the player, so like airstrikes  won't kill the player
			return;
		}
	}
	
	// Notify the player of a zombie swipe if the persistent Insta Kill upgrade is active
	if( ( isdefined( level.pers_upgrade_insta_kill ) && level.pers_upgrade_insta_kill ) )
	{
		self zm_pers_upgrades_functions::pers_insta_kill_melee_swipe( sMeansOfDeath, eAttacker );
	}

	if( IsDefined( self.overridePlayerDamage ) )
	{
		iDamage = self [[self.overridePlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
	}
	else if( IsDefined( level.overridePlayerDamage ) )
	{
		iDamage = self [[level.overridePlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
	}

	Assert(IsDefined(iDamage), "You must return a value from a damage override function.");

//	self Callback("on_player_damage");

	if (( isdefined( self.magic_bullet_shield ) && self.magic_bullet_shield ))
	{
		// save out and restore the maxHealth, because setting health below modifies it
		maxHealth = self.maxHealth;

		// increase health by damage, because it will be subtracted back out below in finishActorDamage
		self.health += iDamage;

		// restore the maxHealth to what it was
		self.maxHealth = maxHealth;
	}
	
	// DtP: When player is diving to prone away from the grenade, the damage is reduced

	// player is diving
	if( isdefined( self.divetoprone ) && self.divetoprone == 1 )
	{
		// grenade splash damage
		if( sMeansOfDeath == "MOD_GRENADE_SPLASH" )
		{
			// if the player is at least 32 units away
			dist = Distance2d(vPoint, self.origin);
			if( dist > 32 )
			{
				// if player is diving away
				dot_product = vectordot( AnglesToForward( self.angles ), vDir ); 
				if( dot_product > 0 )
				{
					// grenade is behind player
					iDamage = int( iDamage * 0.5 ); // halves damage
				}
			}
		}
	}
	
/#	println("CB PD");	#/

	// players can only hurt themselves, zombie players can hurt any other player and be hurt by human players
/*	if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && !eAttacker HasPerk( "specialty_playeriszombie" ) && !IS_TRUE( self.is_zombie ) )
	{
		if ( self != eAttacker )
		{
			//one player shouldn't damage another player, grenades, airstrikes called in by another player
			println("Exiting - players can't hurt each other.");
			return;
		}
		else if ( sMeansOfDeath != "MOD_GRENADE_SPLASH"
				&& sMeansOfDeath != "MOD_GRENADE"
				&& sMeansOfDeath != "MOD_EXPLOSIVE"
				&& sMeansOfDeath != "MOD_PROJECTILE"
				&& sMeansOfDeath != "MOD_PROJECTILE_SPLASH"
				&& sMeansOfDeath != "MOD_BURNED"
				&& sMeansOfDeath != "MOD_SUICIDE" )
		{
			println("Exiting - damage type verbotten.");
			//player should be able to damage they're selves with grenades and stuff
			//otherwise don't damage the player, so like airstrikes  won't kill the player
			return;
		}
	}*/

	if ( isdefined( level.prevent_player_damage ) )
	{
		if ( self [[ level.prevent_player_damage ]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime ) )
		{
			return;
		}
	}
	

	iDFlags = iDFlags | level.iDFLAGS_NO_KNOCKBACK;
	
	//damage should have been reduced to 0 if it really was a riotshield hit
	if( iDamage > 0 && sHitLoc == "riotshield" )
	{
		sHitLoc = "torso_upper";
	}

/#	PrintLn("Finishplayerdamage wrapper.");		#/
	wasDowned = 0;
	if ( isPlayer(self))
	{
		wasDowned = !startedInLastStand && self laststand::player_is_in_laststand();
	}

	/#
		if(IsDefined(eAttacker))
		{
			Record3DText( "D:" + iDamage + " H: " + self.health + " A: " + eAttacker GetEntityNumber(), self.origin, ( 1, 0, 0 ), "Script", self );
		}
		else
		{
			Record3DText( "D:" + iDamage + " H: " + self.health + " A: undefined", self.origin, ( 1, 0, 0 ), "Script", self );
		}
	#/
	
	self finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal );
	bb::logdamage(eattacker, self, weapon, idamage, smeansofdeath, shitloc, self.Health <= 0, wasDowned);
}

function finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal )
{
	self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ); 
	
	// the MP version of finishPlayerDamage does not take 11 parameters 
	// the 11 parameter version in SP does not take these parameters (10 is modelIndex and 11 is pOffsetTime)
	//surface = "flesh";
	//self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, surface );

}

function register_player_friendly_fire_callback( callback )
{
	if (!isdefined(level.player_friendly_fire_callbacks))
		level.player_friendly_fire_callbacks = [];
	level.player_friendly_fire_callbacks[level.player_friendly_fire_callbacks.size] = callback;
}

function process_friendly_fire_callbacks( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex )
{
	if (isdefined(level.player_friendly_fire_callbacks))
	{
		foreach( callback in level.player_friendly_fire_callbacks )
		{
			self [[callback]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex );
		}
	}
}

function init_flags()
{
	level flag::init( "solo_game" );
	level flag::init( "start_zombie_round_logic" );
	level flag::init( "start_encounters_match_logic" );
	level flag::init( "spawn_point_override" );
	level flag::init( "crawler_round" );
	level flag::init( "spawn_zombies", true );
	level flag::init( "special_round" );
	level flag::init( "dog_round" );
	level flag::init( "raps_round" );
	level flag::init( "begin_spawning" );
	level flag::init( "end_round_wait" );
	level flag::init( "wait_and_revive" );
	level flag::init( "instant_revive" );
	level flag::init( "initial_blackscreen_passed" );
	level flag::init( "initial_players_connected" );
	
	level flag::init( "power_on" );
	//DCS: this will init all zone controlling power switch flags.
	power_trigs = GetEntArray( "use_elec_switch", "targetname" );
	foreach(trig in power_trigs)
	{
		if(IsDefined(trig.script_int))
		{
			level flag::init("power_on" + trig.script_int);
		}
	}
}

function init_client_field_callback_funcs()
{	
	// Client fields for actors

	clientfield::register("actor", "zombie_riser_fx", 1, 1, "int");
	
	if ( ( isdefined( level.use_water_risers ) && level.use_water_risers ) )
	{
		clientfield::register("actor", "zombie_riser_fx_water", 1, 1, "int");
	}
	
	if ( ( isdefined( level.use_foliage_risers ) && level.use_foliage_risers ) )
	{
		clientfield::register("actor", "zombie_riser_fx_foliage", 1, 1, "int");
	}
	
	if ( ( isdefined( level.use_low_gravity_risers ) && level.use_low_gravity_risers ) )
	{
		clientfield::register("actor", "zombie_riser_fx_lowg", 1, 1, "int");
	}
	
	clientfield::register("actor", "zombie_has_eyes", 1, 1, "int");		
	clientfield::register("actor", "zombie_ragdoll_explode", 1, 1, "int");	
	clientfield::register("actor", "zombie_gut_explosion", 1, 1, "int");
	clientfield::register("actor", "sndZombieContext", (0 - 1), 1, "int");
	clientfield::register("actor", "zombie_keyline_render", 1, 1, "int");
	
	bits = 4;
	trigs = GetEntArray( "use_elec_switch", "targetname" );
	if ( IsDefined( trigs ) )
	{
		bits = GetMinBitCountForNum( trigs.size + 1 );
	}
	clientfield::register("world", "zombie_power_on", 1, bits, "int");
	clientfield::register("world", "zombie_power_off", 1, bits, "int");

	clientfield::register("world", "round_complete_time", 1, 20, "int");
	clientfield::register("world", "round_complete_num", 1, 8, "int");
	clientfield::register("world", "game_end_time", 1, 20, "int");
	clientfield::register("world", "quest_complete_time", 1, 20, "int");
	clientfield::register("world", "game_start_time", 15001, 20, "int" );
}

function init_fx()
{
	level.createfx_callback_thread = &delete_in_createfx;

//	level._effect["wood_chunk_destory"]	 		= "_t6/impacts/fx_large_woodhit" ;
	level._effect["fx_zombie_bar_break"]		= "_t6/maps/zombie/fx_zombie_bar_break";
	level._effect["fx_zombie_bar_break_lite"]	= "_t6/maps/zombie/fx_zombie_bar_break_lite";
	
	if ( !( isdefined( level.FX_exclude_edge_fog ) && level.FX_exclude_edge_fog ) )
	{
		level._effect["edge_fog"]			 		= "_t6/maps/zombie/fx_fog_zombie_amb"; 
	}
	
	level._effect["chest_light"]		 		= "zombie/fx_weapon_box_open_glow_zmb"; 
	level._effect["chest_light_closed"]		 	= "zombie/fx_weapon_box_closed_glow_zmb"; 

	if ( !( isdefined( level.FX_exclude_default_eye_glow ) && level.FX_exclude_default_eye_glow ) )
	{
		level._effect["eye_glow"]				= "zombie/fx_glow_eye_orange";
	}

	level._effect["headshot"]				= "zombie/fx_bul_flesh_head_fatal_zmb";
	level._effect["headshot_nochunks"]		= "zombie/fx_bul_flesh_head_nochunks_zmb";
	level._effect["bloodspurt"]				= "zombie/fx_bul_flesh_neck_spurt_zmb";

	if ( !( isdefined( level.FX_exclude_tesla_head_light ) && level.FX_exclude_tesla_head_light ) )
	{
		level._effect["tesla_head_light"]		= "_t6/maps/zombie/fx_zombie_tesla_neck_spurt";
	}
	level._effect["zombie_guts_explosion"]      = "zombie/fx_blood_torso_explo_lg_zmb";


	level._effect["rise_burst_water"]			= "zombie/fx_spawn_dirt_hand_burst_zmb";
	level._effect["rise_billow_water"]			= "zombie/fx_spawn_dirt_body_billowing_zmb";
	level._effect["rise_dust_water"]			= "zombie/fx_spawn_dirt_body_dustfalling_zmb";

	level._effect["rise_burst"]					= "zombie/fx_spawn_dirt_hand_burst_zmb";
	level._effect["rise_billow"]				= "zombie/fx_spawn_dirt_body_billowing_zmb";
	level._effect["rise_dust"]					= "zombie/fx_spawn_dirt_body_dustfalling_zmb";	

	level._effect["fall_burst"]					= "zombie/fx_spawn_dirt_hand_burst_zmb";
	level._effect["fall_billow"]				= "zombie/fx_spawn_dirt_body_billowing_zmb";
	level._effect["fall_dust"]					= "zombie/fx_spawn_dirt_body_dustfalling_zmb";	

	// Flamethrower
	level._effect["character_fire_death_sm"]    = "zombie/fx_fire_torso_zmb";
	level._effect["character_fire_death_torso"] = "zombie/fx_fire_torso_zmb";

	if ( !( isdefined( level.fx_exclude_default_explosion ) && level.fx_exclude_default_explosion ) )
	{
		level._effect["def_explosion"]				= "_t6/explosions/fx_default_explosion";
	}
//	level._effect["betty_explode"]				= "_t6/weapon/bouncing_betty/fx_explosion_betty_generic";
	
//	level._effect["default_weapon_glow"]		= "_t6/maps/zombie/fx_zmb_tranzit_weapon_glow";
	
	if ( !( isdefined( level.disable_fx_upgrade_aquired ) && level.disable_fx_upgrade_aquired ) )
	{
		level._effect["upgrade_aquired"]			= "_t6/maps/zombie/fx_zmb_tanzit_upgrade";
	}
}

// Handles the intro screen
function zombie_intro_screen( string1, string2, string3, string4, string5 )
{
	level flag::wait_till( "start_zombie_round_logic" );
}

function players_playing()
{
	// initialize level.players_playing
	players = GetPlayers();
	level.players_playing = players.size;

	wait( 20 );

	players = GetPlayers();
	level.players_playing = players.size;
}


//
// NETWORK SECTION ====================================================================== //
//

function onPlayerConnect_clientDvars()
{
	self SetClientCompass( 0 );
	self SetClientThirdPerson( 0 );
	self resetFov();
	self SetClientThirdPersonAngle( 0 );
	self SetClientUIVisibilityFlag( "weapon_hud_visible", 1 );
	self SetClientMiniScoreboardHide( 1 );
	self SetClientHUDHardcore( 0 );
	self SetClientPlayerPushAmount( 1 );

	self SetDepthOfField( 0, 0, 512, 4000, 4, 0 );
	
	self zm_laststand::player_getup_setup();
}



function checkForAllDead(excluded_player)
{
	players = GetPlayers();
	count = 0;
	for( i = 0; i < players.size; i++ )
	{
		if(isDefined(excluded_player) && excluded_player == players[i])
		{
			continue;
		}
		if( !(players[i] laststand::player_is_in_laststand()) && !(players[i].sessionstate == "spectator") )
		{
			count++;
		}
	}
	
	if( count==0 && !( isdefined( level.no_end_game_check ) && level.no_end_game_check ) )
	{
		level notify( "end_game" );
	}
}
	

//
//	Runs when the player spawns into the map
//	self is the player.surprise!
//
function onPlayerSpawned()
{
	self endon( "disconnect" ); 
	self notify("stop_onPlayerSpawned");
	self endon("stop_onPlayerSpawned");
	
	for( ;; )
	{
		self waittill( "spawned_player" ); 
		
		if(!( isdefined( level.host_ended_game ) && level.host_ended_game ))
		{
			self freezecontrols( false );
			/# println(" Unfreeze controls 7"); #/

		}
		self.hits = 0;

		self zm_utility::init_player_offhand_weapons();
		
		lethal_grenade = self zm_utility::get_player_lethal_grenade();
		if( !self HasWeapon( lethal_grenade ) )
		{
			self GiveWeapon( lethal_grenade );	
			self SetWeaponAmmoClip( lethal_grenade, 0 );
		}
		
		self RecordPlayerReviveZombies( self );
		
/#
		if ( GetDvarInt( "zombie_cheat" ) >= 1 && GetDvarInt( "zombie_cheat" ) <= 3 ) 
		{
			self EnableInvulnerability();
		}
#/

		self SetActionSlot( 3, "altMode" );
		self PlayerKnockback( false );

		self SetClientThirdPerson( 0 );
		self resetFov();
		self SetClientThirdPersonAngle( 0 );

		self SetDepthOfField( 0, 0, 512, 4000, 4, 0 );

		self cameraactivate(false);

		self.num_perks = 0;
		self.on_lander_last_stand = undefined;
		self setblur(0, 0.1);
		self.zmbDialogQueue = [];
		self.zmbDialogActive = false;
		self.zmbDialogGroups = [];
		self.zmbDialogGroup = "";
		
		if ( ( isdefined( level.player_out_of_playable_area_monitor ) && level.player_out_of_playable_area_monitor ) )
		{
			self thread player_out_of_playable_area_monitor();
		}

		if ( ( isdefined( level.player_too_many_weapons_monitor ) && level.player_too_many_weapons_monitor ) )
		{
			self thread [[level.player_too_many_weapons_monitor_func]]();
		}
		
		if ( ( isdefined( level.player_too_many_players_check ) && level.player_too_many_players_check ) )
		{
			level thread [[level.player_too_many_players_check_func]]();
		}

		self.disabled_perks = [];
		
		if( isdefined( self.player_initialized ) )
		{
			if( self.player_initialized == false )
			{
				self.player_initialized = true; 
				
				//t6.5todo self freezecontrols( true ); // first spawn only, intro_black_screen will pull them out of it

				self giveweapon( self zm_utility::get_player_lethal_grenade() );	
				self setweaponammoclip( self zm_utility::get_player_lethal_grenade(), 0);
				self SetClientUIVisibilityFlag( "weapon_hud_visible", 1 );
				self SetClientMiniScoreboardHide( 0 );				
				// ww: set the is_drinking variable
				self.is_drinking = 0;

				self thread player_zombie_breadcrumb();				
			
				self thread player_monitor_travel_dist();	
				self thread player_monitor_time_played();
				
				if(isDefined(level.custom_player_track_ammo_count))
				{
					self thread [[level.custom_player_track_ammo_count]]();
				}
				else
				{
					self thread player_track_ammo_count();	
				}
				
				self thread zm_utility::shock_onpain();

				self thread player_grenade_watcher();
				self laststand::revive_hud_create();
			
				if(isDefined(level.zm_gamemodule_spawn_func))
				{
					self thread [[level.zm_gamemodule_spawn_func]]();
				}
				
				self thread player_spawn_protection();
				if(!isDefined(self.lives))
				{
					self.lives = 0;
				}
			}
		}
	}
}

function player_spawn_protection()
{
	self endon("disconnect");
	self zm_utility::increment_ignoreme();
	x = 0;
	while( x < 60 )
	{
		x++;
		wait(.05);
	}	
	self zm_utility::decrement_ignoreme();
}

function spawn_life_brush( origin, radius, height )
{
	life_brush = spawn( "trigger_radius", origin, 0, radius, height );
	life_brush.script_noteworthy = "life_brush";
	
	return life_brush;
}


function in_life_brush()
{
	life_brushes = getentarray( "life_brush", "script_noteworthy" );

	if ( !IsDefined( life_brushes ) )
	{
		return false;
	}
	
	for ( i = 0; i < life_brushes.size; i++ )
	{

		if ( self IsTouching( life_brushes[i] ) )
		{
			return true;
		}
	}

	return false;
}


function spawn_kill_brush( origin, radius, height )
{
	kill_brush = spawn( "trigger_radius", origin, 0, radius, height );
	kill_brush.script_noteworthy = "kill_brush";
	
	return kill_brush;
}


function in_kill_brush()
{
	kill_brushes = getentarray( "kill_brush", "script_noteworthy" );

	self.kill_brush = undefined; 
	
	if ( !IsDefined( kill_brushes ) )
	{
		return false;
	}
	
	for ( i = 0; i < kill_brushes.size; i++ )
	{

		if ( self IsTouching( kill_brushes[i] ) )
		{
			self.kill_brush = kill_brushes[i]; 
			return true;
		}
	}

	return false;
}


function in_enabled_playable_area()
{
	zm_zonemgr::wait_zone_flags_updating(); 
		
	playable_area = getentarray( "player_volume", "script_noteworthy" );

	if( !IsDefined( playable_area ) )
	{
		return false;
	}
	
	for ( i = 0; i < playable_area.size; i++ )
	{
		if ( zm_zonemgr::zone_is_enabled( playable_area[i].targetname ) && self IsTouching( playable_area[i] ) )
		{
			return true;
		}
	}

	return false;
}


function get_player_out_of_playable_area_monitor_wait_time()
{
/#
	if ( ( isdefined( level.check_kill_thread_every_frame ) && level.check_kill_thread_every_frame ) )
	{
		return 0.05;
	}
#/

	return 3;
}


function player_out_of_playable_area_monitor()
{
	self notify( "stop_player_out_of_playable_area_monitor" );
	self endon( "stop_player_out_of_playable_area_monitor" );
	self endon( "disconnect" );
	level endon( "end_game" );
	
	while(!isDefined(self.characterindex))
	{
		wait(.05);
	}
	// load balancing
	wait( (0.15 * self.characterindex) );

	while ( true )
	{
		// skip over players in spectate, otherwise Sam keeps laughing every 3 seconds since their corpse is still invisibly in a kill area
		if ( self.sessionstate == "spectator" )
		{
			wait( get_player_out_of_playable_area_monitor_wait_time() );
			continue;
		}
		
		if(( isdefined( level.hostmigration_occured ) && level.hostmigration_occured ))
		{
			wait( get_player_out_of_playable_area_monitor_wait_time() );
			continue;
		}
		
		if ( !self in_life_brush() && (self in_kill_brush() || !self in_enabled_playable_area() || ( isdefined(level.player_out_of_playable_area_override) && ( isdefined( self [[level.player_out_of_playable_area_override]]() ) && self [[level.player_out_of_playable_area_override]]() ) ) ) )
		{
			if ( !isdefined( level.player_out_of_playable_area_monitor_callback ) || self [[level.player_out_of_playable_area_monitor_callback]]() )
			{
/#
				if ( ( isdefined( level.kill_thread_test_mode ) && level.kill_thread_test_mode ) )
				{
					PrintTopRightln( "out of playable area: " + self.origin );
					wait( get_player_out_of_playable_area_monitor_wait_time() );
					continue;
				}

				if ( self isinmovemode( "ufo", "noclip" ) || ( isdefined( level.disable_kill_thread ) && level.disable_kill_thread ) || GetDvarInt( "zombie_cheat" ) > 0 )
				{
					wait( get_player_out_of_playable_area_monitor_wait_time() );
					continue;
				}
#/

				//track the cheaters
				self zm_stats::increment_map_cheat_stat( "cheat_out_of_playable" );
				self zm_stats::increment_client_stat( "cheat_out_of_playable",false );
				self zm_stats::increment_client_stat( "cheat_total",false );
						
				self playlocalsound( level.zmb_laugh_alias );
			
				wait( 0.5 );

				if ( GetPlayers().size == 1 && level flag::get( "solo_game" ) && ( isdefined( self.waiting_to_revive ) && self.waiting_to_revive ) )
				{
					level notify( "end_game" );
				}
				else
				{
					self DisableInvulnerability();
					self.lives = 0;
					self dodamage( self.health + 1000, self.origin );
					self.bleedout_time = 0;
				}
			}
		}
		
		wait( get_player_out_of_playable_area_monitor_wait_time() );
	}
}


function get_player_too_many_weapons_monitor_wait_time()
{
	return 3;
}


function player_too_many_weapons_monitor_takeaway_simultaneous( primary_weapons_to_take )
{
	self endon( "player_too_many_weapons_monitor_takeaway_sequence_done" );

	self util::waittill_any( "player_downed", "replace_weapon_powerup" );

	for ( i = 0; i < primary_weapons_to_take.size; i++ )
	{
		self TakeWeapon( primary_weapons_to_take[i] );
	}

	self zm_score::player_reduce_points( "take_all" );
	self zm_utility::give_start_weapon( false );
	if ( !self laststand::player_is_in_laststand() )
	{
		self zm_utility::decrement_is_drinking();
	}
	else if ( level flag::get( "solo_game" ) )
	{
		self.score_lost_when_downed = 0;
	}

	self notify( "player_too_many_weapons_monitor_takeaway_sequence_done" );
}


function player_too_many_weapons_monitor_takeaway_sequence( primary_weapons_to_take )
{
	self thread player_too_many_weapons_monitor_takeaway_simultaneous( primary_weapons_to_take );

	self endon( "player_downed" );
	self endon( "replace_weapon_powerup" );

	self zm_utility::increment_is_drinking();
	score_decrement = zm_utility::round_up_to_ten( int( self.score / (primary_weapons_to_take.size + 1) ) );

	for ( i = 0; i < primary_weapons_to_take.size; i++ )
	{
		self playlocalsound( level.zmb_laugh_alias );
		self SwitchToWeapon( primary_weapons_to_take[i] );
		self zm_score::player_reduce_points( "take_specified", score_decrement );
		wait( 3 );

		self TakeWeapon( primary_weapons_to_take[i] );
	}

	self playlocalsound( level.zmb_laugh_alias );
	self zm_score::player_reduce_points( "take_all" );
	wait( 1 );
	self zm_utility::give_start_weapon( true );
	self zm_utility::decrement_is_drinking();

	self notify( "player_too_many_weapons_monitor_takeaway_sequence_done" );
}

function player_too_many_weapons_monitor()
{
	self notify( "stop_player_too_many_weapons_monitor" );
	self endon( "stop_player_too_many_weapons_monitor" );
	self endon( "disconnect" );
	level endon( "end_game" );

	// load balancing
	scalar = self.characterindex;
	
	if ( !isdefined( scalar ) )
	{
		scalar = self GetEntityNumber();
	}
	
	wait( (0.15 * scalar) );

	while ( true )
	{
		if ( self zm_utility::has_powerup_weapon() || self laststand::player_is_in_laststand() || self.sessionstate == "spectator" || isdefined( self.laststandpistol ) )
		{
			wait( get_player_too_many_weapons_monitor_wait_time() );
			continue;
		}

/#
		if ( GetDvarInt( "zombie_cheat" ) > 0 )
		{
			wait( get_player_too_many_weapons_monitor_wait_time() );
			continue;
		}
#/

		weapon_limit = zm_utility::get_player_weapon_limit( self );

		primaryWeapons = self GetWeaponsListPrimaries();

		if ( primaryWeapons.size > weapon_limit )
		{
			self zm_weapons::take_fallback_weapon();
			primaryWeapons = self GetWeaponsListPrimaries();
		}

		primary_weapons_to_take = [];
		for ( i = 0; i < primaryWeapons.size; i++ )
		{
			if ( zm_weapons::is_weapon_included( primaryWeapons[i] ) || zm_weapons::is_weapon_upgraded( primaryWeapons[i] ) )
			{
				primary_weapons_to_take[primary_weapons_to_take.size] = primaryWeapons[i];
			}
		}

		if ( primary_weapons_to_take.size > weapon_limit )
		{
			if ( !isdefined( level.player_too_many_weapons_monitor_callback ) || self [[level.player_too_many_weapons_monitor_callback]]( primary_weapons_to_take ) )
			{
				//track the cheaters
				self zm_stats::increment_map_cheat_stat( "cheat_too_many_weapons" );
				self zm_stats::increment_client_stat( "cheat_too_many_weapons",false );
				self zm_stats::increment_client_stat( "cheat_total",false );
				
				
				self thread player_too_many_weapons_monitor_takeaway_sequence( primary_weapons_to_take );
				self waittill( "player_too_many_weapons_monitor_takeaway_sequence_done" );
			}
		}

		wait( get_player_too_many_weapons_monitor_wait_time() );
	}
}


function player_monitor_travel_dist()
{
	self endon("disconnect");
	self notify("stop_player_monitor_travel_dist");
	self endon("stop_player_monitor_travel_dist");
	
	prevpos = self.origin;
	while(1)
	{
		wait .1;

		self.pers["distance_traveled"] += distance( self.origin, prevpos ) ; 
		prevpos = self.origin;
	}
}

function player_monitor_time_played()
{
	self endon( "disconnect" );
	self notify("stop_player_monitor_time_played");
	self endon("stop_player_monitor_time_played");

	level flag::wait_till( "start_zombie_round_logic" );
	
	for ( ;; )
	{
		wait ( 1.0 );
		zm_stats::increment_client_stat( "time_played_total" );
	}
}

function player_grenade_multiattack_bookmark_watcher( grenade )
{
	self endon( "disconnect" );

	waittillframeend;
	
	if ( !IsDefined( grenade ) )
	{
		return;
	}

	inflictorEntNum = grenade getEntityNumber();
	inflictorEntType = grenade getEntityType();
	inflictorBirthTime = 0;
	if ( isDefined( grenade.birthTime ) )
		inflictorBirthTime = grenade.birthTime;

	ret_val = grenade util::waittill_any_ex( 15, "explode", "death", self, "disconnect" );

	if ( !IsDefined( self ) || (IsDefined( ret_val ) && "timeout" == ret_val) )
	{
		return;
	}

	self.grenade_multiattack_count = 0;
	self.grenade_multiattack_ent = undefined;
	self.grenade_multikill_count = 0; // Black Ops 3 - TU1 Fix; track multikill count in addition to multiattack, for the Five-for-One Challenge

	waittillframeend;

	if ( !IsDefined( self ) )
	{
		return;
	}

	count = level.grenade_multiattack_bookmark_count;
	if ( ( isdefined( grenade.grenade_multiattack_bookmark_count ) && grenade.grenade_multiattack_bookmark_count ) )
	{
		count = grenade.grenade_multiattack_bookmark_count;
	}

	bookmark_string = "zm_player_grenade_multiattack";
	if ( ( isdefined( grenade.use_grenade_special_long_bookmark ) && grenade.use_grenade_special_long_bookmark ) )
	{
		bookmark_string = "zm_player_grenade_special_long";
	}
	else if ( ( isdefined( grenade.use_grenade_special_bookmark ) && grenade.use_grenade_special_bookmark ) )
	{
		bookmark_string = "zm_player_grenade_special";
	}

	if ( count <= self.grenade_multiattack_count && IsDefined( self.grenade_multiattack_ent ) )
	{
		addDemoBookmark( bookmark_string, gettime(), self GetEntityNumber(), 255, 0, inflictorEntNum, inflictorEntType, inflictorBirthTime, false, self.grenade_multiattack_ent GetEntityNumber() );
	}

	if ( 5 <= self.grenade_multikill_count )  // Black Ops 3 - TU1 Fix; track multikill count in addition to multiattack, for the Five-for-One Challenge
	{
		self zm_stats::increment_challenge_stat( "ZOMBIE_HUNTER_EXPLOSION_MULTIKILL" );
	}

	self.grenade_multiattack_count = 0;
	self.grenade_multikill_count = 0;  // Black Ops 3 - TU1 Fix; track multikill count in addition to multiattack, for the Five-for-One Challenge
}


function player_grenade_watcher()
{
	self endon( "disconnect" );
	
	self notify("stop_player_grenade_watcher");
	self endon("stop_player_grenade_watcher");
	
	self.grenade_multiattack_count = 0;
	self.grenade_multikill_count = 0;  //this was added to "player_grenade_multiattack_bookmark_watcher" for TU1, but not added here, which was causing a SRE.

	while ( 1 )
	{
		self waittill( "grenade_fire", grenade, weapon );

		if( isdefined( grenade ) && isalive( grenade ) )
		{
			grenade.team = self.team;
		}

		self thread player_grenade_multiattack_bookmark_watcher( grenade );

		if ( isdefined( level.grenade_watcher ) )
		{
			self [[ level.grenade_watcher ]]( grenade, weapon );
		}
	}
}

function player_prevent_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime )
{
	if( !isdefined( eInflictor ) || !isdefined( eAttacker ) )
	{
		return false;
	}
	
	if ( eInflictor == self || eAttacker == self )
	{
		return false;
	}

	if ( isdefined( eInflictor ) && isdefined( eInflictor.team ) )
	{
		if (!( isdefined( eInflictor.damage_own_team ) && eInflictor.damage_own_team ))
			if ( eInflictor.team == self.team )
			{
				return true;
			}
	}

	return false;
}

//
//	Keep track of players going down and getting revived
function player_revive_monitor()
{
	self endon( "disconnect" ); 
	self notify("stop_player_revive_monitor");
	self endon("stop_player_revive_monitor");
	
	while (1)
	{
		self waittill( "player_revived", reviver );	
		
		self playsoundtoplayer( "zmb_character_revived", self );
		
		if(( isdefined( level.isresetting_grief ) && level.isresetting_grief ))
		{
			continue;
		}
		
		//self laststand_giveback_player_perks();

		if ( IsDefined(reviver) )
		{
			if( reviver != self )
			{
				if( math::cointoss() )
					self zm_audio::create_and_play_dialog( "general", "revive_up" );
				else
					reviver zm_audio::create_and_play_dialog( "general", "revive_support" );
			}
			else
			{
				self zm_audio::create_and_play_dialog( "general", "revive_up" );
			}
			
			//reviver maps\_zombiemode_rank::giveRankXp( "revive" );
			//maps\_zombiemode_challenges::doMissionCallback( "zm_revive", reviver );

			// Check to see how much money you lost from being down.
			points = self.score_lost_when_downed;
			
			if ( !isdefined( points ) )
			{
				points = 0;
			}
			
		/#	println( "ZM >> LAST STAND - points = " + points);	#/
			
			reviver zm_score::player_add_points( "reviver", points );
			self.score_lost_when_downed = 0;

			if ( IsPlayer( reviver ) && reviver != self )
			{
				reviver zm_stats::increment_challenge_stat( "SURVIVALIST_REVIVE" );
			}
		}
	}
}


// self = a player
// If the player has just 1 perk, they wil always get it back
// If the player has more than 1 perk, they will lose a single perk
function laststand_giveback_player_perks()
{
	if ( IsDefined( self.laststand_perks ) )
	{
		// Calculate a lost perk index
		lost_perk_index = int( -1 );
		if( self.laststand_perks.size > 1 )
		{
			lost_perk_index = RandomInt( self.laststand_perks.size-1 );
		}
		
		// Give the player back their perks
		for ( i=0; i<self.laststand_perks.size; i++ )
		{
			if ( self HasPerk( self.laststand_perks[i] ) )
			{
				continue;
			}
			if( i == lost_perk_index )
			{
				continue;
			}
			
			zm_perks::give_perk( self.laststand_perks[i] );
		}
	}
}

function remote_revive_watch()
{
	self endon( "death" );
	self endon( "player_revived" );

	keep_checking = true;
	while( keep_checking )
	{
		self waittill( "remote_revive", reviver );
	
		//Check the remote reviver is on the same team
		if( reviver.team == self.team )
			keep_checking = false;
	}
	
	self zm_laststand::remote_revive( reviver );
}

function player_laststand( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration )
{
/#	println( "ZM >> LAST STAND - player_laststand called" );	#/
	
	b_alt_visionset = false;
		
	self AllowJump(false);
	
	currWeapon = self GetCurrentWeapon();
	
	self AddWeaponStat( currWeapon, "deathsDuringUse", 1 );

	// Grab the perks if we have the player persistent ability "perk lose"
	if( ( isdefined( self.pers_upgrades_awarded["perk_lose"] ) && self.pers_upgrades_awarded["perk_lose"] ) )
	{
		self zm_pers_upgrades_functions::pers_upgrade_perk_lose_save();
	}

	players = GetPlayers();
	if ( players.size == 1 && level flag::get( "solo_game" ) )
	{
		if ( self.lives > 0 && self HasPerk("specialty_quickrevive") )
		{
			self thread wait_and_revive();
		}
	}
	
	self zm_utility::clear_is_drinking();

	self thread remote_revive_watch();
	
	self zm_score::player_downed_penalty();
	
	// Turns out we need to do this after all, but we don't want to change _laststand.gsc postship, so I'm doing it here manually instead
	self DisableOffhandWeapons();

	self thread last_stand_grenade_save_and_return();
	
	if( sMeansOfDeath != "MOD_SUICIDE" && sMeansOfDeath != "MOD_FALLING" )
	{		
		if( !( isdefined( self.intermission ) && self.intermission ) )
			self zm_audio::create_and_play_dialog( "general", "revive_down" );
		else
		{
			if(isDefined(level.custom_player_death_vo_func) && !self [[level.custom_player_death_vo_func]]() )
			{
				self zm_audio::create_and_play_dialog( "general", "exert_death" );
			}
		}
	}
	
	if( IsDefined( level._zombie_minigun_powerup_last_stand_func ) )
	{
		self thread [[level._zombie_minigun_powerup_last_stand_func]]();
	}
	
	if( IsDefined( level._zombie_tesla_powerup_last_stand_func ) )
	{
		self thread [[level._zombie_tesla_powerup_last_stand_func]]();
	}
	
	if ( self HasPerk( "specialty_electriccherry" ) )
	{
		b_alt_visionset = true;
		
		if ( IsDefined( level.custom_laststand_func ) )
		{
			self thread [[ level.custom_laststand_func ]]();
		}
	}

	if( IsDefined( self.intermission ) && self.intermission )
	{
		//maps\_zombiemode_challenges::doMissionCallback( "playerDied", self );
		
		wait(.5);
		self stopsounds();
		
		level waittill( "forever" );
	}
	
	if ( !( b_alt_visionset ) )
	{
		visionset_mgr::activate( "visionset", "zombie_last_stand", self, 1 );
	}
}


function failsafe_revive_give_back_weapons(excluded_player)
{
	for ( i = 0; i < 10; i++ )
	{
		{wait(.05);};

		players = GetPlayers();
		foreach(player in players )
		{
			if (player == excluded_player|| !isdefined( player.reviveProgressBar ) || player zm_laststand::is_reviving_any() )
			{
				continue;
			}
	
			// he's not reviving anyone but he still has revive stuff up, clean it all up
		/#
		iprintlnbold( "FAILSAFE CLEANING UP REVIVE HUD AND GUN" );
		#/
			// pass in "none" since we have no idea what the weapon they should be showing is
			player zm_laststand::revive_give_back_weapons( level.weaponNone );
		
			if ( isdefined( player.reviveProgressBar ) )
			{
				player.reviveProgressBar hud::destroyElem();
			}
		
			if ( isdefined( player.reviveTextHud ) )
			{
				player.reviveTextHud destroy();
			}		
		}
	}
}

function set_intermission_point()
{
	points =struct::get_array( "intermission", "targetname" );

	if( points.size < 1 )
	{
		return;
	}	
	points = array::randomize( points );

	point = points[0]; 
	
	SetDemoIntermissionPoint( point.origin, point.angles );
}


function spawnSpectator()
{
	self endon( "disconnect" ); 
	self endon( "spawned_spectator" ); 
	self notify( "spawned" ); 
	self notify( "end_respawn" );

	if( level.intermission )
	{
		return;
	}

	if( IsDefined( level.no_spectator ) && level.no_spectator )
	{
		wait( 3 );
		ExitLevel();
	}

	// The check_for_level_end looks for this
	self.is_zombie = true;

	//failsafe against losing viewarms due to the thread returning them getting an endon from "zombified"
	level thread failsafe_revive_give_back_weapons(self);

	// Remove all reviving abilities
	self notify ( "zombified" );

	if( isdefined( self.revivetrigger ) )
	{
		self.revivetrigger delete();
		self.revivetrigger = undefined;
	}

	self.zombification_time = GetTime(); //set time when player died

	resetTimeout(); 

	// Stop shellshock and rumble
	self StopShellshock(); 
	self StopRumble( "damage_heavy" ); 

	self.sessionstate = "spectator"; 
	self.spectatorclient = -1;

	self.maxhealth = self.health;
	self.shellshocked = false; 
	self.inWater = false; 
	self.friendlydamage = undefined; 
	self.hasSpawned = true; 
	self.spawnTime = GetTime(); 
	self.afk = false; 

/#	println( "*************************Zombie Spectator***" );	#/
	self detachAll();

	if( isdefined( level.custom_spectate_permissions ) )
	{
	   self [[level.custom_spectate_permissions]]();
	}
	else
	{
		self setSpectatePermissions( true );
	}
	self thread spectator_thread();

	self Spawn( self.origin, self.angles );
	self notify( "spawned_spectator" );
}

function setSpectatePermissions( isOn )
{
	self AllowSpectateTeam( "allies", isOn && self.team == "allies" );
	self AllowSpectateTeam( "axis", isOn && self.team == "axis" );
	self AllowSpectateTeam( "freelook", false );
	self AllowSpectateTeam( "none", false );	
}

function spectator_thread()
{
	self endon( "disconnect" ); 
	self endon( "spawned_player" );

/*	we are not currently supporting the shared screen tech
	if( self IsSplitScreen() )
	{
		last_alive = undefined;
		players = GetPlayers();

		for( i = 0; i < players.size; i++ )
		{
			if( !players[i].is_zombie )
			{
				last_alive = players[i];
			}
		}

		share_screen( last_alive, true );

		return;
	}
*/

//	self thread spectator_toggle_3rd_person();
}

function spectator_toggle_3rd_person()
{
	self endon( "disconnect" ); 
	self endon( "spawned_player" );

	third_person = true;
	self set_third_person( true );
}


function set_third_person( value )
{
	if( value )
	{
		self SetClientThirdPerson( 1 );
		self SetClientThirdPersonAngle( 354 );

		self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 );
	}
	else
	{
		self SetClientThirdPerson( 0 );
		self SetClientThirdPersonAngle( 0 );

		self setDepthOfField( 0, 0, 512, 4000, 4, 0 );
	}
	self resetFov();
}

function last_stand_revive()
{
	level endon( "between_round_over" );

	players = GetPlayers();


	//If everyone is in last stand or spectate then revive all
	laststand_count = 0;
	foreach(player in players)
	{
		if( !zm_utility::is_player_valid( player ) )
		{
			laststand_count ++;
		}
	}

	if( laststand_count == players.size )
	{
		for ( i = 0; i < players.size; i++ )
		{
			if ( players[i] laststand::player_is_in_laststand() && players[i].revivetrigger.beingRevived == 0 )
			{
				players[i] zm_laststand::auto_revive( players[i] );
			}
		}
	}
}

// ww: arrange the last stand pistols so when it come time to choose which one they are inited
function last_stand_pistol_rank_init()
{
	level.pistol_values = [];	
	
	// ww: in a solo game the ranking of the pistols is a bit different based on the upgraded 1911 swap
	// any pistol ranked level.pistol_value_solo_replace_below or lower will be ignored and the player will be given the upgraded 1911
	level.pistol_values[ level.pistol_values.size ] = level.default_laststandpistol;
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "pistol_burst" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "pistol_fullauto" );
	level.pistol_value_solo_replace_below = level.pistol_values.size-1;  // EO: anything scoring lower than this should be replaced
	level.pistol_values[ level.pistol_values.size ] = level.default_solo_laststandpistol;
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "pistol_burst_upgraded" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "pistol_fullauto_upgraded" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "ray_gun" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "raygun_mark2" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "ray_gun_upgraded" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "raygun_mark2_upgraded" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "raygun_mark3" );
	level.pistol_values[ level.pistol_values.size ] = GetWeapon( "raygun_mark3_upgraded" );
}

function last_stand_pistol_swap()
{
	if ( self zm_utility::has_powerup_weapon() )
	{
		// this will force the laststand module to switch us to any primary weapon, since we will no longer have this after revive
		self.lastActiveWeapon = level.weaponNone;
	}
	
	// PORTIZ: chance to override the player's last stand pistol, but only if the override is stronger than the current one
	if ( isdefined( self.w_min_last_stand_pistol_override ) )
	{
		self last_stand_minimum_pistol_override();
	}

	if ( !self HasWeapon( self.laststandpistol ) )
	{
		self GiveWeapon( self.laststandpistol );
	}
	ammoclip = self.laststandpistol.clipSize;
	doubleclip = ammoclip * 2;
	
	if( ( isdefined( self._special_solo_pistol_swap ) && self._special_solo_pistol_swap ) || (self.laststandpistol == level.default_solo_laststandpistol && !self.hadpistol) )
	{
		self._special_solo_pistol_swap = 0;
		self.hadpistol = false;
		self SetWeaponAmmoStock( self.laststandpistol, doubleclip );
	}
	else if( level flag::get("solo_game") && self.laststandpistol == level.default_solo_laststandpistol )
	{
		self SetWeaponAmmoStock( self.laststandpistol, doubleclip );
	}
	else if ( self.laststandpistol == level.default_laststandpistol )
	{
		self SetWeaponAmmoStock( self.laststandpistol, doubleclip );
	}
	else if ( !isdefined( self.stored_weapon_info ) || !isdefined( self.stored_weapon_info[ self.laststandpistol ] ) )
	{
		self SetWeaponAmmoStock( self.laststandpistol, doubleclip ); // PORTIZ: in the case that we awarded a temporary last stand weapon, just set the stock and skip the ammo tracking
	}
	else if ( self.laststandpistol.name == "ray_gun" || self.laststandpistol.name == "ray_gun_upgraded" )
	{
		if ( self.stored_weapon_info[ self.laststandpistol ].total_amt >= ammoclip )
		{
			self SetWeaponAmmoClip( self.laststandpistol, ammoclip );
			self.stored_weapon_info[ self.laststandpistol ].given_amt = ammoclip;
		}
		else
		{
			self SetWeaponAmmoClip( self.laststandpistol, self.stored_weapon_info[ self.laststandpistol ].total_amt );
			self.stored_weapon_info[ self.laststandpistol ].given_amt = self.stored_weapon_info[ self.laststandpistol ].total_amt;
		}
		self SetWeaponAmmoStock( self.laststandpistol, 0 );
	}
	else
	{
		if ( self.stored_weapon_info[ self.laststandpistol ].stock_amt >= doubleclip )
		{
			self SetWeaponAmmoStock( self.laststandpistol, doubleclip );
			self.stored_weapon_info[ self.laststandpistol ].given_amt = doubleclip + self.stored_weapon_info[ self.laststandpistol ].clip_amt + self.stored_weapon_info[ self.laststandpistol ].left_clip_amt;
		}
		else
		{
			self SetWeaponAmmoStock( self.laststandpistol, self.stored_weapon_info[ self.laststandpistol ].stock_amt );
			self.stored_weapon_info[ self.laststandpistol ].given_amt = self.stored_weapon_info[ self.laststandpistol ].total_amt;
		}
	}
	
	self SwitchToWeapon( self.laststandpistol );
}

// PORTIZ: this runs if self.w_min_last_stand_pistol_override is defined, and just before the player is given his last stand pistol.
function last_stand_minimum_pistol_override()
{
	for( i = 0; i < level.pistol_values.size; i++ )
	{
		if( level.pistol_values[i] == self.w_min_last_stand_pistol_override )
		{
			n_min_last_stand_pistol_value = i; // find the value corresponding to our override weapon
			break;
		}
	}
	
	for( k = 0; k < level.pistol_values.size; k++ )
	{
		if( level.pistol_values[k] == self.laststandpistol )
		{
			n_default_last_stand_pistol_value = k; // find the value corresponding to our default last stand weapon
			break;
		}
	}
	
	if ( n_min_last_stand_pistol_value > n_default_last_stand_pistol_value )
	{
		self.hadpistol = false;
		self.laststandpistol = self.w_min_last_stand_pistol_override;
	}
}

// ww: make sure the player has the best pistol when they go in to last stand
function last_stand_best_pistol()
{
	pistol_array = [];

	current_weapons = self GetWeaponsListPrimaries();
	
	for( i = 0; i < current_weapons.size; i++ )
	{
		// make sure the weapon is a pistol
		wclass = current_weapons[i].weapClass;
		if ( current_weapons[i].isBallisticKnife )
		{
			wclass = "knife";
		}
		
		if ( wclass == "pistol" || wclass == "pistolspread" || wclass == "pistol spread" )
		{
			if ( (current_weapons[i] != level.default_solo_laststandpistol && !level flag::get("solo_game") )  || (!level flag::get("solo_game") && current_weapons[i] != level.default_solo_laststandpistol ))
			{
				if( (current_weapons[i] != self.laststandpistol) || (self.laststandpistol != level.default_laststandpistol) )
				{
					if ( self GetAmmoCount( current_weapons[i] ) <= 0 )
					{
						continue;
					}
				}
			}

			pistol_array_index = pistol_array.size; // set up the spot in the array 
			pistol_array[ pistol_array_index ] = SpawnStruct(); // struct to store info on
			
			pistol_array[ pistol_array_index ].weapon = current_weapons[i];
			pistol_array[ pistol_array_index ].value = 0; // add a value in case a new weapon is introduced that hasn't been set up in level.pistol_values
			
			// compare the current weapon to the level.pistol_values to see what the value is
			for( j = 0; j < level.pistol_values.size; j++ )
			{
				if( level.pistol_values[j] == current_weapons[i].rootWeapon )
				{
					pistol_array[ pistol_array_index ].value = j;
					break;
				}
			}
		}
	}
	
	self.laststandpistol = last_stand_compare_pistols( pistol_array );
}

// ww: compares the array passed in for the highest valued pistol
function last_stand_compare_pistols( struct_array )
{
	if( !IsArray( struct_array ) || struct_array.size <= 0 )
	{
		self.hadpistol = false;
		
		//array will be empty if the pistol had no ammo...so lets see if the player had the pistol
		if(isDefined(self.stored_weapon_info))
		{
			stored_weapon_info = GetArrayKeys( self.stored_weapon_info );
			for( j = 0; j < stored_weapon_info.size; j++ )
			{
				if( stored_weapon_info[ j ].rootWeapon == level.laststandpistol)
				{
					self.hadpistol = true;
					return stored_weapon_info[ j ];
				}
			}
		}
				
		return level.laststandpistol; // nothing in the array then give the level last stand pistol
	}
	
	highest_score_pistol = struct_array[0]; // first time through give the first one to the highest score

	for( i = 1; i < struct_array.size; i++ )
	{
		if( struct_array[i].value > highest_score_pistol.value )
		{
			highest_score_pistol = struct_array[i];
		}
	}

	if( level flag::get( "solo_game" ) )
	{
		self._special_solo_pistol_swap = 0; // ww: this way the weapon knows to pack texture when given
		if( highest_score_pistol.value <= level.pistol_value_solo_replace_below ) 
		{
			self.hadpistol = false;
			self._special_solo_pistol_swap = 1;

			if ( ( isdefined( level.force_solo_quick_revive ) && level.force_solo_quick_revive ) && ( !self HasPerk( "specialty_quickrevive" ) ) )
			{
				return highest_score_pistol.weapon; // ar: gun won't be replaced
			}
			else
			{
				return level.laststandpistol; // ww: if it scores too low the player gets the 1911 upgraded
			}
		}
		else
		{
			return highest_score_pistol.weapon; // ww: gun is high in ranking and won't be replaced
		}
	}
	else // ww: happens when not in solo
	{
		return highest_score_pistol.weapon;
	}

}

// ww: override function for saving player pistol ammo count
function last_stand_save_pistol_ammo()
{
	weapon_inventory = self GetWeaponsList( true );
	self.stored_weapon_info = [];

	for( i = 0; i < weapon_inventory.size; i++ )
	{
		weapon = weapon_inventory[i];

		wclass = weapon.weapClass;
		if ( weapon.isBallisticKnife )
		{
			wclass = "knife";
		}
		
		if ( wclass == "pistol" || wclass == "pistolspread" || wclass == "pistol spread" ) 
		{
			self.stored_weapon_info[ weapon ] = SpawnStruct();
			self.stored_weapon_info[ weapon ].clip_amt = self GetWeaponAmmoClip( weapon );
			self.stored_weapon_info[ weapon ].left_clip_amt = 0;
			dual_wield_weapon = weapon.dualWieldWeapon;
			if ( level.weaponNone != dual_wield_weapon )
			{
				self.stored_weapon_info[ weapon ].left_clip_amt = self GetWeaponAmmoClip( dual_wield_weapon );
			}
			self.stored_weapon_info[ weapon ].stock_amt = self GetWeaponAmmoStock( weapon );
			self.stored_weapon_info[ weapon ].total_amt = self.stored_weapon_info[ weapon ].clip_amt + self.stored_weapon_info[ weapon ].left_clip_amt + self.stored_weapon_info[ weapon ].stock_amt;
			self.stored_weapon_info[ weapon ].given_amt = 0;
		}
	}
	
	self last_stand_best_pistol();
}

// ww: override to restore the player's pistol ammo after being picked up
function last_stand_restore_pistol_ammo()
{
	self.weapon_taken_by_losing_specialty_additionalprimaryweapon = level.weaponNone;

	if ( !IsDefined( self.stored_weapon_info ) )
	{
		return;
	}
	
	weapon_inventory = self GetWeaponsList( true );
	weapon_to_restore = GetArrayKeys( self.stored_weapon_info );
	
	for( i = 0; i < weapon_inventory.size; i++ )
	{
		weapon = weapon_inventory[i];
		
		if ( weapon != self.laststandpistol )
		{
			continue;
		}
		
		// for this loop, weapon == self.laststandpistol
		for( j = 0; j < weapon_to_restore.size; j++ )
		{
			if ( weapon == weapon_to_restore[j] )
			{
				dual_wield_weapon = weapon_to_restore[j].dualWieldWeapon;
				if ( weapon != level.default_laststandpistol )
				{
					last_clip = self GetWeaponAmmoClip( weapon );
					last_left_clip = 0;
					if ( level.weaponNone != dual_wield_weapon )
					{
						last_left_clip = self GetWeaponAmmoClip( dual_wield_weapon );
					}
					last_stock = self GetWeaponAmmoStock( weapon );
					last_total = last_clip + last_left_clip + last_stock;

					used_amt = self.stored_weapon_info[ weapon ].given_amt - last_total;

					if ( used_amt >= self.stored_weapon_info[ weapon ].stock_amt )
					{
						used_amt -= self.stored_weapon_info[ weapon ].stock_amt;
						self.stored_weapon_info[ weapon ].stock_amt = 0;

						self.stored_weapon_info[ weapon ].clip_amt -= used_amt;
						if ( self.stored_weapon_info[ weapon ].clip_amt < 0 )
						{
							self.stored_weapon_info[ weapon ].clip_amt = 0;
						}
					}
					else 
					{
						new_stock_amt = self.stored_weapon_info[ weapon ].stock_amt - used_amt;
						if ( new_stock_amt < self.stored_weapon_info[ weapon ].stock_amt )
						{
							self.stored_weapon_info[ weapon ].stock_amt = new_stock_amt;
						}
					}
				}

				self SetWeaponAmmoClip( weapon, self.stored_weapon_info[weapon].clip_amt );
				if ( level.weaponNone != dual_wield_weapon )
				{
					self SetWeaponAmmoClip( dual_wield_weapon, self.stored_weapon_info[weapon].left_clip_amt );
				}
				self SetWeaponAmmoStock( weapon, self.stored_weapon_info[weapon].stock_amt );
				break;
			}
		}
	}
}

function last_stand_take_thrown_grenade()
{
	self endon( "disconnect" );
	self endon( "bled_out" );
	self endon( "player_revived" );
	
	self waittill ( "grenade_fire", grenade, weapon );

	if ( isdefined(self.lsgsar_lethal) && weapon == self.lsgsar_lethal )
	{
		self.lsgsar_lethal_nade_amt--;
	}

	if ( isdefined(self.lsgsar_tactical) && weapon == self.lsgsar_tactical )
	{
		self.lsgsar_tactical_nade_amt--;
	}
}

// ww: zeros out the player's grenades until they revive
function last_stand_grenade_save_and_return()
{
	if(( isdefined( level.isresetting_grief ) && level.isresetting_grief )) //don't do this for Grief when resetting the round
	{
		return;
	}
	self endon( "disconnect" );
	self endon ("bled_out");
	
	self.lsgsar_lethal_nade_amt = 0;
	self.lsgsar_has_lethal_nade = false;
	self.lsgsar_tactical_nade_amt = 0;
	self.lsgsar_has_tactical_nade = false;
	self.lsgsar_lethal = undefined;
	self.lsgsar_tactical = undefined;

	if (self IsThrowingGrenade())
	{
		self thread last_stand_take_thrown_grenade();
	}
	
	weapon = self zm_utility::get_player_lethal_grenade();
	if ( weapon != level.weaponNone )
	{
		self.lsgsar_has_lethal_nade = true;
		self.lsgsar_lethal = weapon;
		self.lsgsar_lethal_nade_amt = self GetWeaponAmmoClip( weapon );
		self SetWeaponAmmoClip( weapon, 0 );
		self TakeWeapon( weapon );
	}

	weapon = self zm_utility::get_player_tactical_grenade();
	if ( weapon != level.weaponNone )
	{
		self.lsgsar_has_tactical_nade = true;
		self.lsgsar_tactical = weapon;
		self.lsgsar_tactical_nade_amt = self GetWeaponAmmoClip( weapon );
		self SetWeaponAmmoClip( weapon, 0 );
		self TakeWeapon( weapon );
	}
	
	self waittill( "player_revived" );
	
	if ( self.lsgsar_has_lethal_nade )
	{
		self zm_utility::set_player_lethal_grenade( self.lsgsar_lethal );
		self GiveWeapon( self.lsgsar_lethal );
		self SetWeaponAmmoClip( self.lsgsar_lethal, self.lsgsar_lethal_nade_amt );
	}
	
	if ( self.lsgsar_has_tactical_nade )
	{
		self zm_utility::set_player_tactical_grenade( self.lsgsar_tactical );
		self GiveWeapon( self.lsgsar_tactical );
		self SetWeaponAmmoClip( self.lsgsar_tactical, self.lsgsar_tactical_nade_amt );
	}

	self.lsgsar_lethal_nade_amt = undefined;
	self.lsgsar_has_lethal_nade = undefined;
	self.lsgsar_tactical_nade_amt = undefined;
	self.lsgsar_has_tactical_nade = undefined;
	self.lsgsar_lethal = undefined;
	self.lsgsar_tactical = undefined;

}

function spectators_respawn()
{
	level endon( "between_round_over" );

	if( !IsDefined( level.zombie_vars["spectators_respawn"] ) || !level.zombie_vars["spectators_respawn"] )
	{
		return;
	}

	while( 1 )
	{
		players = GetPlayers();
		for( i = 0; i < players.size; i++ )
		{
			e_player = players[i];
			e_player spectator_respawn_player();
		}

		wait( 1 );
	}
}

function spectator_respawn_player()
{
	if( self.sessionstate == "spectator" && IsDefined( self.spectator_respawn ))
	{
		if( !IsDefined( level.custom_spawnPlayer ) )
		{
			// Custom spawn call for when they respawn from spectator
			level.custom_spawnPlayer = &spectator_respawn;
		}
				
		self [[level.spawnPlayer]]();
		thread refresh_player_navcard_hud();
		if (isDefined(level.script) && level.round_number > 6 && self.score < 1500)
		{
			self.old_score = self.score;
			if(isDefined(level.spectator_respawn_custom_score))
			{
				self [[level.spectator_respawn_custom_score]]();
			}
			self.score = 1500;
		}
	}
}

function spectator_respawn()
{
/#	println( "*************************Respawn Spectator***" );	#/
	assert( IsDefined( self.spectator_respawn ) );

	origin = self.spectator_respawn.origin;
	angles = self.spectator_respawn.angles;

	self setSpectatePermissions( false );

	new_origin = undefined;
	
	
	if ( isdefined( level.check_valid_spawn_override ) )
	{
		new_origin = [[ level.check_valid_spawn_override ]]( self );
	}

	if ( !isdefined( new_origin ) )
	{
		new_origin = check_for_valid_spawn_near_team( self,true );
	}
	

	if( IsDefined( new_origin ) )
	{
		if(!isDefined(new_origin.angles))
		{
			angles = (0, 0, 0);
		}
		else
		{	
			angles = new_origin.angles;
		}	
		self Spawn( new_origin.origin, angles );
	}
	else
	{
		self Spawn( origin, angles );
	}


	if ( IsDefined( self zm_utility::get_player_placeable_mine() ) )
	{
		self TakeWeapon( self zm_utility::get_player_placeable_mine() );
		self zm_utility::set_player_placeable_mine( level.weaponNone );
	}

	self zm_equipment::take();

	self.is_burning = undefined;
	self.abilities = [];

	// The check_for_level_end looks for this
	self.is_zombie = false;
	zm_laststand::set_ignoreme( false );

	self clientfield::set( "zmbLastStand", 0 );
	self RevivePlayer();

	self notify( "spawned_player" );
	self callback::callback( #"on_player_spawned" );

	if(IsDefined(level._zombiemode_post_respawn_callback))
	{
		self thread [[level._zombiemode_post_respawn_callback]]();
	}
	
	// Penalize the player when we respawn, since he 'died'
	self zm_score::player_reduce_points( "died" );
	
	self zm_melee_weapon::spectator_respawn_all();

	self thread player_zombie_breadcrumb();

	self thread zm_perks::return_retained_perks();
	
	return true;
}


//*****************************************************************************
//*****************************************************************************

function check_for_valid_spawn_near_team( revivee, return_struct )
{
	// if level override is detected, use it instead of normal respawn logic
	if ( IsDefined( level.check_for_valid_spawn_near_team_callback ) )
	{
		spawn_location = [[ level.check_for_valid_spawn_near_team_callback ]](revivee, return_struct);
		return spawn_location;
	}
	else  // normal respawn logic below here
	{
		players = GetPlayers();
		spawn_points = zm_gametype::get_player_spawns_for_gametype();
		
		closest_group = undefined;
		closest_distance = 100000000;
		backup_group = undefined;
		backup_distance = 100000000;
	
		if( spawn_points.size == 0 )
			return undefined;

		a_enabled_zone_entities = zm_zonemgr::get_active_zones_entities();
	
		// Look for the closest group that is within the specified ideal distances
		//	If we can't find one within a valid area, use the closest unlocked group.
		for( i = 0; i < players.size; i++ )
		{
			if( zm_utility::is_player_valid( players[i],undefined,true ) && (players[i] != self) )
			{
				for( j = 0 ; j < spawn_points.size; j++ )
				{
					if( isdefined(spawn_points[j].script_int) )
						ideal_distance = spawn_points[j].script_int;
					else
						ideal_distance = 1000;
					
					// Safety check, check the spawn point is inside an enabled zone
					// There have been cases where a spawn point has the wrong zone KVP on it compared to the zone it exists inside
					if ( zm_utility::check_point_in_enabled_zone( spawn_points[j].origin, false, a_enabled_zone_entities ) == false )
					{
						continue;
					}
						
					if ( spawn_points[j].locked == false )
					{
						plyr_dist = DistanceSquared( players[i].origin, spawn_points[j].origin );
						if( plyr_dist < ( ideal_distance * ideal_distance ) )
						{
							if ( plyr_dist < closest_distance )
							{
								closest_distance = plyr_dist;
								closest_group = j;
							}
						}
						else
						{
							if ( plyr_dist < backup_distance )
							{
								backup_group = j;
								backup_distance = plyr_dist;
							}
						}
					}
				}
			}
	
			//	If we don't have a closest_group, let's use the backup
			if( !IsDefined( closest_group ) )
			{
				closest_group = backup_group;
			}
	
			if( IsDefined( closest_group ) )
			{
				spawn_location = get_valid_spawn_location( revivee, spawn_points, closest_group, return_struct );
				if( IsDefined(spawn_location) )
				{
					return( spawn_location );
				}
			}
		}
	
		return undefined;
	}
}


//*****************************************************************************
//*****************************************************************************

function get_valid_spawn_location( revivee, spawn_points, closest_group, return_struct )
{
	spawn_array =struct::get_array( spawn_points[closest_group].target, "targetname" );
	spawn_array = array::randomize(spawn_array);
			
	for( k = 0; k < spawn_array.size; k++ )
	{
		if(IsDefined(spawn_array[k].plyr) && spawn_array[k].plyr == revivee GetEntityNumber())
		{
			if(positionWouldTelefrag(spawn_array[k].origin))
			{
				spawn_array[k].plyr = undefined;
				break;
			}
			else if(( isdefined( return_struct ) && return_struct ))
			{
				return spawn_array[k];
			}
			else
			{			
				return spawn_array[k].origin;
			}
		}	
	}
			
	for( k = 0; k < spawn_array.size; k++ )
	{
		if(positionWouldTelefrag(spawn_array[k].origin))
		{
			continue;
		}
		if(!IsDefined(spawn_array[k].plyr) || spawn_array[k].plyr == revivee GetEntityNumber())
		{
			spawn_array[k].plyr = revivee GetEntityNumber();
			if(( isdefined( return_struct ) && return_struct ))
			{
				return spawn_array[k];
			}
			else
			{			
				return spawn_array[k].origin;
			}
		}
	}	

	if(( isdefined( return_struct ) && return_struct ))
	{
		return spawn_array[0];
	}
			
	return spawn_array[0].origin;
}


//*****************************************************************************
//*****************************************************************************

function check_for_valid_spawn_near_position( revivee, v_position, return_struct )
{
	spawn_points = zm_gametype::get_player_spawns_for_gametype();

	if( spawn_points.size == 0 )
	{
		return( undefined );
	}
	
	closest_group = undefined;
	closest_distance = 100000000;
	backup_group = undefined;
	backup_distance = 100000000;

	for( i=0 ; i<spawn_points.size; i++ )
	{
		if( IsDefined(spawn_points[i].script_int) )
		{
			ideal_distance = spawn_points[i].script_int;
		}
		else
		{
			ideal_distance = 1000;
		}

		if ( spawn_points[i].locked == false )
		{
			dist = DistanceSquared( v_position, spawn_points[i].origin );
			if( dist < ( ideal_distance * ideal_distance ) )
			{
				if ( dist < closest_distance )
				{
					closest_distance = dist;
					closest_group = i;
				}
			}
			else
			{
				if ( dist < backup_distance )
				{
					backup_group = i;
					backup_distance = dist;
				}
			}
		}
		//	If we don't have a closest_group, let's use the backup
		if ( !IsDefined( closest_group ) )
		{
			closest_group = backup_group;
		}
	}

	if( IsDefined( closest_group ) )
	{
		spawn_location = get_valid_spawn_location( revivee, spawn_points, closest_group, return_struct );
		if( IsDefined(spawn_location) )
		{
			return( spawn_location );
		}
	}

	return undefined;
}



//*****************************************************************************
// Get a valid spawn point within the (min_distance, max_distance) ranges
//*****************************************************************************

function check_for_valid_spawn_within_range( revivee, v_position, return_struct, min_distance, max_distance )
{
	spawn_points = zm_gametype::get_player_spawns_for_gametype();

	if( spawn_points.size == 0 )
	{
		return( undefined );
	}
	
	closest_group = undefined;
	closest_distance = 100000000;

	for( i=0 ; i<spawn_points.size; i++ )
	{
		if ( spawn_points[i].locked == false )
		{
			dist = Distance( v_position, spawn_points[i].origin );
			if( (dist >= min_distance) && (dist <= max_distance) )
			{
				if( dist < closest_distance )
				{
					closest_distance = dist;
					closest_group = i;
				}
			}
		}
	}

	if( IsDefined( closest_group ) )
	{
		spawn_location = get_valid_spawn_location( revivee, spawn_points, closest_group, return_struct );
		if( IsDefined(spawn_location) )
		{
			return( spawn_location );
		}
	}

	return undefined;
}


//*****************************************************************************
//*****************************************************************************

function get_players_on_team(exclude)
{
	teammates = [];

	players = GetPlayers();
	for(i=0;i<players.size;i++)
	{		
		//check to see if other players on your team are alive and not waiting to be revived
		if(players[i].spawn_side == self.spawn_side && !isDefined(players[i].revivetrigger) && players[i] != exclude )
		{
			teammates[teammates.size] = players[i];
		}
	}

	return teammates;
}



function get_safe_breadcrumb_pos( player )
{
	players = GetPlayers();
	valid_players = [];

	min_dist = 150 * 150;
	for( i = 0; i < players.size; i++ )
	{
		if( !zm_utility::is_player_valid( players[i] ) )
		{
			continue;
		}

		valid_players[valid_players.size] = players[i];
	}

	for( i = 0; i < valid_players.size; i++ )
	{
		count = 0;
		for( q = 1; q < player.zombie_breadcrumbs.size; q++ )
		{
			if( DistanceSquared( player.zombie_breadcrumbs[q], valid_players[i].origin ) < min_dist )
			{
				continue;
			}
			
			count++;
			if( count == valid_players.size )
			{
				return player.zombie_breadcrumbs[q];
			}
		}
	}

	return undefined;
}

function round_spawning()
{
	level endon( "intermission" );
	level endon( "end_of_round" );
	level endon( "restart_round" );
/#
	level endon( "kill_round" );
#/

	if( level.intermission )
	{
		return;
	}

	if ( cheat_enabled( 2 ) ) 
	{
		return;
	}

	if( level.zm_loc_types[ "zombie_location" ].size < 1 )
	{
		ASSERTMSG( "No active spawners in the map.  Check to see if the zone is active and if it's pointing to spawners." ); 
		return; 
	}

	zombie_utility::ai_calculate_health( level.round_number ); 

	count = 0; 

	//CODER MOD: TOMMY K
	players = GetPlayers();
	for( i = 0; i < players.size; i++ )
	{
		players[i].zombification_time = 0;
	}

	// Now set the total for the new round, except when it's already been set by the kill counter
	if ( !( IsDefined( level.kill_counter_hud ) && level.zombie_total > 0 ) )
	{
		level.zombie_total = get_zombie_count_for_round( level.round_number, level.players.size );
		level.zombie_respawns = 0;	// reset number of zombies needing respawn
		level notify( "zombie_total_set" );
	}
	
	if ( IsDefined( level.zombie_total_set_func ) )
	{
		level thread [[ level.zombie_total_set_func ]]();
	}
	 
	if ( level.round_number < 10 || level.speed_change_max > 0 )
	{
		level thread zombie_utility::zombie_speed_up();
	}
	
	old_spawn = undefined;
	while( 1 )
	{
		while( zombie_utility::get_current_zombie_count() >= level.zombie_ai_limit || level.zombie_total <= 0 )
		{
			wait( 0.1 );
		}
		
		while ( zombie_utility::get_current_actor_count() >= level.zombie_actor_limit )
		{
			zombie_utility::clear_all_corpses();
			wait( 0.1 );
		}

		// if we're using something that can pause the world, wait until finished to continue spawning
		if(flag::exists("world_is_paused"))
		{
			level flag::wait_till_clear("world_is_paused");
		}

		// added ability to pause zombie spawning
		level flag::wait_till( "spawn_zombies" );
		
		//Not great fix for this being zero - which it should NEVER be! (2 days to ship - PETER)
		while( level.zm_loc_types[ "zombie_location" ].size <= 0 )
		{
			wait( 0.1 );
		}
		
		run_custom_ai_spawn_checks();
		
		if ( ( isdefined( level.hostMigrationTimer ) && level.hostMigrationTimer ) )
		{
			util::wait_network_frame();
			continue;
		}
		
		//	Run custom round spawn logic - returns TRUE if we spawned something, FALSE if we need to spawn a zombie.
		if ( isdefined( level.fn_custom_round_ai_spawn ) )
		{
			if ( [[ level.fn_custom_round_ai_spawn ]]() )
			{
				// we handled the spawn
				util::wait_network_frame();
				continue;
			}
		}

		if( IsDefined( level.zombie_spawners ) )
		{
			// Check for custom zombie spawner selection
			if ( isdefined( level.fn_custom_zombie_spawner_selection ) )
			{
				spawner = [[ level.fn_custom_zombie_spawner_selection ]]();
			}

			// Default zombie spawner selection
			else
			{
				if( ( isdefined( level.use_multiple_spawns ) && level.use_multiple_spawns ) )
				{
					if( isdefined( level.spawner_int ) && ( isdefined( level.zombie_spawn[level.spawner_int].size ) && level.zombie_spawn[level.spawner_int].size ) )
					{
						spawner = array::random( level.zombie_spawn[level.spawner_int] );
					}
					else
					{
						spawner = array::random( level.zombie_spawners );
					}				
				}
				else
				{
					spawner = array::random( level.zombie_spawners );
				}
			}

			ai = zombie_utility::spawn_zombie( spawner, spawner.targetname ); 
 		}
 		
		if( IsDefined( ai ) )
		{
			level.zombie_total--;
			if ( level.zombie_respawns > 0 )
			{
				level.zombie_respawns--;
			}

			ai thread zombie_utility::round_spawn_failsafe();
			count++; 
			
			if ( ai ai::has_behavior_attribute( "can_juke" ) )
			{
				ai ai::set_behavior_attribute("can_juke", false);
			}

			// Get zombies into the map quicker if they're respawning from cleanup
			if ( level.zombie_respawns > 0 )//&& level.zombie_vars["zombie_spawn_delay"] > 1.0 )
			{
				wait 0.1;
			}
			else
			{
				wait( level.zombie_vars["zombie_spawn_delay"] );
			}
		}

		util::wait_network_frame();
	}
}

function get_zombie_count_for_round( n_round, n_player_count )
{
	max = level.zombie_vars["zombie_max_ai"];

	multiplier = n_round / 5;
	if( multiplier < 1 )
	{
		multiplier = 1;
	}

	// After round 10, exponentially have more AI attack the player
	if( n_round >= 10 )
	{
		multiplier *= n_round * 0.15;
	}

	if( n_player_count == 1 )
	{
		max += int( ( 0.5 * level.zombie_vars["zombie_ai_per_player"] ) * multiplier ); 
	}
	else
	{
		max += int( ( ( n_player_count - 1 ) * level.zombie_vars["zombie_ai_per_player"] ) * multiplier ); 
	}

	if( !isDefined( level.max_zombie_func ) )
	{
		level.max_zombie_func = &zombie_utility::default_max_zombie_func;
	}

	n_zombie_count = [[ level.max_zombie_func ]]( max, n_round );
	
	return n_zombie_count;
}

// Add custom ai (quads, etc.) to zombie spawner arrays for spawning
function run_custom_ai_spawn_checks()
{
	foreach ( str_id, s in level.custom_ai_spawn_check_funcs )
	{
		if ( [[ s.func_check ]]() )
		{
			a_spawners = [[ s.func_get_spawners ]]();
			level.zombie_spawners = ArrayCombine( level.zombie_spawners, a_spawners, false, false );
				
			if ( ( isdefined( level.use_multiple_spawns ) && level.use_multiple_spawns ) )
			{
				foreach ( sp in a_spawners )
				{
					if ( IsDefined( sp.script_int ) )
					{
						if ( !IsDefined( level.zombie_spawn[ sp.script_int ] ) )
						{
							level.zombie_spawn[ sp.script_int ] = [];
						}
						
						if ( !IsInArray( level.zombie_spawn[ sp.script_int ], sp ) )
						{
							if ( !isdefined( level.zombie_spawn[ sp.script_int ] ) ) level.zombie_spawn[ sp.script_int ] = []; else if ( !IsArray( level.zombie_spawn[ sp.script_int ] ) ) level.zombie_spawn[ sp.script_int ] = array( level.zombie_spawn[ sp.script_int ] ); level.zombie_spawn[ sp.script_int ][level.zombie_spawn[ sp.script_int ].size]=sp;;
						}
					}
				}
			}
			
			if ( IsDefined( s.func_get_locations ) )
			{
				a_locations = [[ s.func_get_locations ]]();
				level.zm_loc_types[ "zombie_location" ] = ArrayCombine( level.zm_loc_types[ "zombie_location" ], a_locations, false, false );
			}
		}
		else
		{
			a_spawners = [[ s.func_get_spawners ]]();
			
			foreach ( sp in a_spawners )
			{
				ArrayRemoveValue( level.zombie_spawners, sp );
			}
			
			if ( ( isdefined( level.use_multiple_spawns ) && level.use_multiple_spawns ) )
			{
				foreach ( sp in a_spawners )
				{
					if ( IsDefined( sp.script_int ) && IsDefined( level.zombie_spawn[ sp.script_int ] ) )
					{
						ArrayRemoveValue( level.zombie_spawn[ sp.script_int ], sp );
					}
				}
			}
			
			if ( IsDefined( s.func_get_locations ) )
			{
				a_locations = [[ s.func_get_locations ]]();
			
				foreach ( s_loc in a_locations )
				{
					ArrayRemoveValue( level.zm_loc_types[ "zombie_location" ], s_loc );
				}
			}
		}
	}
}

function register_custom_ai_spawn_check( str_id, func_check, func_get_spawners, func_get_locations )
{
	if ( !IsDefined( level.custom_ai_spawn_check_funcs[str_id] ) )
	{
		level.custom_ai_spawn_check_funcs[ str_id ] = SpawnStruct();
	}
	
	level.custom_ai_spawn_check_funcs[ str_id ].func_check = func_check;
	level.custom_ai_spawn_check_funcs[ str_id ].func_get_spawners = func_get_spawners;
	level.custom_ai_spawn_check_funcs[ str_id ].func_get_locations = func_get_locations;
}

// TESTING: spawn one zombie at a time
function round_spawning_test()
{
	while (true)
	{
		spawn_point = array::random( level.zm_loc_types[ "zombie_location" ] );	// grab a random spawner

		spawner = array::random(level.zombie_spawners);		
		ai = zombie_utility::spawn_zombie( spawner,spawner.targetname,spawn_point); 
		
		ai waittill("death");

		wait 5;
	}
}


/////////////////////////////////////////////////////////

// round_text( text )
// {
// 	if( level.first_round )
// 	{
// 		intro = true;
// 	}
// 	else
// 	{
// 		intro = false;
// 	}
// 
// 	hud = create_simple_hud();
// 	hud.horzAlign = "center"; 
// 	hud.vertAlign = "middle";
// 	hud.alignX = "center"; 
// 	hud.alignY = "middle";
// 	hud.y = -100;
// 	hud.foreground = 1;
// 	hud.fontscale = 16.0;
// 	hud.alpha = 0; 
// 	hud.color = ( 1, 1, 1 );
// 
// 	hud SetText( text ); 
// 	hud FadeOverTime( 1.5 );
// 	hud.alpha = 1;
// 	wait( 1.5 );
// 
// 	if( intro )
// 	{
// 		wait( 1 );
// 		level notify( "intro_change_color" );
// 	}
// 
// 	hud FadeOverTime( 3 );
// 	//hud.color = ( 0.8, 0, 0 );
// 	hud.color = ( 0.21, 0, 0 );
// 	wait( 3 );
// 
// 	if( intro )
// 	{
// 		level waittill( "intro_hud_done" );
// 	}
// 
// 	hud FadeOverTime( 1.5 );
// 	hud.alpha = 0;
// 	wait( 1.5 ); 
// 	hud destroy();
// }


//	Allows the round to be paused.  Displays a countdown timer.
//
function round_pause( delay )
{
	if ( !IsDefined( delay ) )
	{
		delay = 30;
	}

	level.countdown_hud = zm_utility::create_counter_hud();
	level.countdown_hud SetValue( delay );
	level.countdown_hud.color = ( 1, 1, 1 );
	level.countdown_hud.alpha = 1;
	level.countdown_hud FadeOverTime( 2.0 );
	wait( 2.0 );

	level.countdown_hud.color = ( 0.21, 0, 0 );
	level.countdown_hud FadeOverTime( 3.0 );
	wait(3);

	while (delay >= 1)
	{
		wait (1);
		delay--;
		level.countdown_hud SetValue( delay );
	}

	// Zero!  Play end sound
	players = GetPlayers();
	for (i=0; i<players.size; i++ )
	{
		players[i] playlocalsound( "zmb_perks_packa_ready" );
	}

	level.countdown_hud FadeOverTime( 1.0 );
	level.countdown_hud.color = (1,1,1);
	level.countdown_hud.alpha = 0;
	wait( 1.0 );

	level.countdown_hud zm_utility::destroy_hud();
}


//	Zombie spawning
//
function round_start()
{
	if ( !isdefined( level.zombie_spawners) || level.zombie_spawners.size == 0 )
	{
		/#PrintLn("***Warning: No spawners found for this level.***");#/
			level flag::set( "begin_spawning" );
			return;
	}
/#	PrintLn( "ZM >> round_start start" );	#/

	if ( IsDefined(level.round_prestart_func) )
	{
		[[ level.round_prestart_func ]]();
	}
	else
	{
		n_delay = 2;
		
		if ( IsDefined( level.zombie_round_start_delay ) )
		{
			n_delay = level.zombie_round_start_delay;
		}
		
		wait n_delay;
	}

	level.zombie_health = level.zombie_vars["zombie_health_start"]; 

	if( GetDvarInt( "scr_writeconfigstrings" ) == 1 )
	{
		wait(5);
		ExitLevel();
		return;
	}

	if ( level.zombie_vars["game_start_delay"] > 0 )
	{
		round_pause( level.zombie_vars["game_start_delay"] );
	}

	level flag::set( "begin_spawning" );

	if( !isDefined(level.round_spawn_func) )
	{
		level.round_spawn_func = &round_spawning;
	}
	
	if(!IsDefined(level.move_spawn_func))
	{
		level.move_spawn_func	= &zm_utility::move_zombie_spawn_location;
	}	
	
/#
	if (GetDvarInt( "zombie_rise_test") )
	{
		level.round_spawn_func = &round_spawning_test;		// FOR TESTING, one zombie at a time, no round advancement
	}
#/

	if ( !isDefined(level.round_wait_func) )
	{
		level.round_wait_func = &round_wait;
	}

	if ( !IsDefined(level.round_think_func) )
	{
		level.round_think_func = &round_think;
	}

	
	level thread [[ level.round_think_func ]]();
}

//
// Lets the players know that you need power to open these
function play_door_dialog()
{
	self endon ("warning_dialog");
	timer = 0;

	while(1)
	{
		{wait(.05);};
		players = GetPlayers();
		for(i = 0; i < players.size; i++)
		{		
			dist = distancesquared(players[i].origin, self.origin );
			if(dist > 70*70)
			{
				timer =0;
				continue;
			}
			while(dist < 70*70 && timer < 3)
			{
				wait(0.5);
				timer++;
			}
			if(dist > 70*70 && timer >= 3)
			{
				self playsound("door_deny");
				
				players[i] zm_audio::create_and_play_dialog( "general", "outofmoney" );	
				wait(3);				
				self notify ("warning_dialog");
				//iprintlnbold("warning_given");
			}
		}
	}
}

function wait_until_first_player()
{
	players = GetPlayers();
	if( !IsDefined( players[0] ) )
	{
		level waittill( "first_player_ready" );
	}
}

//
//	Set the current round number hud display
function round_one_up()
{
	level endon("end_game");
	if( isdefined(level.noRoundNumber) && level.noRoundNumber==true )
	{
		return;
	}
	
	if(!IsDefined(level.doground_nomusic))
	{
		level.doground_nomusic = 0;
	}

	if( level.first_round )
	{
		intro = true;
		if( isdefined( level._custom_intro_vox ) )
		{
			level thread [[level._custom_intro_vox]]();
		}
		else
		{
			level thread play_level_start_vox_delayed();
		};
	}
	else
	{
		intro = false;
	}
	
	//Round Number Specific Lines
	if( level.round_number == 5 || level.round_number == 10 || level.round_number == 20 || level.round_number == 35 || level.round_number == 50 )
	{
	    players = GetPlayers();
	    rand = RandomIntRange(0,players.size);
	    players[rand] thread zm_audio::create_and_play_dialog( "general", "round_" + level.round_number );
	}
	
	if( intro )
	{
		if(( isdefined( level.host_ended_game ) && level.host_ended_game ))
		{
			return;
		}
		
		wait( 6.25 );
		level notify( "intro_hud_done" );
		wait( 2 );

	}
	else
	{
		wait( 2.5 );
	}
	
	ReportMTU(level.round_number);	// In network debug instrumented builds, causes network spike report to generate.
}


//	Flash the round display at the end of the round
//
function round_over()
{
	if( isdefined(level.noRoundNumber) && level.noRoundNumber==true )
	{
		return;
	}
	
	time = [[level.func_get_delay_between_rounds]]();

	players = GetPlayers();
	for ( player_index = 0; player_index < players.size; player_index++ )
	{
		if ( !IsDefined( players[player_index].pers["previous_distance_traveled"] ) )
		{
			players[player_index].pers["previous_distance_traveled"] = 0;
		}
		distanceThisRound = int( players[player_index].pers["distance_traveled"] - players[player_index].pers["previous_distance_traveled"] );
		players[player_index].pers["previous_distance_traveled"] = players[player_index].pers["distance_traveled"];
		players[player_index] IncrementPlayerStat("distance_traveled", distanceThisRound );
		if ( players[player_index].pers["team"] != "spectator" )
		{
			players[player_index] recordRoundEndStats();
		}
	}
	RecordZombieRoundEnd();
	// waiting for the Pulse from lua
	wait( time );
}

function get_delay_between_rounds()
{
	return( level.zombie_vars["zombie_between_round_time"] );
}

function recordPlayerRoundWeapon(weapon, statname)
{
	if (isDefined(weapon))
	{
		weaponIdx = GetBaseWeaponItemIndex(weapon);
		if (isDefined(weaponIdx))
		{
			self incrementplayerstat(statname, weaponIdx);
		}
	}
}

function recordPrimaryWeaponsStats(base_stat_name, max_weapons)
{
	current_weapons = self GetWeaponsListPrimaries();
	for (index = 0; index < max_weapons && index < current_weapons.size; index++)
	{
		recordPlayerRoundWeapon(current_weapons[index], base_stat_name + index);
	}
}

function recordRoundStartStats() //self == player
{
	zoneName = self zm_utility::get_current_zone();
	if( IsDefined(zoneName) )
	{
		self RecordZombieZone("startingZone", zoneName);
	}
	
	self incrementplayerstat("score", self.score);
	primaryWeapon =  self GetCurrentWeapon();

	self recordPrimaryWeaponsStats("roundStartPrimaryWeapon", 3);
	self RecordMapEvent(8, GetTime(), self.origin, level.round_number);
}

function recordRoundEndStats() //self == player
{
	zoneName = self zm_utility::get_current_zone();
	if( IsDefined(zoneName) )
	{
		self RecordZombieZone( "endingZone", zoneName );
	}
	
	self recordPrimaryWeaponsStats("roundEndPrimaryWeapon", 3);
	self RecordMapEvent(9, GetTime(), self.origin,  level.round_number);	
}

function round_think( restart = false )
{
/#	PrintLn( "ZM >> round_think start" );	#/
	
	level endon("end_round_think");
	
	if(!( isdefined( restart ) && restart ))
	{
		// Wait for blackscreen to end if in use
		if ( IsDefined( level.initial_round_wait_func ))
			[[level.initial_round_wait_func]]();
		
		if(!( isdefined( level.host_ended_game ) && level.host_ended_game ))
		{
			//unfreeze the players controls now
			players = GetPlayers();
			foreach(player in players)
			{
				if(!( isdefined( player.hostMigrationControlsFrozen ) && player.hostMigrationControlsFrozen ))
				{
					player FreezeControls(false);
					/# println(" Unfreeze controls 8"); #/
				}

				// set the initial round_number
				player zm_stats::set_global_stat( "rounds", level.round_number );
			}
		}
	}
	
	SetRoundsPlayed( level.round_number );
	
	for( ;; )
	{
		//////////////////////////////////////////
		//designed by prod DT#36173
		maxreward = 50 * level.round_number;
		if ( maxreward > 500 )
			maxreward = 500;
		level.zombie_vars["rebuild_barrier_cap_per_round"] = maxreward;
		//////////////////////////////////////////

		level.pro_tips_start_time = GetTime();
		level.zombie_last_run_time = GetTime();	// Resets the last time a zombie ran
	
		if ( IsDefined( level.zombie_round_change_custom ) )
		{
			[[ level.zombie_round_change_custom ]]();
		}
		else 
		{
			if( !( isdefined( level.sndMusicSpecialRound ) && level.sndMusicSpecialRound ) )
			{
				if( ( isdefined( level.sndGotoRoundOccurred ) && level.sndGotoRoundOccurred ))
					level.sndGotoRoundOccurred = false;
				else if( level.round_number == 1 )
					level thread zm_audio::sndMusicSystem_PlayState( "round_start_first" );
				else if( level.round_number <= 5 )
					level thread zm_audio::sndMusicSystem_PlayState( "round_start" );
				else
					level thread zm_audio::sndMusicSystem_PlayState( "round_start_short" );
			}
			round_one_up();
			//		round_text( &"ZOMBIE_ROUND_BEGIN" );
		}

		zm_powerups::powerup_round_start();

		players = GetPlayers();
		array::thread_all( players, &zm_blockers::rebuild_barrier_reward_reset );
		
		if(!( isdefined( level.headshots_only ) && level.headshots_only ) && !restart ) //no grenades for headshot only mode, or when grief restarts the round after everyone dies
		{
			level thread award_grenades_for_survivors();
		}
		
	/#	PrintLn( "ZM >> round_think, round="+level.round_number+", player_count=" + players.size );		#/

		level.round_start_time = GetTime();
	
		//Not great fix for this being zero - which it should NEVER be! (post ship - PETER)
		while( level.zm_loc_types[ "zombie_location" ].size <= 0 )
		{
			wait( 0.1 );
		}

	/#
		//Reset spawn counter for zones
		zkeys = GetArrayKeys( level.zones );
		for ( i = 0; i < zkeys.size; i++ )
		{
			zoneName = zkeys[i];
			level.zones[zoneName].round_spawn_count = 0;
		}
	#/

		level thread [[level.round_spawn_func]]();

		level notify( "start_of_round" );
		RecordZombieRoundStart();
		bb::logRoundEvent("start_of_round");
		players = GetPlayers();
		for ( index = 0; index < players.size; index++ )
		{
			players[index] recordRoundStartStats();
		}
		if(isDefined(level.round_start_custom_func))
		{
			[[level.round_start_custom_func]]();
		}
		
		[[level.round_wait_func]]();

		level.first_round = false;
		level notify( "end_of_round" );
		bb::logRoundEvent("end_of_round");
		UploadStats();
		
		if(isDefined(level.round_end_custom_logic))
		{
			[[level.round_end_custom_logic]]();
		}
		
		players = GetPlayers();
		
		// PORTIZ 7/27/16: now that no_end_game_check is being used more regularly, I was tempted to remove/change this because it seems to arbitrarily add 
		// the revival of last stand players on top of whatever mechanic toggled the bool in the first place. however, it doesn't seem to do harm, and I'd rather avoid
		// affecting these core systems if possible. for example, is there badness if a round transitions/starts and ALL players are in last stand? this can be revisited if
		// any specific bugs/exploits arise from it...
		if( ( isdefined( level.no_end_game_check ) && level.no_end_game_check ) )
		{
			level thread last_stand_revive();
			level thread spectators_respawn();
		}
		else if ( 1 != players.size )
		{
			level thread spectators_respawn();
			//level thread last_stand_revive();
		}

		players = GetPlayers(); 
		array::thread_all( players, &zm_pers_upgrades_system::round_end );

		if ( int(level.round_number / 5) * 5 == level.round_number )
		{
			level clientfield::set( "round_complete_time", int( ( level.time - level.n_gameplay_start_time + 500 ) / 1000 ) );
			level clientfield::set( "round_complete_num", level.round_number );
		}
		
		// 
		// Increase the zombie move speed
		//level.zombie_move_speed = level.round_number * level.zombie_vars["zombie_move_speed_multiplier"];

		if( level.gamedifficulty == 0 ) //easy
		{
			level.zombie_move_speed			= level.round_number * level.zombie_vars["zombie_move_speed_multiplier_easy"]; 
		}
		else	//normal
		{
			level.zombie_move_speed			= level.round_number * level.zombie_vars["zombie_move_speed_multiplier"]; 
		}

		set_round_number( 1 + get_round_number() );
		SetRoundsPlayed( get_round_number() );

		// Here's the difficulty increase over time area
		//level.zombie_vars["zombie_spawn_delay"] = get_zombie_spawn_delay( level.round_number );
		level.zombie_vars["zombie_spawn_delay"] = [[level.func_get_zombie_spawn_delay]]( get_round_number() );

		//	round_text( &"ZOMBIE_ROUND_END" );
		
		matchUTCTime = GetUTC();

		players = GetPlayers(); // delay in round_over allows a player that leaves during that time to remain in the players array - leading to round based SRES.  Bad.
		foreach(player in players)
		{
			if ( level.curr_gametype_affects_rank && get_round_number() > (3 + level.start_round) )
			{
				player zm_stats::add_client_stat( "weighted_rounds_played",get_round_number() );
			}
			player zm_stats::set_global_stat( "rounds", get_round_number() );

			// update the game played time
			player zm_stats::update_playing_utc_time( matchUTCTime );
			
			// Reset the health if necessary
			player zm_perks::perk_set_max_health_if_jugg( "health_reboot", true, true );

			//XP event stuff
			for ( i = 0; i < 4; i++ )
			{
				player.number_revives_per_round[i] = 0;
			}

			if ( IsAlive( player ) && player.sessionstate != "spectator" && !( isdefined( level.skip_alive_at_round_end_xp ) && level.skip_alive_at_round_end_xp ) )
			{
				player zm_stats::increment_challenge_stat( "SURVIVALIST_SURVIVE_ROUNDS" );

				score_number = get_round_number() - 1;
				if ( score_number < 1 )
				{
					score_number = 1;
				}
				else if ( score_number > 20 )
				{
					score_number = 20;
				}
				scoreevents::processScoreEvent( ("alive_at_round_end_" + score_number), player );
			}
		}
		
		if( isdefined( level.check_quickrevive_hotjoin ) )
		{
			[[ level.check_quickrevive_hotjoin ]]();
		}
		
		level.round_number = get_round_number();  

		level round_over();

		level notify( "between_round_over" );

		level.skip_alive_at_round_end_xp = false;
		
		restart = false;
	}
}


function award_grenades_for_survivors()
{
	players = GetPlayers();

	for (i = 0; i < players.size; i++)
	{
		if (!players[i].is_zombie && !( isdefined( players[i].altbody ) && players[i].altbody ) && !players[i] laststand::player_is_in_laststand() )
		{
			lethal_grenade = players[i] zm_utility::get_player_lethal_grenade();
			if( !players[i] HasWeapon( lethal_grenade ) )
			{
				players[i] GiveWeapon( lethal_grenade );	
				players[i] SetWeaponAmmoClip( lethal_grenade, 0 );
			}

			frac = players[i] GetFractionMaxAmmo( lethal_grenade );
			if ( frac < .25 )
			{
				players[i] SetWeaponAmmoClip( lethal_grenade, 2 );
			}
			else if ( frac < .5 )
			{
				players[i] SetWeaponAmmoClip( lethal_grenade, 3 );
			}
			else
			{
				players[i] SetWeaponAmmoClip( lethal_grenade, 4 );
			}
		}
	}
}


//	Calculate the correct spawn delay for the round number
function get_zombie_spawn_delay( n_round )
{
	if ( n_round > 60 )	// Don't let this loop too many times
	{
		n_round = 60;
	}
	
	// Decay rate
	n_multiplier = 0.95;
	// Base delay
	switch( level.players.size )
	{
		case 1:
			n_delay = 2.0;		// 0.95 == 0.1 @ round 60
			break;
		case 2:
			n_delay = 1.5;		// 0.95 == 0.1 @ round 54
			break;
		case 3:
			n_delay = 0.89;		// 0.95 == 0.1 @ round 60
			break;
		case 4:
			n_delay = 0.67;		// 0.95 == 0.1 @ round 60
			break;
	}

	for( i=1; i<n_round; i++ )
	{
		n_delay *= n_multiplier;
		
		if ( n_delay <= 0.1 )
		{
			n_delay = 0.1;
			break;			
		}
	}
	
	return n_delay;
}


/#
function round_spawn_failsafe_debug()
{
	level notify( "failsafe_debug_stop" );
	level endon( "failsafe_debug_stop" );

	start = GetTime();
	level.chunk_time = 0;

	while ( 1 )
	{
		level.failsafe_time = GetTime() - start;

		if ( isdefined( self.lastchunk_destroy_time ) )
		{
			level.chunk_time = GetTime() - self.lastchunk_destroy_time;
		}
		util::wait_network_frame();
	}
}
#/

/#
function print_zombie_counts()
{
	while ( true )
	{
		if( GetDvarInt("zombiemode_debug_zombie_count") )
		{	
			if( !isdefined( level.debug_zombie_count_hud ) )
			{
				level.debug_zombie_count_hud= NewDebugHudElem();
				level.debug_zombie_count_hud.alignX = "right";
				level.debug_zombie_count_hud.x = 100;
				level.debug_zombie_count_hud.y = 10;
				level.debug_zombie_count_hud SetText( "COUNTS" );
			}

			currentCount = zombie_utility::get_current_zombie_count();
			number_to_kill = level.zombie_total;
			level.debug_zombie_count_hud SetText( "ALIVE=" + currentCount + " / TOGO=" + number_to_kill );
			//println( "ALIVE=" + currentCount + " / TOGO=" + number_to_kill );
		}
		else
		{
			if( isdefined( level.debug_zombie_count_hud ) )
			{
				level.debug_zombie_count_hud Destroy();
				level.debug_zombie_count_hud = undefined;
			}
		}	

		wait( 0.1 );
	}
}
#/


// Waits for the time and the ai to die
function round_wait()
{
	level endon("restart_round");
/#
	level endon( "kill_round" );
#/

/#
    if (GetDvarInt( "zombie_rise_test") )
	{
		level waittill("forever"); // TESTING: don't advance rounds
	}
#/

	if ( cheat_enabled( 2 ) )
	{
		level waittill("forever");
	}

/#
	if ( GetDvarInt( "zombie_default_max" ) == 0 )
	{
		level waittill( "forever" );
	}
#/

	wait( 1 );

/#
	level thread print_zombie_counts();
	level thread sndMusicOnKillRound();
#/

	while( 1 )
	{
		should_wait = ( zombie_utility::get_current_zombie_count() > 0 || level.zombie_total > 0 || level.intermission );	
		if( !should_wait )
		{
			level thread zm_audio::sndMusicSystem_PlayState( "round_end" );
			return;
		}			
			
		if( level flag::get( "end_round_wait" ) )
		{
			level thread zm_audio::sndMusicSystem_PlayState( "round_end" );
			return;
		}
		wait( 1.0 );
	}
}
//To make sure music plays when using debug to switch rounds
function sndMusicOnKillRound()
{
	level endon( "end_of_round" );
	
	level waittill( "kill_round" );
	level thread zm_audio::sndMusicSystem_PlayState( "round_end" );
}



function zombify_player() // self = player
{
	self zm_score::player_died_penalty(); 

	self RecordPlayerDeathZombies();

	if ( IsDefined( level.deathcard_spawn_func ) )
	{
		self [[level.deathcard_spawn_func]]();
	}
	
	if( isdefined( level.func_clone_plant_respawn ) && isdefined( self.s_clone_plant ) )
	{
		self [[level.func_clone_plant_respawn]]();
		return;
	}

	if( !IsDefined( level.zombie_vars["zombify_player"] ) || !level.zombie_vars["zombify_player"] )
	{
		self thread spawnSpectator(); 
		return; 
	}

	self.ignoreme = true; 
	self.is_zombie = true; 
	self.zombification_time = GetTime(); 

	self.team = level.zombie_team;
	self notify( "zombified" ); 

	if( IsDefined( self.revivetrigger ) )
	{
		self.revivetrigger Delete(); 
	}
	self.revivetrigger = undefined; 

	self setMoveSpeedScale( 0.3 ); 
	self reviveplayer(); 

	self TakeAllWeapons(); 
	self DisableWeaponCycling(); 
	self DisableOffhandWeapons(); 

	self thread zombie_utility::zombie_eye_glow(); 

	self thread playerzombie_player_damage(); 
	self thread playerzombie_soundboard(); 
}

function playerzombie_player_damage()
{
	self endon( "death" ); 
	self endon( "disconnect" ); 

	self thread playerzombie_infinite_health();  // manually keep regular health up
	self.zombiehealth = level.zombie_health; 

	// enable PVP damage on this guy
	// self EnablePvPDamage(); 

	while( 1 )
	{
		self waittill( "damage", amount, attacker, directionVec, point, type ); 

		if( !IsDefined( attacker ) || !IsPlayer( attacker ) )
		{
			{wait(.05);}; 
			continue; 
		}

		self.zombiehealth -= amount; 

		if( self.zombiehealth <= 0 )
		{
			// "down" the zombie
			self thread playerzombie_downed_state(); 
			self waittill( "playerzombie_downed_state_done" ); 
			self.zombiehealth = level.zombie_health; 
		}
	}
}

function playerzombie_downed_state()
{
	self endon( "death" ); 
	self endon( "disconnect" ); 

	downTime = 15; 

	startTime = GetTime(); 
	endTime = startTime +( downTime * 1000 ); 

	self thread playerzombie_downed_hud(); 

	self.playerzombie_soundboard_disable = true; 
	self thread zombie_utility::zombie_eye_glow_stop(); 
	self DisableWeapons(); 
	self AllowStand( false ); 
	self AllowCrouch( false ); 
	self AllowProne( true ); 

	while( GetTime() < endTime )
	{
		{wait(.05);}; 
	}

	self.playerzombie_soundboard_disable = false; 
	self thread zombie_utility::zombie_eye_glow(); 
	self EnableWeapons(); 
	self AllowStand( true ); 
	self AllowCrouch( false ); 
	self AllowProne( false ); 

	self notify( "playerzombie_downed_state_done" ); 
}

function playerzombie_downed_hud()
{
	self endon( "death" ); 
	self endon( "disconnect" ); 

	text = NewClientHudElem( self ); 
	text.alignX = "center"; 
	text.alignY = "middle"; 
	text.horzAlign = "user_center"; 
	text.vertAlign = "user_bottom"; 
	text.foreground = true; 
	text.font = "default"; 
	text.fontScale = 1.8; 
	text.alpha = 0; 
	text.color = ( 1.0, 1.0, 1.0 ); 
	text SetText( &"ZOMBIE_PLAYERZOMBIE_DOWNED" ); 

	text.y = -113; 	
	if( self IsSplitScreen() )
	{
		text.y = -137; 
	}

	text FadeOverTime( 0.1 ); 
	text.alpha = 1; 

	self waittill( "playerzombie_downed_state_done" ); 

	text FadeOverTime( 0.1 ); 
	text.alpha = 0; 
}

function playerzombie_infinite_health()
{
	self endon( "death" ); 
	self endon( "disconnect" ); 

	bighealth = 100000; 

	while( 1 )
	{
		if( self.health < bighealth )
		{
			self.health = bighealth; 
		}

		wait( 0.1 ); 
	}
}

function playerzombie_soundboard()
{
	self endon( "death" ); 
	self endon( "disconnect" ); 

	self.playerzombie_soundboard_disable = false; 

	self.buttonpressed_use = false; 
	self.buttonpressed_attack = false; 
	self.buttonpressed_ads = false; 

	self.useSound_waitTime = 3 * 1000;  // milliseconds
	self.useSound_nextTime = GetTime(); 
	useSound = "playerzombie_usebutton_sound"; 

	self.attackSound_waitTime = 3 * 1000; 
	self.attackSound_nextTime = GetTime(); 
	attackSound = "playerzombie_attackbutton_sound"; 

	self.adsSound_waitTime = 3 * 1000; 
	self.adsSound_nextTime = GetTime(); 
	adsSound = "playerzombie_adsbutton_sound"; 

	self.inputSound_nextTime = GetTime();  // don't want to be able to do all sounds at once

	while( 1 )
	{
		if( self.playerzombie_soundboard_disable )
		{
			{wait(.05);}; 
			continue; 
		}

		if( self UseButtonPressed() )
		{
			if( self can_do_input( "use" ) )
			{
				self thread playerzombie_play_sound( useSound ); 
				self thread playerzombie_waitfor_buttonrelease( "use" ); 
				self.useSound_nextTime = GetTime() + self.useSound_waitTime; 
			}
		}
		else if( self AttackButtonPressed() )
		{
			if( self can_do_input( "attack" ) )
			{
				self thread playerzombie_play_sound( attackSound ); 
				self thread playerzombie_waitfor_buttonrelease( "attack" ); 
				self.attackSound_nextTime = GetTime() + self.attackSound_waitTime; 
			}
		}
		else if( self AdsButtonPressed() )
		{
			if( self can_do_input( "ads" ) )
			{
				self thread playerzombie_play_sound( adsSound ); 
				self thread playerzombie_waitfor_buttonrelease( "ads" ); 
				self.adsSound_nextTime = GetTime() + self.adsSound_waitTime; 
			}
		}

		{wait(.05);}; 
	}
}

function can_do_input( inputType )
{
	if( GetTime() < self.inputSound_nextTime )
	{
		return false; 
	}

	canDo = false; 

	switch( inputType )
	{
	case "use":
		if( GetTime() >= self.useSound_nextTime && !self.buttonpressed_use )
		{
			canDo = true; 
		}
		break; 

	case "attack":
		if( GetTime() >= self.attackSound_nextTime && !self.buttonpressed_attack )
		{
			canDo = true; 
		}
		break; 

	case "ads":
		if( GetTime() >= self.useSound_nextTime && !self.buttonpressed_ads )
		{
			canDo = true; 
		}
		break; 

	default:
		ASSERTMSG( "can_do_input(): didn't recognize inputType of " + inputType ); 
		break; 
	}

	return canDo; 
}

function playerzombie_play_sound( alias )
{
	self zm_utility::play_sound_on_ent( alias ); 
}

function playerzombie_waitfor_buttonrelease( inputType )
{
	if( inputType != "use" && inputType != "attack" && inputType != "ads" )
	{
		ASSERTMSG( "playerzombie_waitfor_buttonrelease(): inputType of " + inputType + " is not recognized." ); 
		return; 
	}

	notifyString = "waitfor_buttonrelease_" + inputType; 
	self notify( notifyString ); 
	self endon( notifyString ); 

	if( inputType == "use" )
	{
		self.buttonpressed_use = true; 
		while( self UseButtonPressed() )
		{
			{wait(.05);}; 
		}
		self.buttonpressed_use = false; 
	}

	else if( inputType == "attack" )
	{
		self.buttonpressed_attack = true; 
		while( self AttackButtonPressed() )
		{
			{wait(.05);}; 
		}
		self.buttonpressed_attack = false; 
	}

	else if( inputType == "ads" )
	{
		self.buttonpressed_ads = true; 
		while( self AdsButtonPressed() )
		{
			{wait(.05);}; 
		}
		self.buttonpressed_ads = false; 
	}
}

function remove_ignore_attacker()
{
	self notify( "new_ignore_attacker" );
	self endon( "new_ignore_attacker" );
	self endon( "disconnect" );
	
	if( !isDefined( level.ignore_enemy_timer ) )
	{
		level.ignore_enemy_timer = 0.4;
	}
	
	wait( level.ignore_enemy_timer );
	
	self.ignoreAttacker = undefined;
}

function player_damage_override_cheat( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime )
{
	player_damage_override( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
	return 0;
}


//
//	player_damage_override
//		MUST return the value of the damage override
//
// MM (08/10/09) - Removed calls to PlayerDamageWrapper because it's always called in 
//		Callback_PlayerDamage now.  We just need to return the damage.
//
function player_damage_override( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime )
{
	iDamage = self check_player_damage_callbacks( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
	
	if( self.scene_takedamage === false )
	{
		return 0;
	}

	if ( IsDefined( eAttacker ) && ( isdefined( eAttacker.b_aat_fire_works_weapon ) && eAttacker.b_aat_fire_works_weapon ) )
	{
		return 0;
	}
	
	if ( ( isdefined( self.use_adjusted_grenade_damage ) && self.use_adjusted_grenade_damage ) )
    {
        self.use_adjusted_grenade_damage = undefined;
        if( ( self.health > iDamage ) )
        {
        	return iDamage;
        }
    }

	if ( !iDamage )
	{
		return 0;
	}
	
	// WW (8/20/10) - Sledgehammer fix for Issue 43492. This should stop the player from taking any damage while in laststand
	if( self laststand::player_is_in_laststand() )
	{
		return 0;
	}
	
	if ( isDefined( eInflictor ) )
	{
		if ( ( isdefined( eInflictor.water_damage ) && eInflictor.water_damage ) )
		{
			return 0;
		}
	}

	if ( isDefined( eAttacker ) )
	{
		if( ( eAttacker.owner === self ) ) 
		{
			return 0;
		}
		
		if( isDefined( self.ignoreAttacker ) && self.ignoreAttacker == eAttacker ) 
		{
			return 0;
		}
		
		// AR (5/30/12) - Stop Zombie players from damaging other Zombie players
		if ( ( isdefined( self.is_zombie ) && self.is_zombie ) && ( isdefined( eAttacker.is_zombie ) && eAttacker.is_zombie ) )
		{
			return 0;
		}
		
		if( (isDefined( eAttacker.is_zombie ) && eAttacker.is_zombie) )
		{
			self.ignoreAttacker = eAttacker;
			self thread remove_ignore_attacker();

			if ( isdefined( eAttacker.custom_damage_func ) )
			{
				iDamage = eAttacker [[ eAttacker.custom_damage_func ]]( self );
			}
		}
		
		eAttacker notify( "hit_player" ); 

		if ( isdefined( eAttacker ) && isdefined( eAttacker.func_mod_damage_override ) )
		{
			sMeansOfDeath = eAttacker [[ eAttacker.func_mod_damage_override ]]( eInflictor, sMeansOfDeath, weapon );
		}
		
		if( sMeansOfDeath != "MOD_FALLING" )
		{
			self thread playSwipeSound( sMeansOfDeath, eattacker );
			if( ( isdefined( eattacker.is_zombie ) && eattacker.is_zombie ) || IsPlayer(eAttacker) )
				self PlayRumbleOnEntity( "damage_heavy" );
			
			if( ( isdefined( eattacker.is_zombie ) && eattacker.is_zombie ) )
			{
				self zm_audio::create_and_play_dialog( "general", "attacked" );
			}
			
			canExert = true;
			
			if ( ( isdefined( level.pers_upgrade_flopper ) && level.pers_upgrade_flopper ) )
			{
				// If the player has persistent flopper power, then no exert on explosion
				if ( ( isdefined( self.pers_upgrades_awarded[ "flopper" ] ) && self.pers_upgrades_awarded[ "flopper" ] ) )
				{
					canExert = ( sMeansOfDeath != "MOD_PROJECTILE_SPLASH" && sMeansOfDeath != "MOD_GRENADE" && sMeansOfDeath != "MOD_GRENADE_SPLASH" );
				}
			}
			
			if ( ( isdefined( canExert ) && canExert ) )
			{
			    if(RandomIntRange(0,1) == 0 )
			    {
			    	self thread zm_audio::playerExert( "hitmed" );
			        //self thread zm_audio::create_and_play_dialog( "general", "hitmed" );
			    }
			    else
			    {
			    	self thread zm_audio::playerExert( "hitlrg" );
			        //self thread zm_audio::create_and_play_dialog( "general", "hitlrg" );
			    }
			}
		}
	}
	
	//Audio(RG:2/1/2016) adding underwater drowning exert.
	if ( isDefined( sMeansOfDeath) && sMeansOfDeath == "MOD_DROWN")
	{
		self thread zm_audio::playerExert( "drowning", true );
		self.voxDrowning = true;
	}
	
	if( isdefined( level.perk_damage_override ) )
	{
		foreach( func in level.perk_damage_override )
		{
			n_damage = self [[ func ]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
			if( isdefined( n_damage ) )
			{
				iDamage = n_damage;		
			}
		}
	}	
	finalDamage = iDamage;
	
		
	// claymores and freezegun shatters, like bouncing betties, harm no players
	if ( zm_utility::is_placeable_mine( weapon ) )
	{
		return 0;
	}

	if ( isDefined( self.player_damage_override ) )
	{
		self thread [[ self.player_damage_override ]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
	}

	// exploding quads should not kill player
	if ( IsDefined( eInflictor ) && IsDefined( eInflictor.archetype ) && eInflictor.archetype == "zombie_quad" )
	{
		if ( sMeansOfDeath == "MOD_EXPLOSIVE" )
		{
			if ( self.health > 75 )
			{
				return 75;
			}
		}
	}
	
	// Players can't die from cooked grenade if trhey have the bgb Danger Closet
	if ( sMeansOfDeath == "MOD_SUICIDE" && self bgb::is_enabled( "zm_bgb_danger_closest" ) )
	{
		return 0;
	}
	
	if ( sMeansOfDeath == "MOD_PROJECTILE" || sMeansOfDeath == "MOD_PROJECTILE_SPLASH" || sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_GRENADE_SPLASH" || sMeansOfDeath == "MOD_EXPLOSIVE" )
	{
		if( self bgb::is_enabled( "zm_bgb_danger_closest" ) )
		{
			return 0;
		}
		
		// player explosive splash damage (caps explosive damage), fixes raygun damage being fatal (or grenades) when damaging yourself
		if ( !( isdefined( self.is_zombie ) && self.is_zombie ) )
		{
			// Don't do this for projectile damage coming from zombies
			if ( !isdefined( eAttacker ) || ( !( isdefined( eAttacker.is_zombie ) && eAttacker.is_zombie ) && !( isdefined( eAttacker.b_override_explosive_damage_cap ) && eAttacker.b_override_explosive_damage_cap ) ) )
			{
				// Only do it for ray gun
				if( isdefined(weapon.name) && ((weapon.name == "ray_gun") || ( weapon.name == "ray_gun_upgraded" )) )
				{
					// Clamp it, we don't want to increase the damage from player raygun splash damage or grenade splash damage
					// Don't create more damage than we are trying to apply
					if ( ( self.health > 25 ) && ( iDamage > 25 ) )
					{
						return 25;
					}
				}
				else if ( ( self.health > 75 ) && ( iDamage > 75 ) )
				{
					return 75;
				}
			}
		}
	}

	if( iDamage < self.health )
	{
		if ( IsDefined( eAttacker ) )
		{
			if( IsDefined( level.custom_kill_damaged_VO ) )
			{
				eAttacker thread [[ level.custom_kill_damaged_VO ]]( self );
			}
			else
			{
				eAttacker.sound_damage_player = self;	
			}
			
			if( ( isdefined( eAttacker.missingLegs ) && eAttacker.missingLegs ) )
			{
			    self zm_audio::create_and_play_dialog( "general", "crawl_hit" );
			}
		}
		
		// MM (08/10/09)
		return finalDamage;
	}
	
	//player died
	if( isdefined( eAttacker ) )
	{
		if(isDefined(eAttacker.animname) && eAttacker.animname == "zombie_dog")
		{
			self zm_stats::increment_client_stat( "killed_by_zdog" );
			self zm_stats::increment_player_stat( "killed_by_zdog" );
		}
		else if(( isdefined( eAttacker.is_avogadro ) && eAttacker.is_avogadro ))
		{
			self zm_stats::increment_client_stat( "killed_by_avogadro", false );
			self zm_stats::increment_player_stat( "killed_by_avogadro" );
		}
	}

	self thread clear_path_timers();
		
	if( level.intermission )
	{
		level waittill( "forever" );
	}
	
	// AR (3/7/12) - Keep track of which player killed player in Zombify modes like Cleansed / Turned
	// Confirmed with Alex 
	if ( level.scr_zm_ui_gametype == "zcleansed" && iDamage > 0 )
	{
		if ( IsDefined( eAttacker ) && IsPlayer( eAttacker ) && eAttacker.team != self.team && ( ( !( isdefined( self.laststand ) && self.laststand ) && !self laststand::player_is_in_laststand() ) || !IsDefined( self.last_player_attacker ) ) )
		{
			// Restore Health To Zombie Player
			//--------------------------------
			if ( IsDefined( eAttacker.maxhealth ) && ( isdefined( eAttacker.is_zombie ) && eAttacker.is_zombie ) )
			{
				eAttacker.health = eAttacker.maxhealth;
			}
			
			//self.last_player_attacker = eAttacker;

			if ( IsDefined( level.player_kills_player ) )
			{
				self thread [[ level.player_kills_player]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
			}			
		}
	}
	
	if ( self.lives > 0 && self HasPerk( "specialty_whoswho" ) )
	{
		self.lives--;
		if ( IsDefined( level.whoswho_laststand_func ) )
		{
			self thread [[ level.whoswho_laststand_func]]();
			return 0;
		}			
	}	
	
	players = GetPlayers();
	count = 0;
	for( i = 0; i < players.size; i++ )
	{
		if( players[i] == self || players[i].is_zombie || players[i] laststand::player_is_in_laststand() || players[i].sessionstate == "spectator" )
		{
			count++;
		}
	}
	
	if( count < players.size || (isDefined(level._game_module_game_end_check) && ![[level._game_module_game_end_check]]()) )
	{
		if ( IsDefined( self.lives ) && self.lives > 0 && ( isdefined( level.force_solo_quick_revive ) && level.force_solo_quick_revive ) &&  self HasPerk( "specialty_quickrevive" ) )
		{
			self thread wait_and_revive();
		}
		
		// MM (08/10/09)
		return finalDamage;
	}
	
	// PORTIZ 7/27/16: added level.no_end_game_check here, because if it's true by this point, this function will end up returning finalDamage anyway. additionally, 
	// no_end_game_check has been updated to support incrementing/decrementing, which makes it more robust than a single level.check_end_solo_game_override as more
	// mechanics are introduced that require solo players to go into last stand instead of losing the game immediately
	if ( players.size == 1 && level flag::get( "solo_game" ) )
	{
		if ( ( isdefined( level.no_end_game_check ) && level.no_end_game_check ) || ( isdefined( level.check_end_solo_game_override ) && [[level.check_end_solo_game_override]]() ) )
		{
			return finalDamage;
		}
		else if ( self.lives == 0 || !self HasPerk( "specialty_quickrevive" ) )
		{
			self.intermission = true;
		}
	}
	
	
	
	
	// WW (01/05/11): When a two players enter a system link game and the client drops the host will be treated as if it was a solo game
	// when it wasn't. This led to SREs about undefined and int being compared on death (self.lives was never defined on the host). While
	// adding the check for the solo game flag we found that we would have to create a complex OR inside of the if check below. By breaking
	// the conditions out in to their own variables we keep the complexity without making it look like a mess.
	solo_death = ( players.size == 1 && level flag::get( "solo_game" ) && ( self.lives == 0  || !self HasPerk("specialty_quickrevive") ) ); // there is only one player AND the flag is set AND self.lives equals 0
	non_solo_death = ( ( count > 1 || ( players.size == 1 && !level flag::get( "solo_game" ) ) ) /*&& !level.is_zombie_level*/ ); // the player size is greater than one OR ( players.size equals 1 AND solo flag isn't set ) AND not a zombify game level
	if ( (solo_death || non_solo_death) && !( isdefined( level.no_end_game_check ) && level.no_end_game_check ) ) // if only one player on their last life or any game that started with more than one player
	{	
		level notify("stop_suicide_trigger");
		self AllowProne( true ); //just in case
		self thread zm_laststand::PlayerLastStand( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime );
		if( !isdefined( vDir ) )
		{
			vDir = ( 1.0, 0.0, 0.0 );
		}
		self FakeDamageFrom(vDir);
		
		level notify("last_player_died");
		if ( isdefined(level.custom_player_fake_death) )
			self thread [[level.custom_player_fake_death]](vDir, sMeansOfDeath);
		else
			self thread player_fake_death();
	}

	if( count == players.size && !( isdefined( level.no_end_game_check ) && level.no_end_game_check ) )
	{
		if ( players.size == 1 && level flag::get( "solo_game" ))
		{
			if ( self.lives == 0 || !self HasPerk("specialty_quickrevive") ) // && !self laststand::player_is_in_laststand()
			{
				self.lives = 0;
				level notify("pre_end_game");
				util::wait_network_frame();
				if(level flag::get("dog_round"))
				{
					increment_dog_round_stat( "lost" );	
						
				}				
				level notify( "end_game" );
			}
			else
			{
				return finalDamage;
			}
		}
		else
		{
			level notify("pre_end_game");
			util::wait_network_frame();
			if(level flag::get("dog_round"))
			{
				increment_dog_round_stat( "lost" );	
					
			}
			level notify( "end_game" );
		}
		return 0;	// MM (09/16/09) Need to return something
	}
	else
	{
		// MM (08/10/09)
		
		surface = "flesh";
		
		return finalDamage;
	}
}

function clear_path_timers()
{
	zombies = GetAITeamArray( level.zombie_team );
	foreach( zombie in zombies )
	{
		if ( isdefined( zombie.favoriteenemy ) && ( zombie.favoriteenemy == self ) )
		{
			zombie.zombie_path_timer = 0;
		}
	}
}


function check_player_damage_callbacks( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime )
{
	if ( !isdefined( level.player_damage_callbacks ) )
	{
		return iDamage;
	}
	
	for ( i = 0; i < level.player_damage_callbacks.size; i++ )
	{
		newDamage = self [[ level.player_damage_callbacks[i] ]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime );
		if ( -1 != newDamage )
		{
			return newDamage;
		}
	}

	return iDamage;
}


function register_player_damage_callback( func )
{
	if ( !isdefined( level.player_damage_callbacks ) )
	{
		level.player_damage_callbacks = [];
	}

	level.player_damage_callbacks[level.player_damage_callbacks.size] = func;
}


function wait_and_revive()
{
	
	self endon( "remote_revive" );
	level flag::set( "wait_and_revive" );
	level.wait_and_revive = true;

	if ( isdefined( self.waiting_to_revive ) && self.waiting_to_revive == true )
	{
		return;
	}

	// Grab the perks if we have the player persistent ability "perk lose"
	if( ( isdefined( self.pers_upgrades_awarded["perk_lose"] ) && self.pers_upgrades_awarded["perk_lose"] ) )
	{
		self zm_pers_upgrades_functions::pers_upgrade_perk_lose_save();
	}

	self.waiting_to_revive = true;
	self.lives--;

	if ( isdefined( level.exit_level_func ) )
	{
		self thread [[ level.exit_level_func ]]();
	}
	else
	{
		if ( GetPlayers().size == 1 )
		{
			player = GetPlayers()[0];
			level.move_away_points =  PositionQuery_Source_Navigation( player.origin, 12*40, 12*80, 120, 20 );
			if ( !isdefined( level.move_away_points ) )
			{
				level.move_away_points =  PositionQuery_Source_Navigation( player.last_valid_position, 12*40, 12*80, 120, 20 );
			}
		}
	}

	solo_revive_time = 10.0;

	name = level.player_name_directive[self GetEntityNumber()];
	self.revive_hud setText( &"ZOMBIE_REVIVING_SOLO", name );
	self laststand::revive_hud_show_n_fade( solo_revive_time );

	level flag::wait_till_timeout( solo_revive_time, "instant_revive" );

	if ( level flag::get( "instant_revive" ) )
	{
		self laststand::revive_hud_show_n_fade( 1.0 );
	}
	
	level flag::clear( "wait_and_revive" );
	level.wait_and_revive = false;

	self zm_laststand::auto_revive( self );
	self.waiting_to_revive = false;

	// Give player his perks back if he has the "perk_lose" persistent ability
	if( ( isdefined( self.pers_upgrades_awarded["perk_lose"] ) && self.pers_upgrades_awarded["perk_lose"] ) )
	{
		self thread zm_pers_upgrades_functions::pers_upgrade_perk_lose_restore();
	}
}

function register_vehicle_damage_callback( func )
{
	if( !IsDefined( level.vehicle_damage_callbacks ) )
	{
		level.vehicle_damage_callbacks = [];
	}
	
	level.vehicle_damage_callbacks[level.vehicle_damage_callbacks.size] = func;
}

function vehicle_damage_override( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal )
{
	//apply damage modifiers on the vehicle
	if( IsDefined( level.vehicle_damage_callbacks ) )
    {
    	for( i = 0; i < level.vehicle_damage_callbacks.size; i++ )
    	{
    		iDamage = self [[ level.vehicle_damage_callbacks[i] ]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal );
    	}
    }
			
	//TODO Sabarish: Move code from globallogic to here to be more consistent with other zombie damage functions
	self globallogic_vehicle::Callback_VehicleDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal );
}

//
//		MUST return the value of the damage override
//
function actor_damage_override( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType )
{
	// skip conditions
	if ( !isdefined( self ) || !isdefined( attacker ) )
		return damage;

	damage = bgb::actor_damage_override( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType );
	
	damage = self check_actor_damage_callbacks( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType );

	self.knuckles_extinguish_flames = (weapon.name == "tazer_knuckles");

	if ( isdefined( attacker.animname ) && attacker.animname == "quad_zombie" )
	{
		if ( isdefined( self.animname ) && self.animname == "quad_zombie" )
		{
			return 0;
		}
	}
	
	if ( isdefined( self.killby_interdimensional_gun_hole ) )
	{
		return damage;
	}
	else if ( isdefined( self.interdimensional_gun_kill ) )
	{
		if ( isdefined( self.idgun_damage_cb ) )
		{
			self [[ self.idgun_damage_cb ]]( inflictor, attacker );
			return 0;
		}
	}
	
	
	if ( isdefined( weapon ) )
	{
		if( is_idgun_damage( weapon ) && ( !IsDefined( meansofdeath ) || meansofdeath != "MOD_EXPLOSIVE" ) )
		{
			if( !( self.archetype === "margwa" ) && !( self.archetype === "mechz" ))
			{
				self.damageOrigin = vpoint;
				self.allowdeath = false;
				self.magic_bullet_shield = true;
				self.interdimensional_gun_kill = true;
				self.interdimensional_gun_weapon = weapon;
				self.interdimensional_gun_attacker = attacker;
			
				if ( isdefined( inflictor ) )
				{
					self.interdimensional_gun_inflictor = inflictor;
				}
				else
				{
					self.interdimensional_gun_inflictor = attacker;
				}
			}

			if ( isdefined( self.idgun_damage_cb ) )
			{
				self [[ self.idgun_damage_cb ]]( inflictor, attacker );
			}
			
			return 0;
		}
	}
	
	attacker thread zm_audio::sndPlayerHitAlert( self, meansofdeath, inflictor, weapon );
	
	if ( !isplayer( attacker ) && isdefined( self.non_attacker_func ) )
	{
		if(( isdefined( self.non_attack_func_takes_attacker ) && self.non_attack_func_takes_attacker ))
		{
			return self [[ self.non_attacker_func ]]( damage, weapon, attacker );
		}
		else
		{
			return self [[ self.non_attacker_func ]]( damage, weapon );
		}
	}
	
	if ( IsDefined( attacker ) && IsAI( attacker ) )
	{
		// AI do not do melee damage to teammates
		if( self.team == attacker.team && meansofdeath == "MOD_MELEE" )
		{
			return 0;
		}
	}
	
	if ( attacker.classname == "script_vehicle" && isDefined( attacker.owner ) )
	{
		attacker = attacker.owner;
	}
	
	if ( !isdefined( damage ) || !isdefined( meansofdeath ) )
	{
		return damage;
	}
	
	if ( meansofdeath == "" )
	{
		return damage;
	}
	
	// This is the AI system's override damage callback, it must come last!
	if ( IsDefined( self.aiOverrideDamage ) )
	{
		for ( index = 0; index < self.aiOverrideDamage.size; index++ )
		{
			damageCallback = self.aiOverrideDamage[index];
			damage = self [[damageCallback]]( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, undefined );
		}
		if ( damage < 1 )
			return 0;
		
		damage = int( damage + 0.5 );
	}

	old_damage = damage;
	final_damage = damage;

	if ( IsDefined( self.actor_damage_func ) )
	{
		final_damage = [[ self.actor_damage_func ]]( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex );
	}

	// debug
/#
		if ( GetDvarInt( "scr_perkdebug") )
			println( "Perk/> Damage Factor: " + final_damage/old_damage + " - Pre Damage: " + old_damage + " - Post Damage: " + final_damage );
#/

	if ( ( isdefined( self.in_water ) && self.in_water ) )
	{
		if ( int( final_damage ) >= self.health )
		{
			self.water_damage = true;
		}
	}
	
	//modifier for the sword in ZOD colliding with zombies
	if ( IsDefined( inflictor ) && IsDefined( inflictor.archetype ) && inflictor.archetype == "glaive" )
	{
		if( meansofdeath == "MOD_CRUSH" )
		{
			if ( ( IsDefined( inflictor.enemy ) && inflictor.enemy != self ) || ( isdefined( inflictor._glaive_must_return_to_owner ) && inflictor._glaive_must_return_to_owner ) )
			{
				if ( IsDefined( self.archetype ) && self.archetype != "margwa" )
				{
					final_damage += self.health;
					if ( IsActor( self ) )
					{
						self zombie_utility::gib_random_parts();
					}
				}
			}
			else
			{
				return 0;
			}
		}
	}
	
	if ( isdefined( inflictor ) && isPlayer( attacker ) && attacker == inflictor ) 
	{
		if ( meansofdeath == "MOD_HEAD_SHOT" || meansofdeath == "MOD_PISTOL_BULLET" || meansofdeath == "MOD_RIFLE_BULLET" )
		{
			attacker.hits++;
		}
	}
	
	if ( ( isdefined( level.headshots_only ) && level.headshots_only ) && isDefined( attacker ) && isplayer( attacker ) )
	{
		if ( meansofdeath == "MOD_MELEE" && ( sHitLoc == "head" || sHitLoc == "helmet" ) )
		{
			return int( final_damage );
		}
		if ( zm_utility::is_explosive_damage( meansofdeath ) )
		{
			return int( final_damage );
		}
		else if ( !zm_utility::is_headshot( weapon, sHitLoc, meansofdeath ) )
		{
			return 0;
		}
	}
	
	return int( final_damage );
}

function check_actor_damage_callbacks( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType )
{
    if ( !isdefined( level.actor_damage_callbacks ) )
    {
        return damage;
    }
    
    for ( i = 0; i < level.actor_damage_callbacks.size; i++ )
    {
        newDamage = self [[ level.actor_damage_callbacks[i] ]]( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType );
        if ( -1 != newDamage )
        {
            return newDamage;
        }
    }

    return damage;
}


function register_actor_damage_callback( func )
{
    if ( !isdefined( level.actor_damage_callbacks ) )
    {
        level.actor_damage_callbacks = [];
    }

    level.actor_damage_callbacks[level.actor_damage_callbacks.size] = func;
}



function actor_damage_override_wrapper( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, modelIndex, surfaceType, vSurfaceNormal )
{
	damage_override = self actor_damage_override( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType );
	willBeKilled = ( self.health - damage_override ) <= 0;
	if( isdefined( level.zombie_damage_override_callbacks ) )
	{
		foreach( func_override in level.zombie_damage_override_callbacks )
		{
			self thread [[ func_override ]]( willBeKilled, inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, psOffsetTime, boneIndex, surfaceType );
		}
	}
	bb::logdamage( attacker, self, weapon, damage_override, meansofdeath, shitloc, willBeKilled, willBeKilled );
	if ( !willBeKilled || !( isdefined( self.dont_die_on_me ) && self.dont_die_on_me ) )
	{
		self finishActorDamage( inflictor, attacker, damage_override, flags, meansofdeath, weapon, vpoint, vdir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, surfaceType, vSurfaceNormal );
	}
}

function register_zombie_damage_override_callback( func )
{
	if(!isdefined(level.zombie_damage_override_callbacks))level.zombie_damage_override_callbacks=[];

	if ( !isdefined( level.zombie_damage_override_callbacks ) ) level.zombie_damage_override_callbacks = []; else if ( !IsArray( level.zombie_damage_override_callbacks ) ) level.zombie_damage_override_callbacks = array( level.zombie_damage_override_callbacks ); level.zombie_damage_override_callbacks[level.zombie_damage_override_callbacks.size]=func;;
}

function actor_killed_override(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime)
{
	if ( game["state"] == "postgame" )
	{
		return;	
	}
	
	if( isai(attacker) && isDefined( attacker.script_owner ) )
	{
		// if the person who called the dogs in switched teams make sure they don't
		// get penalized for the kill
		if ( attacker.script_owner.team != self.team )
			attacker = attacker.script_owner;
	}
		
	if( attacker.classname == "script_vehicle" && isDefined( attacker.owner ) )
	{
		attacker = attacker.owner;
	}
		
	if ( isdefined( attacker ) && isplayer( attacker ) )
	{
		multiplier = 1;
		if( zm_utility::is_headshot( weapon, sHitLoc, sMeansOfDeath ) )
		{
			multiplier = 1.5;
		}

		type = undefined;

		//MM (3/18/10) no animname check
		if ( IsDefined(self.animname) )
		{
			switch( self.animname )
			{
			case "quad_zombie":
				type = "quadkill";
				break;
			case "ape_zombie":
				type = "apekill";
				break;
			case "zombie":
				type = "zombiekill";
				break;
			case "zombie_dog":
				type = "dogkill";
				break;
			}
		}
	}
	
	if(( isdefined( self.is_ziplining ) && self.is_ziplining ))
	{
		self.deathanim = undefined;
	}

	if ( IsDefined( self.actor_killed_override ) )
	{
		self [[ self.actor_killed_override ]]( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime );
	}
	
	if ( IsDefined( self.deathFunction ) )
	{
		self [[ self.deathFunction ]]( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime );
	}
}


function round_end_monitor()
{
	while(1)
	{
		level waittill( "end_of_round" );

		demo::bookmark( "zm_round_end", gettime(), undefined, undefined, 1 );
		BBPostDemoStreamStatsForRound( level.round_number );
		zm_utility::upload_zm_dash_counters();

		{wait(.05);};
	}
}


//*****************************************************************************
//*****************************************************************************

function updateEndOfMatchCounters()
{
	zm_utility::increment_zm_dash_counter( "end_per_game", 1 );
	zm_utility::increment_zm_dash_counter( "end_per_player", level.players.size );
	
	if( !( isdefined( level.dash_counter_round_reached_5 ) && level.dash_counter_round_reached_5 ) )
	{
		zm_utility::increment_zm_dash_counter( "end_less_5", 1 );
	}
	else
	{
		if( !( isdefined( level.dash_counter_round_reached_10 ) && level.dash_counter_round_reached_10 ) )
		{
			zm_utility::increment_zm_dash_counter( "end_reached_5_less_10", 1 );
		}
		else
		{
			zm_utility::increment_zm_dash_counter( "end_reached_10", 1 );
		}
	}
	
	if( !zm_utility::is_solo_ranked_game() )
	{
		if( level.dash_counter_start_player_count != level.players.size )
		{
			zm_utility::increment_zm_dash_counter( "end_player_count_diff", 1 );
		}
	}
}

function end_game()
{
	level waittill ( "end_game" );
	
	check_end_game_intermission_delay();

/#	println( "end_game TRIGGERED " );	#/

	setmatchflag( "game_ended", 1 );

	level clientfield::set("gameplay_started", 0);
	level clientfield::set("game_end_time", int( ( GetTime() - level.n_gameplay_start_time + 500 ) / 1000 ) );

	util::clientnotify( "zesn" );
	
	level thread zm_audio::sndMusicSystem_PlayState( "game_over" );
	
	//AYERS: Turn off ANY last stand audio at the end of the game
	players = GetPlayers();
	for ( i = 0; i < players.size; i++ )
	{
		players[i] clientfield::set( "zmbLastStand", 0 );
	}

	for ( i = 0; i < players.size; i++ )
	{	
		if ( players[i] laststand::player_is_in_laststand() )
		{
			players[i] RecordPlayerDeathZombies();
			players[i] zm_stats::increment_player_stat( "deaths" );
			players[i] zm_stats::increment_client_stat( "deaths" );
			players[i] zm_pers_upgrades_functions::pers_upgrade_jugg_player_death_stat();
		}
		
		//clean up the revive text hud if it's active
		if( isdefined( players[i].reviveTextHud ) )
		{
			players[i].reviveTextHud destroy();
		}
	}

	StopAllRumbles();

	level.intermission = true;
	level.zombie_vars["zombie_powerup_insta_kill_time"] = 0;
	level.zombie_vars["zombie_powerup_fire_sale_time"] = 0;
	level.zombie_vars["zombie_powerup_double_points_time"] = 0;
	wait 0.1;

	game_over = [];
	survived = [];

	players = GetPlayers();

	//disabled the ingame pause menu from opening after a game ends
	setMatchFlag( "disableIngameMenu", 1 );
	foreach( player in players )
	{
		player closeInGameMenu();
		player CloseMenu( "StartMenu_Main" );
	}


	//AAR - set stat for each player (this will show the menu)
	foreach( player in players )
	{
		player setDStat( "AfterActionReportStats", "lobbyPopup", "summary" );
	}
	
	if(!isDefined(level._supress_survived_screen))
	{
		
		for( i = 0; i < players.size; i++ )
		{
			game_over[i] = NewClientHudElem( players[i] );
			survived[i] = NewClientHudElem( players[i] );
			if ( IsDefined( level.custom_game_over_hud_elem ) )
			{
				[[ level.custom_game_over_hud_elem ]]( players[i], game_over[i], survived[i] );
			}
			else
			{
				game_over[i].alignX = "center";
				game_over[i].alignY = "middle";
				game_over[i].horzAlign = "center";
				game_over[i].vertAlign = "middle";
				game_over[i].y -= 130;
				game_over[i].foreground = true;
				game_over[i].fontScale = 3;
				game_over[i].alpha = 0;
				game_over[i].color = ( 1.0, 1.0, 1.0 );
				game_over[i].hidewheninmenu = true;
				game_over[i] SetText( &"ZOMBIE_GAME_OVER" );
	
				game_over[i] FadeOverTime( 1 );
				game_over[i].alpha = 1;
				if ( players[i] isSplitScreen() )
				{
					game_over[i].fontScale = 2;
					game_over[i].y += 40;
				}

				survived[i].alignX = "center";
				survived[i].alignY = "middle";
				survived[i].horzAlign = "center";
				survived[i].vertAlign = "middle";
				survived[i].y -= 100;
				survived[i].foreground = true;
				survived[i].fontScale = 2;
				survived[i].alpha = 0;
				survived[i].color = ( 1.0, 1.0, 1.0 );
				survived[i].hidewheninmenu = true;
				if ( players[i] isSplitScreen() )
				{
					survived[i].fontScale = 1.5;
					survived[i].y += 40;
				}
			}
	

			//OLD COUNT METHOD
			if( level.round_number < 2 )
			{
				if( level.script == "zm_moon" )
				{
					if( !isdefined(level.left_nomans_land) )
					{
						nomanslandtime = level.nml_best_time;
						player_survival_time = int( nomanslandtime/1000 );
						player_survival_time_in_mins = zm::to_mins( player_survival_time );
						survived[i] SetText( &"ZOMBIE_SURVIVED_NOMANS", player_survival_time_in_mins );
					}
					else if( level.left_nomans_land == 2 )
					{
						survived[i] SetText( &"ZOMBIE_SURVIVED_ROUND" );
					}
				}
				else
				{
					survived[i] SetText( &"ZOMBIE_SURVIVED_ROUND" );
				}
			}
			else
			{
				survived[i] SetText( &"ZOMBIE_SURVIVED_ROUNDS", level.round_number );
			}
	
			survived[i] FadeOverTime( 1 );
			survived[i].alpha = 1;
		}
	}
	

	//check to see if we are in a game module that wants to do something with PvP damage
	if(isDefined(level.custom_end_screen))
	{
		level [[level.custom_end_screen]]();
	}

	for (i = 0; i < players.size; i++)
	{
		players[i] SetClientUIVisibilityFlag( "weapon_hud_visible", 0 );
		players[i] SetClientMiniScoreboardHide( true );
		//players[i] setDStat( "AfterActionReportStats", "lobbyPopup", "summary" );

		players[i] notify( "report_bgb_consumption" );
		
		players[i] zm_utility::zm_dash_stats_game_end();
	}

	//LUINotifyEvent( &"force_scoreboard", 0 );

	UploadStats();
	zm_stats::update_players_stats_at_match_end( players );
	zm_stats::update_global_counters_on_match_end();
	bb::logroundevent("end_game");
	upload_leaderboards();
	
	recordGameResult( "draw" );
	globallogic::recordZMEndGameComScoreEvent( "draw" );
	globallogic_player::recordActivePlayersEndGameMatchRecordStats();
	updateEndOfMatchCounters();
	
	//update dash and promo counters
	if( SessionModeIsOnlineGame() )
	{
	   	level thread zm_utility::upload_zm_dash_counters_end_game();
	}
	
	// Finalize Match Record
	finalizeMatchRecord();
	
	//zm_utility::play_sound_at_pos( "end_of_game", ( 0, 0, 0 ) );

	players = GetPlayers();
	foreach( player in players )
	{
		if( IsDefined( player.sessionstate ) && player.sessionstate == "spectator" )
		{
			player.sessionstate = "playing";
			player thread end_game_player_was_spectator();
		}
	}
	{wait(.05);};

	/#	
		if ( !( isdefined( level.host_ended_game ) && level.host_ended_game ) && GetDvarInt( "scr_restart_on_wipe" ) > 1 )
		{
			LUINotifyEvent( &"force_scoreboard", 0 );
			map_restart( true );
			wait( 666 );
		}
	#/	
		
	players = GetPlayers();

	LUINotifyEvent( &"force_scoreboard", 1, 1 );
	
	intermission();

	wait( level.zombie_vars["zombie_intermission_time"] );

	// hide the gameover message
	if ( !isDefined( level._supress_survived_screen ) )
	{
		for ( i = 0; i < players.size; i++ )
		{
			survived[i] Destroy();
			game_over[i] Destroy();
		}
	}
	else
	{
		for ( i = 0; i < players.size; i++ )
		{
			if(isDefined(players[i].survived_hud ) )
				players[i].survived_hud  Destroy();
			if (isDefined( players[i].game_over_hud ) )
				players[i].game_over_hud Destroy();
		}
	}
	
	level notify( "stop_intermission" );
	array::thread_all( GetPlayers(), &player_exit_level );

	wait( 1.5 );

	players = GetPlayers();
	for ( i = 0; i < players.size; i++ )
	{
		players[i] CameraActivate( false );
	}
	
	/#	
		if ( !( isdefined( level.host_ended_game ) && level.host_ended_game ) && GetDvarInt( "scr_restart_on_wipe" ) )
		{
			LUINotifyEvent( &"force_scoreboard", 1, 0 );
			map_restart( true );
			wait( 666 );
		}
	#/	
		    
	ExitLevel( false );

	// Let's not exit the function
	wait( 666 );
}

//*****************************************************************************
//*****************************************************************************

function end_game_player_was_spectator()
{
	{wait(.05);};

	self Ghost();
	self FreezeControls( true );
}

//*****************************************************************************
//*****************************************************************************

function disable_end_game_intermission( delay )
{
	level.disable_intermission = true;
	wait( delay );
	level.disable_intermission = undefined;
}


//*****************************************************************************
//*****************************************************************************

function check_end_game_intermission_delay()
{
	if( IsDefined(level.disable_intermission) )
	{
		while( 1 )
		{
			if( !IsDefined(level.disable_intermission) )
			{
				break;
			}
			wait( 0.01 );
		}
	}
}


//*****************************************************************************
// This will save the leaderboard data per round, for use in single player
//*****************************************************************************

function upload_leaderboards()
{	
	// place restrictions on whether leaderboards are uploaded to in the precache leaderboards function
	players = GetPlayers();
	for( i = 0; i < players.size; i++ )
	{
		players[i] uploadleaderboards();
	}
}


function initializeStatTracking()
{
	level.global_zombies_killed = 0;
	level.zombies_timeout_spawn = 0;
	level.zombies_timeout_playspace = 0;
	level.zombies_timeout_undamaged = 0;	
	level.zombie_player_killed_count = 0;
	level.zombie_trap_killed_count = 0;
	level.zombie_pathing_failed = 0;
	level.zombie_breadcrumb_failed = 0;		
	
}

function uploadGlobalStatCounters()
{
	incrementCounter( "global_zombies_killed", level.global_zombies_killed );
	incrementCounter( "global_zombies_killed_by_players", level.zombie_player_killed_count );
	incrementCounter( "global_zombies_killed_by_traps", level.zombie_trap_killed_count );	
}

function player_fake_death()
{
	level notify ("fake_death");
	self notify ("fake_death");

	self TakeAllWeapons();
	self AllowStand( false );
	self AllowCrouch( false );
	self AllowProne( true );

	self.ignoreme = true;
	self EnableInvulnerability();

	wait( 1 );
	self FreezeControls( true );
}

function player_exit_level()
{
	self AllowStand( true );
	self AllowCrouch( false );
	self AllowProne( false );

	//self thread lui::screen_fade_out( 1 );
}


function player_killed_override(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration)
{
	// BLANK
	level waittill( "forever" );
}
	
function player_zombie_breadcrumb()
{
	self notify("stop_player_zombie_breadcrumb");
	self endon("stop_player_zombie_breadcrumb");
	self endon( "disconnect" ); 
	self endon( "spawned_spectator" ); 
	level endon( "intermission" );

	self.zombie_breadcrumbs = []; 
	self.zombie_breadcrumb_distance = 24 * 24; // min dist (squared) the player must move to drop a crumb
	self.zombie_breadcrumb_area_num = 3;	   // the number of "rings" the area breadcrumbs use
	self.zombie_breadcrumb_area_distance = 16; // the distance between each "ring" of the area breadcrumbs

	self store_crumb( self.origin ); 
	last_crumb = self.origin;

	self thread zm_utility::debug_breadcrumbs(); 

	while( 1 )
	{
		wait_time = 0.1;
		
		
		if( self.ignoreme )
		{
			wait( wait_time ); 
			continue;
		}

		//For cloaking ability
		//if( self.ignoreme )
		//{
		//	wait( wait_time ); 
		//	continue;
		//}


		store_crumb = true; 
		airborne = false;
		crumb = self.origin;

//TODO TEMP SCRIPT for vehicle testing Delete/comment when done
		if ( !self IsOnGround() && self isinvehicle() )
		{
			trace = bullettrace( self.origin + (0,0,10), self.origin, false, undefined );
			crumb = trace["position"];
		}

//TODO TEMP DISABLE for vehicle testing.  Uncomment when reverting
// 		if ( !self IsOnGround() )
// 		{
// 			airborne = true;
// 			store_crumb = false; 
// 			wait_time = 0.05;
// 		}
// 		
		if( !airborne && DistanceSquared( crumb, last_crumb ) < self.zombie_breadcrumb_distance )
		{
			store_crumb = false; 
		}

		if ( airborne && self IsOnGround() )
		{
			// player was airborne, store crumb now that he's on the ground
			store_crumb = true;
			airborne = false;
		}
		
		if( isDefined( level.custom_breadcrumb_store_func ) )
		{
			store_crumb = self [[ level.custom_breadcrumb_store_func ]]( store_crumb );
		}
		
		if( isDefined( level.custom_airborne_func ) )
		{
			airborne = self [[ level.custom_airborne_func ]]( airborne );
		}
		
		if( store_crumb )
		{
			zm_utility::debug_print( "Player is storing breadcrumb " + crumb );
			
			if( IsDefined(self.node) )
			{
				zm_utility::debug_print( "has closest node " );
			}
			
			last_crumb = crumb;
			self store_crumb( crumb );
		}

		wait( wait_time ); 
	}
}


function store_crumb( origin )
{
	offsets = [];
	height_offset = 32;
	
	index = 0;
	for( j = 1; j <= self.zombie_breadcrumb_area_num; j++ )
	{
		offset = ( j * self.zombie_breadcrumb_area_distance );
		
		offsets[0] = ( origin[0] - offset, origin[1], origin[2] );
		offsets[1] = ( origin[0] + offset, origin[1], origin[2] );
		offsets[2] = ( origin[0], origin[1] - offset, origin[2] );
		offsets[3] = ( origin[0], origin[1] + offset, origin[2] );

		offsets[4] = ( origin[0] - offset, origin[1], origin[2] + height_offset );
		offsets[5] = ( origin[0] + offset, origin[1], origin[2] + height_offset );
		offsets[6] = ( origin[0], origin[1] - offset, origin[2] + height_offset );
		offsets[7] = ( origin[0], origin[1] + offset, origin[2] + height_offset );

		for ( i = 0; i < offsets.size; i++ )
		{
			self.zombie_breadcrumbs[index] = offsets[i];
			index++;
		}
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////LEADERBOARD CODE///////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function to_mins( seconds )
{
	hours = 0; 
	minutes = 0; 
	
	if( seconds > 59 )
	{
		minutes = int( seconds / 60 );

		seconds = int( seconds * 1000 ) % ( 60 * 1000 );
		seconds = seconds * 0.001; 

		if( minutes > 59 )
		{
			hours = int( minutes / 60 );
			minutes = int( minutes * 1000 ) % ( 60 * 1000 );
			minutes = minutes * 0.001; 		
		}
	}

	if( hours < 10 )
	{
		hours = "0" + hours; 
	}

	if( minutes < 10 )
	{
		minutes = "0" + minutes; 
	}

	seconds = Int( seconds ); 
	if( seconds < 10 )
	{
		seconds = "0" + seconds; 
	}

	combined = "" + hours  + ":" + minutes  + ":" + seconds; 

	return combined; 
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//
// INTERMISSION =========================================================== //
//

function intermission()
{
	level.intermission = true;
	level notify( "intermission" );

	players = GetPlayers();
	for( i = 0; i < players.size; i++ )
	{
		players[i] SetClientThirdPerson( 0 );
		players[i] resetFov();

		players[i].health = 100; // This is needed so the player view doesn't get stuck
		players[i] thread [[level.custom_intermission]]();
		
		players[i] StopSounds();
	}

	wait( 5.25 );

	players = GetPlayers();
	for( i = 0; i < players.size; i++ )
	{
		players[i] clientfield::set( "zmbLastStand", 0 );
	}

	level thread zombie_game_over_death();
}

function zombie_game_over_death()
{
	// Kill remaining zombies, in style!
	zombies = GetAiTeamArray( level.zombie_team );
	for( i = 0; i < zombies.size; i++ )
	{
		if( !IsAlive( zombies[i] ) )
		{
			continue;
		}

		zombies[i] SetGoal( zombies[i].origin );
	}

	for( i = 0; i < zombies.size; i++ )
	{
		if( !IsAlive( zombies[i] ) )
		{
			continue;
		}

		if ( ( isdefined( zombies[i].ignore_game_over_death ) && zombies[i].ignore_game_over_death ) )
		{
			continue;
		}
		
		wait( 0.5 + RandomFloat( 2 ) );

		if ( isdefined( zombies[i] ) )
		{
			if( !IsVehicle( zombies[i] ) )
			{
				zombies[i] zombie_utility::zombie_head_gib();
			}
			
			zombies[i] Kill();
		}
	}
}




function screen_fade_in( n_time, v_color, str_menu_id )
{
	lui::screen_fade( n_time, 0, 1, v_color, false, str_menu_id );
	wait n_time;
}

	
function player_intermission()
{
	self closeInGameMenu();
	self CloseMenu( "StartMenu_Main" );

	self notify("player_intermission");
	self endon("player_intermission");
	level endon( "stop_intermission" );
	self endon("disconnect");
	self endon("death");
	self notify( "_zombie_game_over" ); // ww: notify so hud elements know when to leave

	//Show total gained point for end scoreboard and lobby
	self.score = self.score_total;	

	points =struct::get_array( "intermission", "targetname" );

	if( !IsDefined( points ) || points.size == 0 )
	{
		points = getentarray( "info_intermission", "classname" ); 
		if( points.size < 1 )
		{
		/#	println( "NO info_intermission POINTS IN MAP" ); 	#/
			return;
		}	
	}

	if ( ( isdefined( level.b_show_single_intermission ) && level.b_show_single_intermission ) )
	{
		a_s_temp_points = array::randomize( points );
		points = [];
		points[0] = array::random( a_s_temp_points );
	}
	else
	{
		points = array::randomize( points );
	}
	
	self zm_utility::create_streamer_hint( points[0].origin, points[0].angles, 0.9 );
	
	wait( 5 );

	self lui::screen_fade_out( 1 );

	// don't do this unti we're ready to "spawn" the players in the exit scenes 
	//   the player camera will clip into the ground if this is done too early
	self.sessionstate = "intermission";
	self.spectatorclient = -1; 
	self.killcamentity = -1; 
	self.archivetime = 0; 
	self.psoffsettime = 0; 
	self.friendlydamage = undefined;
	if ( isdefined(	level.player_intemission_spawn_callback ) )
	{
		self thread [[	level.player_intemission_spawn_callback ]] ( points[0].origin, points[0].angles );
	}
	
	while( 1 )
	{
		for( i = 0; i < points.size; i++ )
		{
			point = points[i];
			nextpoint = points[i+1];
			
			self SetOrigin( point.origin );
			self SetPlayerAngles( point.angles );
			wait 0.15;

			self notify("player_intermission_spawned");
			
			if ( IsDefined(nextpoint) )
			{
				self zm_utility::create_streamer_hint( nextpoint.origin, nextpoint.angles, 0.9 );
				self screen_fade_in( 2 );
	
				wait( 3 );
				
				self lui::screen_fade_out( 2 );
			}
			else
			{
				self screen_fade_in( 2 );
				
				if ( points.size == 1 )
				{
					return;
				}
			}
		}
	}
}

function fade_up_over_time(t)
{
		self FadeOverTime( t );
		self.alpha = 1;
}

function default_exit_level()
{
	zombies = GetAiTeamArray( level.zombie_team );
	for ( i = 0; i < zombies.size; i++ )
	{
		if ( ( isdefined( zombies[i].ignore_solo_last_stand ) && zombies[i].ignore_solo_last_stand ) )
		{
			continue;
		}

		if ( isDefined( zombies[i].find_exit_point ) )
		{
			zombies[i] thread [[ zombies[i].find_exit_point ]]();
			continue;
		}

		if ( zombies[i].ignoreme )
		{
			zombies[i] thread default_delayed_exit();
		}
		else
		{
			zombies[i] thread default_find_exit_point();
		}
	}
}

function default_delayed_exit()
{
	self endon( "death" );

	while ( 1 )
	{
		if ( !level flag::get( "wait_and_revive" ) )
		{
			return;
		}

		// broke through the barricade, find an exit point
		if ( !self.ignoreme )
		{
			break;
		}
		wait( 0.1 );
	}

	self thread default_find_exit_point();
}

function default_find_exit_point()
{
	self endon( "death" );

	player = GetPlayers()[0];

	dist_zombie = 0;
	dist_player = 0;
	dest = 0;

	away = VectorNormalize( self.origin - player.origin );
	endPos = self.origin + VectorScale( away, 600 );

	if ( isdefined( level.zm_loc_types[ "wait_location" ] ) && level.zm_loc_types[ "wait_location" ].size > 0 )
    {
		locs = array::randomize( level.zm_loc_types[ "wait_location" ] );
    }
	//TODO Add wait_locations to Der Reise and we can remove this line
	else	// Legacy catch
	{
		locs = array::randomize( level.zm_loc_types[ "dog_location" ] );
	}

	for ( i = 0; i < locs.size; i++ )
	{
		dist_zombie = DistanceSquared( locs[i].origin, endPos );
		dist_player = DistanceSquared( locs[i].origin, player.origin );

		if ( dist_zombie < dist_player )
		{
			dest = i;
			break;
		}
	}

	self notify( "stop_find_flesh" );
	self notify( "zombie_acquire_enemy" );

	if( isdefined( locs[dest] ) )
	{
		self SetGoal( locs[dest].origin );
	}

	while ( 1 )
	{
		// in the event this function isn't run through a solo revive, use level override to dictate behavior; don't change existing behavior if this doesn't exist
		b_passed_override = true;
		
		// Returning 'false' from the level override will put zombies back into find_flesh
		if ( IsDefined( level.default_find_exit_position_override ) )
		{
			b_passed_override = [[ level.default_find_exit_position_override ]]();
		}
		
		if ( !level flag::get( "wait_and_revive" ) && b_passed_override )
		{
			break;
		}
		wait( 0.1 );
	}
	
}

function play_level_start_vox_delayed()
{
    wait(3);
    players = GetPlayers();
	num = RandomIntRange( 0, players.size );
	players[num] zm_audio::create_and_play_dialog( "general", "intro" );
}


function register_sidequest( id, sidequest_stat )
{
	if ( !IsDefined( level.zombie_sidequest_stat ) )
	{
		level.zombie_sidequest_previously_completed = [];
		level.zombie_sidequest_stat = [];
	}
	
	level.zombie_sidequest_stat[id] = sidequest_stat;

	//level flag::wait_till( "all_players_spawned" );
	level flag::wait_till( "start_zombie_round_logic" );

	level.zombie_sidequest_previously_completed[id] = false;

	// don't do stats stuff if it's not an online game
	if ( !level.onlineGame )
	{
		return;
	}
	
	//don't do stats stuff if stats are disabled
	if ( ( isdefined( level.zm_disable_recording_stats ) && level.zm_disable_recording_stats ) )
	{	
		return;	
	}

	players = GetPlayers();
	for ( i = 0; i < players.size; i++ )
	{
		if ( players[i] zm_stats::get_global_stat( level.zombie_sidequest_stat[id] ) )
		{
			level.zombie_sidequest_previously_completed[id] = true;
			return;
		}
	}
}


function is_sidequest_previously_completed(id)
{
	return ( isdefined( level.zombie_sidequest_previously_completed[id] ) && level.zombie_sidequest_previously_completed[id] );
}


function set_sidequest_completed(id)
{
	level notify( "zombie_sidequest_completed", id );
	level.zombie_sidequest_previously_completed[id] = true;

	// don't do stats stuff if it's not an online game
	if ( !level.onlineGame )
	{
		return;
	}

	//don't do stats stuff if stats are disabled
	if ( ( isdefined( level.zm_disable_recording_stats ) && level.zm_disable_recording_stats ) )
	{	
		return;	
	}	

	players = GetPlayers();
	for ( i = 0; i < players.size; i++ )
	{
		if ( isdefined( level.zombie_sidequest_stat[id] ) )
		{
			players[i] zm_stats::add_global_stat( level.zombie_sidequest_stat[id], 1 );
		}
	}
}

function playSwipeSound( mod, attacker )
{
	if( ( isdefined( attacker.is_zombie ) && attacker.is_zombie ) || (isdefined( attacker.archetype ) && attacker.archetype == "margwa" ) )
	{
		self playsoundtoplayer( "evt_player_swiped", self );
		return;
	}
}

function precache_zombie_leaderboards()
{
	// don't save leaderboards for systemlink or custom games
	if( SessionModeIsSystemlink() )
	{
		return; 
	}

	// GLOBAL LEADERBOARDS
	globalLeaderboards = "LB_ZM_GB_BULLETS_FIRED_AT ";
	globalLeaderboards += "LB_ZM_GB_BULLETS_HIT_AT "; 
	globalLeaderboards += "LB_ZM_GB_DISTANCE_TRAVELED_AT "; 
	globalLeaderboards += "LB_ZM_GB_DOORS_PURCHASED_AT "; 
	globalLeaderboards += "LB_ZM_GB_GRENADE_KILLS_AT "; 
	globalLeaderboards += "LB_ZM_GB_HEADSHOTS_AT "; 
	globalLeaderboards += "LB_ZM_GB_KILLS_AT "; 
	globalLeaderboards += "LB_ZM_GB_PERKS_DRANK_AT "; 
	globalLeaderboards += "LB_ZM_GB_REVIVES_AT ";
	
	//Multirank
	globalLeaderboards += "LB_ZM_GB_KILLSTATS_MR ";
	globalLeaderboards += "LB_ZM_GB_GAMESTATS_MR ";

	if ( !level.rankedMatch && (GetDvarInt( "zm_private_rankedmatch", 0 ) == 0) )
	{
		precacheLeaderboards( globalLeaderboards );
		return;
	}

	// MAP LEADERBOARD
	mapname = GetDvarString( "mapname" );
	expectedPlayerNum = GetNumExpectedPlayers();	
	
	mapLeaderboard = "LB_ZM_MAP_"+ getsubstr( mapname, 3, mapname.size ) + "_" + expectedPlayerNum + "PLAYER"; 

	precacheLeaderboards( globalLeaderboards + mapLeaderboard );
}

function zm_on_player_connect()
{
	if( level.passed_introscreen)
	{
		self setClientUIVisibilityFlag( "hud_visible", 1 );
		self setClientUIVisibilityFlag( "weapon_hud_visible", 1 );
		
		zm_utility::increment_zm_dash_counter( "hotjoined", 1 );
		zm_utility::upload_zm_dash_counters();
	}
	
	self flag::init( "used_consumable" );
	self thread zm_utility::zm_dash_stats_game_start();
	self thread zm_utility::zm_dash_stats_wait_for_consumable_use();
	
	thread  refresh_player_navcard_hud();
	self thread watchDisconnect();

	self.hud_damagefeedback = newdamageindicatorhudelem(self);
	self.hud_damagefeedback.horzAlign = "center";
	self.hud_damagefeedback.vertAlign = "middle";
	self.hud_damagefeedback.x = -12;
	self.hud_damagefeedback.y = -12;
	self.hud_damagefeedback.alpha = 0;
	self.hud_damagefeedback.archived = true;
	self.hud_damagefeedback setShader( "damage_feedback", 24, 48 );
	self.hitSoundTracker = true;
	
}

function zm_on_player_disconnect()
{
	thread refresh_player_navcard_hud();
	
	zm_utility::increment_zm_dash_counter( "left_midgame", 1 );
	zm_utility::upload_zm_dash_counters();
}

function watchDisconnect()
{
	self notify("watchDisconnect");
	self endon("watchDisconnect");
	self waittill("disconnect");
	zm_on_player_disconnect();
}



function increment_dog_round_stat(stat)
{
	players = GetPlayers();
	foreach(player in players)
	{
		player zm_stats::increment_client_stat( "zdog_rounds_" + stat );
	}
}

function setup_player_navcard_hud()
{
	level flag::wait_till( "start_zombie_round_logic" );
	thread refresh_player_navcard_hud();
}


function refresh_player_navcard_hud()
{
	if (!IsDefined(level.navcards))
		return;
	players = GetPlayers();
	foreach( player in players )
	{
		navcard_bits = 0;
		for(i = 0;i < level.navcards.size; i++)
		{
			hasit = player zm_stats::get_global_stat( level.navcards[i] );
			if (isdefined(player.navcard_grabbed) && player.navcard_grabbed == level.navcards[i] )
				hasit = 1;
			if( hasit )
			{
				navcard_bits +=( 1 << i );
			}
		}
		util::wait_network_frame();
		player clientfield::set( "navcard_held", 0 );
		if ( navcard_bits > 0 )
		{
			util::wait_network_frame();
			player clientfield::set( "navcard_held", navcard_bits );
		}
	}

}

function set_default_laststand_pistol(solo_mode)
{
		
	if (!solo_mode )
	{
		level.laststandpistol = level.default_laststandpistol;
	}
	else 
	{
		level.laststandpistol = level.default_solo_laststandpistol;
	}
	
}

//109171 	Zombies - ZM_Global - Add check for player count at beginning of the game
//monitors during the match for exploits extra players being able to join the match
function player_too_many_players_check()
{
	max_players = 4;
	if ( level.scr_zm_ui_gametype == "zgrief" || level.scr_zm_ui_gametype == "zmeat" )
	{
		max_players = 8;
	}

	if ( GetPlayers().size > max_players )
	{
		zm_game_module::freeze_players(true);
		level notify("end_game");
	}

}

//vehicle response to idgun damage
function is_idgun_damage( weapon )
{
	if( isdefined( level.idgun_weapons ) )
	{
		if ( IsInArray( level.idgun_weapons, weapon ) )
			return true; 
	}
	return false;
}

function zm_on_player_spawned()
{
	thread update_zone_name();
	thread update_is_player_valid();
}

function update_is_player_valid()
{
	self endon( "death" );
	self endon( "disconnnect" );
		
	self.am_i_valid = true;	
	while ( isDefined( self ) )
	{
		self.am_i_valid = zm_utility::is_player_valid( self, true );
		{wait(.05);}
	}
}

function update_zone_name()
{
	self endon( "death" );
	self endon( "disconnnect" );
	
	self.zone_name = zm_utility::get_current_zone();
	if ( isDefined( self.zone_name ) )
	{
		self.previous_zone_name = self.zone_name;
	}
	
	while ( isDefined( self ) )
	{
		if( isDefined( self.zone_name ) )
		{
			self.previous_zone_name = self.zone_name;
		}
		self.zone_name = zm_utility::get_current_zone();
		
		wait RandomFloatRange( 0.5, 1.0 );
	}
}

/#
function printHashIDs()
{
	outputString = "\n------------------- BEGIN HASH ID DUMP -----------------------------\n";
		
	
	//Print Craftables
	outputString += "** CRAFTABLES **\n";
	foreach( s_craftable in level.zombie_include_craftables )
	{
		outputString += "+" + s_craftable.name + "," + s_craftable.hash_id + "\n";
		if (!isDefined(s_craftable.a_piecestubs))
		{
			continue;	
		}
		
		foreach( s_piece in s_craftable.a_piecestubs )
		{
			outputString += s_piece.pieceName + "," + s_piece.hash_id + "\n";
		}
	}
	
	//Print Powerups
	outputString += "** POWERUPS **\n";
	foreach ( powerup in level.zombie_powerups)
	{
		outputString += powerup.powerup_name + "," + powerup.hash_id + "\n";
	}
	
	//Print AATs
	outputString += "** AAT **\n";
	if( ( isdefined( level.aat_in_use ) && level.aat_in_use ) )
	{
		foreach (aat in level.aat)
		{
			if (!isDefined(aat) || !isDefined(aat.name) || aat.name == "none")
			{
				continue;	
			}
			outputString += aat.name + "," + aat.hash_id + "\n";
		}
	}
	
	//Print AATs
	outputString += "** PERKS **\n";
	foreach (perk in level._custom_perks)
	{
		if (!isDefined(perk) || !isDefined(perk.alias))
		{
			continue;	
		}
		outputString += perk.alias + "," + perk.hash_id + "\n";
	}
	
	outputString += "------------------- END HASH ID DUMP -----------------------------\n";
	PrintLn(outputString);
}
	
#/
