#include extreme\_ex_weapons;

//******************************************************************************
// eXtreme+ launch main threads
//******************************************************************************
main()
{
	// call init in 0.iwd to get the iwd in the list of referenced iwd's (to force download)
	//maps\mp\gametypes\_gt0::init();

	// reposition flags to fix placement bug on linux
	axis_flag = getent("axis_flag", "targetname");
	if(isDefined(axis_flag))
	{
		trace = bulletTrace(axis_flag.origin + (0,0,50), axis_flag.origin - (0,0,100), true, axis_flag);
		axis_flag.origin = trace["position"];
		axis_flag.home_origin = axis_flag.origin;
		axis_flag.flagmodel.origin = axis_flag.home_origin;
		axis_flag.basemodel.origin = axis_flag.home_origin;
	}

	allied_flag = getent("allied_flag", "targetname");
	if(isDefined(allied_flag))
	{
		trace = bulletTrace(allied_flag.origin + (0,0,50), allied_flag.origin - (0,0,100), true, allied_flag);
		allied_flag.origin = trace["position"];
		allied_flag.home_origin = allied_flag.origin;
		allied_flag.flagmodel.origin = allied_flag.home_origin;
		allied_flag.basemodel.origin = allied_flag.home_origin;
	}

	// get the map dimensions and playing field dimensions
	if(!isDefined(game["mapArea_Centre"]))
	{
		extreme\_ex_utils::GetMapDim(true);
		extreme\_ex_utils::GetFieldDim();
	}

	// setup weather FX
	if(level.ex_weather) thread extreme\_ex_weather::main();

	// initialize bots (dumb or meat)
	if(level.ex_testclients || level.ex_mbot) thread extreme\_ex_bots::main();

	// set up map rotation
	if(getCvar("ex_maprotdone") == "")
	{
		// set up player based rotation
		if(level.ex_pbrotate) extreme\_ex_maprotation::pbRotation();
		// save rotation for rotation stacker
		setCvar("ex_maprotation", getCvar("sv_maprotation"));
		setCvar("ex_maprotdone","1");
	}

	// fix the map rotation (executed only once)
	if(level.ex_fixmaprotation) level extreme\_ex_maprotation::fixMapRotation();

	// set a random map rotation (executed only once)
	if(level.ex_randommaprotation) level thread extreme\_ex_maprotation::randomMapRotation();

	// rotation stacker
	if(!level.ex_pbrotate)
	{
		ex_maprotation = getCvar("ex_maprotation");
		maprotcur = getcvar("sv_maprotationcurrent");
		if(maprotcur == "")
		{
			maprotno = getCvar("ex_maprotno");
			if(maprotno == "") maprotno = 0;
				else maprotno = getCvarInt("ex_maprotno");
			maprotno++;
			maprot = getcvar("sv_maprotation" + maprotno);
			if(maprot != "")
			{
				setCvar("sv_maprotation", maprot);
				setCvar("ex_maprotno", maprotno);
			}
			else if(maprotno != 1)
			{
				maprotno = 0;
				setCvar("sv_maprotation", ex_maprotation);
				setCvar("ex_maprotno", maprotno);
			}
		}
	}

	// rotate if empty
	if(level.ex_rotateifempty) level thread extreme\_ex_rotate::main();

	// launch name checker
	if(level.ex_namechecker) level thread extreme\_ex_namecheck::main();

	// eXtreme+ command monitor
	if(level.ex_cmdmonitor) level thread extreme\_ex_cmdmonitor::main();

	// monitor the world for potatoes!
	level thread potatoMonitor();

	// clear any camping players
	if(level.ex_campwarntime || level.ex_campsniper_warntime) level thread extreme\_ex_camper::removeCampers();

	// text logos for mod and server
	level thread extreme\_ex_modinfo::main();

	// show custom clan logo (mylogo)
	level thread extreme\_ex_mylogo::main();

	// Bash-mode level announcement
	if(level.ex_bash_only || level.ex_frag_fest) level thread modeAnnounceLevel();

	// Clan-mode level announcement
	if(level.ex_clanvsnonclan) level thread clanAnnounceLevel();

	// ----- READYUP: Stop here when in ready-up mode ----------------------------
	if(level.ex_readyup && !isDefined(game["readyup_done"]))
	{
		// Tell DRM to stop processing "small", "medium" and "large" extensions
		game["ex_modstate"] = "initialized";

		// Start bot system
		level notify("gobots");

		return;
	}
	// ----- READYUP -------------------------------------------------------------

	// monitor nades
	thread extreme\_ex_nades::main();

	// time announcer
	if(level.ex_timeannouncer) level thread extreme\_ex_timeannouncer::main();
	
	// server messages
	if(level.ex_svrmsg) level thread extreme\_ex_messages::serverMessages();
	
	// flares
	if(level.ex_flares >= 1) level thread extreme\_ex_flares_ambient::main();

	// mortars
	if(level.ex_mortars >= 1) level thread extreme\_ex_mortar_ambient::main();

	// artillery
	if(level.ex_artillery >= 1) level thread extreme\_ex_artillery_ambient::main();

	// planes
	if(level.ex_planes >= 1) level thread extreme\_ex_airplanes::main();

	// sky effects
	level thread extreme\_ex_skyeffects::main();

	// livestats
	if(level.ex_livestats && level.ex_teamplay) level thread extreme\_ex_livestats::main();

	// ammo crates
	if(level.ex_amc_perteam) level thread extreme\_ex_ammocrates::main();

	// turrets monitor
	if(level.ex_turrets) level thread extreme\_ex_turrets::main();

	// launch inactivity monitor
	if(level.ex_inactive_plyr || level.ex_inactive_spec) level thread extreme\_ex_inactivity::main();

	// compass changer
	level thread extreme\_ex_compass::main();

	// call vote delay
	if(level.ex_callvote_mode) level thread extreme\_ex_callvote::main();

	// Start gunship
	if(level.ex_gunship) level thread extreme\_ex_gunship::main();

	// procedure to free up entities by removing unused spawnpoints (DEBUG)
	if(level.ex_entities) level thread extreme\_ex_entities::main();

	// Tell DRM to stop processing "small", "medium" and "large" extensions
	game["ex_modstate"] = "initialized";

	// Start bot system
	level notify("gobots");
}

//******************************************************************************
// eXtreme+ launch player threads
//******************************************************************************
playerThreads()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	// just to be sure
	if(level.ex_gameover) return;

	// make sure the level scripts are started before starting player threads
	while(!isDefined(game["ex_modstate"])) wait( [[level.ex_fpstime]](0.05) );

	// start toolbox procedures
	self thread extreme\_ex_toolbox::main();

	// turn off intro, spec and death music
	if( (level.ex_intromusic && self.pers["intro_on"]) || (level.ex_specmusic && self.pers["spec_on"]) || (level.ex_deathmusic && self.pers["dth_on"]) )
	{
		self.pers["intro_on"] = false;
		self.pers["spec_on"] = false;
		self.pers["dth_on"] = false;
		self playLocalSound("spec_music_null");
		self playLocalSound("spec_music_stop");
	}

	// start the move monitor
	self thread moveMonitor();

	// ----- MBOTS: Stop here if you are an mbot -------------------------------
	if(level.ex_mbot && isDefined(self.isbot)) return;
	// ----- MBOTS ---------------------------------------------------------------

	// force clientside dvars
	if(level.ex_forceclientdvars) self thread extreme\_ex_forcedvar::main();

	// range finder
	if(level.ex_rangefinder) self thread rangeFinder();

	// apply laser dot
	if(level.ex_laserdot) self thread extreme\_ex_laserdot::main();

	// prone shoot delay monitor
	if(level.ex_stanceshoot) self thread stanceShootMonitor();

	// start cold breath fx if wintermap
	if(level.ex_wintermap && level.ex_coldbreathfx) self thread coldbreathMonitor();

	// sniper zoom level monitor
	if(level.ex_zoom) self thread extreme\_ex_zoom::main();

	// monitor for knife
	self thread extreme\_ex_knife::main();

	// monitor for flamethrower
	self thread extreme\_ex_flamethrower::main();

	// start the burst monitor
	if(level.ex_burst_mode) self thread extreme\_ex_weapons::burstMonitor();

	// start the sprint system
	if(level.ex_sprint) self thread extreme\_ex_sprintsystem::main();

	// check names and show welcome messages
	self thread handleWelcome();

	// ----- READYUP: Stop here when in ready-up mode ----------------------------
	if(level.ex_readyup && !isDefined(game["readyup_done"])) return;
	// ----- READYUP -------------------------------------------------------------

	// spawn protection
	if(level.ex_spwn_time) self thread extreme\_ex_spawnpro::main();

	// parachutes
	if(level.ex_parachutes) self thread extreme\_ex_parachute::main();

	// arcade style HUD element
	if(level.ex_arcade) self thread extreme\_ex_arcade::main();

	// mobile mg monitor
	if(level.ex_turrets == 2) self thread extreme\_ex_turrets::monitorMobileMG();

	// health bar
	if(level.ex_healthsystem) self thread healthBar();

	// good luck message
	if(level.ex_goodluck)
	{
		if(isDefined(self.ex_team_changed)) self.ex_glplay = undefined;
		if(!isDefined(self.ex_glplay)) self thread extreme\_ex_messages::goodluckMsg();
	}

	// remove team switching flag (re-allow spawn delay if enabled)
	self.ex_team_changed = undefined;

	// camper check
	if(level.ex_campwarntime || level.ex_campsniper_warntime) self thread extreme\_ex_camper::main();

	// ----- BOT: Stop here if you are a bot -----------------------------------
	if(isDefined(self.pers["isbot"]) && self.pers["isbot"])
	{
		if(level.ex_testclients_freeze) self extreme\_ex_utils::punishment("enable", "freeze");
		return;
	}
	// ----- BOT -----------------------------------------------------------------

	// start weather FX
	if(level.ex_weather) self thread extreme\_ex_weather::start();

	// start the rank hud system
	if(level.ex_ranksystem && level.ex_rankhud) self thread extreme\_ex_ranksystem::rankhud();

	// start the call for medic monitor
	if(level.ex_medicsystem == 1) self thread firstaidMonitor();

	// first aid system
	if(level.ex_medicsystem >= 1) self thread extreme\_ex_firstaid::main();

	// monitor use of binoculars
	self thread binocularMonitor();

	// Scoped-On HUD indicator
	if(level.ex_scopedon) self thread extreme\_ex_scopedon::main();

	// tripwire monitor
	if(level.ex_tweapon) self thread extreme\_ex_tripwires::main();

	// start the grenade warning monitor
	if(level.ex_grenadewarn && level.ex_teamplay) self thread grenadeMonitor();

	// Jukebox
	if(level.ex_jukebox) self thread extreme\_ex_jukebox::main();

	// show call vote delay status
	self thread extreme\_ex_callvote::voteShowStatus();

	// display round number at spawn for roundbased gametypes
	self thread roundDisplay();

	// Bash mode player announcement
	if( (level.ex_bash_only && level.ex_bash_only_msg > 1) ||
	    (level.ex_frag_fest && level.ex_frag_fest_msg > 1)) self thread modeAnnouncePlayer();

	// Clan mode player announcement
	if(level.ex_clanvsnonclan && level.ex_clanvsnonclan_msg > 1) self thread clanAnnouncePlayer();
}

//******************************************************************************
// bash mode or nade fest annoucement
//******************************************************************************
modeAnnounceLevel()
{
	if(level.ex_bash_only_msg != 1 && level.ex_bash_only_msg != 4 && level.ex_bash_only_msg != 5) return;

	if(!isDefined(level.ex_modeannouncer))
	{
		level.ex_modeannouncer = newHudElem();
		level.ex_modeannouncer.archived = false;
		level.ex_modeannouncer.horzAlign = "fullscreen";
		level.ex_modeannouncer.vertAlign = "fullscreen";
		level.ex_modeannouncer.alignX = "right";
		level.ex_modeannouncer.alignY = "middle";
		level.ex_modeannouncer.x = 632;
		level.ex_modeannouncer.y = 461;
		level.ex_modeannouncer.fontScale = 1.3;
		level.ex_modeannouncer setText(level.ex_specialmodemsg);
	}
}

modeAnnouncePlayer()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	if( (level.ex_bash_only_msg == 2 || level.ex_bash_only_msg == 4) && isDefined(self.ex_bashann) ) return;
	self.ex_bashann = true;

	if(!isDefined(self.ex_modeannouncer))
	{
		self.ex_modeannouncer = newClientHudElem(self);
		self.ex_modeannouncer.archived = false;
		self.ex_modeannouncer.horzAlign = "fullscreen";
		self.ex_modeannouncer.vertAlign = "fullscreen";
		self.ex_modeannouncer.alignX = "center";
		self.ex_modeannouncer.alignY = "top";
		self.ex_modeannouncer.x = 320;
		self.ex_modeannouncer.y = 90;
		self.ex_modeannouncer.fontscale = 3;
		self.ex_modeannouncer setText(level.ex_specialmodemsg);
	}

	wait( [[level.ex_fpstime]](1.5) );

	if(isDefined(self.ex_modeannouncer))
	{
		self.ex_modeannouncer fadeOverTime(.5);
		self.ex_modeannouncer.alpha = 0;
		wait( [[level.ex_fpstime]](0.5) );
	}

	if(isDefined(self.ex_modeannouncer)) self.ex_modeannouncer destroy();
}

//******************************************************************************
// clan mode annoucement
//******************************************************************************
clanAnnounceLevel()
{
	if(level.ex_clanvsnonclan_msg != 1 && level.ex_clanvsnonclan_msg != 4 && level.ex_clanvsnonclan_msg != 5) return;

	if(!isDefined(level.ex_clanannouncer))
	{
		level.ex_clanannouncer = newHudElem();
		level.ex_clanannouncer.archived = false;
		level.ex_clanannouncer.horzAlign = "fullscreen";
		level.ex_clanannouncer.vertAlign = "fullscreen";
		level.ex_clanannouncer.alignX = "left";
		level.ex_clanannouncer.alignY = "middle";
		level.ex_clanannouncer.x = 8;
		level.ex_clanannouncer.y = 80;
		level.ex_clanannouncer.fontScale = 1.0;
		level.ex_clanannouncer setText(level.ex_clanmodemsg);
	}
}

clanAnnouncePlayer()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

 	if( (level.ex_clanvsnonclan_msg == 2 || level.ex_clanvsnonclan_msg == 4) && isDefined(self.ex_clanann) ) return;
	self.ex_clanann = true;

	if(!isDefined(self.ex_clanannouncer))
	{
		self.ex_clanannouncer = newClientHudElem(self);
		self.ex_clanannouncer.archived = false;
		self.ex_clanannouncer.horzAlign = "fullscreen";
		self.ex_clanannouncer.vertAlign = "fullscreen";
		self.ex_clanannouncer.alignX = "center";
		self.ex_clanannouncer.alignY = "top";
		self.ex_clanannouncer.x = 320;
		self.ex_clanannouncer.y = 120;
		self.ex_clanannouncer.fontscale = 3;
		self.ex_clanannouncer setText(level.ex_clanmodemsg);
	}

	wait( [[level.ex_fpstime]](1.5) );

	if(isDefined(self.ex_clanannouncer))
	{
		self.ex_clanannouncer fadeOverTime(.5);
		self.ex_clanannouncer.alpha = 0;
		wait( [[level.ex_fpstime]](0.5) );
	}

	if(isDefined(self.ex_clanannouncer)) self.ex_clanannouncer destroy();
}

//******************************************************************************
// eXtreme+ monitors
//******************************************************************************
coldbreathMonitor()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	wait( [[level.ex_fpstime]](2 + randomint(3)) );

	while(isPlayer(self) && self.sessionstate == "playing")
	{
		playfxontag (level.ex_effect["coldbreathfx"], self, "TAG_EYE");
		
		if(self.ex_playsprint || self.ex_sprintreco) wait( [[level.ex_fpstime]](randomfloatrange(0.5,1.5)) );
			else wait( [[level.ex_fpstime]](randomfloatrange(1.5,3.5)) );
	}
}

firstaidMonitor()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	while(isPlayer(self) && self.sessionstate == "playing")
	{
		if(self.health < level.ex_medic_callout)
		{
			self thread extreme\_ex_firstaid::callformedic();
			wait( [[level.ex_fpstime]](20) );
		}
		
		wait( [[level.ex_fpstime]](0.5) );
	}
}

binocularMonitor()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	while(isPlayer(self) && self.sessionstate == "playing")
	{
		self waittill("binocular_enter");
		self.ex_binocuse = true;
		self waittill("binocular_exit");
		self.ex_binocuse = false;

		wait( [[level.ex_fpstime]](0.1) );
	}
}

grenadeMonitor()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	for(;;)
	{
		grenadetype = undefined;

		// how many of each grenade do they have?
		frags = self getammocount(self.pers["fragtype"]) + self getammocount(self.pers["enemy_fragtype"]);
		smokes = self getammocount(self.pers["smoketype"]) + self getammocount(self.pers["enemy_smoketype"]);

		while(isPlayer(self) && self.sessionstate == "playing" && !self.ex_plantwire)
		{
			wait( [[level.ex_fpstime]](0.05) );
	
			// how many of each grenade do they have now?
			frags_check = self getammocount(self.pers["fragtype"]) + self getammocount(self.pers["enemy_fragtype"]);
			smokes_check = self getammocount(self.pers["smoketype"]) + self getammocount(self.pers["enemy_smoketype"]);				
	
			// player may have thrown both!, so no else here....
			if(frags_check < frags && !self.ex_plantwire)
			{
				self thread maps\mp\gametypes\_quickmessages::quickwarning("frag", 480, true, true);
				frags = frags_check;
			}
			else frags = frags_check;
	
			if(smokes_check < smokes && !self.ex_plantwire)
			{
				self thread maps\mp\gametypes\_quickmessages::quickwarning("smoke", 480, true, true);
				smokes = smokes_check;
			}
			else smokes = smokes_check;
		}
		
		wait( [[level.ex_fpstime]](0.05) );
	}
}

moveMonitor()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	self.ex_stance = 0;
	self.ex_pace = false;
	self.ex_moving = false;
	if(level.ex_antirun) self.antirun_puninprog = false;

	while(isPlayer(self) && isAlive(self) && self.sessionstate=="playing")
	{
		// Get the stance
		self.ex_stance = [[level.ex_getStance]](false);

		if(isPlayer(self))
		{
			// Calculate current speed
			mark = self.origin;
			wait( [[level.ex_fpstime]](0.1) );

			if(isPlayer(self))
			{
				dist = distance(mark, self.origin);

				if(dist > 1) self.ex_moving = true;
					else self.ex_moving = false;

				if(dist > 10) self.ex_pace = true;
					else self.ex_pace = false;

				// Sniper anti-run
				if(level.ex_antirun && !self.ex_invulnerable)
				{
					if(self.ex_sprinting || (self.ex_stance == 0 && self.ex_moving))
					{
						if(isdefined(self.antirun_mark))
						{
							if(int(distance(self.antirun_mark, self.origin)) > level.ex_antirun_distance)
							{
								self thread antirunPunish();
								self.antirun_mark = undefined;
							}
						}
						else self.antirun_mark = mark;
					}
					else self.antirun_mark = undefined;
				}
			}
		}
	}
}

stanceShootMonitor()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	lastprone = 2;
	lastjump = 3;

	jumpcheck = false;
	jumpsensitivity = level.ex_jump_sensitivity;
	jumpsensor = 0;

	for(;;)
	{
		wait( [[level.ex_fpstime]](0.05) );

		if(self.ex_plantwire || self.ex_defusewire || self.handling_mine || isDefined(self.ex_isparachuting)) continue;
		if(isDefined(self.ex_planting) || isDefined(self.ex_defusing)) continue;

		stance = [[level.ex_getStance]](false);
		jump = [[level.ex_getStance]](true);
		doit = false;

		switch(level.ex_stanceshoot)
		{
			case 1:
				if(stance == 2 && lastprone != 2) doit = true;
				break;
			case 2:
				if(jump == 3 && lastjump != 3) jumpcheck = true;
				break;
			default: {
				if(stance == 2 && lastprone != 2) doit = true;
					else if(jump == 3 && lastjump != 3) jumpcheck = true;
			}
		}

		if(jumpcheck)
		{
			jumpsensor++;
			if(jumpsensor > jumpsensitivity)
			{
				jumpsensor = 0;
				doit = true;
			}
			jumpcheck = false;
		}
		else jumpsensor = 0;

		lastprone = stance;
		if(jumpsensor == 0) lastjump = jump;

		if(doit) self thread extreme\_ex_utils::weaponPause(0.6);
	}
}

potatoMonitor()
{
	level endon("ex_gameover");
	
	while(!level.ex_gameover)
	{
		//Clean the "world" of dropped potatoes
		thread maps\mp\_utility::deletePlacedEntity("weapon_sprint_mp");
		thread maps\mp\_utility::deletePlacedEntity("weapon_dummy1_mp");
		thread maps\mp\_utility::deletePlacedEntity("weapon_dummy2_mp");
		thread maps\mp\_utility::deletePlacedEntity("weapon_dummy3_mp");
		thread maps\mp\_utility::deletePlacedEntity("weapon_dummy4_mp");
		wait( [[level.ex_fpstime]](0.25) );
	}
}

//******************************************************************************
// eXtreme+ anti-run punishments
//******************************************************************************
antirunPunish()
{
	if(!isdefined(self.antirun_punlevel))
		self.antirun_punlevel = 0;

	if(!isdefined(self.antirun_puninprog))
		self.antirun_puninprog = false;

	if(self.antirun_puninprog) return;

	self.antirun_puninprog = true;
	self iprintlnbold(&"SPRINT_RUNWARNINGA");
	self iprintlnbold(&"SPRINT_RUNWARNINGB");

	switch(self.antirun_punlevel)
	{
		case 0:
			self.antirun_punlevel++;
			self iprintlnbold(&"SPRINT_FIRST_PLAYER");
			iprintln(&"SPRINT_FIRST_ALL", [[level.ex_pname]](self));
			extreme\_ex_utils::forceto("crouch");
			self [[level.ex_dWeapon]]();
			self shellshock("default", 5);
			wait( [[level.ex_fpstime]](5) );
			self [[level.ex_eWeapon]]();
			break;

		case 1:
			self.antirun_punlevel++;
			self iprintlnbold(&"SPRINT_SECOND_PLAYER");
			iprintln(&"SPRINT_SECOND_ALL", [[level.ex_pname]](self));
			extreme\_ex_utils::forceto("crouch");
			self.health = int(self.health / 2);
			self [[level.ex_dWeapon]]();
			self shellshock("default", 10);
			wait( [[level.ex_fpstime]](10) );
			self [[level.ex_eWeapon]]();
			break;

		case 2:
			self.antirun_punlevel++;
			self iprintlnbold(&"SPRINT_THIRD_PLAYER");
			iprintln(&"SPRINT_THIRD_ALL", [[level.ex_pname]](self));
			extreme\_ex_utils::forceto("crouch");
			self thread extreme\_ex_punishments::doWarp(true);
			wait( [[level.ex_fpstime]](30) );
			break;

		case 3:
			self.antirun_punlevel = 0;
			self iprintlnbold(&"SPRINT_FOURTH_PLAYERA");
			extreme\_ex_utils::forceto("crouch");
			self [[level.ex_dWeapon]]();
			self shellshock("default", 5);
			wait( [[level.ex_fpstime]](5) );
			self iprintlnbold(&"SPRINT_FOURTH_PLAYERB");
			wait( [[level.ex_fpstime]](3) );
			iprintln(&"SPRINT_FOURTH_ALL", [[level.ex_pname]](self));
			kick(self getEntityNumber());
			break;
	}

	self.antirun_puninprog = false;
}

//******************************************************************************
// eXtreme+ health system
//******************************************************************************
healthBar()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");

	if(isDefined(self.ex_healthbar)) return;

	self.ex_healthcross = newClientHudElem(self);
	self.ex_healthcross.archived = true;
	self.ex_healthcross.horzAlign = "fullscreen";
	self.ex_healthcross.vertAlign = "fullscreen";
	self.ex_healthcross.alignX = "right";
	self.ex_healthcross.alignY = "top";
	self.ex_healthcross.x = 535; //543;
	self.ex_healthcross.y = 455;
	self.ex_healthcross setShader("gfx/hud/hud@health_cross.tga", 10, 10);

	self.ex_healthback = newClientHudElem(self);
	self.ex_healthback.archived = true;
	self.ex_healthback.horzAlign = "fullscreen";
	self.ex_healthback.vertAlign = "fullscreen";
	self.ex_healthback.alignX = "left";
	self.ex_healthback.alignY = "top";
	self.ex_healthback.x = 539; //547;
	self.ex_healthback.y = 455;
	self.ex_healthback setShader("gfx/hud/hud@health_back.tga", 90, 10);

	self.ex_healthbar = newClientHudElem(self);
	self.ex_healthbar.archived = true;
	self.ex_healthbar.horzAlign = "fullscreen";
	self.ex_healthbar.vertAlign = "fullscreen";
	self.ex_healthbar.alignX = "left";
	self.ex_healthbar.alignY = "top";
	self.ex_healthbar.x = 540; //548;
	self.ex_healthbar.y = 456;
	self.ex_healthbar.color = ( 0, 1, 0);
	self.ex_healthbar setShader("gfx/hud/hud@health_bar.tga", 88, 8);

	self thread healthBarUpdate();
}

healthBarUpdate()
{
	self endon("disconnect");
	self endon("ex_dead");

	oldhealth = self.health;
	width = undefined;

	for(;;)
	{
		wait( [[level.ex_fpstime]](0.1) );

		if(isPlayer(self) && isDefined(self.health) && self.health != oldhealth)
		{
			health = self.health / self.maxhealth;

			width = int(health * 88);

			if(width < 1) width = 1;

			if(isDefined(self.ex_healthbar))
			{
				self.ex_healthbar setShader("gfx/hud/hud@health_bar.tga", width, 8);
				self.ex_healthbar.color = ( 1.0 - health, health, 0);
			}

			oldhealth = self.health;
		}
	}
}

firstaidDrop()
{
	self endon("disconnect");
	
	health_nr = RandomInt(3) + 1;

	switch(health_nr)
	{
		case 1: modeltype = "xmodel/health_small"; break;
		case 2: modeltype = "xmodel/health_medium"; break;
		default: modeltype = "xmodel/health_large"; break;
	}

	item_health = spawn("script_model", (0,0,0));	
	item_health setModel(modeltype);
	item_health.targetname = "item_healths";
	item_health hide();
	item_health.origin = self.origin;
	item_health.angles = (0, randomint(360), 0);
	item_health show(); 

	rotation = (randomFloat(180), randomFloat(180), randomFloat(180));
	velocity = (randomInt(4) + 4, randomInt(4) + 4, randomInt(6) + 6);

	item_health extreme\_ex_utils::bounceObject(rotation, velocity, (0,0,0), (0,0,0), 5, 0.4, undefined, undefined, "health");
	item_health thread healthThink(health_nr);
}

healthThink(health_nr)
{
	while(isDefined(self))
	{
		wait( [[level.ex_fpstime]](0.2) );

		if(!isDefined(self)) return;

		player = "";
		players = getentarray("player", "classname");

		for(i = 0; i < players.size; i++)
		{
			player = players[i];

			if(isPlayer(player) && isDefined(self) && player.sessionstate == "playing" && distance(self.origin,player.origin) < 50 && (player.health < player.maxhealth || level.ex_medicsystem && level.ex_firstaid_collect && player.ex_firstaidkits < 9))
			{
				player endon("disconnect");

				if(player.health < player.maxhealth)
				{
					player.health += health_nr * 30;
					if(player.health > player.maxhealth) player.health = player.maxhealth;

					if(health_nr == 1) player playLocalSound("health_pickup_small");
					else if(health_nr == 2) player playLocalSound("health_pickup_medium");
					else player playLocalSound("health_pickup_large");

					if(isDefined(self)) self delete();
				}
				else if(level.ex_medicsystem && level.ex_firstaid_collect && player.ex_firstaidkits < level.ex_firstaid_collect)
				{
					player.ex_firstaidkits++;
					player playLocalSound("health_pickup_medium");
					player iprintln(&"FIRSTAID_PICKEDUP");
					player.ex_canheal = true;
					if(isDefined(player.ex_firstaidval))
					{
						player.ex_firstaidval setValue(player.ex_firstaidkits);
						player.ex_firstaidval.color = (1, 1, 1);
					}

					if(isDefined(self)) self delete();
				}
			}
		}				
	}
}

//************************************************************************************************************************************************************************
// eXtreme+ gametype additional routines
//************************************************************************************************************************************************************************
swapTeams()
{
	level endon("ex_gameover");

	if(game["roundsplayed"] == 0 || !game["matchstarted"]) return;

	if(level.ex_swapteams == 2 && game["roundnumber"] > level.half_time) return;

	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		// don't do anything with spectators
		if(!isDefined(players[i].pers["team"]) || players[i].pers["team"] == "spectator") continue;

		newTeam = undefined;

		if(players[i].pers["team"] == "axis") newTeam = "allies";
		else if(players[i].pers["team"] == "allies") newTeam = "axis";

		players[i].pers["team"] = newTeam;
		players[i].pers["savedmodel"] = undefined;
		players[i] thread extreme\_ex_clientcontrol::clearWeapons();
		//players[i] maps\mp\gametypes\_spectating::setSpectatePermissions();
	}

	tempscore = game["alliedscore"];
	game["alliedscore"] = game["axisscore"];
	game["axisscore"] = tempscore;
	setTeamScore("allies", game["alliedscore"]);
	setTeamScore("axis", game["axisscore"]);
}

exPreSpawn()
{
	level endon("ex_gameover");
	self endon("disconnect");

	// set spawn variables
	setPlayerVariables();

	// spawn protection pre-spawn settings
	if(level.ex_spwn_time)
	{
		self.ex_invulnerable = true;
		self.ex_spawnprotected = true;
		if(level.ex_spwn_invisible) self hide();
	}

	// allow team change option on weapons menu
	self setClientCvar("ui_allow_teamchange", 0);

	// start rank monitor
	if(level.ex_ranksystem) self thread extreme\_ex_ranksystem::playerRankMonitor();

	// hide mbots until they are completely ready
	if(level.ex_mbot && isDefined(self.isbot)) self hide();
}

exPostSpawn()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self notify("ex_spawned");
	// wait for threads to die
	wait( [[level.ex_fpstime]](0.01) );
	
	if(isDefined(self.ex_redirected)) return;

	if(isPlayer(self) && !level.ex_gameover)
	{
		// Attach head marker, used by Sprint System and LR Hitloc
		if(!isDefined(self.ex_headmarker))
		{
			self.ex_headmarker = spawn("script_origin",(0,0,0));
			//self.ex_headmarker linkto (self, "J_Head",(0,50,0),(0,0,0));
			self.ex_headmarker linkto(self, "J_Head",(0,0,0),(0,0,0));
		}
		// Attach spine marker, used by GetStance() and LR Hitloc
		if(!isDefined(self.ex_spinemarker))
		{
			self.ex_spinemarker = spawn("script_origin",(0,0,0));
			self.ex_spinemarker linkto(self, "J_Spine4",(0,0,0),(0,0,0));
		}
		// Attach eye marker, used by Range Finder and LR Hitloc
		if(!isDefined(self.ex_eyemarker))
		{
			self.ex_eyemarker = spawn("script_origin",(0,0,0));
			self.ex_eyemarker linkto(self, "tag_eye",(0,0,0),(0,0,0));
		}
		// Attach thumb marker, used by Knife and Unfixed Turrets
		if(!isDefined(self.ex_thumbmarker))
		{
			self.ex_thumbmarker = spawn("script_origin",(0,0,0));
			self.ex_thumbmarker linkto(self, "J_Thumb_ri_1",(0,0,0),(0,0,0));
		}

		if(level.ex_lrhitloc)
		{
			// Attach left ankle marker, used by LR Hitloc
			if(!isDefined(self.ex_lankmarker))
			{
				self.ex_lankmarker = spawn("script_origin",(0,0,0));
				self.ex_lankmarker linkto(self, "j_ankle_le",(0,0,0),(0,0,0));
			}
			// Attach right ankle marker, used by LR Hitloc
			if(!isDefined(self.ex_rankmarker))
			{
				self.ex_rankmarker = spawn("script_origin",(0,0,0));
				self.ex_rankmarker linkto(self, "j_ankle_ri",(0,0,0),(0,0,0));
			}
			// Attach left wrist marker, used by LR Hitloc
			if(!isDefined(self.ex_lwristmarker))
			{
				self.ex_lwristmarker = spawn("script_origin",(0,0,0));
				self.ex_lwristmarker linkto(self, "j_wrist_le",(0,0,0),(0,0,0));
			}
			// Attach right wrist marker, used by LR Hitloc
			if(!isDefined(self.ex_rwristmarker))
			{
				self.ex_rwristmarker = spawn("script_origin",(0,0,0));
				self.ex_rwristmarker linkto(self, "j_wrist_ri",(0,0,0),(0,0,0));
			}
		}

		if(level.ex_mbot)
		{
			self.mark = [];

			// keep tag_eye first, because it's being addressed as index [0] later on
			self.mark[0] = self.ex_eyemarker;
			//self.mark[0] = spawn("script_origin", (0,0,0));
			//self.mark[0] linkto(self, "tag_eye", (0,0,0),(0,0,0));

			if(level.ex_diana && isDefined(self.pers["diana"]))
			{
				self.mark[1] = spawn("script_origin", (0,0,0));
				self.mark[1] linkto(self, "j_spine2", (0,0,0),(0,0,0));
			}
			else
			{
				self.mark[1] = spawn("script_origin", (0,0,0));
				self.mark[1] linkto(self, "j_spine1", (0,0,0),(0,0,0));
			}

			self.mark[2] = spawn("script_origin", (0,0,0));
			self.mark[2] linkto(self, "j_shoulder_le", (0,0,0),(0,0,0));

			self.mark[3] = spawn("script_origin", (0,0,0));
			self.mark[3] linkto(self, "j_shoulder_ri", (0,0,0),(0,0,0));

			self.mark[4] = spawn("script_origin", (0,0,0));
			self.mark[4] linkto(self, "j_elbow_bulge_le", (0,0,0),(0,0,0));

			self.mark[5] = spawn("script_origin", (0,0,0));
			self.mark[5] linkto(self, "j_elbow_bulge_ri", (0,0,0),(0,0,0));

			/*
			self.mark[6] = spawn("script_origin", (0,0,0));
			self.mark[6] linkto(self, "j_hip_le", (0,0,0),(0,0,0));

			self.mark[7] = spawn("script_origin", (0,0,0));
			self.mark[7] linkto(self, "j_hip_ri", (0,0,0),(0,0,0));
			*/

			if(level.ex_mbot_dev && (self.name == level.ex_mbot_devname))
			{
				self thread extreme\_ex_mbot_dev::devDebug();
				self thread extreme\_ex_mbot_dev::devWaypoints();
				self thread extreme\_ex_mbot_dev::devMenu();
			}
		}

		self thread playerThreads();
	}
}

exPlayerDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime)
{
	self endon("disconnect");

	if(level.ex_readyup && !isDefined(game["readyup_done"])) return;

	if (!isDefined(vPoint)) vPoint = self.origin + (0,0,11);

	// gunship eject
	if(level.ex_gunship && iDamage > self.health && isPlayer(eAttacker) && eAttacker != self)
	{
		if(isDefined(level.ex_gunship_player) && level.ex_gunship_player == self && ((level.ex_gunship_eject & 2) == 2))
		{
			extreme\_ex_gunship::gunshipDetachPlayer(true);
			return;
		}
	}

	// napalm?
	napalm = false;
	if(sMeansOfDeath == "MOD_PROJECTILE" && sWeapon == "planebomb_mp") napalm = true;

	// disable or drop weapon after a fall, for a bit anyway
	if(level.ex_droponfall && sMeansOfDeath == "MOD_FALLING" && randomInt(100) < level.ex_droponfall)
		self thread weaponfall(1.5);

	// no damage if invulnerable
	if(self.ex_invulnerable)
	{
		// punish attacking player for attacking spawn protected players
		if(level.ex_spwn_punish_attacker && isPlayer(eAttacker) && eAttacker != self && !isDefined(self.ex_crybaby))
		{
			// exclude wmd, nades and satchel charges
			if(isDefined(sWeapon) && !(isWeaponType(sWeapon, "wmd") || isWeaponType(sWeapon, "fraggrenade") || isWeaponType(sWeapon, "firegrenade") || isWeaponType(sWeapon, "gasgrenade") || isWeaponType(sWeapon, "satchelcharge")))
			{
				punish = true;

				// spawn protection punishment threshold check
				if(level.ex_spwn_punish_threshold)
				{
				  eAttacker.ex_spwn_punish_counter += iDamage;
					if(eAttacker.ex_spwn_punish_counter < level.ex_spwn_punish_threshold) punish = false;
				}

				if(punish)
				{
					if(isDefined(eAttacker.onturret))
						eAttacker thread extreme\_ex_spawnpro::punish("turretattack");
					else
						eAttacker thread extreme\_ex_spawnpro::punish("attacking");
				}
			}
		}
		return;
	}

	// punish protected player for abusing spawn protection
	if(level.ex_spwn_punish_self && isPlayer(eAttacker) && eAttacker != self && eAttacker.ex_invulnerable && eAttacker.usedweapons)
	{
		eAttacker thread extreme\_ex_spawnpro::punish("abusing");
		return;
	}

	// figure out if extreme damage fx should be applied to the victim
	dodamagefx = false;
	if(!level.ex_teamplay) dodamagefx = true;

	if(isPlayer(eAttacker))
	{
		if(eAttacker == self) dodamagefx = true;
			else if(level.ex_teamplay && ((self.pers["team"] != eAttacker.pers["team"]) || level.friendlyfire == "1" || level.friendlyfire == "3")) dodamagefx = true;

		if(eAttacker != self)
		{
			// close kill detection
			if(level.ex_closekill)
			{
				range = int(distance(eAttacker.origin, self.origin));
				if(level.ex_closekill_units) calcdist = int(range * 0.0254); // Range in Metres
					else calcdist = int(range * 0.02778); // Range in Yards

				if(calcdist < level.ex_closekill_distance)
				{
					if(level.ex_closekill_msg)
					{
						if(level.ex_closekill_units)
						{
							eAttacker iprintlnBold(&"CLOSEKILL_RANGE_METRES", calcdist);
							eAttacker iprintlnBold(&"CLOSEKILL_MINRANGE_METRES", level.ex_closekill_range);
							if(level.ex_closekill_msg == 2) self iprintln(&"CLOSEKILL_PROTECTION");
						}
						else
						{
							eAttacker iprintlnBold(&"CLOSEKILL_RANGE_YARDS", calcdist);
							eAttacker iprintlnBold(&"CLOSEKILL_MINRANGE_YARDS", level.ex_closekill_range);
							if(level.ex_closekill_msg == 2) self iprintln(&"CLOSEKILL_PROTECTION");
						}
					}

					if(!isDefined(eAttacker.ckcount)) eAttacker.ckcount = 0;
					eAttacker.ckcount++;

					if(eAttacker.ckcount == 1)
						eAttacker shellshock("default", 5);
					else if(eAttacker.ckcount == 2)
						eAttacker shellshock("default", 10);
					else if(eAttacker.ckcount == 3)
					{
						eAttacker.ckcount = 0;
						eAttacker.ex_forcedsuicide = true;
						eAttacker suicide();
					}

					return;
				}
			}

			// firstaid disable if team mate
			if(level.ex_teamplay && level.ex_medicsystem && level.ex_medic_penalty && (self.pers["team"] == eAttacker.pers["team"]))
			{
				check_healing = true;
				if(level.ex_gunship && isDefined(level.ex_gunship_player) && level.ex_gunship_player == eAttacker) check_healing = false;
				if(check_healing) eAttacker thread extreme\_ex_firstaid::disablePlayerHealing();
			}
		}

		// Splatter on attacker?
		if(dodamagefx && level.ex_bloodonscreen && (sMeansOfDeath == "MOD_MELEE" || distance(eAttacker.origin , self.origin ) < 50))
			eAttacker thread bloodonscreen();

		// bulletholes?
		if(dodamagefx && level.ex_bulletholes && (sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET"))
			self thread extreme\_ex_bulletholes::bullethole();

		// Pain sound
		if(dodamagefx && level.ex_painsound)
		{
			if(napalm && randomInt(100) < 25) self thread extreme\_ex_utils::playSoundOnPlayer("generic_pain", "pain");
				else if(!napalm && randomInt(100) < 50) self thread extreme\_ex_utils::playSoundOnPlayer("generic_pain", "pain");
		}

		// Helmet pop
		if(level.ex_pophelmet && !self.ex_helmetpopped)
		{
			switch(sHitLoc)
			{
				case "helmet":
				case "head":
					if(randomInt(100)+1 <= level.ex_pophelmet)
					{
						self thread popHelmet(vDir, iDamage);
						if(dodamagefx) self thread bloodonscreen();
					}
					break;
			}
		}
	}
	else dodamagefx = true;

	// Damage modifiers, weapons
	if(level.ex_wdmodon && isDefined(sWeapon) && sMeansOfDeath != "MOD_MELEE")
	{
		wdmWeapon = sWeapon;
		if(isWeaponType(wdmWeapon, "fraggrenade") || isWeaponType(wdmWeapon, "fragspecial")) wdmWeapon = "fraggrenade";
			else if(isWeaponType(wdmWeapon, "smokegrenade") || isWeaponType(wdmWeapon, "smokespecial")) wdmWeapon = "smokegrenade";

		if(isDefined(level.ex_wdm[wdmWeapon])) iDamage = int((iDamage / 100) * level.ex_wdm[wdmWeapon]);
			else logprint("WDM: no record for weapon " + wdmWeapon + " in WDM array!\n");
	}

	iDamage = int(iDamage);

	if(isAlive(self))
	{	
		switch(sHitLoc)
		{
			case "right_hand":
			case "left_hand":
			case "gun":
				if(level.ex_droponhandhit && randomInt(100) < level.ex_droponhandhit) self thread extreme\_ex_weapons::dropcurrentweapon();
				break;
			
			case "right_arm_lower":
			case "left_arm_lower":
				if(level.ex_droponarmhit && randomInt(100) < level.ex_droponarmhit) self thread extreme\_ex_weapons::dropcurrentweapon();
				break;
	
			case "right_foot":
			case "left_foot":
				if(level.ex_triponfoothit && randomInt(100) < level.ex_triponfoothit) self thread spankme(1);
				break;

			case "right_leg_lower":
			case "left_leg_lower":
				if(level.ex_triponleghit && randomInt(100)<level.ex_triponleghit) self thread spankme(1);
				break;

			case "torso_lower":
				if(isDefined(self.tankonback) && (randomInt(100) < level.ex_ft_tank_explode))
				{
					if(dodamagefx)
					{
						level thread extreme\_ex_flamethrower::tankExplosion(self, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);
						return;
					}
				}
				break;
		}
	}

	if(dodamagefx && !napalm && level.ex_bleeding && (self.health - iDamage < level.ex_startbleed))
		self thread extreme\_ex_bleeding::doPlayerBleed(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);

	if(level.ex_mbot) self thread extreme\_ex_bots::playerDamage(eAttacker, iDamage);

	[[level.ex_callbackPlayerDamage]](eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);
}

exPlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc)
{
	self endon("disconnect");
	self notify("ex_dead");

	// kamikaze (suicide bombing)
	if(sMeansOfDeath == "MOD_SUICIDE" && level.ex_kamikaze && isDefined(attacker) && isPlayer(attacker))
	{
		// suicide bomber
		if(attacker == self && !isDefined(self.switching_teams) && !isDefined(self.ex_forcedsuicide) && sWeapon != "dummy1_mp" && isWeaponType(self.ex_lastoffhand, "suicidebomb"))
			self thread suicideBomb(attacker);
	}

	// drop health packs
	if(level.ex_firstaid_drop) self thread firstaidDrop();

	// clean the hud
	self extreme\_ex_hud::cleanplayer();

	// clean mbot marks
	if(level.ex_mbot) self extreme\_ex_bots::playerKilled();

 	// spawn protection punishment threshold reset
	if(level.ex_spwn_punish_attacker && level.ex_spwn_punish_threshold && level.ex_spwn_punish_threshold_reset)
	  self.ex_spwn_punish_counter = 0;

	// turret abuse check
	if(level.ex_turretabuse && (extreme\_ex_weapons::isWeaponType(sWeapon,"turret") || (sWeapon == "none" && sMeansOfDeath == "MOD_RIFLE_BULLET")) && isDefined(attacker))
	{
		wepname = maps\mp\gametypes\_weapons::getWeaponName(sWeapon);
		attacker.pers["turretkill"]++;
		
		if(attacker.pers["turretkill"] == level.ex_turretabuse_warn)
		{
			attacker iprintlnbold(&"TURRET_ABUSER_WARN_PMSG_0");
			attacker iprintlnbold(&"TURRET_ABUSER_WARN_PMSG_1");
		}
		else if(attacker.pers["turretkill"] >= level.ex_turretabuse_kill)
		{
			if(sWeapon == "none")
			{
				attacker iprintlnbold(&"TURRET_ABUSER_PMSG");
				iprintln(&"TURRET_ABUSER_OBITMSG_0", [[level.ex_pname]](attacker));
				iprintln(&"TURRET_ABUSER_OBITMSG_2");
			}
			else
			{
				attacker iprintlnbold(&"TURRET_ABUSERWEP_PMSG", wepname);
				iprintln(&"TURRET_ABUSER_OBITMSG_0", [[level.ex_pname]](attacker));
				iprintln(&"TURRET_ABUSER_OBITMSG_1", wepname);
			}

			wait( [[level.ex_fpstime]](2) );
			playfx(level.ex_effect["blowthefag"], attacker.origin);
			attacker playsound("mortar_explosion1");
			wait( [[level.ex_fpstime]](0.05) );
			attacker.pers["turretkill"] = 0;
			attacker.ex_forcedsuicide = true;
			attacker suicide();
		}
	}

	// Helmet pop
	if(level.ex_pophelmet && !self.ex_helmetpopped)
	{
		switch(sHitLoc)
		{
			case "helmet":
			case "head":
				if(randomInt(100)+1 <= level.ex_pophelmet)
				{
					self thread popHelmet(vDir, iDamage);
					//if(dodamagefx) self thread bloodonscreen();
				}
				break;
		}
	}

	// attacker taunt
	if(level.ex_taunts >= 2 && isPlayer(attacker))
	{
		if(level.ex_teamplay && attacker.pers["team"] != self.pers["team"]) attacker thread taunts(randomInt(10));
			else attacker thread taunts(4); // DM taunts set to Got one! and Got him!
	}

	// team kill check
	if(level.ex_sinbin && level.ex_teamplay && isPlayer(attacker) && attacker != self)
	{
		if(attacker.pers["team"] == self.pers["team"])
		{
			attacker.pers["teamkill"]++;
			if(attacker.pers["teamkill"] > level.ex_sinbinmaxtk)
			{
				attacker thread extreme\_ex_sinbin::main();
				attacker.pers["conseckill"] = 0;
			}
		}
	}
}

exEndMap()
{
	level.ex_gameover = true;
	level notify("ex_gameover");
	wait( [[level.ex_fpstime]](0.05) );

	// Disconnect bots
	disconnectBots();

	// end-of-game music (Tnic)
	if(level.ex_endmusic || level.ex_mvmusic || level.ex_statsmusic)
		level thread extreme\_ex_utils::playSoundOnPlayers("spec_music_null");

	// announce result
	if(isDefined(level.ex_resultsound)) level thread extreme\_ex_utils::playSoundOnPlayers(level.ex_resultsound);

	// set players to spectate mode
	players = getentarray("player", "classname");
	for(mx = 0; mx < players.size; mx++)
	{
		if(isPlayer(players[mx]))
		{
			players[mx] thread extreme\_ex_spawn::SpawnSpectator();
			if(level.ex_ranksystem) players[mx] thread setPlayerVariables();
			players[mx] thread extreme\_ex_hud::cleanplayerend();
		}
	}

	// clear hud elements
	level thread extreme\_ex_hud::cleanallhud();

	// play end music
	if(level.ex_endmusic) level thread extremeMusic();

	// launch statsboard
	if(level.ex_stbd) extreme\_ex_statsboard::main();

	// if playerbased map rotation is enabled and map voting is disabled change the rotation
	// now that the player size may have changed from the start of the game
	if(level.ex_pbrotate && !level.ex_mapvote) extreme\_ex_maprotation::pbRotation();

	// launch mapvote
	if(level.ex_mapvote) extreme\_ex_mapvote::main();
	
	// fade the end-of-game music during intermission
	level notify("endmusic");

	// stop any ambient effects
	level notify("ex_stop_ambient_fx");

	// save the number of players for map sizing in DRM
	players = getentarray("player", "classname");
	setCvar("drm_players", players.size);
}

disconnectBots()
{
	if(level.ex_mbot)
	{
		spectateBots();
		return;
	}

	bot_entities = [];
	players = getEntArray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		if(isPlayer(players[i]) && isDefined(players[i].pers["isbot"]))
		{
			bot_entity = players[i] getEntityNumber();
			bot_entities[bot_entities.size] = bot_entity;
			kick(bot_entity);
			wait( [[level.ex_fpstime]](0.1) );
		}
	}

	if(bot_entities.size)
	{
		maxclients = getCvarInt("sv_maxclients");
		entities = getEntArray();
		for(i = 0; i < maxclients; i++)
		{
			for(j = 0; j < bot_entities.size; j++)
			{
				if(i == bot_entities[j])
					entities[i] = undefined;
			}
		}
	}
}

spectateBots()
{
	players = getEntArray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		if(isPlayer(players[i]) && isDefined(players[i].isbot))
		{
			players[i].pers["mbot"] = true;
			players[i] thread extreme\_ex_bots::botJoin("spectator");
		}
	}
}

extremeMusic()
{
	// play random track
	musicplay("gom_music_" + (randomInt(10) + 1));

	// wait here till stats and mapvote are done
	level waittill("endmusic");
	
	// wait for intermission time minus music fade time
	wait( [[level.ex_fpstime]](level.ex_intermission - 5) );

	// fade music in last 5 seconds
	musicstop(5);
	wait( [[level.ex_fpstime]](5) );
}

//******************************************************************************
// eXtreme+ player additional routines
//******************************************************************************
suicideBomb(eAttacker)
{
	// sEffect "none" is OK, because the exploding nade/satchel charge will have effects
	if(isWeaponType(self.ex_lastoffhand, "satchelcharge"))
	{
		iRadius = level.ex_kamikaze_satchel_radius;
		iMaxDamage = level.ex_kamikaze_satchel_damage;
		sEffect = "none"; // sEffect = "satchel";
	}
	else
	{
		iRadius = level.ex_kamikaze_frag_radius;
		iMaxDamage = level.ex_kamikaze_frag_damage;
		sEffect = "none"; // sEffect = "generic";
	}

	if(isPlayer(eAttacker)) eAttacker.kamikaze_victims = undefined;
	explosion = spawn("script_origin", self.origin);
	explosion thread extreme\_ex_utils::scriptedfxradiusdamage(eAttacker, undefined, "MOD_GRENADE_SPLASH", self.ex_lastoffhand, iRadius, iMaxDamage, iMaxDamage, sEffect, undefined, false, true, true, "kamikaze");
	explosion delete();

	if(level.ex_reward_kamikaze && isDefined(eAttacker.kamikaze_victims))
	{
		wait( [[level.ex_fpstime]](0.05) );
		kamikaze_bonus = level.ex_reward_kamikaze * eAttacker.kamikaze_victims;
		//logprint("KAMIKAZE DEBUG: " + eAttacker.name + " received " + kamikaze_bonus + " bonus for killing " + eAttacker.kamikaze_victims + " players\n");
		eAttacker.score += kamikaze_bonus;
		eAttacker.pers["bonus"] += kamikaze_bonus;
		// added for arcade style HUD points
		eAttacker notify("update_playerscore_hud");
	}
}

roundDisplay()
{
	level endon("ex_gameover");
	self endon("disconnect");

	if(!isDefined(game["roundnumber"]) || !game["roundnumber"] || game["roundnumber"] == self.pers["roundshown"] || isDefined(self.ex_roundnumber)) return;

	self.pers["roundshown"] = game["roundnumber"];

	// display the round number once each round for roundbased games
	self.ex_roundnumber = newClientHudElem(self);
	self.ex_roundnumber.archived = false;
	self.ex_roundnumber.horzAlign = "fullscreen";
	self.ex_roundnumber.vertAlign = "fullscreen";
	self.ex_roundnumber.alignX = "center";
	self.ex_roundnumber.alignY = "middle";
	self.ex_roundnumber.x = 320;
	self.ex_roundnumber.y = 100;
	self.ex_roundnumber.alpha = 0;
	self.ex_roundnumber.fontscale = 2.4;

	if(isDefined(level.roundlimit)) rlimit = level.roundlimit;
		else rlimit = 0;

	if(rlimit == game["roundnumber"]) self.ex_roundnumber setText(&"MISC_LASTROUND");
	else
	{
		self.ex_roundnumber.label = &"MISC_ROUNDNUMBER";
		self.ex_roundnumber setValue(game["roundnumber"]);
	}

	self.ex_roundnumber fadeOverTime(2);
	self.ex_roundnumber.alpha = 1;

	wait( [[level.ex_fpstime]](7) );

	if(isPlayer(self))
	{
		if(isDefined(self.ex_roundnumber))
		{
			self.ex_roundnumber fadeOverTime(2);
			self.ex_roundnumber.alpha = 0;
		}
		
		wait( [[level.ex_fpstime]](2) );
			
		if(isDefined(self.ex_roundnumber)) self.ex_roundnumber destroy();
	}
}

setPlayerVariables()
{
	self thread resetFlagVars();

	// apply these stats if not defined already
	count = 1;
	for(;;)
	{
		stat = getPlayerVariable(count);
		if(stat == "") break;
		if(isPlayer(self) && !isDefined(self.pers[stat])) self.pers[stat] = 0;
		count++;
	}

	// spawn protection punishment threshold
	if(level.ex_spwn_punish_attacker && level.ex_spwn_punish_threshold && !isDefined(self.ex_spwn_punish_counter))
	  self.ex_spwn_punish_counter = 0;

	// reset streak variables
	self.pers["conseckill"] = 0;
	self.pers["conskillnumb"] = 0;
	self.pers["conskilltime"] = 0;
	self.pers["conskillprev"] = 0;
	self.pers["noobstreak"] = 0;
	self.pers["weaponstreak"] = 0;
	self.pers["weaponname"] = "";

	// reset turret abuse counter
	self.pers["turretkill"] = 0;

	// misc variables
	if(!isDefined(game[self.name])) game[self.name] = [];

	// clear the grenades
	if(isDefined(self.pers["fragtype"])) self setWeaponClipAmmo(self.pers["fragtype"], 0);
	if(isDefined(self.pers["smoketype"])) self setWeaponClipAmmo(self.pers["smoketype"], 0);
	if(isDefined(self.pers["enemy_fragtype"])) self setWeaponClipAmmo(self.pers["enemy_fragtype"], 0);
	if(isDefined(self.pers["enemy_smoketype"])) self setWeaponClipAmmo(self.pers["enemy_smoketype"], 0);	
}

resetPlayerVariables()
{
	self thread resetFlagVars();

	// reset the stats
	count = 1;
	for(;;)
	{
		stat = getPlayerVariable(count);
		if(stat == "") break;
		if(isPlayer(self)) self.pers[stat] = 0;
		count++;
	}

	// reset score and deaths
	self.score = 0;
	self.deaths = 0;

	// reset streak variables to 0
	self.pers["conseckill"] = 0;
	self.pers["noobstreak"] = 0;
	self.pers["weaponstreak"] = 0;
	self.pers["weaponname"] = "";

	// misc variables
	if(isDefined(game[self.name])) game[self.name] = [];

	// reset the player rank
	if(level.ex_ranksystem)
	{
		self.pers["special"] = 0;
		self.pers["rank"] = self.pers["preset_rank"];
		self.pers["newrank"] = self.pers["rank"];
	}

	// reset all weapons and firstaid
	self thread extreme\_ex_weapons::replenishWeapons(true);
	self thread extreme\_ex_weapons::replenishGrenades(true);
	self thread extreme\_ex_weapons::replenishFirstaid(true);	
}

resetFlagVars()
{
	// stop binocular weapons
	self notify("binocular_exit");

	// stop mortars
	self.ex_mortar_strike = false;
	self notify("mortar_over");
	self notify("end_mortar");

	// stop artillery
	self.ex_artillery_strike = false;
	self notify("artillery_over");
	self notify("end_artillery");

	// stop airstrikes
	self.ex_air_strike = false;
	self notify("airstrike_over");
	self notify("end_airstrike");

	// stop gunship
	self.ex_gunship = false;
	self.ex_gunship_ejected = false;
	self.ex_gunship_kills = 0;
	self notify("gunship_over");
	self notify("end_gunship");

	// eXtreme+
	self.ex_disabledWeapon = 0;
	self.ex_iscamper = false;
	self.ex_isonfire = undefined;
	self.ex_puked = undefined;
	if(!isDefined(self.ex_isunknown)) self.ex_isunknown = false;
	if(!isDefined(self.ex_isdupname)) self.ex_isdupname = false;
	self.ex_ispunished = false;
	self.ex_hasnoweapon = false;
	self.ex_sinbin = false;
	self.ex_oldweapon = undefined;
	self.ex_invulnerable = false;
	self.ex_ishealing = undefined;
	self.ex_helmetpopped = false;
	self.ex_sprinttime = 0;
	self.ex_playsprint = false;
	self.ex_sprintreco = false;
	self.ex_sprinting = false;
	self.ex_binocuse = false;
	self.ex_warningwire = undefined;
	self.ex_plantwire = false;
	self.ex_defusewire = false;
	self.ex_stopwepmon = false;
	self.ex_bleeding = false;
	self.ex_bsoundinit = false;
	self.ex_bshockinit = false;
	self.ex_pace = false;
	self.ex_checkingwmd = undefined;
	self.ex_spwn_punish = undefined;
	self.ex_firstaidkits = 0;
	self.ex_inmenu = false;
	self.ex_isparachuting = undefined;
	self.handling_mine = false;

	// some maps have drowning. Make sure we reset it on death
	self.drowning = undefined;

	// stock
	self.usedweapons = false;
	self.spamdelay = undefined;
}

taunts(tauntno)
{
	self endon("disconnect");

	chance = randomInt(20);

	if(chance == 10)
	{
		// delay for death sound to finish
		wait( [[level.ex_fpstime]](1.5) );

		// if the attacker is still here, play the sound now
		switch(randomInt(2))
		{
			case 1: { if(isPlayer(self)) self thread maps\mp\gametypes\_quickmessages::quicktaunts(tauntno, true); break; }
			default: { if(isPlayer(self)) self thread maps\mp\gametypes\_quickmessages::quicktauntsb(tauntno, true); break; }
		}
	}
}

popHelmet(damageDir, damage)
{
	self.ex_helmetpopped = true;

	if(!isDefined(self.hatModel) || isDefined(self.ex_newmodel)) return;

	// make sure the helmet is still there
	helmet_attached = false;
	attachedSize = self getAttachSize();
	for(i = 0; i < attachedSize; i++)
	{
		attachedModel = self getAttachModelName(i);
		if(attachedModel == self.hatModel) helmet_attached = true;
	}
	if(!helmet_attached) return;

	self detach(self.hatModel , "");

	self.ex_stance = [[level.ex_getStance]](false);

	if(isPlayer(self))
	{
		switch(self.ex_stance)
		{
			case 2: helmetoffset = (0,0,15);	break;
			case 1: helmetoffset = (0,0,44);	break;
			default: helmetoffset = (0,0,64);	break;
		}
	}
	else helmetoffset = (0,0,15);

	switch(self.hatModel)
	{
		case "xmodel/helmet_russian_trench_a_hat":
		case "xmodel/helmet_russian_trench_b_hat":
		case "xmodel/helmet_russian_trench_c_hat":
		case "xmodel/helmet_russian_padded_a":
			bounce = 0.2;
			impactsound = undefined;
			break;
		default:
			bounce = 0.7;
			impactsound = "helmet_bounce_";
			break;
	}		

	rotation = (randomFloat(360), randomFloat(360), randomFloat(360));
	offset = (0,0,3);
	radius = 6;
	velocity = maps\mp\_utility::vectorScale(damageDir, (damage/20 + randomFloat(5)) ) + (0,0,(damage/20 + randomFloat(5)));

	helmet = spawn("script_model", self.origin + helmetoffset );
	helmet setmodel( self.hatModel );
	helmet.angles = self.angles;
	helmet.targetname = "poppedhelmet";
	helmet thread extreme\_ex_utils::bounceObject(rotation, velocity, offset, (0,0,0), radius, bounce, impactsound, undefined, "helmet");
}

handleDeadBody(team, owner)
{
	//Give the body a model
	self setModel(owner.model);

	// sink body in to the ground
	switch(level.ex_deadbodyfx)
	{
		case 1: self thread bodySink(); break;
		case 2: self thread bodyRise(); break;
	}
}

bodySink()
{
	wait( [[level.ex_fpstime]](15) );
	
	for(i = 0; i < 100; i++)
	{
		if(!isDefined(self)) return;
		self.origin = self.origin - (0,0,0.2);
		wait( [[level.ex_fpstime]](0.05) );
	}
	if(isdefined(self)) self delete();
}

bodyRise()
{
	wait( [[level.ex_fpstime]](15) );

	for(i = 0; i < 150; i++)
	{
		if(!isDefined(self)) return;
		self.origin = self.origin + (0,0,0.2);
		wait( [[level.ex_fpstime]](0.05) );
	}
	if(isdefined(self)) self delete();
}

bloodonscreen()
{
	level endon("ex_gameover");
	self endon("ex_dead");

	if(!isDefined(self.ex_bloodonscreen))
	{
		self.ex_bloodonscreen = newClientHudElem(self);
		self.ex_bloodonscreen.archived = false;
		self.ex_bloodonscreen.horzAlign = "fullscreen";
		self.ex_bloodonscreen.vertAlign = "fullscreen";
		self.ex_bloodonscreen.alignX = "left";
		self.ex_bloodonscreen.alignY = "top";
		self.ex_bloodonscreen.x = randomint(496);
		self.ex_bloodonscreen.y = randomint(336);
		self.ex_bloodonscreen.color = (1,1,1);
		self.ex_bloodonscreen.alpha = 1;

		self.ex_bloodonscreen1 = newClientHudElem(self);
		self.ex_bloodonscreen1.archived = false;
		self.ex_bloodonscreen1.horzAlign = "fullscreen";
		self.ex_bloodonscreen1.vertAlign = "fullscreen";
		self.ex_bloodonscreen1.alignX = "left";
		self.ex_bloodonscreen1.alignY = "top";
		self.ex_bloodonscreen1.x = randomint(496);
		self.ex_bloodonscreen1.y = randomint(336);
		self.ex_bloodonscreen1.color = (1,1,1);
		self.ex_bloodonscreen1.alpha = 1;

		self.ex_bloodonscreen2 = newClientHudElem(self);
		self.ex_bloodonscreen2.archived = false;
		self.ex_bloodonscreen2.horzAlign = "fullscreen";
		self.ex_bloodonscreen2.vertAlign = "fullscreen";
		self.ex_bloodonscreen2.alignX = "left";
		self.ex_bloodonscreen2.alignY = "top";
		self.ex_bloodonscreen2.x = randomint(496);
		self.ex_bloodonscreen2.y = randomint(336);
		self.ex_bloodonscreen2.color = (1,1,1);
		self.ex_bloodonscreen2.alpha = 1;

		self.ex_bloodonscreen3 = newClientHudElem(self);
		self.ex_bloodonscreen3.archived = false;
		self.ex_bloodonscreen3.horzAlign = "fullscreen";
		self.ex_bloodonscreen3.vertAlign = "fullscreen";
		self.ex_bloodonscreen3.alignX = "left";
		self.ex_bloodonscreen3.alignY = "top";
		self.ex_bloodonscreen3.x = randomint(496);
		self.ex_bloodonscreen3.y = randomint(336);
		self.ex_bloodonscreen3.color = (1,1,1);
		self.ex_bloodonscreen3.alpha = 1;

		bs = randomint(48);
		bs1 = randomint(48);
		bs2 = randomint(48);
		bs3 = randomint(48);

		self.ex_bloodonscreen SetShader("gfx/impact/flesh_hit2",96 + bs , 96 + bs);
		self.ex_bloodonscreen1 SetShader("gfx/impact/flesh_hitgib",96 + bs1 , 96 + bs1);
		self.ex_bloodonscreen2 SetShader("gfx/impact/flesh_hit2",96 + bs2 , 96 + bs2);
		self.ex_bloodonscreen3 SetShader("gfx/impact/flesh_hitgib",96 + bs3 , 96 + bs3);

		wait( [[level.ex_fpstime]](4) );

		if(!isDefined(self.ex_bloodonscreen)) return;

		self.ex_bloodonscreen fadeOverTime(2);
		self.ex_bloodonscreen.alpha = 0;
		self.ex_bloodonscreen1 fadeOverTime(2);
		self.ex_bloodonscreen1.alpha = 0;
		self.ex_bloodonscreen2 fadeOverTime(2);
		self.ex_bloodonscreen2.alpha = 0;
		self.ex_bloodonscreen3 fadeOverTime(2);
		self.ex_bloodonscreen3.alpha = 0;

		wait( [[level.ex_fpstime]](2) );

		if(!isDefined(self.ex_bloodonscreen)) return;

		if(isDefined(self.ex_bloodonscreen3)) self.ex_bloodonscreen3 destroy();
		if(isDefined(self.ex_bloodonscreen2)) self.ex_bloodonscreen2 destroy();
		if(isDefined(self.ex_bloodonscreen1)) self.ex_bloodonscreen1 destroy();
		if(isDefined(self.ex_bloodonscreen)) self.ex_bloodonscreen destroy();
	}
}

distortPlayerView()
{
	level endon("ex_gameover");
	level endon("intermission");
	self endon("disconnect");
	self endon("ex_spawned");
	self endon("ex_dead");

	horiz[1] = .26;
	horiz[2] = .26;
	horiz[3] = .25;
	horiz[4] = .25;
	horiz[5] = .25;
	horiz[6] = .25;
	horiz[7] = .25;
	horiz[8] = .25;
	horiz[9] = .25;
	horiz[10] = .25;
	horiz[11] = .25;
	horiz[12] = .15;
	horiz[13] = .13;
	vert[1] = 0.0;
	vert[2] = 0.025;
	vert[3] = 0.036;
	vert[4] = 0.037;
	vert[5] = 0.053;
	vert[6] = 0.072;
	vert[7] = 0.080;
	vert[8] = 0.100;
	vert[9] = 0.11;
	vert[10] = 0.15;
	vert[11] = 0.244;
	vert[12] = 0.238;
	vert[13] = 0.085;
	
	wait( [[level.ex_fpstime]](2) );

	i = 1;
	idir = 0;
	pshift = 0;
	yshift = 0;

	if(isPlayer(self))
	{
		for(;;)
		{
			VMag = self.VaxisMag;
			YMag = self.YaxisMag;

			if(i >= 1 && i <= 13)
			{
				pShift = horiz[i]*VMag;
				yShift = (0 - vert[i])*YMag;
			}
			else if(i >= 14 && i <= 26)
			{
				j = 14 - (i -13);
				pShift = (0 - horiz[j])*VMag;
				yShift = (0 - vert[j])*YMag;
			}
			else if(i >= 27 && i <= 39)
			{
				pShift = (0-horiz[i-26])*VMag;
				yShift = (vert[i-26])*YMag;
			}
			else if(i >= 40 && i <= 52)
			{
				j = 14 - (i -39);
				pShift = (horiz[j])*VMag;
				yShift = (vert[j])*YMag;
			}

			angles = self getplayerangles();
			self setPlayerAngles(angles + (pShift, yShift, 0));

			if(randomInt(50) == 0)
			{
				if(idir == 0) idir = 1;
				else idir = 0;
				i = i + 26;
			}

			if(idir == 0) i++;
			if(idir == 1) i--;
			if( i > 52) i = i - 52;
			if( i < 0) i = 52 - i; 
			wait( [[level.ex_fpstime]](0.05) );
		}
	}
}

weaponfall(delay)
{
	self endon("disconnect");
	self endon("ex_spawned");
	self endon("ex_dead");

	// good strong healthy boy can hold weapon!
	if(self.health > 80) return;
	else if(self.health > 50 && self.health < 80)
	{
		if(randomInt(100) < 50)
		{
			if(isPlayer(self)) self [[level.ex_dWeapon]]();
			wait( [[level.ex_fpstime]](delay) );
			if(isPlayer(self) && self.sessionstate == "playing") self [[level.ex_eWeapon]]();
		}
		else self thread extreme\_ex_weapons::dropcurrentweapon();
	}
	else self thread extreme\_ex_weapons::dropcurrentweapon();
}

spankme(time)
{
	level endon("ex_gameover");
	self notify("ex_spankme");
	self endon("ex_spankme");
	self endon("ex_spawned");
	self endon("ex_dead");

	for(i=0;i<(time*5);i++)
	{
		if(isPlayer(self))
		{
			self extreme\_ex_utils::forceto("prone");
			self thread extreme\_ex_weapons::dropcurrentweapon();
		}

		wait( [[level.ex_fpstime]](0.2) );
	}
}

handleWelcome()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");
	self endon("ex_freefall");

	if(isPlayer(self))
	{
		// Resetting the tag ex_ispunished is done in resetFlagVars()

		// Did the Name Checker already tag the player for using an unacceptable name?
		if(isDefined(self.ex_isunknown) && self.ex_isunknown)
		{
			self thread handleUnknown(false);
		}
		else
		{
			// Did the Name Checker already tag the player for using a duplicate name?
			if(isDefined(self.ex_isdupname) && self.ex_isdupname)
			{
				self thread handleDupName();
			}
			else
			{
				// If Name Checker is disabled and Unknown Soldier handling is enabled,
				// check for unacceptable names ourselves
				if (level.ex_uscheck && isUnknown(self))
				{
					self thread handleUnknown(false);
				}
				else self thread extreme\_ex_messages::welcomemsg();
			}
		}
	}
}

handleDupName()
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");
	self endon("ex_freefall");

	// Tag the player to prevent the Name Checker to kick in more than once
	self.ex_isdupname = true;

	self iprintlnbold(&"NAMECHECK_DNCHECK_DUPNAME1", [[level.ex_pname]](self));
	self setClientCvar("name", "Unknown Soldier");
	self iprintlnbold(&"NAMECHECK_DNCHECK_NEWUNKNOWN");

	if(level.ex_ncskipwarning)
	{
		if(level.ex_usclanguest) self iprintlnbold(&"NAMECHECK_DNCHECK_NEXTCLANGUEST");
			else self iprintlnbold(&"NAMECHECK_DNCHECK_NEXTGUEST");
	}
	else self iprintlnbold(&"NAMECHECK_DNCHECK_NEXTUNKNOWN");

	// Wait several seconds before starting the Unknown Soldier handling code
	wait( [[level.ex_fpstime]](10) );
	if(isPlayer(self))
	{
		self thread handleUnknown(level.ex_ncskipwarning);

		// Remove the tag; the player is officially an Unknown Soldier now
		self.ex_isdupname = false;
	}
}

handleUnknown(skipwarning)
{
	level endon("ex_gameover");
	self endon("disconnect");
	self endon("ex_dead");
	self endon("ex_freefall");

	// Tag the player to prevent the Name Checker to kick in more than once
	self.ex_isunknown = true;

	usname = [];

	if(!skipwarning)
	{
		if(isPlayer(self))
		{
			// Warn them first
			if(level.ex_usclanguest)
			{
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_UNACCEPTABLE", [[level.ex_pname]](self));
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_CHANGEIT");
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_CLANGUEST", level.ex_uswarndelay1);
			}
			else
			{
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_UNACCEPTABLE", [[level.ex_pname]](self));
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_CHANGEIT");
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_GUEST", level.ex_uswarndelay1);
			}
		}
		// Now give them some time to change their name
		waitWhileUnknown(level.ex_uswarndelay1);
	}

	if(isPlayer(self) && isUnknown(self))
	{
		// Get a free guest number (1 to sv_maxclients)
		level.ex_usguestno = getFreeGuestSlot();

		if(level.ex_usclanguest)
		{
			usname = level.ex_usclanguestname + level.ex_usguestno; // Clan Guest
			self setClientCvar("name", usname);
			wait( [[level.ex_fpstime]](1) );
			if(isPlayer(self))
			{
				self iprintlnbold(&"UNKNOWNSOLDIER_NEWNAME_BYSERVER");
				self iprintlnbold(&"UNKNOWNSOLDIER_NEWNAME_CLANGUEST", [[level.ex_pname]](self));
			}

			// Clan guests are now off the hook; show welcome messages and return
			self.ex_isunknown = false;
			wait( [[level.ex_fpstime]](3) );
			if(isPlayer(self)) self thread extreme\_ex_messages::welcomemsg();
			return;
		}
		else
		{
			// Only assign guest name if not already using an assigned guest name
			if(!isAssignedName(self))
			{
				usname = level.ex_usguestname + level.ex_usguestno; // Non-clan Guest
				self setClientCvar("name", usname);
				wait( [[level.ex_fpstime]](1) );
				if(isPlayer(self))
				{
					self iprintlnbold(&"UNKNOWNSOLDIER_NEWNAME_BYSERVER");
					self iprintlnbold(&"UNKNOWNSOLDIER_NEWNAME_GUEST", [[level.ex_pname]](self));
					self iprintlnbold(&"UNKNOWNSOLDIER_NEWNAME_CHANGEIT", level.ex_uswarndelay2);
				}

				// After name assignment, non-clan guests get a second chance to change their name
				waitWhileUnknown(level.ex_uswarndelay2);
			}
		}
	}

	if(isPlayer(self) && isUnknown(self))
	{
		// My god, don't they understand? ok, time for punishment!
		count = 0;
		while(isPlayer(self) && isUnknown(self) && count < level.ex_uspunishcount)
		{
			if(!isDefined(self.ex_sinbin) || !self.ex_sinbin)
			{
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_TEMPORARY", [[level.ex_pname]](self));
				self iprintlnbold(&"UNKNOWNSOLDIER_MSG_CHANGEIT");
				self iprintlnbold(&"UNKNOWNSOLDIER_STILL_PUNISH");
				self thread extreme\_ex_utils::punishment("drop", "freeze");
				waitWhileUnknown(10);
				if(isPlayer(self)) self thread extreme\_ex_utils::punishment("enable", "release");
				waitWhileUnknown(20 + randomInt(20));
				count++;
			}
			else break;
		}

		// Now, if still using assigned name, allow them to play without punishment until they die
		if(isPlayer(self) && isAssignedName(self))
		{
			// Set punished-tag so Name Checker doesn't kick in again
			self.ex_ispunished = true;
			self iprintlnbold(&"UNKNOWNSOLDIER_STILL_RELIEF1");
			self iprintlnbold(&"UNKNOWNSOLDIER_STILL_RELIEF2");
			self iprintlnbold(&"UNKNOWNSOLDIER_MSG_CHANGEIT");
		}
	}

	// Allow the Name Checker to iterate once to catch duplicate names.
	// Keep this wait statement outside the following if-block to catch players
	// that would otherwise fall through by quickly changing their name from US
	// to a valid name and back to US again (highly unlikely, but possible with key bindings)
	wait( [[level.ex_fpstime]](5) );

	if(isPlayer(self) && !self.ex_ispunished && !isUnknown(self))
	{
		// Has the Name Checker tagged him because of using a duplicate name?
		if(isPlayer(self) && !self.ex_isdupname)
		{
			// No, so thank them, and show the welcome messages
			self iprintlnbold(&"UNKNOWNSOLDIER_MSG_THANKS", [[level.ex_pname]](self));
			wait( [[level.ex_fpstime]](3) );
			if(isPlayer(self)) self thread extreme\_ex_messages::welcomemsg();
		}
		else self thread handleDupName();
	}

	// Remove the tag; the player is either renamed, punished or dupname-tagged
	self.ex_isunknown = false;
}

waitWhileUnknown(seconds)
{
	// Wait for x seconds as long as player has unacceptable name
	for(i = 0; i < seconds; i++)
	{
		if(isPlayer(self) && !isUnknown(self)) return;
			else wait( [[level.ex_fpstime]](1) );
	}
}

isUnknownSoldier(player)
{
	// Check if player is Unknown Soldier
	// Color codes are removed. Name is lowercased, so it will reject any case combination

	self endon("disconnect");
	self endon("ex_dead");

	playernorm = "";
	if(isPlayer(player)) playernorm = extreme\_ex_utils::monotone(player.name);
	playernorm = extreme\_ex_utils::lowercase(playernorm);

	if(playernorm == "" || playernorm == "unknown soldier" || playernorm == "unknownsoldier") return true;
	return false;
}

isAssignedName(player)
{
	// Check if player has an assigned guest name
	// Do NOT check for assigned clan guest names!

	self endon("disconnect");
	self endon("ex_dead");

	maxguestno = getCvarInt("sv_maxclients");

	for(i = 1; i <= maxguestno; i++)
	{
		chkname = level.ex_usguestname + i;
		if(player.name == chkname) return true;
	}
	return false;
}

isUnknown(player)
{
	// Check if player has unacceptable name

	self endon("disconnect");
	self endon("ex_dead");
	
	if(isUnknownSoldier(player)) return true;
	if(isAssignedName(player)) return true;
	return false;
}

getFreeGuestSlot()
{
	// Get a free guest number.

	self endon("disconnect");
	self endon("ex_dead");

	maxguestno = getCvarInt("sv_maxclients");
	players = getentarray("player", "classname");
	maxplayers = players.size;

	if(level.ex_usclanguest) usname = level.ex_usclanguestname;
		else usname = level.ex_usguestname;

	i = 1;
	while(i <= maxguestno)
	{
		chkname = usname + i;
		found = false;
		for(j = 0; j < maxplayers; j++)
		{
			if(players[j].name == chkname)
			{
				found = true;
				break;
			}
		}
		if(found) i++;
			else break;
	}
	return i;
}

rangeFinder()
{
	self endon("disconnect");
	self endon("death");
	self endon("ex_dead");

	while(isAlive(self))
	{
		wait( [[level.ex_fpstime]](0.5) );

		while(isAlive(self) && self.ex_binocuse || (self playerads() && extreme\_ex_weapons::isWeaponType(self getcurrentweapon(),"sniper")))
		{
			wait( [[level.ex_fpstime]](0.2) );

			startOrigin = self.ex_eyemarker.origin;
			forward = anglesToForward(self getplayerangles());
			forward = [[level.ex_vectorscale]](forward, 100000);
			endOrigin = startOrigin + forward;

			rangedist = undefined;
			trace = bulletTrace(startOrigin, endOrigin, true, self);
			range = int(distance(startOrigin, trace["position"]));

			if(level.ex_rfrange == 1) rangedist = int(range * 0.02778); // Range in Yards
				else rangedist = int(range * 0.0254);	// Range in Metres

			if(!isDefined(self.ex_rangehud))
			{
				self.ex_rangehud = newClientHudElem(self);
				self.ex_rangehud.archived = false;
				self.ex_rangehud.horzAlign = "fullscreen";
				self.ex_rangehud.vertAlign = "fullscreen";
				self.ex_rangehud.alignx = "center";
				self.ex_rangehud.aligny = "middle";
				self.ex_rangehud.x = 320;
				self.ex_rangehud.y = 360;
				self.ex_rangehud.alpha =0.8;
				self.ex_rangehud.fontScale = 1;
			}

			if(level.ex_rfrange == 1)
			{
				self.ex_rangehud.label = &"MISC_RANGE";
				self.ex_rangehud setvalue(rangedist);
			}
			else
			{
				self.ex_rangehud.label = &"MISC_RANGE2";
				self.ex_rangehud setvalue(rangedist);
			}
		}

		if(isDefined(self.ex_rangehud)) self.ex_rangehud destroy();
	}
}

getPlayerVariable(stat)
{
	switch(stat)
	{
		// kills
		case 1:  return "kill";
		case 2:  return "grenadekill";
		case 3:  return "tripwirekill";
		case 4:  return "headshotkill";
		case 5:  return "bashkill";
		case 6:  return "sniperkill";
		case 7:  return "knifekill";
		case 8:  return "mortarkill";
		case 9:  return "artillerykill";
		case 10: return "airstrikekill";
		case 11: return "napalmkill";
		case 12: return "panzerkill";
		case 13: return "spawnkill";
		case 14: return "spamkill";
		case 15: return "teamkill";
		case 16: return "flamethrowerkill";
		case 17: return "landminekill";
		case 18: return "firenadekill";
		case 19: return "gasnadekill";
		case 20: return "satchelchargekill";
		case 21: return "gunshipkill";

		// deaths
		case 22: return "death";
		case 23: return "grenadedeath";
		case 24: return "tripwiredeath";
		case 25: return "headshotdeath";
		case 26: return "bashdeath";
		case 27: return "sniperdeath";
		case 28: return "knifedeath";
		case 29: return "mortardeath";
		case 30: return "artillerydeath";
		case 31: return "airstrikedeath";
		case 32: return "napalmdeath";
		case 33: return "panzerdeath";
		case 34: return "spawndeath";
		case 35: return "planedeath";
		case 36: return "flamethrowerdeath";
		case 37: return "fallingdeath";
		case 38: return "minefielddeath";
		case 39: return "suicide";
		case 40: return "landminedeath";
		case 41: return "firenadedeath";
		case 42: return "gasnadedeath";
		case 43: return "satchelchargedeath";
		case 44: return "gunshipdeath";

		// other
		case 45: return "turretkill";
		case 46: return "noobstreak";
		case 47: return "conseckill";
		case 48: return "weaponstreak";
		case 49: return "roundshown";
		case 50: return "longdist";
		case 51: return "longhead";
		case 52: return "longspree";
		case 53: return "flagcap";
		case 54: return "flagret";
		case 55: return "bonus";

		// empty signals end
		default: return "";
	}
}
