//==================================================================//
//============<      #BATTLE GAMEMODE BY #OCTOTHORP     >===========//
//============<                   v1.1                  >===========//
//==================================================================//
//=====================< OCTOBATTLE - settings >====================//
//=============================<general>============================//
private const bool SHOW_SETTINGS = true;			    			// set to "true" to announce settings in the beginning
private const bool GIB_ON_LEAVE = true;				    			// set to "true" to gib players when they leave
//==========================<hardcore mode>=========================//
private const bool HARDCORE_ACTIVATED = true;			    		// set to "true" to enable hardcore mode
private const float HC_GUNS_MULTIPLIER = (float) 2.0;		    	// damage multiplier for guns
private const float HC_MELEE_MULTIPLIER = (float) 2.0;		   		// damage multiplier for melee
private const float HC_FALL_MULTIPLIER = (float) 3.5;		    	// damage multiplier for fall
private const float HC_FIRE_MULTIPLIER = (float) 3.0;		    	// damage multiplier for fire
private const float HC_EXPL_MULTIPLIER = (float) 2.0;		    	// damage multiplier for explosions
private const bool HC_BLOCK_DAMAGE = true;			    			// set to "true" to make blocked attacks damage players
private const int HC_BLOCK_DAMAGE_AMOUNT = 3;		    			// damage in HP given by blocked attack
private const bool HC_BLEEDING = true;				    			// set te "true" to enable bleeding on getting some damage
private const bool HC_BLEEDING_ON_START = false;		    		// set te "true" to make people bleed in the very beginning
private const int HC_BLEEDING_CHANCE = 3;			    			// bleeding will start with chance "1/x" where x is this variable
private const int HC_BLEEDING_TRIGGER_HP = 20;	    				// damage in HP after that bleeding will start find a chance
private const bool HC_CANT_PUT_FIRE_OFF = false;		    		// set to "true" to disallow players to roll to stop burning
//private const bool HC_WEAPONS_BREAK = false;			    		// set to "true" to allow weapons to break [WIP]
//========================<revive system>===========================//
private const bool REVIVE_ACTIVATED = true;			    			// set to "true" to activate reviving system
private const bool REVIVE_TEAMMATES_FASTER = true;		    		// set to "true" to revive teammates faster
private const int REVIVE_DURATION = 8;				    			// time in seconds needed to stay at dead player to revive him
private const int REVIVE_TIME_ALIVE = 25;			    			// time in seconds for recently died player to be revived
private const bool REVIVE_CAPTURE_ENEMIES = true;		    		// set to "true" to allow reviving enemies and making them allies
private const bool REVIVE_BLEEDING = true;			    			// set to "true" to make players bleed before death
private const bool REVIVE_FINISH_PLAYERS = true;		    		// set to "true" to allow players finish people
private const int REVIVE_FINISH_HEALTH = 20;			    		// health of unfinished player
private const bool REVIVE_KILL_FALLEN_TO_GAMEOVER = true;			// set to "true" to end round only when all players are completely dead
//========================<instagib mode>===========================//
private const bool INSTAGIB_ACTIVATED = true;			    		// set to "true" to activate instagib mode
private const bool INSTAGIB_RANDOM = true;			    			// set to "true" to make instagib randomly appear with selected chance
private const int INSTAGIB_DELAY = 4;			    				// time after that players would be given a weapon
private const int INSTAGIB_CHANCE = 10;				    			// instagib will start with chance "1/x" where x is this variable
private const bool INSTAGIB_MELEE = false;			    			// set to "true" to set melee weapons only
private const bool INSTAGIB_EXPLODE = true;			    			// set to "true" explode players on death
private const bool INSTAGIB_GIVE_SLOMO = true;			    		// set to "true" to give players slow motion at start
//=========================<commentator>============================//
private const bool COMMENT_ACTIVATED = false;			    		// set to "true" to make commenter speak through pop-ups
private const int COMMENT_TYPE_SPEED = 50;			    			// number of leters per seconds when the commenter speaks
//=========================<time limits>============================//
private const bool TIME_LIMIT_ACTIVATED = true;		    			// set to "true" to activate time limit
private const int TIME_LIMIT_SECONDS = 1 * 60;			    		// choose the amount of time given to players to finish the round
private const string TIME_LIMIT_MODE = "auto";			    		// set to "main": start timer at beginning; "auto": when 2 teams or 2 players left
//===========================<hosting>==============================//
private const bool HOSTING_ACTIVATED = false;			    		// set to "true" to remove (or explode) host player at start
private const bool HOSTING_TIME_BOMB = false;			    		// set to "true" to set host on timer bomb
private const int HOSTING_BOMB_TIMER = 5;			    			// starting time on host's time bomb
//==========================<Kill dying players>====================//
private const bool EXPLODE_ON_DEATH = false;			    		// set to true to make dying players explode
private const int EXPLODE_ON_DEATH_DELAY = 5;		    			// Time before the corpse explode. Set to 0 for instant explosion. keep an f after the number
//==================================================================//
//=================< DO NOT TOUCH ANYTHING IN BELOW >===============//
//==============< IF YOU'RE NOT SURE WHAT YOU'RE DOING >============//
//==================================================================//

// general mod data
public class ModData{
	// we'll add something else later
	public string Version = "1.1e";
}
private ModData modData = new ModData();

private Random rand = new Random();
private bool INSTAGIB_ON = false;

// hosting vars
private IPlayer hostPlayer = null;
IObjectText bombTimeLabel = null;

// time limit vars
private float m_startTime = 0;
private int m_lastElapsedSeconds = 0;
private bool m_twoOpponentsRemaining = false;
private bool WAS_ACTIVATED;

private int currentChar = 1;
private string text = "";
private string [] typingMachine_templatesWelcome = new string []{
	"Welcome everyone!",
	"May the best wins!",
	"Where is my pop-corn?",
	"Internet explorer is proud to wish you a good year 2002!" };
private string [] typingMachine_templatesRevive = new string [] {
	"{0} is back!",
	"{0} has been revived by {1}",
	"{0} was saved by {1}",
	"{0} got on his feet with help of {1}",
	"{0} wasn't supposed to be dead?" };
private string [] typingMachine_templatesDeath= new string [] {
	"{0} is knocked down",
	"Someone killed {0}",
	"(Un)fortunately {0} died",
	"{0} is dead because of lags (as he says)",
	"{0} wasn't my bet anyway",
	"We've got here {0}'s corpse!" };
private string [] typingMachine_templatesGib = new string [] {
	"{0} has spilled his/her guts on the ground",
	"{0}'s blood painted the wall in red!",
	"{0} felt no pain (in fact yes)",
	"{0} is looking like an IKEA kit!",
	"{0} will never look as before"}; 
private string [] typingMachine_templatesDisconnect = new string [] {
	"{0} left us",
	"{0} gave up",
	"{0} abandoned the battle",
	"{0} knew it wasn't his war!",
	"{0} cowardly ran away"}; 
private string [] typingMachine_templatesDraw = new string [] {
	"No one won, such a shame...",
	"Draws !\n No, not with a pen...",
	"What have you done ?",
	"And the winner is ... OH ! COME ON !",
	"He drew first."}; 
private string [] typingMachine_templatesWin = new string [] {
	"And the winner is ... {0} !",
	"{0} is the winner !",
	"Well played , {0} !",
	"{0} won because he is a noob!",
	"Today's baddas is {0} !"}; 
private string [] typingMachine_templatesWinTeam = new string [] {
	"And the winner is ... {0} !",
	"{0} is the winner !",
	"Nice demonstration of teamwork , {0} !",
	"{0} won because other team is noob!",
	"{0} > all"}; 

private bool gameOverSaid = false;

string[] restrictedMaps = new string[]{
	"#Abadoned Subway (BG)"
};

// where players' data will be stored
private class PlayerData {
	public float StartHealth = 0f;
	public float LastHealth = 100f;
	public float LastDamage = 0f; 
	public float BleedDamage = 0f;
	public float HealthNeeded = 0f;
	public float HealthAdded = 0f;

	// dead player data
	public float DeathTime = 0f;
	public int RevivePoints = 0;
	public float DeathDamageTaken = 0f;
	public IObjectText Label = null;

	public IUser User = null;
	public IPlayer Body = null;
	public PlayerTeam Team = PlayerTeam.Independent;
	public bool WasSetOnFire = false;
	public bool IsBleeding = HC_BLEEDING_ON_START;
	public PlayerData(float startHealth, IUser user, IPlayer body, bool wasSetOnFire){
		this.StartHealth = startHealth;
		this.User = user;
		this.Body = body;
		this.WasSetOnFire = wasSetOnFire;
		this.Team = body.GetTeam();
	}

	public PlayerData(){}
}


// ============================ |
// ========< THE CODE >======== |
// ============================ V

public void OnStartup(){
	bool mapIsGood = true;
	if (Game.GetMapType() == MapType.Custom) mapIsGood = false;
	foreach (string mapName in restrictedMaps) if (mapName == Game.MapName) mapIsGood = false;
	if (mapIsGood){
		INSTAGIB_ON = (INSTAGIB_ACTIVATED && (rand.Next(INSTAGIB_CHANCE) == 0 || !INSTAGIB_RANDOM));
		if (!INSTAGIB_ON) Game.SetMapType(MapType.Custom);

		// set up the triggers
			// fast tick trigger
			IObjectTimerTrigger timerTriger = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
			timerTriger.SetIntervalTime(100);
			timerTriger.SetRepeatCount(0);
			timerTriger.SetScriptMethod("TickFast");
			timerTriger.Trigger();
			// medium tick trigger
			IObjectTimerTrigger timerTriger2 = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
			timerTriger2.SetIntervalTime(500);
			timerTriger2.SetRepeatCount(0);
			timerTriger2.SetScriptMethod("TickMid");
			timerTriger2.Trigger();
			// slow tick trigger
			IObjectTimerTrigger timerTriger3 = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
			timerTriger3.SetIntervalTime(1000);
			timerTriger3.SetRepeatCount(0);
			timerTriger3.SetScriptMethod("TickSlow");
			timerTriger3.Trigger();
			// death tigger
			IObjectTrigger deathTrigger = (IObjectTrigger)Game.CreateObject("OnPlayerDeathTrigger"); 
			deathTrigger.SetScriptMethod("Death");

			// show settings trigger
			if (SHOW_SETTINGS){
				ShowSettings();
				IObjectTimerTrigger timerTriger4 = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
				timerTriger4.SetIntervalTime(5000);
				timerTriger4.SetRepeatCount(1);
				timerTriger4.SetScriptMethod("HideSettings");
				timerTriger4.CustomId = "HideSettingsTrigger";
				timerTriger4.Trigger();
			}

		// settings based method executions
		if (INSTAGIB_ON) Prepare_Instagib();
		else Prepare_CustomDamage();

		if (HOSTING_ACTIVATED){
			hostPlayer = Game.GetActiveUsers()[0].GetPlayer();

			if (!HOSTING_TIME_BOMB) hostPlayer.Remove();
			else {
				// tick trigger
				IObjectTimerTrigger timerTriger5 = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
				timerTriger5.SetIntervalTime(1000);
				timerTriger5.SetRepeatCount(0);
				timerTriger5.SetScriptMethod("Tick_Bomb");
				timerTriger5.CustomId = "Tick_BombTrigger";
				timerTriger5.Trigger();
			}
		}

		// some messages
		ModMessage("v" + modData.Version);
		if (HOSTING_ACTIVATED) ModMessage("Host is AFK");
		if (INSTAGIB_ON && INSTAGIB_RANDOM) ModMessage("INSTAGIB ROUND!");
	} else {
		ModMessage("This map is restricted.");
	}
}

public void TickFast(TriggerArgs args){
	if (INSTAGIB_ON) Tick_Instagib();
	if (HARDCORE_ACTIVATED && !INSTAGIB_ON) Tick_Hardcore_Main();
}

public void TickMid(TriggerArgs args){
	if (REVIVE_ACTIVATED && !INSTAGIB_ON) Tick_Revive();
}

public void TickSlow(TriggerArgs args){
	if (TIME_LIMIT_ACTIVATED) Tick_TimeLimit();
	if (COMMENT_ACTIVATED) Tick_Commentator();
	if (EXPLODE_ON_DEATH) Tick_ExplodeOnDeath();
}

private void ShowSettings(){
	string settings = "";
	if (INSTAGIB_ON){
		settings += "| - Instagib mode\n";
		if (INSTAGIB_EXPLODE) settings += "   | - explode players is ON\n";
			else settings += "   | - explode players is OFF\n";
		if (INSTAGIB_GIVE_SLOMO) settings += "   | - give slow motion is ON\n";
			else settings += "   | - give slow motion is OFF\n";
	} else {
		if (INSTAGIB_ACTIVATED)
			if (INSTAGIB_RANDOM)
				settings += "| - Instagib chance: " + string.Format("{0:0}%\n", 1f/(float)INSTAGIB_CHANCE * 100f);
		if (HARDCORE_ACTIVATED){
			settings += "| - hardcore mode is ON\n";
			if (HC_BLEEDING) settings += "   | - bleeding is ON\n";
				else settings += "   | - bleeding is OFF\n";
			if (HC_BLOCK_DAMAGE) settings += "   | - block damage is ON\n";
				else settings += "   | - block damage is OFF\n";
			/*if (HC_WEAPONS_BREAK) settings += "   | - weapon break is ON\n";
				else settings += "   | - weapon break is OFF\n";*/
			if (HC_CANT_PUT_FIRE_OFF) settings += "   | - can't put fire off is ON\n";
				else settings += "   | - can't put fire off is OFF\n";
		} else settings += "| - hardcore mode is OFF\n";
		if (REVIVE_ACTIVATED){
			settings += "| - Revive is ON\n";
			if (REVIVE_CAPTURE_ENEMIES) settings += "   | - capture enemies is ON\n";
				else settings += "   | - capture enemies is OFF\n";
			if (REVIVE_FINISH_PLAYERS) settings += "   | - finish players is ON\n";
				else settings += "   | - finish players is OFF\n";
		} else settings += "| - Revive is OFF\n";
	}
	if (EXPLODE_ON_DEATH) settings += "| - explode players on death is ON\n";
	if (COMMENT_ACTIVATED) settings += "| - Commentator is ON\n";
		else settings += "| - Commentator is OFF\n";
	if (TIME_LIMIT_ACTIVATED) settings += "| - Time limit in mode '" + TIME_LIMIT_MODE + "' is ON";


	Game.ShowPopupMessage("Current #Battle settings:\n" + settings, new Color(100,220,255));
}

public void HideSettings(TriggerArgs args){
	//Welcoming message
	Game.GetSingleObjectByCustomId("HideSettingsTrigger").Remove();
	if (COMMENT_ACTIVATED) TypingMachine_Print("beginning" , new string[]{""});
	else Game.HidePopupMessage();
}

public void Death(TriggerArgs args){
	IPlayer ply = (IPlayer)args.Sender;
	IUser user = ply.GetUser();

	if (user == null && GIB_ON_LEAVE){
		ply.Gib();
	} else {
		// add dead player to list
		if ((!INSTAGIB_ON) && ply != null && !ply.IsRemoved && user != null){
			IObjectText label = (IObjectText)Game.CreateObject("Text", ply.GetWorldPosition() + new Vector2(-6, 16), 0);
			float damage = ply.Statistics.TotalExplosionDamageTaken + ply.Statistics.TotalFallDamageTaken +
				ply.Statistics.TotalFireDamageTaken + ply.Statistics.TotalMeleeDamageTaken + ply.Statistics.TotalProjectileDamageTaken;

			int index = GetPlayerDataIndex(ply);
			players[index].Label = label;
			players[index].DeathDamageTaken = damage;
			players[index].DeathTime = Game.TotalElapsedGameTime;

			if (EXPLODE_ON_DEATH && EXPLODE_ON_DEATH_DELAY == 0) Game.TriggerExplosion(ply.GetWorldPosition());
		}
	}
	if (COMMENT_ACTIVATED){
		if (ply.GetUser() != null) TypingMachine_Print("death" , new string[] {ply.GetUser().Name});
		else TypingMachine_Print("disconnect", new string[] {ply.GetProfile().Name});
	}

	CheckGameover();
}

// ============================= |
// =========< OPTIONS >========= |
// ============================= V

private void Tick_Revive(){
	// travel through dead players list
	for(int i = players.Count - 1; i >=0; i--){
		IPlayer body = players[i].Body;
		if (body.IsDead){
			// check if body is present and time's not passed
			if (body != null && !body.IsRemoved &&
			((Game.TotalElapsedGameTime - players[i].DeathTime < (REVIVE_TIME_ALIVE * 1000f)) || players[i].RevivePoints != 0)){
				bool revivingNow = false;
				bool spawnedNow = false;
				bool diedNow = false;

				IObjectText label = null;
				if (players[i].Label != null){
					players[i].Label.SetWorldPosition(body.GetWorldPosition() + new Vector2(-6, 16));
					label = players[i].Label;
				} else {
					label = (IObjectText)Game.CreateObject("Text", body.GetWorldPosition() + new Vector2(-6, 16), 0);
				}

				float damage = (body.Statistics.TotalFallDamageTaken / 3f) +
					body.Statistics.TotalMeleeDamageTaken +
					body.Statistics.TotalProjectileDamageTaken +
					body.Statistics.TotalExplosionDamageTaken +
					body.Statistics.TotalFireDamageTaken;
					
				if (REVIVE_FINISH_PLAYERS && damage > players[i].DeathDamageTaken + (float)REVIVE_FINISH_HEALTH){
					diedNow = true;
					if (players[i].Label != null) players[i].Label.Remove();
					players.RemoveAt(i);
					CheckGameover();
				}

				// check for crouching players nearby
				if (!diedNow){
					foreach (IPlayer ply in Game.GetPlayers()){
						if (!ply.IsDead && !ply.IsRemoved && (ply.GetWorldPosition() - body.GetWorldPosition()).LengthSquared() < 144 && ply.IsCrouching &&
							(REVIVE_CAPTURE_ENEMIES || ply.GetTeam() == players[i].Body.GetTeam())){
							revivingNow = true;
							// spawn player if enough time passed
							if (players[i].RevivePoints >= REVIVE_DURATION * 2){
								// spawn player
								if (UserStillHere(players[i].User))
									RevivePlayer(body, ply, players[i].User, ply.GetTeam());
								// if disconnected, gib body
								else body.Gib();
								// remove the rest
								label.Remove();
								players.RemoveAt(i);
								CheckGameover();

								spawnedNow = true;
								break;
							} else {
								// make team revives faster
								if (ply.GetTeam() == players[i].Body.GetTeam() || !REVIVE_TEAMMATES_FASTER) 
									players[i].RevivePoints += 2;
								else
									players[i].RevivePoints ++;
							}
						}
					} 
				}
				// if player just spawned, do not do anything else
				if (!spawnedNow && !diedNow){
					if (players[i].RevivePoints != 0 && !revivingNow) players[i].RevivePoints = 0;
					if (REVIVE_BLEEDING) Game.PlayEffect("TR_B",body.GetWorldPosition() + new Vector2(rand.Next(-4,4),rand.Next(-4,4)));
					// draw time left if not being revived
					if (!revivingNow){
						int totalSeconds = (int)((REVIVE_TIME_ALIVE*1000f + players[i].DeathTime - Game.TotalElapsedGameTime) / 1000f);
						int minutes = totalSeconds / 60;
						int seconds = totalSeconds - (minutes * 60);

						label.SetText(players[i].User.GetProfile().Name + "\n" + string.Format(minutes.ToString() + ":{0:00}", seconds));
						label.SetTextColor(GetTeamColor(players[i].Body.GetTeam()));
					}
					// draw progress of reviving if is being revived
					else {
						label.SetWorldPosition(body.GetWorldPosition() + new Vector2(-28, 16));
						int progress = (int)(10f * ((float)players[i].RevivePoints / (REVIVE_DURATION * 2f)));
						string filling = "";
						for (int j = 1; j <= progress; j++) filling += "=";
						if (progress != 10) filling += ">";
						for (int j = progress + 2; j <= 10; j++)	filling += "  ";

						label.SetText(players[i].User.GetProfile().Name + "\n" + string.Format("[" + filling + "]"));
						label.SetTextColor(GetTeamColor(players[i].Body.GetTeam()));
					}

					players[i].Label = label;
				}
			// if player's body is removed, gibbed or time has run out, remove entry from list
			} else {
				if (players[i].Label != null) players[i].Label.Remove();
				players.RemoveAt(i);
				CheckGameover();
			}
		}
	}
}

private void RevivePlayer(IPlayer body, IPlayer reviver, IUser user, PlayerTeam team){
	float health = (float)rand.Next(15,50);

	IPlayer revivedPlayer = Game.CreatePlayer(body.GetWorldPosition());
	revivedPlayer.SetUser(user);
	revivedPlayer.SetProfile(user.GetProfile());
	revivedPlayer.SetHealth(health);
	revivedPlayer.SetTeam(team);
	body.Remove();
	Game.PlayEffect("CFTXT", revivedPlayer.GetWorldPosition() + new Vector2(0f, 30f), "REVIVE!");

	if (HARDCORE_ACTIVATED) players.Add(new PlayerData(health, revivedPlayer.GetUser(), revivedPlayer, false));
	if (COMMENT_ACTIVATED) TypingMachine_Print("revive" , new string[] {revivedPlayer.GetProfile().Name, reviver.GetProfile().Name});
}

private void Tick_Instagib(){
	foreach (IPlayer ply in Game.GetPlayers()){
		float damage = 0f;
		if (INSTAGIB_MELEE){
			ply.SetHealth(100f - ply.Statistics.TotalDamageTaken + ply.Statistics.TotalExplosionDamageTaken);
			damage = ply.Statistics.TotalMeleeDamageTaken + ply.Statistics.TotalProjectileDamageTaken;
		} else damage = ply.Statistics.TotalProjectileDamageTaken;

		if (damage != 0){
			if (INSTAGIB_EXPLODE) Game.TriggerExplosion(ply.GetWorldPosition());
			if (COMMENT_ACTIVATED) TypingMachine_Print("gib" , new string[] {ply.GetProfile().Name});
			ply.Gib();
		}
	}
}

private void Prepare_Instagib(){
	Game.RunCommand("/IA 1");
	foreach (IObject obj in Game.GetObjectsByName(new string[]{"SpawnWeapon", "SpawnWeaponArea", "SpawnWeaponAreaCeiling"})){
		obj.Remove();
	}

	IObjectTimerTrigger timerTriger = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
	timerTriger.SetIntervalTime(INSTAGIB_DELAY * 1000);
	timerTriger.SetRepeatCount(1);
	timerTriger.SetScriptMethod("Prepare_Instagib_GiveItems");
	timerTriger.Trigger();
}

public void Prepare_Instagib_GiveItems(TriggerArgs args){
	foreach (IPlayer ply in Game.GetPlayers()){
		ply.GiveWeaponItem(WeaponItem.SNIPER);
		if (INSTAGIB_GIVE_SLOMO) ply.GiveWeaponItem(WeaponItem.SLOWMO_5);
	}
}

private List<PlayerData> players = new List<PlayerData>();
private void Prepare_CustomDamage(){
	foreach (IPlayer ply in Game.GetPlayers())
		if (ply.GetUser() != null)
			players.Add(new PlayerData(100f, ply.GetUser(), ply, false));
}

private void Tick_Hardcore_Main(){
	for(int i = players.Count - 1; i >=0; i--){
		PlayerData data = players[i];
		if (data.Body.IsRemoved) {
			if (players[i].Label != null) players[i].Label.Remove();
			players.RemoveAt(i);
			CheckGameover();
		} else {
			IPlayerStatistics stats = data.Body.Statistics;
			float damage =
				stats.TotalFallDamageTaken * HC_FALL_MULTIPLIER +
				stats.TotalMeleeDamageTaken * HC_MELEE_MULTIPLIER +
				stats.TotalProjectileDamageTaken * HC_GUNS_MULTIPLIER +
				stats.TotalExplosionDamageTaken * HC_EXPL_MULTIPLIER +
				stats.TotalFireDamageTaken * HC_FIRE_MULTIPLIER +
				data.BleedDamage;
			if (HC_BLOCK_DAMAGE) damage +=  stats.TotalBlockedAttacks * (float)HC_BLOCK_DAMAGE_AMOUNT;
			float damage_causingBleed = stats.TotalMeleeDamageTaken * HC_MELEE_MULTIPLIER +
				stats.TotalProjectileDamageTaken * HC_GUNS_MULTIPLIER +
				stats.TotalExplosionDamageTaken * HC_EXPL_MULTIPLIER;

			if (data.StartHealth - damage > 0f){
				float healthNow = data.Body.GetHealth();
				if (healthNow > data.LastHealth /*&& data.HealthNeeded < healthNow - data.LastHealth*/){
					if (data.IsBleeding){
						data.IsBleeding = false;
						data.HealthNeeded += healthNow - data.LastHealth - 25f;
					} else data.HealthNeeded += healthNow - data.LastHealth + 10f;
				} else if (data.HealthNeeded > 0f){
					data.HealthAdded += 0.5f;
					data.HealthNeeded -= 0.5f;
				} else data.HealthNeeded = 0f;

				if (data.WasSetOnFire){
					data.Body.SetMaxFire();
				}

				if (data.IsBleeding){
					data.BleedDamage += 0.1f;
					if (rand.Next(6) == 0)
						Game.PlayEffect("TR_B", data.Body.GetWorldPosition() + new Vector2(8,0) + new Vector2(rand.Next(-2,2),rand.Next(-2,2)));
				}

				data.Body.SetHealth(data.StartHealth - damage + data.HealthAdded);
				if (HC_BLEEDING && damage_causingBleed - data.LastDamage > (float)HC_BLEEDING_TRIGGER_HP && rand.Next(HC_BLEEDING_CHANCE) == 0){ 
					data.IsBleeding = true;
					Game.PlayEffect("CFTXT", data.Body.GetWorldPosition() + new Vector2(0f, 30f), "BLEEDING!");
				} if (HC_CANT_PUT_FIRE_OFF && stats.TotalFireDamageTaken != 0) data.WasSetOnFire = true;
				data.LastHealth = data.Body.GetHealth();
				data.LastDamage = damage_causingBleed;
			} else data.Body.Kill();
		}
	}
}

int secondsRemaining = HOSTING_BOMB_TIMER;
public void Tick_Bomb(TriggerArgs args){
	if (bombTimeLabel != null) bombTimeLabel.Remove();
	if (secondsRemaining > 0 && !hostPlayer.IsRemoved){
		bombTimeLabel = (IObjectText)Game.CreateObject("Text", hostPlayer.GetWorldPosition() + new Vector2(-10, 24), 0);
		bombTimeLabel.SetText("0:" + string.Format("{0:00}", secondsRemaining));
		bombTimeLabel.SetTextColor(new Color(255, 0, 0));
		secondsRemaining --;
	} else {
		if (!hostPlayer.IsRemoved){
			Game.TriggerExplosion(hostPlayer.GetWorldPosition());
			hostPlayer.Gib();
		}
		Game.GetSingleObjectByCustomId("Tick_BombTrigger").Remove();
	}
}

private void Tick_TimeLimit(){
	if (TimeLimit_Check() && Game.GetSingleObjectByCustomId("HideSettingsTrigger") == null){
		if (!m_twoOpponentsRemaining){
			// start tracking time
			// store current elapsed time
			m_startTime = Game.TotalElapsedGameTime;
			m_twoOpponentsRemaining = true;
			WAS_ACTIVATED = true;
		}
	   	int currentElapsedSeconds = (int)((Game.TotalElapsedGameTime - m_startTime) / 1000f);
	   	if (currentElapsedSeconds != m_lastElapsedSeconds){
		  // update text
		  	int totalSecondsRemaining = TIME_LIMIT_SECONDS - currentElapsedSeconds;
		  	int minutesRemaining = totalSecondsRemaining / 60;
		  	int secondsRemaining = totalSecondsRemaining - minutesRemaining * 60;
		  	if (totalSecondsRemaining > 0){
			  	if (minutesRemaining > 0){
				 	Game.ShowPopupMessage(string.Format("Time remaining {0}:{1:00} minutes", minutesRemaining, secondsRemaining));
			  	} else {
				 	Game.ShowPopupMessage(string.Format("Time remaining {0:00} seconds", secondsRemaining));
			  	}
		  	} else {
			 	Game.HidePopupMessage();
			 	Game.SetGameOver("Time's up!");
		  	}
		  m_lastElapsedSeconds = currentElapsedSeconds;
	   	}
   	} else if (WAS_ACTIVATED) {
		Game.HidePopupMessage();
   	}
}

private bool TimeLimit_Check(){
	Dictionary<PlayerTeam, int> teams = new Dictionary<PlayerTeam, int>();
	if (TIME_LIMIT_MODE == "main") return true;
	else if (TIME_LIMIT_MODE == "auto"){
		PlayerTeam playerATeam = PlayerTeam.Independent;
		PlayerTeam playerBTeam = PlayerTeam.Independent;
		int opponents = 0;

		foreach (IPlayer ply in Game.GetPlayers()){
			if (!ply.IsDead){
 			PlayerTeam team = ply.GetTeam();
				if (!teams.ContainsKey(team)) teams.Add(team, 1);
				else teams[team] ++;
		
				opponents++;
				switch(opponents){
					case 1: playerATeam = ply.GetTeam(); break;
					case 2: playerBTeam = ply.GetTeam(); break;
 				}   
  			}
		}

		return ((teams.Count == 2 && teams.ContainsValue(1)) || (opponents == 2 && playerATeam == PlayerTeam.Independent));
	}

	return false;
}


private void Tick_Commentator(){
	IObjectTimerTrigger timerTriger6 = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
	timerTriger6.SetIntervalTime(10000);
	timerTriger6.SetRepeatCount(1);
	timerTriger6.SetScriptMethod("GameOver_Commentator");
	timerTriger6.Trigger();
}

public void GameOver_Commentator(TriggerArgs args){
	if(Game.IsGameOver && (!gameOverSaid)){
		List <IPlayer> tempPlyList = new List <IPlayer>();
		foreach(IPlayer ply in Game.GetPlayers()){
			if(!ply.IsDead) tempPlyList.Add(ply);
		}
		switch(tempPlyList.Count) {
			case 0 : TypingMachine_Print("draw", new string[] {""}); break;
			case 1 : TypingMachine_Print("win", new string[] {tempPlyList[0].GetProfile().Name}); break;
			default : {
				if (LastTeam() != "none") TypingMachine_Print("winTeam", new string[]{LastTeam()});
				else TypingMachine_Print("draw", new string[] {""});
				break;
			}
		}
		gameOverSaid = true;
	}
}

private void Tick_ExplodeOnDeath(){
	if (players.Count != 0){
		for(int i = players.Count -1; i > -1; i--){
			PlayerData data = players[i];
			if (data.Body.IsDead && !data.Body.IsRemoved && Game.TotalElapsedGameTime - data.DeathTime > (float)EXPLODE_ON_DEATH_DELAY){
				Game.TriggerExplosion(data.Body.GetWorldPosition());
				if (players[i].Label != null) players[i].Label.Remove();
				players.RemoveAt(i);
				CheckGameover();
			}
		}
	}
}

private void CheckGameover(){
	if (!INSTAGIB_ON){
		List <PlayerTeam> teams = new List<PlayerTeam>();
		String winner = "";

		//if (Game.GetActiveUsers().Length < 2) return;

		foreach (PlayerData data in players)
			if (!data.Body.IsRemoved && !teams.Contains(data.Team))
				if (REVIVE_KILL_FALLEN_TO_GAMEOVER || (!REVIVE_KILL_FALLEN_TO_GAMEOVER && !data.Body.IsDead))
					teams.Add(data.Team);

		switch (teams.Count){
			case 5:
				Game.ShowPopupMessage("Wut?");
				break;
			case 4:
			case 3:
			case 2:
				return;
			case 1:
				if (teams[0] != PlayerTeam.Independent){
					switch (teams[0]) {
						case PlayerTeam.Team1 : winner = "Blue team"; break;
						case PlayerTeam.Team2 : winner = "Red team"; break;
						case PlayerTeam.Team3 : winner = "Green team"; break;
						case PlayerTeam.Team4 : winner = "Yellow team"; break;
					}
				} else {
					int plysAlive = 0;
					int plysFallen = 0;
					foreach (PlayerData data in players){
						if (!data.Body.IsDead){
							plysAlive ++;
							if (winner == "") winner = data.Body.GetProfile().Name;
						} else plysFallen ++;
					}

					int plys = 0;
					if (REVIVE_KILL_FALLEN_TO_GAMEOVER) plys = plysAlive + plysFallen;
					else plys = plysAlive;

					if (plys > 1) return;
				}
				Game.SetGameOver(winner + " wins!");
				break;
			case 0:
				Game.SetGameOver("Draw!");
				break;
		}
	}
}


// ============================= |
// =========< HELPERS >========= |
// ============================= V

private void ModMessage(string text){
	Game.RunCommand("/MSG #Battle mod: " + text);
}

private Color GetTeamColor(PlayerTeam team){
	if (team == PlayerTeam.Team1) return new Color(124, 124, 246);
	if (team == PlayerTeam.Team2) return new Color(245, 78, 78);
	if (team == PlayerTeam.Team3) return new Color(5, 219, 5);
	if (team == PlayerTeam.Team4) return new Color(214, 214, 0);
	return new Color(255, 255, 255);
}

private PlayerData GetPlayerData(IPlayer ply){
	for (int i = players.Count - 1; i >=0; i--){
		if (players[i].Body == ply) return players[i];
	}
	return new PlayerData(); 
}

private int GetPlayerDataIndex(IPlayer ply){
	for (int i = players.Count -1; i>=0; i--)
		if (ply == players[i].Body) return i;
	return 0;
}

private bool UserStillHere(IUser user){
	foreach (IUser usr in Game.GetActiveUsers())
		if (usr.UserId == user.UserId) return true;
	return false;
}


private void TypingMachine_Print(string reason, string[] args) {

	// determine what type of text should be displayed
	switch (reason){
		case "death" : text = string.Format( typingMachine_templatesDeath [ rand.Next( typingMachine_templatesDeath.Length ) ], args[0]); break;
		case "revive" : text = string.Format( typingMachine_templatesRevive [ rand.Next( typingMachine_templatesRevive.Length ) ], args[0], args[1]); break;
		case "beginning" : text = typingMachine_templatesWelcome [ rand.Next( typingMachine_templatesWelcome.Length ) ]; break; 
		case "gib" : text = string.Format(typingMachine_templatesGib[rand.Next( typingMachine_templatesGib.Length ) ], args[0]); break;
		case "disconnect" :  text = string.Format( typingMachine_templatesDisconnect [ rand.Next( typingMachine_templatesDisconnect.Length ) ], args[0]); break;
		case "draw" : text = typingMachine_templatesDraw [ rand.Next( typingMachine_templatesDraw.Length ) ]; break; 
		case "win" : text = string.Format( typingMachine_templatesWin [ rand.Next( typingMachine_templatesWin.Length ) ], args[0]); break;
		case "winTeam" : text = string.Format( typingMachine_templatesWinTeam [ rand.Next( typingMachine_templatesWinTeam.Length ) ], args[0]); break;
		default : break;
	}

	// reset speaker engine
	currentChar = 0;
	foreach (IObject obj in (IObject[])Game.GetObjectsByCustomId("TypingMachineTrigger"))obj.Remove();

	// create a speaker engine
	IObjectTimerTrigger timerTrigger = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
	timerTrigger.SetIntervalTime(1000/COMMENT_TYPE_SPEED);
	timerTrigger.SetRepeatCount(0);
	timerTrigger.SetScriptMethod("TypingMachine_AddLetter");
	timerTrigger.CustomId = "TypingMachineTrigger";
	timerTrigger.Trigger();
}

public void TypingMachine_AddLetter(TriggerArgs args) {
	if (currentChar < text.Length) {
		currentChar ++;
		Game.ShowPopupMessage(text.Substring(0, currentChar));
		Game.PlaySound("OutOfAmmoLight", Vector2.Zero, 1f);
	}
	else {
		foreach (IObject obj in (IObject [])Game.GetObjectsByCustomId("TypingMachineTrigger"))obj.Remove();
		TypingMachine_Stop();
	}
}

private void TypingMachine_Stop(){
	IObjectTimerTrigger timerTrigger = (IObjectTimerTrigger)Game.CreateObject("TimerTrigger"); 
	timerTrigger.SetIntervalTime(3000);
	timerTrigger.SetRepeatCount(1);
	timerTrigger.SetScriptMethod("TypingMachine_Clear");
	timerTrigger.CustomId = "TypingMachineTrigger";
	timerTrigger.Trigger();
}

public void TypingMachine_Clear(TriggerArgs args){
	Game.HidePopupMessage();
}

private string LastTeam(){
	PlayerTeam testTeam = PlayerTeam.Independent;
	foreach(IPlayer ply in Game.GetPlayers()){
		if(!ply.IsDead) testTeam = ply.GetTeam(); break;
	}
		//return "none" if at least one living player got a different team
	foreach(IPlayer ply in Game.GetPlayers()){
		if(!ply.IsDead && ply.GetTeam() != testTeam) return "none";
	}
	switch (testTeam) {
		case PlayerTeam.Team1 : return "Blue team"; break;
		case PlayerTeam.Team2 : return "Red team"; break;
		case PlayerTeam.Team3 : return "Green team"; break;
		case PlayerTeam.Team4 : return "Yellow team"; break;
		default : break;
	}
	return "none";
}