Index: binaries/data/mods/public/maps/random/survivalofthefittest.js
===================================================================
--- binaries/data/mods/public/maps/random/survivalofthefittest.js	(revision 18431)
+++ binaries/data/mods/public/maps/random/survivalofthefittest.js	(working copy)
@@ -202,8 +202,11 @@
 	var femaleLocation = getTIPIADBON([ix, iz], [mapSize / 2, mapSize / 2], [-3 , 3.5], 1, 3);
 	if (femaleLocation !== undefined)
 	{
+		var spawnPoints =  ["C","D","E","F"];
 		placeObject(femaleLocation[0], femaleLocation[1], "skirmish/units/default_support_female_citizen", id, playerAngle[i] + PI);
 		addToClass(floor(femaleLocation[0]), floor(femaleLocation[1]), clWomen);
+		placeObject(femaleLocation[0], femaleLocation[1], "special/trigger_point_" + spawnPoints[id-1], id, playerAngle[i] + PI);
+		// addToClass(floor(femaleLocation[0]), floor(femaleLocation[1]), clPlayer);
 	}
 }
 
Index: binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js	(revision 18431)
+++ binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js	(working copy)
@@ -1,5 +1,8 @@
-var treasures =
-[
+/*
+ * This is the Script containing all the Triggers for the "Survival of the Fittest" Map.
+ */
+
+var g_treasures = [
 	"gaia/special_treasure_food_barrel",
 	"gaia/special_treasure_food_bin",
 	"gaia/special_treasure_food_crate",
@@ -10,36 +13,42 @@
 	"gaia/special_treasure_wood",
 	"gaia/special_treasure_wood"
 ];
-var attackerEntityTemplates =
-[
+
+var g_resourceLoopCount = 0;
+
+var g_resourceLoopTime = 1.5; // Time in minutes when resources are distributed periodically
+var g_resourceLoopOffset = 15; // Time in minutes after which resources are distributed periodically.
+
+// These are the templates of the attacking units, sorted by civ
+var g_attackerEntityTemplates = [
 	[
 		"units/athen_champion_infantry",
 		"units/athen_champion_marine",
 		"units/athen_champion_ranged",
-		"units/athen_siege_lithobolos_packed",
-		"units/athen_siege_oxybeles_packed",
+		"units/athen_mechanical_siege_lithobolos_packed",
+		"units/athen_mechanical_siege_oxybeles_packed"
 	],
 	[
 		"units/brit_champion_cavalry",
 		"units/brit_champion_infantry",
-		"units/brit_mechanical_siege_ram",
+		"units/brit_mechanical_siege_ram"
 	],
 	[
 		"units/cart_champion_cavalry",
 		"units/cart_champion_elephant",
 		"units/cart_champion_infantry",
-		"units/cart_champion_pikeman",
+		"units/cart_champion_pikeman"
 	],
 	[
 		"units/gaul_champion_cavalry",
 		"units/gaul_champion_fanatic",
 		"units/gaul_champion_infantry",
-		"units/gaul_mechanical_siege_ram",
+		"units/gaul_mechanical_siege_ram"
 	],
 	[
 		"units/iber_champion_cavalry",
 		"units/iber_champion_infantry",
-		"units/iber_mechanical_siege_ram",
+		"units/iber_mechanical_siege_ram"
 	],
 	[
 		"units/mace_champion_cavalry",
@@ -46,7 +55,7 @@
 		"units/mace_champion_infantry_a",
 		"units/mace_champion_infantry_e",
 		"units/mace_mechanical_siege_lithobolos_packed",
-		"units/mace_mechanical_siege_oxybeles_packed",
+		"units/mace_mechanical_siege_oxybeles_packed"
 	],
 	[
 		"units/maur_champion_chariot",
@@ -53,22 +62,23 @@
 		"units/maur_champion_elephant",
 		"units/maur_champion_infantry",
 		"units/maur_champion_maiden",
-		"units/maur_champion_maiden_archer",
+		"units/maur_champion_maiden_archer"
 	],
 	[
 		"units/pers_champion_cavalry",
-		"units/pers_champion_infantry",
 		"units/pers_champion_elephant",
+		"units/pers_champion_infantry"
+
 	],
 	[
 		"units/ptol_champion_cavalry",
-		"units/ptol_champion_elephant",
-	],
+		"units/ptol_champion_elephant"
+	]
 	[
 		"units/rome_champion_cavalry",
 		"units/rome_champion_infantry",
 		"units/rome_mechanical_siege_ballista_packed",
-		"units/rome_mechanical_siege_scorpio_packed",
+		"units/rome_mechanical_siege_scorpio_packed"
 	],
 	[
 		"units/sele_champion_cavalry",
@@ -75,13 +85,13 @@
 		"units/sele_champion_chariot",
 		"units/sele_champion_elephant",
 		"units/sele_champion_infantry_pikeman",
-		"units/sele_champion_infantry_swordsman",
+		"units/sele_champion_infantry_swordsman"
 	],
 	[
 		"units/spart_champion_infantry_pike",
 		"units/spart_champion_infantry_spear",
 		"units/spart_champion_infantry_sword",
-		"units/spart_mechanical_siege_ram",
+		"units/spart_mechanical_siege_ram"
 	],
 ];
 
@@ -88,12 +98,12 @@
 Trigger.prototype.StartAnEnemyWave = function()
 {
 	let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
-	let attackerEntities = attackerEntityTemplates[Math.floor(Math.random() * attackerEntityTemplates.length)];
+	let attackerEntities = g_attackerEntityTemplates[Math.floor(Math.random() * g_attackerEntityTemplates.length)];
 	// A soldier for each 2-3 minutes of the game. Should be waves of 20 soldiers after an hour
 	let nextTime = Math.round(120000 + Math.random() * 60000);
 	let attackerCount = Math.round(cmpTimer.GetTime() / nextTime / attackerEntities.length);
 
-	// spawn attackers
+	// Spawn attackers
 	let attackers =  [];
 	for (let attackerEntity of attackerEntities)
 		attackers.push(TriggerHelper.SpawnUnitsFromTriggerPoints("A", attackerEntity, attackerCount, 0));
@@ -103,21 +113,21 @@
 		for (let origin in entityType)
 		{
 			let cmpPlayer = QueryOwnerInterface(+origin, IID_Player);
-			if (cmpPlayer.GetState() != "active")
+			if (!cmpPlayer || !cmpTrigger.playerCivicCenter[cmpPlayer.GetPlayerID()])
 				continue;
 
-			let cmpPosition =  Engine.QueryInterface(this.playerCivicCenter[cmpPlayer.GetPlayerID()], IID_Position);
-			// this shouldn't happen if the player is still active
+			let cmpPosition =  Engine.QueryInterface(cmpTrigger.playerCivicCenter[cmpPlayer.GetPlayerID()], IID_Position);
+			// This shouldn't happen if the player is still active
 			if (!cmpPosition || !cmpPosition.IsInWorld)
 				continue;
 
-			// store the x and z coordinates in the command
+			// Store the x and z coordinates in the command
 			let cmd = cmpPosition.GetPosition();
 			cmd.type = "attack-walk";
 			cmd.entities = entityType[origin];
 			cmd.queued = true;
 			cmd.targetClasses = undefined;
-			// send the attack-walk command
+			// Send the attack-walk command
 			ProcessCommand(0, cmd);
 		}
 	}
@@ -130,18 +140,75 @@
 	cmpTrigger.DoAfterDelay(nextTime, "StartAnEnemyWave", {}); // The next wave will come in 3 minutes
 };
 
+/*
+ * Try to mark inactive players by tracking the number of entities they own.
+ * An unassigned Player will never produce a new entity.
+ * This will stop running once all players have been marked.
+ * So, when checking cmpTrigger.playerTrack[playerID].unassigned, you must always check if cmpTrigger.playerTrack[playerID] exists.
+ *
+ * For each player marked as Unassigned, less treasures will spawn.
+ */
+Trigger.prototype.MarkUnassignedPlayers = function(data)
+{
+	if (!data || !data.playerID)
+	{
+		warn("no information provided to player tracker");
+		return;
+	}
+	if (!cmpTrigger.playerTrack[data.playerID])
+		cmpTrigger.playerTrack[data.playerID] = {
+			"eCount": data.eCount,
+			"unassigned": false,
+			"loop": 1
+		};
+	else
+	{
+		++cmpTrigger.playerTrack[data.playerID].loop;
+
+		let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+		let playerEntities = cmpRangeManager.GetEntitiesByPlayer(data.playerID);
+		let eCount = playerEntities.length;
+
+		// The player must have produced something, so being for real. Exit the loop.
+		if (eCount > cmpTrigger.playerTrack[data.playerID].eCount)
+			return;
+
+		cmpTrigger.playerTrack[data.playerID].eCount = eCount;
+
+		if (cmpTrigger.playerTrack[data.playerID].loop > 5)
+		{
+			cmpTrigger.playerTrack[data.playerID].unassigned = true; // So long created nothing, must be placeholder
+			return;
+		}
+	}
+	cmpTrigger.DoAfterDelay(0.95 * 60 * 1000, "MarkUnassignedPlayers", { "playerID": data.playerID });
+};
+
+/*
+ * The main init function, called at the start
+ */
 Trigger.prototype.InitGame = function()
 {
 	let numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
-	// Find all of the civic centers, disable some structures
+	// Find all of the civic centers and females, disable some structures, setup activity watch
 	for (let i = 1; i < numberOfPlayers; ++i)
 	{
+		/*// At first, determine how many players are actually playing (humans)
+		let cmpPlayer = TriggerHelper.GetPlayerComponent(i);
+		warn(cmpPlayer.GetState());*/
+
+		// Now handle the other stuff
 		let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
 		let playerEntities = cmpRangeManager.GetEntitiesByPlayer(i); // Get all of each player's entities
 
 		for (let entity of playerEntities)
+		{
 			if (TriggerHelper.EntityHasClass(entity, "CivilCentre"))
 				cmpTrigger.playerCivicCenter[i] = entity;
+			if (TriggerHelper.EntityHasClass(entity, "Female"))
+				cmpTrigger.females[i] =  entity;
+		}
+		this.MarkUnassignedPlayers({ "eCount": playerEntities.length, "playerID": i });
 	}
 
 	// Fix alliances
@@ -183,12 +250,40 @@
 	let triggerPoints = cmpTrigger.GetTriggerPoints(point);
 	for (let point of triggerPoints)
 	{
-		let template = treasures[Math.floor(Math.random() * treasures.length)]
-		TriggerHelper.SpawnUnits(point, template, 1, 0);
+		let template = g_treasures[Math.floor(Math.random() * g_treasures.length)];
+		let cmpPosition = Engine.QueryInterface(point, IID_Position);
+		let ent = Engine.AddEntity(template);
+
+		let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
+		let cmpEntPosition = Engine.QueryInterface(ent, IID_Position);
+
+		if (cmpEntOwnership)
+			cmpEntOwnership.SetOwner(0);
+
+		let xOffset = RandomInt(0,23);
+		let zOffset = RandomInt(0,23);
+
+		if (Math.random() >= 0.8 )
+		{
+			xOffset += RandomInt(5,8);
+			zOffset += RandomInt(5,8);
+		}
+
+		if (Math.round(Math.random()) == 1 )
+			xOffset = xOffset * -1;
+
+		if (Math.round(Math.random()) == 1 )
+			zOffset = zOffset * -1;
+
+		cmpEntPosition.JumpTo(cmpPosition.GetPosition().x + xOffset, cmpPosition.GetPosition().z - zOffset);
 	}
-	cmpTrigger.DoAfterDelay(4*60*1000, "PlaceTreasures", {}); //Place more treasures after 4 minutes
+	let time = (3 + Math.random() * 10) * 60 * 1000; // Place more treasures after 2-4 minutes
+	cmpTrigger.DoAfterDelay(time, "PlaceTreasures", {});
 };
 
+/*
+ * This function starts the timer for the waves
+ */
 Trigger.prototype.InitializeEnemyWaves = function()
 {
 	let time = (5 + Math.round(Math.random() * 10)) * 60 * 1000;
@@ -200,10 +295,160 @@
 	cmpTrigger.DoAfterDelay(time, "StartAnEnemyWave", {});
 };
 
-Trigger.prototype.DefeatPlayerOnceCCIsDestroyed = function(data)
+var TimeString = function(data)
 {
+	if (!data.time)
+	{
+		warn("No time provided in TimeString");
+		return "";
+	}
+	let displayTime = data.time/60000;
+	let grammar = "";
+	if (displayTime > 1)
+		grammar = "minutes";
+	else if (data.time/1000 < 60)
+	{
+		grammar = "seconds";
+		displayTime = data.time/1000;
+	}
+	else if (displayTime == 1)
+		grammar = "minute";
+	else if (data.time/1000 == 1)
+	{
+		grammar = "second";
+		displayTime = data.time/1000;
+	}
+	return displayTime + " " + grammar;
+};
+
+/*
+ * NotifyPlayers: Keep the active players up to date about whats happening. This is called from inside the other functions.
+ * "data" mut be an object which contains string "situation", and any "argument" properties. "argument" is optional.
+*/
+Trigger.prototype.NotifyPlayers = function(data)
+{
+	if (!data || !data.situation)
+	{
+		error("Nothing to talk :-(");
+		return;
+	}
+
+	let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+	let players = [];
+	let numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
+
+	// Only deliver messages to active players (not e.g. dead)
+	for (let i = 1; i < numberOfPlayers; ++i)
+		if (QueryPlayerIDInterface(i).GetState() == "active")
+			players.push(i);
+
+	switch (data.situation)
+	{
+	case "beginGame":
+		cmpGUIInterface.PushNotification({
+			"players": players,
+			"message": markForTranslation("Welcome to Survival of the Fittest"),
+			"translateMessage": true
+		});
+		cmpGUIInterface.PushNotification({
+			"players": players,
+			"message": markForTranslation("Collect treasures with your woman to prepare for the enemies."),
+			"translateMessage": true
+		});
+		cmpGUIInterface.PushNotification({
+			"players": players,
+			"message": markForTranslation("The first wave will start in " + TimeString({ "time": data.argument }) + "!"),
+			"translateMessage": true
+		});
+		return;
+
+	case "beforeStart":
+		let timeRemaining = data.argument;
+		if (!timeRemaining)
+			return;
+		cmpGUIInterface.PushNotification({
+			"players": players,
+			"message": markForTranslation("The first wave will start in " + TimeString({ "time": timeRemaining }) +", be prepared!"),
+			"translateMessage": true
+		});
+		return;
+
+	case "treasuresPlaced":
+		cmpGUIInterface.PushNotification({
+			"players": players,
+			"message": markForTranslation("New treasures have been placed!"),
+			"translateMessage": true
+		});
+		return;
+
+	case "waveIncoming":
+		cmpGUIInterface.PushNotification({
+			"players": players,
+			"message": markForTranslation("Rioting soldiers are attacking you! Defend your City!"),
+			"translateMessage": true
+		});
+		return;
+
+	case "notifyRespawn":
+		if (!data.argument.time || !data.argument.playerIDs)
+		{
+			warn("NotifyPlayers doesnt know whom to address");
+			return;
+		}
+		cmpGUIInterface.PushNotification({
+			"players": data.argument.playerIDs,
+			"message": markForTranslation("Your treasure seeker has died! You will get a new one in " + TimeString({ "time": data.argument.time }) + "."),
+			"translateMessage": true
+		});
+		return;
+
+	case "resourceLoopStarted":
+		cmpGUIInterface.PushNotification({
+			"players": players,
+			"message": markForTranslation("From now on, you will receive some resources from time to time"),
+			"translateMessage": true
+		});
+		return;
+
+	default:
+		warn("NotifyPlayers is panicking, some unknown gossip came in");
+		return;
+	}
+};
+
+Trigger.prototype.SpawnNewWoman = function(data)
+{
+	if (!data.playerId || data.playerId < 1)
+	{
+		warn("No women for gaia");
+		return;
+	}
+
+	if (!cmpTrigger.females[data.playerId])
+	{
+		warn("This is not a treasure seeker: Player " + data.playerId);
+		return;
+	}
+
+	let spawnPoints =  ["C","D","E","F"];
+	if (TriggerHelper.GetOwner(cmpTrigger.females[data.playerId]) != -1)
+	{
+		warn("Female still alive, exiting");
+		return;
+	}
+
+	let triggerPoints = cmpTrigger.GetTriggerPoints(spawnPoints[data.playerId-1]);
+	let newWoman = TriggerHelper.SpawnUnits(triggerPoints[0], data.template, 1, data.playerId);
+	cmpTrigger.females[data.playerId] = newWoman[0];
+};
+
+/*
+ * HandleSpecialEntities occurs when a unit dies, and checks for anything to be done in case its a treasure woman/civic center
+ */
+Trigger.prototype.HandleSpecialEntities = function(data)
+{
 	// Defeat a player that has lost his civic center
-	if (data.entity == cmpTrigger.playerCivicCenter[data.from])
+	if (data.entity == cmpTrigger.playerCivicCenter[data.from] && data.to == -1)
 	{
 		TriggerHelper.DefeatPlayer(data.from);
 
@@ -222,11 +467,60 @@
 		if (numPlayersStanding == 1)
 			TriggerHelper.SetPlayerWon(lastPlayerStanding);
 	}
+
+	if (cmpTrigger.females[data.from] && data.from >= 1 && data.entity == cmpTrigger.females[data.from] && data.to == -1)
+	{
+		let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+		let template = cmpTemplateManager.GetCurrentTemplateName(data.entity);
+		let time = 15 * 1000; // Time after which women respawn
+
+		cmpTrigger.DoAfterDelay(time, "SpawnNewWoman", {
+			"playerId": data.from,
+			"template": template
+		});
+
+		this.NotifyPlayers({
+			"situation": "notifyRespawn",
+			"argument": {
+				"time": time,
+				"playerIDs": [data.from]
+			}
+		});
+	}
 };
 
+/*
+ * This function is constantly Looping and provides resources to all players.
+ * It starts after resourceLoopOffset
+ */
+Trigger.prototype.ResourceLoop = function(data)
+{
+	++g_resourceLoopCount;
+
+	if (g_resourceLoopCount == 1)
+		this.NotifyPlayers({ "situation": "resourceLoopStarted" });
+
+	let numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
+	for (let i = 1; i < numberOfPlayers; ++i)
+	{
+		let cmpPlayer = QueryPlayerIDInterface(i);
+		cmpPlayer.AddResources({
+			"wood": 100,
+			"stone": 100,
+			"metal": 100,
+			"food": 100
+		});
+	}
+	cmpTrigger.DoAfterDelay(g_resourceLoopTime * 60 * 1000, "ResourceLoop" , {});
+};
+
 var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
 cmpTrigger.playerCivicCenter = {};
+cmpTrigger.females = {};
+cmpTrigger.playerTrack = {};
+
 cmpTrigger.DoAfterDelay(0, "InitGame", {});
 cmpTrigger.DoAfterDelay(1000, "InitializeEnemyWaves", {});
+cmpTrigger.DoAfterDelay(g_resourceLoopOffset * 60 * 1000, "ResourceLoop" , {});
 
-cmpTrigger.RegisterTrigger("OnOwnershipChanged", "DefeatPlayerOnceCCIsDestroyed", { "enabled": true });
+cmpTrigger.RegisterTrigger("OnOwnershipChanged", "HandleSpecialEntities", { "enabled": true });
