#include "/include/empire_lib.as"

const string@ str_Queued_ColonyShips = "cs_q", str_QueuedTime_ColonyShips = "cs_t";

const string@ str_difficulty = "Difficulty", str_cheats = "Cheats", strVictory = "Victory";
const string@ str_aiOwned = "AIOwned";

bool initialized = false;

double queuedTimeout = 60.0 * 10.0;
const float checkResTime = 2.f * 60.f;
const float systemSearchInterval = 2.f * 60.f;

//AI Cheats
const float AIResGenPerSecond = 25000.f; //Resource generation per second for cheating AI. Scales up per minute of game time

void init_basic_ai() {
	if(!initialized) {
		loadDefaults(true);
		initialized = true;
	}
}

void prep_basic_ai_defaults(Empire@ emp) {
}

//Focus (interface)
//=================
//Focuses are specific aspects of an empire that the AI can consider
//Focuses may act, but acting should be left to Tasks
//NOTE: Focus switches are delayed a few seconds, to emulate the player
interface Focus {	
	//update
	//======
	//Called every tick where this is the active focus
	//Return true to indicate the end of the focus, which will free the reference
	bool update(Empire@ emp, EmpireAIData@ data, float tick);
};

//Task (interface)
//================
//Tasks are specific actions that the AI takes
//Tasks are performed every tick, one per tick at the fastest
interface Task {
	//update
	//======
	//Called every tick where this is the active task
	//Return true to indicate that the task is finished
	bool update(Empire@ emp, EmpireAIData@ data, float tick);
};

enum FleetMood {
	FM_Explore,
	FM_Defend,
	FM_Assault
};

//Empire  0 = Us
//Empires 1+ = Enemies
//TODO: Work with allies
float DefenseSearch(const System@ sys, SysSearchSettings& search, const SysStats& stats) {
	uint empCount = search.empireCount;
	if(empCount <= 1)
		return 0;
	
	Empire@ us = search.getEmpire(0);
	float presence = stats.getStat(us, str_presence);
	//Require our presence
	if(presence < 0.5f)
		return 0;
	
	float enemyPresence = 0;
	for(uint i = 1; i < empCount; ++i)
		enemyPresence += stats.getStat(search.getEmpire(i), str_presence);
	
	//Inflate enemy presence
	enemyPresence = (enemyPresence * 1.5f) + 5.f;
	
	//Respond to places we're likely to lose, and larger places that are losing
	return enemyPresence * sqrt(presence);
}

//Empire  0 = Us
//Empires 1+ = Enemies
float AttackSearch(const System@ sys, SysSearchSettings& search, const SysStats& stats) {
	uint empCount = search.empireCount;
	if(empCount <= 1)
		return 0;
	
	Empire@ us = search.getEmpire(0);
	
	//Pay attention only to recently visited systems
	if(!sys.isVisibleTo(us) &&
		stats.getStat(us, str_visited) < float(gameTime - 120.0))
		return 0;
	
	float weight = 1;
	
	//Find the smallest grouping of enemies
	for(uint i = 1; i < empCount; ++i) {
		if (!sys.isVisibleTo(search.getEmpire(i)))
			continue;
		float enemyLevel = stats.getStat(search.getEmpire(i), str_presence);
		if(enemyLevel < 0.5f)
			continue;
		weight /= (1.f + enemyLevel);
	}
	
	if(weight != 1.f) //Found anything
		return weight;
	
	return 0; //Nothing to attack
}

float CheatingAttackSearch(const System@ sys, SysSearchSettings& search, const SysStats& stats) {
	uint empCount = search.empireCount;
	if(empCount <= 1)
		return 0;
	
	Empire@ us = search.getEmpire(0);
	float weight = 1;
	
	//Find the smallest grouping of enemies
	for(uint i = 1; i < empCount; ++i) {
		if (!sys.isVisibleTo(search.getEmpire(i)))
			continue;
		float enemyLevel = stats.getStat(search.getEmpire(i), str_presence);
		if(enemyLevel < 0.5f)
			continue;
		weight /= (1.f + enemyLevel);
	}
	
	if(weight != 1.f) //Found anything
		return weight;
	
	return 0; //Nothing to attack
}

//Empire 0 = Us
float ExploreSearch(const System@ sys, SysSearchSettings& search, const SysStats& stats) {
	Empire@ us = search.getEmpire(0);
	//Avoid systems we have a presence in
	if (sys.isVisibleTo(us))
		return 0;

	return randomf(0.8f, 1.2f);
}

//Empire 0 = us
//Empire 1 = space
//Empires 2+ = enemies (meta 1 = enemy)
float ColonizeSearch(const System@ sys, SysSearchSettings& search, const SysStats& stats) {
	Empire@ us = search.getEmpire(0);
	
	//Require our presence
	if(!sys.isVisibleTo(us))
		return 0;
	
	//Find places with planets remaining
	int planetCount = int(stats.getStat(search.getEmpire(1), str_planets));
	if(planetCount <= 0)
		return 0;
	
	int ourPlanets = int(stats.getStat(us, str_planets));
	if(ourPlanets >= planetCount)
		return 0;
	
	double curTime = gameTime;
	if(stats.getStat(us, str_QueuedTime_ColonyShips) >= float(curTime - queuedTimeout)) {
		int queued = int(stats.getStat(us, str_Queued_ColonyShips));
		if(queued >= planetCount)
			return 0;
		if(queued > ourPlanets)
			ourPlanets = queued;
	}
	
	planetCount -= ourPlanets;
	if(planetCount <= 0)
		return 0;
	
	//And no enemies...
	uint empCount = search.empireCount, isEnemy;
	for(uint i = 2; i < empCount; ++i) {
		Empire@ other = search.getEmpire(i, isEnemy);
		int otherPlanetCount = int(stats.getStat(other, str_planets));
		
		planetCount -= otherPlanetCount;
		if(planetCount <= 0)
			return 0;
		
		if(isEnemy != 0) {
			if(stats.getStat(other, str_presence) > float(otherPlanetCount + 2))
				return 0;
		}
	}
	
	return float(planetCount);
}

float CheatingColonizeSearch(const System@ sys, SysSearchSettings& search, const SysStats& stats) {
	Empire@ us = search.getEmpire(0);
	
	//Find places with planets remaining
	int planetCount = int(stats.getStat(search.getEmpire(1), str_planets));
	if(planetCount <= 0)
		return 0;
	
	int ourPlanets = int(stats.getStat(us, str_planets));
	if(ourPlanets >= planetCount)
		return 0;
	
	double curTime = gameTime;
	if(stats.getStat(us, str_QueuedTime_ColonyShips) >= float(curTime - queuedTimeout)) {
		int queued = int(stats.getStat(us, str_Queued_ColonyShips));
		if(queued >= planetCount)
			return 0;
		if(queued > ourPlanets)
			ourPlanets = queued;
	}
	
	planetCount -= ourPlanets;
	if(planetCount <= 0)
		return 0;
	
	//And no enemies...
	uint empCount = search.empireCount, isEnemy;
	for(uint i = 2; i < empCount; ++i) {
		Empire@ other = search.getEmpire(i, isEnemy);
		int otherPlanetCount = int(stats.getStat(other, str_planets));
		
		planetCount -= otherPlanetCount;
		if(planetCount <= 0)
			return 0;
		
		if(isEnemy != 0) {
			if(stats.getStat(other, str_presence) > float(otherPlanetCount + 2))
				return 0;
		}
	}
	
	return float(planetCount);
}

float NewSysSearch(const System@ sys, SysSearchSettings& search, const SysStats& stats) {
	Empire@ us = search.getEmpire(0);
	//Avoid systems we have a presence in
	if(stats.getStat(us, str_aiOwned) > 0.f)
		return 0;
	float planets = stats.getStat(us, str_planets);
	return randomf(0.8f, 1.2f) * planets;
}

class EmpireAIData {	
	bool log;

	//Personality
	AIPersonality@ pers;
	
	//Difficuly
	uint difficulty;
	bool cheats;

	float focusDelay;
	float tradeMargin;
	
	//State tracking vars
	bool enemiesChanged;
	float thinkDelay;
	float nextSystemSearch;
	float nextPrepare;
	float nextResCheck;

	string@ designateGovernor;
	float[] resourceState;
	
	//Relations vars
	int[] currentEnemies;
	DiploAttitude attitude;
	
	//As the emperor gets bored, he'll be more likely to go to war
	float boredom;

	bool initializedDesigns;
	
	//Memory vars
	Object@ homeworld;
	Object@ capital_ship;
	System@ capital_dest;
	System@ lastColonizeDest;
	
	ShipDesign@[] shipDesigns;
	WeightList colonizeSystemDesigns;
	WeightList militarySystemDesigns;
	WeightList defenseSystemDesigns;
	
	Focus@[] empireFocuses;
	Focus@[] systemFocuses;
	FleetManager@[] fleetFocuses;
	Focus@ curFocus;
	Task@[] tasks;
	
	set_int busyObjects;
	
	SysSearchSettings sss_defend;
	SysSearchSettings sss_attack;
	SysSearchSettings sss_explore;
	SysSearchSettings sss_colonize;
	SysSearchSettings sss_new_sys;
	
	void setDiffVars(uint Difficulty, bool Cheats) {
		difficulty = Difficulty;
		cheats = Cheats;
		focusDelay = max(0.25f, 4.f - float(Difficulty));
		tradeMargin = 1.f + (0.05f * float(Difficulty));
	}
	
	EmpireAIData(Empire@ emp) {
		log = logEmpire; logEmpire = false;
		initializedDesigns = false;
		addTask(SetupHomeworld(gameTime < 1.f));

		// Pick a random personality
		uint personality = round(emp.getSetting("Personality"));
		if (personality > 0 && personality <= personalities.length()) {
			@pers = personalities[personality-1];
		}
		else {
			@pers = pickRandomPersonality();
			for (uint i = 0; i < personalities.length(); ++i) {
				if (pers.id == personalities[i].id) {
					emp.setSetting("Personality", float(i+1));
				}
			}
		}
		
		setDiffVars(uint(emp.getSetting(str_difficulty)), emp.getSetting(str_cheats) >= 0.5f);

		// Prepare traits only when the game is just starting
		if (gameTime < 1.f) {
			EmpireTraits traits;
			traits.prepare(emp);

			// Set shipset
			emp.setRandomShipSet();

			// Add traits
			if (traits.getCount() == 0) {
				for (uint i = 0; i < pers.traits.length(); ++i)
					emp.addTrait(pers.traits[i]);
			}
		}
		
		enemiesChanged = false;
		thinkDelay = 5; //Think after a few seconds to acquire an early snapshot
		nextSystemSearch = 0;
		nextPrepare = 0;
		attitude = DA_Aggressive;
		
		prepare(emp);
		
		addEmpireFocus(Diplomacy());
		addEmpireFocus(ResearchManager(emp, this));
		addEmpireFocus(DesignShips());

		@designateGovernor = null;
		nextResCheck = checkResTime;

		resourceState.resize(pers.checkResources.length());

		if(log)
			print("Logging Enabled: " + emp.getName());
	}
	
	EmpireAIData(Empire@ emp, XMLReader@ xml) {
		init_basic_ai();
		
		log = logEmpire; logEmpire = false;
		attitude = DA_Aggressive;
		initializedDesigns = true;
		
		setDiffVars(3, false);
		
		nextSystemSearch = systemSearchInterval;
		nextPrepare = 0;

		@designateGovernor = null;
		nextResCheck = checkResTime;
		@pers = null;
		
		while(xml.advance()) {
			string@ name = xml.getNodeName();
			switch(xml.getNodeType()) {
				case XN_Element:
				if(name == "vars") {
					while(xml.advance()) {
						string@ varName = xml.getNodeName();
						if(xml.getNodeType() == XN_Element) {
							string@ value = xml.getAttributeValue("v");
							if(varName == "think")
								thinkDelay = s_to_f(value);
							else if(varName == "findsys")
								nextSystemSearch = s_to_f(value);
							else if(varName == "diff")
								difficulty = s_to_i(value);
							else if (varName == "cheats")
								cheats = value == "true";
							else if (varName == "hw")
								@homeworld = getObjectByID(s_to_i(value));
							else if (varName == "pers")
								@pers = getPersonalityByID(value);
						}
						else if(xml.getNodeType() == XN_Element_End && varName == "vars") {
							break;
						}
					}
				}
				else if(name == "dsgns") {
					uint index = 0;
					while(xml.advance()) {
						string@ node_name = xml.getNodeName();
						if(xml.getNodeType() == XN_Element && node_name == "d") {
							ShipDesign@ design = ShipDesign(xml.getAttributeValue("n"));
							design.goalID = s_to_i(xml.getAttributeValue("g"));
							shipDesigns.resize(index + 1);
							@shipDesigns[index] = @design;
							++index;
						}
						else if(xml.getNodeType() == XN_Element_End && node_name == "dsgns") {
							if(shipDesigns.length() == 0) {
								uint realLayoutCount = 0, designCount = pers.startingDesigns.length();
								shipDesigns.resize(designCount);
								for(uint i = 0; i < designCount; ++i)
									@shipDesigns[i] = @pers.startingDesigns[i];
							}
							break;
						}
					}
				}
				else if(name == "sys_list") {
					while(xml.advance()) {
						string@ nodeName = xml.getNodeName();
						if(xml.getNodeType() == XN_Element && nodeName == "sys") {
							SystemManager@ manager = SystemManager(getObjectByID( s_to_i(xml.getAttributeValue("id")) ).toSystem());
							manager.mode = SystemModes( s_to_i( xml.getAttributeValue("mode") ) );
							addSystemFocus( manager );
						}
						else if(xml.getNodeType() == XN_Element_End && nodeName == "sys_list") {
							break;
						}
					}
				}
				else if(name == "fleet_list") {
					while(xml.advance()) {
						string@ nodeName = xml.getNodeName();
						if(xml.getNodeType() == XN_Element && nodeName == "fleet") {
							FleetManager@ manager = FleetManager(emp, xml.getAttributeValue("name"));
							manager.fleetSizeReq = s_to_i( xml.getAttributeValue("req") );
							addFleetFocus( manager );
						}
						else if(xml.getNodeType() == XN_Element_End && nodeName == "fleet_list") {
							break;
						}
					}
				}
				break;
			}
		}
		
		if (pers is null)
			@pers = pickRandomPersonality();

		// Set shipset
		if (pers.shipset != "")
			emp.setShipSet(pers.shipset);

		resourceState.resize(pers.checkResources.length());
		
		addEmpireFocus(Diplomacy());
		addEmpireFocus(ResearchManager(emp, this));
		addEmpireFocus(DesignShips());

		setDiffVars(difficulty, cheats);

		prepare(emp);
		
		if(log)
			print("Logging Enabled: " + emp.getName());
	}
	
	void save(XMLWriter@ xml) {
		xml.createHeader();
		
		xml.addElement("vars", false);
		xml.addElement("think", true, "v", ftos_nice(thinkDelay));
		xml.addElement("findsys", true, "v", ftos_nice(nextSystemSearch));
		xml.addElement("diff", true, "v", i_to_s(difficulty));
		xml.addElement("cheats", true, "v", cheats ? "true" : "false");
		xml.addElement("hw", true, "v", i_to_s(homeworld is null ? 0 : homeworld.uid));
		xml.addElement("pers", true, "v", pers.id);
		xml.closeTag("vars");
		
		if(shipDesigns.length() > 0) {
			xml.addElement("dsgns", false);
			for(uint i = 0; i < shipDesigns.length(); ++i) {
				ShipDesign@ design = shipDesigns[i];
				if (design !is null)
					xml.addElement("d", true, "n", design.className, "g", i_to_s(design.goalID));
			}
			xml.closeTag("dsgns");
		}
		
		if(systemFocuses.length() > 0) {
			xml.addElement("sys_list", false);
			for(uint i = 0; i < systemFocuses.length(); ++i) {
				SystemManager@ sys = cast<SystemManager>(@systemFocuses[i]);
				xml.addElement("sys", true, "id", i_to_s(sys.sys.toObject().uid), "mode", i_to_s(sys.mode));
			}
			xml.closeTag("sys_list");
		}
		

		if(fleetFocuses.length() > 0) {
			xml.addElement("fleet_list", false);
			for(uint i = 0; i < fleetFocuses.length(); ++i) {
				FleetManager@ fm = @fleetFocuses[i];
				string@ name = null;
				if (@fm.fleetName != null)
					@name = @fm.fleetName;
				else if (@fm.commander != null)
					@name = @fm.commander.toHulledObj().getFleet().getName();

				if (@name != null)
					xml.addElement("fleet", true, "name", fm.fleetName, "req", i_to_s(fm.fleetSizeReq));
			}
			xml.closeTag("fleet_list");
		}

		//TODO: Save Tasks
	}
	
	uint getEnemyCount() {
		uint count = 0, listLen = currentEnemies.length();
		for(uint i = 0; i < listLen; ++i)
			if(currentEnemies[i] >= 0)
				count += 1;
		return count;
	}
	
	void addTask(Task@ task) {
		uint len = tasks.length();
		tasks.resize(len + 1);
		@tasks[len] = @task;
	}
	
	void addEmpireFocus(Focus@ focus) {
		uint pos = empireFocuses.length();
		empireFocuses.resize(pos + 1);
		@empireFocuses[pos] = @focus;
	}
	
	void addSystemFocus(Focus@ focus) {
		uint pos = systemFocuses.length();
		systemFocuses.resize(pos + 1);
		@systemFocuses[pos] = @focus;
	}
	
	bool hasSystemInFocus(System@ sys) {
		uint len = systemFocuses.length();
		for(uint i = 0; i < len; ++i)
			if(cast<SystemManager>(@systemFocuses[i]).sys is sys)
				return true;
		return false;
	}
	
	void addFleetFocus(FleetManager@ focus) {
		uint pos = fleetFocuses.length();
		fleetFocuses.resize(pos + 1);
		@fleetFocuses[pos] = @focus;
	}

	void removeFleetFocus(FleetManager@ focus) {
		for(uint i = 0, cnt = fleetFocuses.length(); i < cnt; ++i) {
			if(fleetFocuses[i] is focus) {
				fleetFocuses.erase(i);
				return;
			}
		}
	}
	
	void removeFocus(Focus@ focus) {
		for(uint i = 0, cnt = systemFocuses.length(); i < cnt; ++i) {
			if(systemFocuses[i] is focus) {
				systemFocuses.erase(i);
				return;
			}
		}
		for(uint i = 0, cnt = empireFocuses.length(); i < cnt; ++i) {
			if(empireFocuses[i] is focus) {
				empireFocuses.erase(i);
				return;
			}
		}
	}
	
	void switchFocusRand() {
		//Simulate human behavior by causing a short delay when changing the focus
		thinkDelay = randomf(focusDelay,focusDelay + 1.f);
		
		//Pick all focuses with equal probability
		uint ef = empireFocuses.length(), sf = systemFocuses.length(), ff = fleetFocuses.length();
		uint foci = ef + sf + ff;
		if(foci == 0) {
			@curFocus = null;
			return;
		}
		uint pick = uint(rand(foci-1));
		if(pick < ef) {
			@curFocus = @empireFocuses[pick];
		}
		else {
			pick -= ef;
			if(pick < sf) {
				@curFocus = @systemFocuses[pick];
			}
			else {
				pick -= sf;
				@curFocus = @fleetFocuses[pick];
			}
		}
	}
	
	void prepare(Empire@ thisEmp) {
		//Make sure at least one system is set to expansion mode
		uint systemCount = systemFocuses.length();
		if(systemCount > 0) {
			bool hasColonizationSystem = false;
			for(uint i = 0; i < systemCount; ++i) {
				SystemManager@ system = cast<SystemManager>(systemFocuses[i]);
				if(system.mode == SM_Colonize) {
					hasColonizationSystem = true;
					break;
				}
			}
			
			if(!hasColonizationSystem) {
				SystemManager@ system = cast<SystemManager>(systemFocuses[rand(systemCount-1)]);
				system.mode = SM_Colonize;
			}
		}

		// Set callbacks
		if (cheats) {
			sss_defend.setCallback("DefenseSearch");
			sss_attack.setCallback("CheatingAttackSearch");
			sss_explore.setCallback("ExploreSearch");
			sss_colonize.setCallback("CheatingColonizeSearch");
			sss_new_sys.setCallback("NewSysSearch");
		}
		else {
			sss_defend.setCallback("DefenseSearch");
			sss_attack.setCallback("AttackSearch");
			sss_explore.setCallback("ExploreSearch");
			sss_colonize.setCallback("ColonizeSearch");
			sss_new_sys.setCallback("NewSysSearch");
		}
	
		nextPrepare = 2.5f * 60.f;
		Empire@ space = getEmpireByID(-1);
		
		//Check for our current war enemy
		currentEnemies.resize(0);
		uint eCount = getEmpireCount();
		for(uint i = 0; i < eCount; ++i) {
			const Empire@ other = getEmpire(i);
			if(other is thisEmp || !other.isValid()) {
				continue;
			} else if(thisEmp.isEnemy(other)) {
				int curEnemyCount = currentEnemies.length();
				
				currentEnemies.resize(curEnemyCount + 1);
				currentEnemies[curEnemyCount] = other.ID;
			}
		}
		
		uint enemyCount = currentEnemies.length();
		
		//Defend
		sss_defend.clearEmpires();
		sss_defend.addEmpire(thisEmp);
		for(uint i = 0; i < enemyCount; ++i)
			sss_defend.addEmpire( getEmpireByID(currentEnemies[i]) );
		
		//Attack
		sss_attack.clearEmpires();
		sss_attack.addEmpire(thisEmp);
		for(uint i = 0; i < enemyCount; ++i)
			sss_attack.addEmpire( getEmpireByID(currentEnemies[i]) );
		
		//Explore
		sss_explore.clearEmpires();
		sss_explore.addEmpire(thisEmp);
		
		//Colonize
		sss_colonize.clearEmpires();
		sss_colonize.addEmpire(thisEmp);
		sss_colonize.addEmpire(space);
		for(uint i = 0; i < eCount; ++i) {
			const Empire@ check = getEmpire(i);
			if(check.isValid() == false || check is thisEmp)
				continue;
			if(check.isEnemy(thisEmp))
				sss_colonize.addEmpire( check, 1 );
			else
				sss_colonize.addEmpire( check, 0 );
		}
		
		//Search for new systems
		sss_new_sys.clearEmpires();
		sss_new_sys.addEmpire(thisEmp);
		
		pers.pickColonizePriorities(colonizeSystemDesigns, cheats, difficulty);
		pers.pickMilitaryPriorities(militarySystemDesigns, cheats, difficulty);
		pers.pickDefensePriorities(defenseSystemDesigns, cheats, difficulty);
	}
	
	ShipDesign@ pickRandomLayout(GoalID goal) {
		uint cnt = shipDesigns.length();
		
		ShipDesign@[] matches;
		matches.resize(cnt);
		uint matchCount = 0;
		
		for(uint i = 0; i < cnt; ++i)
			if(shipDesigns[i].goalID == goal)
				@matches[matchCount++] = @shipDesigns[i];
		
		if(matchCount == 0)
			return null;
		return matches[rand(matchCount - 1)];
	}
	
	void addGoodSystem(Empire@ emp) {
		if(log)
			print(emp.getName() + " looking for new system");
		nextSystemSearch = systemSearchInterval;
		addTask(FindNewSys(this));
	}

	void tick(Empire@ emp, float time) {
		// If we're dead, we don't want to do *anything*
		if (emp.getSetting(strVictory) > 1.5f)
			return;

		nextPrepare -= time;
		nextResCheck -= time;

		if (!initializedDesigns) {
			initializedDesigns = true;

			ResearchWeb web;
			web.prepare(emp);
			
			uint realLayoutCount = 0, designCount = pers.startingDesigns.length();
			shipDesigns.resize(designCount);
			for(uint i = 0; i < designCount; ++i) {
				if (pers.startingDesigns[i].hasDesign(emp)) {
					@shipDesigns[realLayoutCount++] = pers.startingDesigns[i];
				}
				else if (pers.startingDesigns[i].canCreate(emp, web)) {
					if (pers.startingDesigns[i].generateForEmpire(emp, true))
						@shipDesigns[realLayoutCount++] = pers.startingDesigns[i];
				}
			}
			shipDesigns.resize(realLayoutCount);

			web.prepare(null);
		}

		if(cheats) {
			float gen = time * float(difficulty / 2 + 1) * AIResGenPerSecond * (gameTime / 60.0);
			emp.addStat("Metals", gen);
			emp.addStat("Electronics", gen * 0.4f);
			emp.addStat("AdvParts", gen * 0.2f);
			emp.addStat("Food", gen);
		}
		
		if(enemiesChanged) {
			prepare(emp);
			enemiesChanged = false;
		}
		else if(thinkDelay <= time) { //Look for changes we aren't notified of
			uint enemies = 0, empCount = getEmpireCount();
			for(uint i = 0; i < empCount; ++i) {
				const Empire@ enemy = getEmpire(i);
				if(enemy.ID == emp.ID || !emp.isValid())
					continue;
				if(emp.isEnemy(enemy))
					enemies += 1;
			}
			
			if(enemies != currentEnemies.length() || nextPrepare < 0.f)
				prepare(emp);
		}

		boredom += time * (1.f + float(difficulty)/3.f);
		thinkDelay -= time;
		nextSystemSearch -= time;
		if(thinkDelay <= 0) {
			thinkDelay = 0.5f;
			
			if(nextSystemSearch <= 0.f) {
				addGoodSystem(emp);
			}
			else {
				if(curFocus is null) {
					switchFocusRand();
				}
				else {
					//TODO: This tick is quite wrong
					if(curFocus.update(emp, this, time)) {
						removeFocus(curFocus);
						@curFocus = null;
					}
				}
			}
			
			uint taskCnt = tasks.length(), maxTasks = clamp(rand(tasks.length()/2),5,40);
			for(uint i = 0; i < taskCnt && i < maxTasks; ++i)
				if(tasks[i].update(emp,this,time)) {
					tasks.erase(i);
					--i; --taskCnt; --maxTasks;
				}
		}

		if (designateGovernor is null) {
			if (cheats) {
				// Pick a random governor
				@designateGovernor = pers.randomGovernors[rand(pers.randomGovernors.length() - 1)];
			}
			else if (nextResCheck < 0) {
				nextResCheck = checkResTime;

				if (randomf(1.f) > pers.randomGovernorChance) {
					// Pick a governor based on resources
					for (uint i = 0; i < pers.checkResources.length(); ++i) {
						float prev = resourceState[i];
						float cur = emp.getStat(pers.checkResources[i]);
						resourceState[i] = cur;

						if (cur < prev || cur < 10.f) {
							@designateGovernor = pers.assignGovernors[i];
							break;
						}
					}
				}
				else {
					// Pick a random governor
					@designateGovernor = pers.randomGovernors[rand(pers.randomGovernors.length() - 1)];
				}
			}
		}
	}

	void onDebugMessage(Empire@ emp, string@ arg1, string@ arg2, string@ arg3) {
		warning("Debug Message:");
		if (arg1 !is null)
			warning(" .. Arg1: "+arg1);
		if (arg2 !is null)
			warning(" .. Arg2: "+arg1);
		if (arg3 !is null)
			warning(" .. Arg3: "+arg1);
	}

	void onDiplomaticMessage(Empire@ emp, Empire@ from, DiploMsg@ msg) {
		// If we're dead, we don't want to do *anything*
		if (emp.getSetting(strVictory) > 1.5f)
			return;

		switch(msg.getTopic()) {
			case DT_War:  //Declare war
				//Why I ought'a...!
				enemiesChanged = true;
				break;
			case DT_Peace:  //Request peace
				{
					//Will they beat us if we continue?
					float relStr = getRelativeStrength(emp, from);
					if(relStr < -pers.warTolerance) {
						emp.sendMessage(from, DT_Accept, relStr < -pers.warTolerance * 2.f ? DM_Begging : DM_Neutral, attitude);
						enemiesChanged = true;
						msg.setResponse(true);
					} else {
						emp.sendMessage(from, DT_Reject, relStr > (2.5f - pers.warTolerance) ? DM_Angry : DM_Neutral, attitude);
						msg.setResponse(false);
					}
				}
				break;
		}
		msg.finish();
	}

	Object@ getClosestFleetCommander(Empire@ emp, Object@ obj, float& distanceSQ) {
		vector pos = obj.getPosition();

		float minDist = 0.f;
		Object@ minCommander = null;
		OrderList orders;

		for (uint i = 0, cnt = fleetFocuses.length(); i < cnt; ++i) {
			FleetManager@ fm = fleetFocuses[i];

			if (!fm.validate(orders, emp, true)) {
				continue;
			}

			if (@fm != null) {
				Object@ commander = fm.commander;
				if (@commander != null) {
					float dist = commander.getPosition().getDistanceFromSQ(pos);

					if (log)
						print("Fleet "+commander.toHulledObj().getFleet().getName()+" is at "+dist);

					if (minDist == 0.f || dist < minDist) {
						minDist = dist;
						@minCommander = commander;
					}
				}
			}
		}

		distanceSQ = minDist;
		return minCommander;
	}

	void pruneObsoleteDesigns(Empire@ emp) {
		// Remove designs that are marked obselete from the build lists
		uint cnt = shipDesigns.length();
		for (uint i = 0; i < cnt; ++i) {
			const HullLayout@ layout = emp.getShipLayout(shipDesigns[i].className);

			if (layout is null || layout.obsolete) {
				shipDesigns.erase(i);
				--i; --cnt;
			}
		}
	}
};

class SetupHomeworld : Task {
	bool initial;

	SetupHomeworld(bool Initial) {
		initial = Initial;
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		EmpireObjects objects;
		objects.prepare(emp);

		// Keep track of how many structures of each type we've removed
		int[] removed;
		uint sysRemoveCnt = data.pers.homeworldRemove.length();
		removed.resize(sysRemoveCnt);

		for (uint i = 0; i < sysRemoveCnt; ++i)
			removed[i] = 0;
		
		do {
			Planet@ pl = objects.getObject();
			if(@pl != null) {
				if (initial) {
					PlanetStructureList list;
					list.prepare(pl);
					uint strCnt = list.getCount();

					// Remove structures we don't want
					for (uint i = 0; i < strCnt; ++i) {
						const subSystem@ sys = list.getStructure(i);

						// Check if it's in the remove list
						for (uint j = 0; j < sysRemoveCnt; ++j) {
							if (getSubSystemDefByName(data.pers.homeworldRemove[j]).ID == sys.type.ID &&
									removed[j] < data.pers.homeworldRemoveCount[j]) {
								pl.removeStructure(i);
								--strCnt; --i;
								++removed[j];
								break;
							}
						}
					}

					// Build all initial structures
					for (uint i = 0; i < data.pers.homeworldBuildOrder.length(); ++i) {
						pl.buildStructure(getSubSystemDefByName(data.pers.homeworldBuildOrder[i]));
					}

					pl.setUseGovernor(true);
				}
				@data.homeworld = pl;
				break;
			}
		} while(objects.nextObject());
		
		objects.prepare(null);
		
		return true;
	}
};

bool buildShipSomewhere(Empire@ who, System@ where, const HullLayout@ what, float minWeight, uint buildAmount, float shipyardMult) {
	SysObjList objects;
	objects.prepare(where);
	
	Object@ best = null;
	const subSystemDef@ shipyard = getSubSystemDefByName("ShipYard");
	float val = 0;
	
	for(uint i = 0; i < objects.childCount; ++i) {
		Object@ obj = objects.getChild(i);
		Planet@ pl = obj.toPlanet();
		if(@pl == null || @obj.getOwner() != @who)
			continue;
		uint queueSize = obj.getConstructionQueueSize();
		uint shipyardCount = pl.getStructureCount(shipyard);
		string@ gov = pl.getGovernorType();
		if(queueSize > 0)
			continue;
		float thisWeight = float(shipyardCount)+(float(pl.getStructureCount())/10.f);
		if(thisWeight > val) {
			val = thisWeight;
			@best = @obj;
		}
	}
	
	if(@best == null || val < minWeight)
		return false;

	//If we have a shipyard multiplier, act accordingly
	if(shipyardMult > 0.01f) {
		buildAmount = ceil(buildAmount*max(1, best.toPlanet().getStructureCount(shipyard))*shipyardMult);
	}
	
	best.makeShip(what, buildAmount);
	return true;
}

float getRelativeStrength(Empire@ compareUs, Empire@ compareTo) {
	float us = compareUs.getStat("str");
	float to = max(compareTo.getStat("str"), 1.f);
	return (us - to) / max(to,10.f);
}

float getStrengthRatio(Empire@ compareUs, Empire@ compareTo) {
	float us = compareUs.getStat("str");
	float to = max(compareTo.getStat("str"), 1.f);
	return max(us,1.f) / max(to,1.f);
}

class FindNewSys : Task {
	SysSearchSettings search;
	
	FindNewSys(EmpireAIData@ data) {
		search = data.sss_new_sys;
		search.findBestSystem();
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(search.searchFinished == false)
			return false;
		System@ sys = search.getBestSystem();
		if(@sys != null) {
			SystemManager@ manager = SystemManager(sys);

			float expandWeight = 0.f;
			uint numSystems = data.systemFocuses.length();

			if (numSystems == 0)
				expandWeight = 1.f;
			else if (numSystems < 5 || getGameTime()/60.0 < 20.f)
				expandWeight = 0.5f;
			else
				expandWeight = 1.f/numSystems;

			manager.mode = randomf(1.f) <= expandWeight ? SM_Colonize : SM_Militarize;

			data.addSystemFocus(manager);
			if(data.log)
				print(emp.getName() + " found new system: " + sys.toObject().getName());
		}
		else {
			if(data.log)
				print(emp.getName() + " found no new system");
		}
		return true;
	}
};

class SendDefenseShip : Task {
	Object@ ship;
	SysSearchSettings search;
	
	SendDefenseShip(Object@ AttackShip, Empire@ emp, EmpireAIData@ data) {
		@ship = @AttackShip;
		data.busyObjects.insert(ship.uid);
		search = data.sss_defend;
		search.setSearchPos(ship.getPosition());
		search.findBestSystem();
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(!ship.isValid()) {
			data.busyObjects.erase(ship.uid);
			return true;
		}
		
		if(search.searchFinished == false)
			return false;
		System@ sys = search.getBestSystem();
		if(@sys != null && @sys != @ship.getParent()) {
			OrderList orders;
			if(orders.prepare(ship)) {
				orders.clearOrders(false);
				orders.giveGotoOrder(sys, true);
				orders.giveGotoOrder(ship.getParent(), true);
				orders.prepare(null);
			}
			data.addTask(ConsiderAvailable(ship.uid, 30.f));
			return true;
		}
		else {
			data.busyObjects.erase(ship.uid);
			return true;
		}
	}
};

class SendAttackShip : Task {
	Object@ ship;
	SysSearchSettings search;
	bool firstStage;
	
	SendAttackShip(Object@ AttackShip, Empire@ emp, EmpireAIData@ data) {
		@ship = @AttackShip;
		data.busyObjects.insert(ship.uid);
		search = data.sss_defend;
		search.setSearchPos(ship.getPosition());
		search.findBestSystem();
		firstStage = true;
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(!ship.isValid()) {
			data.busyObjects.erase(ship.uid);
			return true;
		}
		
		if(search.searchFinished == false)
			return false;
		System@ sys = search.getBestSystem();
		if(@sys != null && @sys != @ship.getParent()) {
			OrderList orders;
			if(orders.prepare(ship)) {
				orders.clearOrders(false);
				orders.giveGotoOrder(sys, true);
				orders.giveGotoOrder(ship.getParent(), true);
				orders.prepare(null);
			}
			data.addTask(ConsiderAvailable(ship.uid, 30.f));
			return true;
		}
		else if(firstStage) {
			firstStage = false;
			search = data.sss_attack;
			search.setSearchPos(ship.getPosition());
			search.findBestSystem();
			return false;
		}
		else {
			data.busyObjects.erase(ship.uid);
			return true;
		}
	}
};

class SendColonyShip : Task {
	Object@ ship;
	SysSearchSettings search;
	
	SendColonyShip(Object@ ColonyShip, Empire@ emp, EmpireAIData@ data) {
		@ship = @ColonyShip;
		data.busyObjects.insert(ship.uid);
		search = data.sss_colonize;
		search.setSearchPos(ship.getPosition());
		//search.setAvoidSystem(data.lastColonizeDest);
		search.findBestSystem();
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(!ship.isValid()) {
			data.busyObjects.erase(ship.uid);
			return true;
		}
		
		if(search.searchFinished == false)
			return false;
		
		System@ sys = search.getBestSystem();
		if(@sys != null && @sys != @ship.getParent()) {
			OrderList orders;
			if(orders.prepare(ship)) {					
				double curTime = gameTime;
				Object@ sysObj = sys;
				
				float prevQueue = sysObj.getStat(emp, str_Queued_ColonyShips);
				if(prevQueue > 0.5 && prevQueue >= sysObj.getStat(getEmpireByID(-1), str_planets)) { //Don't send off the colony ship if it's pointless
					data.addTask(ConsiderAvailable(ship.uid, 30.f));
					return true;
				}
				
				if(prevQueue < 1 || sysObj.getStat(emp, str_QueuedTime_ColonyShips) < float(curTime - queuedTimeout))
					sysObj.setStat(emp, str_Queued_ColonyShips, 1);
				else
					sysObj.setStat(emp, str_Queued_ColonyShips, prevQueue + 1);
				sysObj.setStat(emp, str_QueuedTime_ColonyShips, float(curTime));
				
				orders.clearOrders(false);
				orders.giveGotoOrder(sys, false);
				orders.giveGotoOrder(ship.getParent(), true);
				orders.prepare(null);
			}
		}
		data.addTask(ConsiderAvailable(ship.uid, 30.f));
		return true;
	}
};

class SendFleet : Task {
	Object@ ship;
	SysSearchSettings search;
	bool firstStage;
	bool finished;
	bool assault_mode;
	System@ dest;
	
	SendFleet(Object@ Commander, Empire@ emp, EmpireAIData@ data, bool assault) {
		@ship = @Commander;
		data.busyObjects.insert(ship.uid);
		if(assault)
			search = data.sss_defend;
		else
			search = data.sss_explore;
		search.setSearchPos(ship.getPosition());
		search.findBestSystem();
		firstStage = true;
		finished = false;
		assault_mode = assault;
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(!ship.isValid()) {
			data.busyObjects.erase(ship.uid);
			return true;
		}
		
		if(search.searchFinished == false)
			return false;
		System@ sys = search.getBestSystem();
		if(@sys != null) {
			if(@sys != @ship.getParent()) {
				OrderList orders;
				if(orders.prepare(ship)) {
					orders.clearOrders(false);
					orders.giveGotoOrder(sys, true);
					orders.giveGotoOrder(ship.getParent(), true);
					orders.prepare(null);
				}
				data.addTask(ConsiderAvailable(ship.uid, 30.f));
			}
			else {
				data.busyObjects.erase(ship.uid);
			}
			@dest = @sys;
			finished = true;
			return true;
		}
		else if(firstStage && assault_mode) {
			firstStage = false;
			search = data.sss_attack;
			search.setSearchPos(ship.getPosition());
			search.findBestSystem();
			return false;
		}
		else {
			data.busyObjects.erase(ship.uid);
			finished = true;
			return true;
		}
	}
};

class DockShip : Task {
	Object@ ship;
	Object@ dockIn;
	
	DockShip(Object@ DockingShip, Object@ DockIn, EmpireAIData@ data) {
		@ship = @DockingShip;
		@dockIn = @DockIn;
		data.busyObjects.insert(ship.uid);
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(!ship.isValid()) {
			data.busyObjects.erase(ship.uid);
			return true;
		}
		
		OrderList orders;
		if(orders.prepare(ship)) {
			orders.clearOrders(false);
			orders.giveDockOrder(dockIn, true);
			orders.prepare(null);
		}
		data.addTask(ConsiderAvailable(ship.uid, 30.f));
		return true;
	}
};

class RetrofitShip : Task {
	Object@ ship;
	Object@ retrofitAt;
	
	RetrofitShip(Object@ RetrofitingShip, Object@ RetrofitAt, EmpireAIData@ data) {
		@ship = @RetrofitingShip;
		@retrofitAt = @RetrofitAt;
		data.busyObjects.insert(ship.uid);
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(!ship.isValid()) {
			data.busyObjects.erase(ship.uid);
			return true;
		}
		
		OrderList orders;
		if(orders.prepare(ship)) {
			orders.clearOrders(false);
			orders.giveRetrofitOrder(retrofitAt, true);
			orders.prepare(null);
		}
		data.addTask(ConsiderAvailable(ship.uid, 30.f));
		return true;
	}
};

class ConsiderAvailable : Task {
	uint id;
	float duration;
	
	ConsiderAvailable(uint UID, float inSeconds) {
		id = UID;
		duration = inSeconds;
	}
	
	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		duration -= tick;
		if(duration > 0)
			data.addTask(this);
		else
			data.busyObjects.erase(id);
		return true;
	}
}

enum SystemModes {
	SM_Colonize,
	SM_Militarize,
};

//SystemManager (from Focus)
//========================
//This focus performs various system-level tasks:
//  Builds ships
//  Orders ships stationed in the system around
class SystemManager : Focus {
	System@ sys;
	SystemModes mode;
	SysPresence pr;
	int colonyShips;
	int scoutShips;

	float bestFree;
	
	SystemManager(System@ Sys) {
		@sys = @Sys;
		mode = SM_Colonize;
		colonyShips = 0;
		scoutShips = 0;
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		sys.toObject().setStat(emp, str_aiOwned, 1.f);
		data.switchFocusRand();
		if(data.log)
			print("AI: System Manager (" + sys.toObject().getName() + ")");
		if(@sys != null) { //Not sure when this could happen, but make sure
			//Check if the system is under attack
			emp.getSystemPresence(sys, pr);

			bool underAttack = pr.enemies >= 0.1f;
			
			//Build a ship in our system
			{
				GoalID goal = GID_Fight;
				SystemModes curMode = mode;
				uint buildAmount = 1;
				float shipyardMult = 0;

				if (underAttack)
					//System is under attack, temporarily behave like we're a 
					//military system
					curMode = SM_Militarize;
				else if (sys.toObject().getStat(getEmpireByID(-1), str_planets) >= 0)
					//System is not fully colonized, temporarily behave like we're
					//a colonization system
					curMode = SM_Colonize;

				switch(curMode) {
					case SM_Colonize:
						if (gameTime/60.f < 5.f && scoutShips < 4 && !data.cheats) {
							// In the first 5 minutes, if we don't have scouts, spam them
							goal = GID_Explore;
							break;
						}
						else if (rand(3)+2 >= colonyShips) {
							goal = GoalID(data.colonizeSystemDesigns.pickRand());
							break;
						}
					case SM_Militarize:
					{
						//Every so often, build a couple of defensive ships
						float chanceDefensive;

						if (underAttack)
							chanceDefensive = data.pers.defensesChanceContested;
						else
							chanceDefensive = data.pers.defensesChance;

						if(data.log)
							print("Defense chance: "+f_to_s(chanceDefensive));

						if(randomf(1.f) >= chanceDefensive)
							goal = GoalID(data.militarySystemDesigns.pickRand());
						else
							goal = GoalID(data.defenseSystemDesigns.pickRand());
					}
					break;
				}

				ShipDesign@ design = data.pickRandomLayout(goal);
				if(@design != null && design.goalID == goal) {
					const HullLayout@ buildShip = @emp.getShipLayout(design.className);

					if (buildShip !is null) {
						if(goal == GID_Explore || goal == GID_StrikeCraft) {
							// Build small ships in short bunches
							buildAmount = (data.difficulty+1)*(data.cheats?2.f:1.f);

							if(goal == GID_StrikeCraft)
								shipyardMult = 0.5f/(buildShip.scale*buildShip.scale)*0.35f;
						}
						else if (goal == GID_Fight || goal == GID_Lead || goal == GID_StaticDefense || goal == GID_FightSpecialised) {
							//If we have lots of shipyards, allow queueing up more fighting ships
							//based on scale, to prevent long systemfocus update lags from interfering
							//with ship-building capabilities.
							shipyardMult = (2.f*max(1.f, data.systemFocuses.length()/15.f))/(buildShip.scale*buildShip.scale);
						}
					
						if(buildShip is null || buildShipSomewhere(emp, sys, buildShip, 2.f, buildAmount, shipyardMult) == false) {
							data.switchFocusRand();
							if(data.log)
								print("  Built a " + design.className);
						}
					}
				}
			}
			
			SysObjList objects;
			objects.prepare(sys);
			
			OrderList orders;
			
			//Limit the number of attack ships we send off, as they should be used for the capital fleet instead
			bool sentOffAttackShip = false;
			bool sentOffRetrofit = false;

			//'Best' planet (most slots) 
			Planet@ bestPlanet = null;
			Planet@ yardPlanet = null;
			int bestPlanetSlots = 0, numPlanets = 0;
			int multiTask = data.difficulty == 5 ? 9999 : data.difficulty*2.f+1.f;
			bool hasResearch = false, hasShipyard = false;

			// We need to track this so we don't send off colony ships if
			// our own system isn't fully colonized yet.
			bool fullyColonized = true;

			scoutShips = colonyShips = 0;
			Empire@ space = getEmpireByID(-1);
			
			uint cnt = objects.childCount;
			for(uint i = 0; i < cnt; ++i) {
				Object@ obj = objects.getChild(i);
				Planet@ pl = obj;
				
				if (@pl != null && obj.getOwner() is space) {
					fullyColonized = false;
				}
				else if(obj.getOwner() is emp) {
					if(@pl != null) {
						string@ govName = pl.getGovernorType();
						++numPlanets;

						if (obj is data.homeworld) {
							hasShipyard = true;
							@yardPlanet = pl;
						}
						else if (!hasShipyard || !hasResearch || data.designateGovernor !is null) {
							if (govName == "default") {
								int slots = pl.getMaxStructureCount()-pl.getStructureCount();
								if (slots >= bestPlanetSlots) {
									bestPlanetSlots = slots;
									@bestPlanet = pl;
								}
							}
							else if (govName == "shipworld") {
								hasShipyard = true;
								@yardPlanet = pl;
							}
							else if (govName == "resworld") {
								hasResearch = true;
							}
						}
					}

					HulledObj@ ship = obj;
					if(ship is null)
						continue;
					const HullLayout@ hull = ship.getHull();
					if(hull is null)
						continue;

					int goal = GoalID(hull.metadata);

					//Has to be done separately so we don't
					//ignore ships that are doing stuff
					if(goal == GID_Colonize)
						++colonyShips;
					else if(goal == GID_Explore) {
						++scoutShips;
					}
				
					if(data.busyObjects.exists(obj.uid))
						continue;
					
					if (hull.obsolete && !sentOffRetrofit && hasShipyard && !underAttack) {
						// Attempt to retrofit
						Planet@ orbiting = ship.inOrbitAround();
						if (orbiting !is null)
							data.addTask(RetrofitShip(obj, orbiting, data));
						else
							data.addTask(RetrofitShip(obj, yardPlanet, data));
					}
					else if(goal == GID_Colonize) {
						//colonize
						if(fullyColonized && multiTask > 0) {
							data.addTask( SendColonyShip( obj, emp, data) );
							--multiTask;
						}
					}
					else if(goal == GID_Fight || goal == GID_FightSpecialised || goal == GID_Carrier) {
						//We need to give up fleet command if the cruiser was recalled from a disabled capital ship
						orders.prepare(obj);

						if(orders.isFleetCommander()) {
							orders.forfeitFleetCommand();
						}

						if (!orders.isInFleet()) {
							//join fleet, defend, or patrol
							float fleetDist = 0.f;
							Object@ comm = data.getClosestFleetCommander(emp, obj, fleetDist);

							if (comm is null || fleetDist < 0.1f || fleetDist > data.pers.fleetMaxDistanceSQ || randomf(1.f) > 0.5f) {
								if(!sentOffAttackShip) {
									data.addTask( SendDefenseShip(obj, emp, data) );
									sentOffAttackShip = true;
								}
							}
							else {
								orders.joinFleet(comm);
							}
						}
					}
					else if (goal == GID_Tanker) {
						orders.prepare(obj);

						if(orders.isFleetCommander()) {
							orders.forfeitFleetCommand();
						}

						if (!orders.isInFleet()) {
							//join fleet, defend, or patrol
							float fleetDist = 0.f;
							Object@ comm = data.getClosestFleetCommander(emp, obj, fleetDist);

							if (comm !is null && fleetDist > 0.1f && fleetDist < data.pers.fleetMaxDistanceSQ) {
								orders.joinFleet(comm);
							}
						}
					}
					else if(goal == GID_Lead) {
						orders.prepare(obj);

						if(!orders.isFleetCommander()) {
							float fleetDist = 0.f;
							Object@ comm = data.getClosestFleetCommander(emp, obj, fleetDist);

							if (comm is null || fleetDist < 0.1f || fleetDist > data.pers.fleetMaxDistanceSQ) {
								// Create a fleet if there are none or all are out of range
								if(obj.getState(str_control).val > 0.f) {
									orders.makeFleet();
									data.addFleetFocus(FleetManager(emp, orders.getFleetName()));
									if(data.log)
										print("  Forming fleet '" + orders.getFleetName() + "'");
								}
							} else {
								// Join nearest fleet
								orders.joinFleet(comm);
							}
						}
					}
				}
			}

			//Set governors, we want:
			if (@bestPlanet != null) {
				if (!hasShipyard) {
					//One shipyard world per system
					bestPlanet.setGovernorType("shipworld");
				}
				else if (!hasResearch && numPlanets >= 4) {
					//One research world for large systems
					bestPlanet.setGovernorType("resworld");
				}
				else if (data.designateGovernor !is null) {
					bestPlanet.setGovernorType(data.designateGovernor);
					@data.designateGovernor = null;
				}
			}
			
			orders.prepare(null);
			objects.prepare(null);
			return false;
		}
		return true;
	}
};

//FleetManager (from Focus)
//========================
//This focus manages a fleet to various ends
class FleetManager : Focus {
	string@ fleetName; Object@ commander; System@ targSystem;
	double nextThink;
	uint fleetSizeReq;
	SendFleet@ fleetMoveOrder;
	
	FleetManager(Empire@ emp, string@ name) {
		if(name is null)
			return;
		@commander = emp.getFleetCommander(name);
		if(commander is null)
			return;
		nextThink = getGameTime();

		Fleet@ fleet = commander.toHulledObj().getFleet();
		if (@fleet != null) {
			fleet.setFormation(FF_Box);
			@fleetName = @fleet.getName();
		}

		fleetSizeReq = 8;
	}

	void splitFleet(Empire@ emp, EmpireAIData@ data) {
		// Splits the fleet into two new ones
		Fleet@ fleet = commander.toHulledObj().getFleet();
		Fleet@ newFleet = null;
		Object@ newLeader = null;

		// First check if we have a leader
		for (uint i = 0; i < fleet.getMemberCount(); ++i) {
			Object@ member = fleet.getMember(i);
			int goal = GoalID(member.toHulledObj().getHull().metadata);

			if (goal == GID_Lead) {
				@newFleet = emp.createFleet(member);
				@newLeader = member;
				break;
			}
		}

		if (newFleet is null)
			return;

		// Split the fleet
		for (uint i = 0; i < fleet.getMemberCount(); i += 2) {
			Object@ member = fleet.getMember(i/2);
			if (@member == null || member is newLeader) continue;
			fleet.removeMember(member);

			newFleet.addMember(member);
		}

		// Add new fleet focus
		data.addFleetFocus(FleetManager(emp, newFleet.getName()));
	}

	bool validate(OrderList& orders, Empire@ emp, bool clearLock) {
		//Validate our references: Get the correct name and correct commander
		if(fleetName is null) {
			if(commander is null || !commander.isValid())
				return false;
			orders.prepare(commander);
			@fleetName = orders.getFleetName();
			if(fleetName is null)
				return false;
			@targSystem = null;

			if (clearLock)
				orders.prepare(null);
		}
		else if(commander is null || !commander.isValid()) {
			@commander = emp.getFleetCommander(fleetName);
			if(commander is null || !commander.isValid())
				return false;
			@targSystem = null;
		}
		return true;
	}
	
	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(fleetMoveOrder !is null) {
			if(fleetMoveOrder.finished) {
				@targSystem = fleetMoveOrder.dest;
				@fleetMoveOrder = null;
			}
			else {
				data.switchFocusRand();
				return false;
			}
		}
	
		OrderList orders;
		if (!validate(orders, emp, false))
			return true;

		if(gameTime < nextThink) {
			data.switchFocusRand();
			return false;
		}
		nextThink = gameTime + 240.0;
		
		orders.prepare(commander);
		if(orders.getFleetSize() == 0) {
			@commander = emp.getFleetCommander(fleetName);
			if(commander is null || !commander.isValid())
				return true;
			@targSystem = null;
			orders.prepare(commander);
		}

		// Check if the leader still has thrust
		if (commander.thrust < 0.001f) {
			orders.forfeitFleetCommand();
			@commander = null;
			return false;
		}

		// Split the fleet if it becomes too big
		uint maxSize = data.pers.fleetBaseSplitSize * data.fleetFocuses.length();
		if (orders.getFleetSize() > maxSize)
			splitFleet(emp, data);
		
		if(data.log)
			print("AI: Fleet Manager (" + fleetName + ")" );
		
		//If the object is no longer owned by us, bring the fleet back
		if(@commander.getOwner() != @emp) {
			@targSystem = null;
			if(commander.isValid()) {
				if(orders.getFleetSize() > 0) {
					orders.clearOrders(false);
					if(@data.homeworld != null)
						orders.giveGotoOrder(data.homeworld, false);
					orders.forfeitFleetCommand();
				}
			}
			@commander = null;
			
			if(data.getEnemyCount() > 0) {
				//We probably lost to an enemy planet or fleet, so increase our response
				fleetSizeReq += min(5, fleetSizeReq);
			}
			
			return false;
		}
		
		System@ inSys = commander.getParent();
		
		//If the commander has stopped for some reason, don't wait
		if(commander.velocity.getLengthSQ() < 1)
			@targSystem = null;
		
		//We aren't at the destination yet, wait patiently
		if(@targSystem != null) {
			if(inSys is targSystem) {
				nextThink = gameTime + 60.0;
				@targSystem = null;
				if(data.log)
					print("  Arrived at " + inSys.toObject().getName());
			}
			data.switchFocusRand();
			return false;
		}
		
		//Once we're large enough, attack
		if(orders.getFleetSize() > 0 && randomf(2.f) > float(fleetSizeReq) / float(orders.getFleetSize()) ) {							
			//Should probably do a defend check here, which supercedes just running around randomly
			@fleetMoveOrder = SendFleet(commander, emp, data, true);
			data.addTask( fleetMoveOrder );
		}
		else {
			@fleetMoveOrder = SendFleet(commander, emp, data, false);
			data.addTask( fleetMoveOrder );
		}
		
		if(data.log && @targSystem != null)
			print("  Moving to " + targSystem.toObject().getName());
		
		data.switchFocusRand();
		orders.prepare(null);
		return false;
	}
};

//DesignShips (from Focus)
//========================
//This focus designs new ship designs as new sub systems are unlocked, or in response to enemy ship designs
class DesignShips : Focus {
	double nextThink;
	
	DesignShips() {
		advanceTick(true, 1);
	}
	
	void advanceTick(bool full, int difficulty) {
		if(full)
			nextThink = gameTime + (11.0 - difficulty * 2) * 60.0;
		else
			nextThink = gameTime + (1.10 - difficulty * 0.2) * 60.0;
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(gameTime > nextThink) {
			if(data.log)
				print("AI: Ship Designer");
			
			uint createDesignCount = data.pers.unlockedDesigns.length();
			
			if(createDesignCount == 0)
				return true;
			
			ShipDesign@ newDesign = @data.pers.unlockedDesigns[rand(createDesignCount - 1)];
			const HullLayout@ preDesigned = emp.getShipLayout(newDesign.className);
			
			if(@preDesigned != null) {
				if(data.log)
					print("  Design alread in use: " + newDesign.className);
				advanceTick(false, data.difficulty);
				data.switchFocusRand();
				return false;
			}
			
			ResearchWeb web; web.prepare(emp);
			if(newDesign.canCreate(emp, web) == false) {
				web.prepare(null);
				if(data.log)
					print("  Could not produce design: " + newDesign.className);
				advanceTick(false, data.difficulty);
				data.switchFocusRand();
				return false;
			}
			web.prepare(null);
			
			//Create and add design
			if (!newDesign.generateForEmpire(emp, true))
				return false;
			
			if(data.log)
				print( ("  " + emp.getName()) + (" created ship design: " + newDesign.className));
			
			// Add new design to build list
			uint designCount = data.shipDesigns.length();
			data.shipDesigns.resize(designCount + 1);
			@data.shipDesigns[designCount] = @newDesign;

			// Remove obsolete designs from the build lists
			data.pruneObsoleteDesigns(emp);
			
			advanceTick(true, data.difficulty);
		}
		data.switchFocusRand();
		return false;
	}
};

//ResearchManager (from Focus)
//=======================
//This focus determines what researches to study at what times
class ResearchManager : Focus {
	const WebItem@ watchTech;
	float goalLevel;
	uint targetTech;
	int link;

	ResearchManager(Empire@ emp, EmpireAIData@ data) {
		switchTechnology(emp, data);
		targetTech = 0;
		link = -1;
	}
	
	void switchTechnology(Empire@ emp, EmpireAIData@ data) {
		goalLevel = 0;
		
		float getNumLevels;
		string@ researchName;
		
		link = -1;
		ResearchWeb web; web.prepare(emp);

		if (gameTime > data.pers.researchTargetDelay && targetTech < data.pers.targetTechs.length() &&
			randomf(1.f) <= data.pers.unlockChance) {
			// Target new technologies
			// Skip over whatever we already have
			getNumLevels = 1.f;

			uint techs = data.pers.targetTechs.length();
			while (targetTech < techs && isUnlocked(web, data.pers.targetTechs[targetTech]))
				++targetTech;

			if (targetTech < techs) {
				if(data.log)
					print("Attempting to unlock "+data.pers.targetTechs[targetTech]);

				// Research a random link from our origin
				@researchName = data.pers.researchForTarget[targetTech];
				const WebItem@ originTech = web.getItem(researchName);

				if (@originTech != null) {
					float progress, cost;

					for (uint i = 0; i < originTech.descriptor.linkCount; ++i) {
						originTech.getLinkLevels(i, progress,cost);
						if (progress < cost) {
							link = i;
							break;
						}
					}
				}
			}
			else {
				web.prepare(null);
				return;
			}
		}
		else {
			getNumLevels = 1.f;

			if (data.pers.priorityTechs.length() > 0 && randomf(1.f) < data.pers.priorityChance)
				@researchName = pickTech(web, data.pers.priorityTechs);
			else
				@researchName = pickTech(web, data.pers.researchTechs);
		}
		
		if(researchName is null)
			return;
		
		const WebItem@ tech = web.getItem(researchName);
		if(tech is null) {
			web.prepare(null);
			return;
		}
		
		@watchTech = @tech;
		
		//Check if we should research links
		if(link < 0) {
			goalLevel = tech.get_level() + getNumLevels;
			web.setActiveTech(tech.descriptor);
		}
		else {
			web.setActiveTech(tech.descriptor,link);
		}
		
		web.prepare(null);
		
		if(data.log)
			print( ("  " + emp.getName() + " now researching '") + (tech.descriptor.name + "' to level " + int(goalLevel)));
	}
	
	bool update(Empire@ emp, EmpireAIData@ data, float tick) {
		if(data.log) {
			print("AI: Research Manager");
			if(@watchTech != null)
				if(link < 0)
					print("  Tech: " + watchTech.get_level() + "/" + goalLevel);
				else
					print("  Tech: Link " + link);
		}
		if(watchTech is null || (link < 0 && watchTech.get_level() >= goalLevel)) {
			switchTechnology(emp, data);
		}
		else if(link >= 0) {
			float progress, cost;
			watchTech.getLinkLevels(link, progress, cost);
			if(progress >= cost)
				switchTechnology(emp, data);
		}
		data.switchFocusRand();
		return false;
	}
};

enum DiploThinkMode {
	DTM_FindWarTarget,
	DTM_RespondToEnemies,
	DTM_RespondToProposals,
	DTM_ConsiderPeace,
	DTM_ConsiderTrade
};

enum DiploNeeds {
	DN_Metals,
	DN_Electronics,
	DN_AdvParts,
	DN_Research,
	DN_Nothing,
};

float approach(float x, float val, float half) {
	return (x * val) / (x + half);
}

//Diplomacy (from Focus)
//======================
//This focus performs all diplomacy
class Diplomacy : Focus {
	DiploThinkMode thinkMode;
	
	Diplomacy() {
		thinkMode = DTM_FindWarTarget;
	}
	
	void considerWar(Empire@ emp, EmpireAIData@ data, float tick) {
		Empire@ possibleEnemy = getEmpire(rand(getEmpireCount() - 1));
				
		if(!possibleEnemy.isValid() || possibleEnemy.ID < 0 || emp.isEnemy(possibleEnemy) || emp.isAllied(possibleEnemy) || !emp.hasMet(possibleEnemy))
			return;
			
		EmpireObjects objects;		
		objects.prepare( emp );
		
		float ourStrength = float(objects.getCount());
		
		//Make sure we have some sort of force to go to war with
		if(ourStrength < 10.f || data.fleetFocuses.length() == 0)
			return;
		
		uint curEnemies = data.currentEnemies.length();
		float totalEnemyStrength = 0;
		for(uint i = 0; i < curEnemies; ++i) {
			if(data.currentEnemies[i] >= 0) {
				objects.prepare( getEmpireByID( data.currentEnemies[i] ));
				totalEnemyStrength += float(objects.getCount());
			}
		}
		
		//Apply an increasing penalty to apparent strength as the number of enemies increases
		totalEnemyStrength *= 0.75f + (0.25f * float(curEnemies));
		
		//If enemies outnumber us already, don't take on more enemies
		if(totalEnemyStrength < ourStrength) {
			//How much do we want to risk ourselves right now? (lower = more risk)
			float requiredFactor = randomf(1.1f, 2.5f - data.pers.warTolerance) * (1.f - (data.boredom / (data.pers.boredomFactor + data.boredom)));
			
			TreatyList treaties;
			treaties.prepare(emp);
			uint tCnt = treaties.getTreatyCount();
			uint foundTreaties = 0;
			for(uint i = 0; i < tCnt; ++i) {
				Treaty@ treaty = treaties.getTreaty(i);
				if(treaty.getFromEmpire() is possibleEnemy || treaty.getToEmpire() is possibleEnemy) {
					//Reduce our effective strength for each treaty we have with this possible enemy
					//Each additional treaty reduces the impact on stability, to counteract treaty spam
					if(foundTreaties < 3)
						ourStrength *= 0.75f + (0.10f * float(foundTreaties));
					else
						break;
					foundTreaties += 1;
				}
			}
			treaties.prepare(null);
			
			objects.prepare( possibleEnemy );
			float otherStrength = objects.getCount();
			objects.prepare(null);
			
			//If we're sufficiently more powerful than all current enemies, declare war
			if(otherStrength > 0 && ourStrength / (totalEnemyStrength + otherStrength) >= requiredFactor) {
				TreatyFactory@ factory = TreatyFactory(emp, possibleEnemy);
				factory.treaty.addClause("war",false);
				factory.propose();
				@factory = null;
				
				data.boredom = 0.f;
				data.enemiesChanged = true;
				if(data.log)
					print("  Declared war on the " + possibleEnemy.getName());
			}
			//else if(data.log) {
			//	print("  No war: Us: " + ourStrength + ", Req%: " + requiredFactor + ", Against: " + totalEnemyStrength);
			//}
		}
	}
	
	void respondToEnemies(Empire@ emp, EmpireAIData@ data, float tick) {
		uint curEnemies = data.currentEnemies.length();
		if(curEnemies == 0)
			return;
		
		Empire@ enemy = getEmpireByID(data.currentEnemies[rand(curEnemies - 1)]);
		if (!emp.hasMet(enemy))
			return;
		
		TreatyList treaties;
		treaties.prepare(emp);
		
		float relStr = getRelativeStrength(emp, enemy);
		if(relStr > 2.f * (1.5f - data.pers.warTolerance)) { //Mock them
			if(randomf(1.f) < 0.075f * data.focusDelay) {
				emp.sendMessage(enemy, DT_Greetings, DM_Angry, DA_Aggressive);
				if(data.log)
					print("  Made fun of the " + enemy.getName() );
			}
			
			//treaties.setAcceptance(enemy, false); //Refuse all of their demands
		}
		else if(relStr < -1.5f * data.pers.warTolerance) { //Beg for mercy
			if(randomf(1.f) < 0.075f * data.focusDelay) {
				emp.sendMessage(enemy, DT_Appease, DM_Angry, DA_Aggressive);
				if(data.log)
					print("  Appeased the " + enemy.getName() );
			}
			
			//treaties.setAcceptance(enemy, true); //Accept all of their demands
		}
		//else {
		//	treaties.setAcceptance(enemy, randomf(1.f) > 0.5f);
		//}
		
		treaties.prepare(null);
	}
	
	void respondToProposals(Empire@ emp, EmpireAIData@ data, float tick) {
		Empire@ other = null;
		for(uint i = 0; i < 3; ++i) {
			Empire@ Other = getEmpire(rand(getEmpireCount()-1));
			if(Other.isValid() && Other.ID >= 0 && @Other != @emp && emp.hasMet(Other)) {
				@other = @Other;
				break;
			}
		}
		if(other is null)
			return;
		
		TreatyList treaties;
		treaties.prepare(emp);
		
		Treaty@ treaty = treaties.getProposedTreaty(other);
		if(treaty is null || @treaty.getToEmpire() != @emp) {
			treaties.prepare(null);
			return;
		}
		
		bool hasPeaceTreaty = false;
		float treatyWeight_plain = 0, treatyWeight_timed = 0, duration = 0.f;
		
		for(uint i = 0; i < treaty.clauseCount; ++i) {
			float clauseWeight_plain = 0, clauseWeight_timed = 0;
			bool reversalConcern = false; //Weighting should take reversal into account
			
			const Clause@ clause = treaty.getClause(i);
			const string@ id = clause.id;
			
			if(id == "trade") {
				clauseWeight_timed = clause.getOption(1).toFloat() / 1000.f;
				const string@ resName = clause.getOption(0).toString();
				if(resName == "Food")
					clauseWeight_timed *= 0.1f;
				else if(resName == "Electronics")
					clauseWeight_timed *= 2.f;
				else if(resName == "AdvParts")
					clauseWeight_timed *= 5.f;
				reversalConcern = true;
			}
			else if(id == "peace") {
				hasPeaceTreaty = true;
				float relStr = getStrengthRatio(emp, other);
				
				if(relStr > 1.f)
					clauseWeight_plain = 1.f - approach(relStr, 2.f, 1.5f); //As we are more powerful, reject peace more often
				else
					clauseWeight_plain = 1.f + (1.f / relStr); //Accept peace more often when we are weaker
				
				if(emp.isEnemy(other))
					clauseWeight_plain = pow(clauseWeight_plain, 2.f);
				clauseWeight_timed = 0.01f * clauseWeight_plain;
			}
			else if(id == "research") {
				reversalConcern = true;
				clauseWeight_timed = 0.001f * clause.getOption(0).toFloat();
			}
			else if(id == "timeout") {
				duration = clause.getOption(0).toFloat();
			}
			
			if(reversalConcern && clause.isReversed()) {
				treatyWeight_plain -= clauseWeight_plain;
				treatyWeight_timed -= clauseWeight_timed;
			}
			else {
				treatyWeight_plain += clauseWeight_plain;
				treatyWeight_timed += clauseWeight_timed;
			}
		}
		
		if(duration <= 0.1f) {
			treaties.setAcceptance(other, false);
			print("    Result: Rejected (No duration)");
			treaties.prepare(null);
			return;
		}
		
		float finalWeight = treatyWeight_plain + (treatyWeight_timed * sqrt(duration));
		
		float finalChance = 0.5f;
		if(finalWeight >= 0)
			finalChance += approach(finalWeight, 0.5f, 2.5f);
		else
			finalChance -= approach(-finalWeight, 0.5f, 2.5f);
		
		if(data.log)
			print("  Proposed treaty from " + other.getName() + " has accept chance: " + finalChance);
		
		bool accepted = randomf(1.f) < finalChance;
		if(data.log)
			if(accepted)
				print("    Result: Accepted");
			else
				print("    Result: Rejected");
		
		treaties.setAcceptance(other, accepted);
		
		if(hasPeaceTreaty && accepted)
			data.enemiesChanged = true;
		
		treaties.prepare(null);
	}
	
	void considerPeace(Empire@ emp, EmpireAIData@ data, float tick) {
		uint curEnemies = data.getEnemyCount();
		if(curEnemies == 0)
			return;
		
		int id = data.currentEnemies[rand(data.currentEnemies.length() - 1)];
		if(id < 0)
			return;
		Empire@ enemy = getEmpireByID(id);
		if (!emp.hasMet(enemy))
			return;
		
		{ //Make sure no prior treaty exists
			TreatyList treaties;
			treaties.prepare(emp);
			
			Treaty@ treaty = treaties.getProposedTreaty(enemy);
			
			treaties.prepare(null);
			if(@treaty != null)
				return;
		}
		
		float relStr = getStrengthRatio(emp, enemy);
		
		if(relStr < 1.f - approach(data.pers.warTolerance + float(curEnemies - 1), 1.f, 2.5f)) { //We're too weak to stand against them
			TreatyFactory@ factory = TreatyFactory(emp, enemy);
			factory.treaty.addClause("peace", false);
			
			ClauseOption@ duration = factory.treaty.addClause("timeout", false).getOption(0);
			duration = 120;
			
			if(relStr < 0.25f + (float(curEnemies - 1) / 10.f)) { //We're very bad off
				Clause@ trade = factory.treaty.addClause("trade", false);
				ClauseOption@ resName = trade.getOption(0), resRate = trade.getOption(1);
				resName = "AdvParts";
				resRate = round(500.f / relStr);
			}
			
			factory.propose();
		}
		else if(relStr > 3.f - (float(curEnemies - 1) / 5.f)) { //We're doing quite well, demand!
			TreatyFactory@ factory = TreatyFactory(emp, enemy);
			factory.treaty.addClause("peace", false);
			
			ClauseOption@ duration = factory.treaty.addClause("timeout", false).getOption(0);			
			duration = 60;
			
			Clause@ trade = factory.treaty.addClause("trade", true);
			ClauseOption@ resName = trade.getOption(0), resRate = trade.getOption(1);
			resName = "AdvParts";
			resRate = round(500.f / relStr);
			
			factory.propose();
		}
	}
	
	float getResLowLevel(float importance) {
		return importance * float(25000.0 * (8.0 + getGameTime() / 60.0) / 8.0);
	}
	
	void considerTrade(Empire@ emp, EmpireAIData@ data, float tick) {
		DiploNeeds need = DN_Research, have = DN_Nothing;
		float needWeight = 999999999.f, haveWeight = 0.f;
		
		float resource, require;
		resource = emp.getStat("Metals");
		require = getResLowLevel(1);
		if(resource < require) {
			needWeight = (require - resource) / 1.f;
			need = DN_Metals;
		}
		else {
			have = DN_Metals;
			haveWeight = resource * 1.f;
		}
		
		resource = emp.getStat("Electronics");
		require = getResLowLevel(1.5f);
		if(resource < require) {
			if(needWeight > (require - resource) / 1.5f) {
				needWeight = (require - resource) / 1.5f;
				need = DN_Electronics;
			}
		}
		else if(haveWeight < resource * 1.5f) {
			have = DN_Electronics;
			haveWeight = resource * 1.5f;
		}
		
		resource = emp.getStat("AdvParts");
		require = getResLowLevel(2.5f);
		if(resource < require) {
			if(needWeight > (require - resource) / 2.5f) {
				needWeight = (require - resource) / 2.5f;
				need = DN_AdvParts;
			}
		}
		else if(haveWeight < resource * 2.5f) {
			have = DN_AdvParts;
			haveWeight = resource * 2.5f;
		}
		
		float canGiveTech = getResLowLevel(0.01f);
		if(haveWeight < canGiveTech) {
			ResearchWeb web; web.prepare(emp);
			canGiveTech = min(canGiveTech, float(web.getResearchRate()));
			web.prepare(null);
			
			if(haveWeight < canGiveTech) {
				have = DN_Research;
			}
		}
		
		if(need == DN_Nothing || have == DN_Nothing)
			return;
		
		Empire@ other = null;
		for(uint i = 0; i < 3; ++i) {
			Empire@ Other = getEmpire(rand(getEmpireCount()-1));
			if(Other.isValid() && Other.ID >= 0 && @Other != @emp && emp.isEnemy(Other) == false && emp.hasMet(Other)) {
				@other = @Other;
				break;
			}
		}
		
		if(other is null)
			return;
		
		float marginLevel = data.tradeMargin; //Amount of resource you'll ask for, compared to the value of what they give
		
		TreatyList treaties;
		treaties.prepare(emp);		
		//Don't propose another treaty
		if(@treaties.getProposedTreaty(other) != null) {
			treaties.prepare(null);
			return;
		}		
		treaties.prepare(null);
		
		TreatyFactory@ factory = TreatyFactory(emp, other);
		Treaty@ treaty = @factory.get_treaty();
		Clause@ clause;
		ClauseOption@ option;
		if(need != DN_Research) {
			@clause = treaty.addClause("trade", true);
			switch(need) {
				//default:
				case DN_Metals:
					@option = clause.getOption(0); option = "Metals";
					@option = clause.getOption(1); option = floor(marginLevel * getResLowLevel(0.005f));
					break;
				case DN_Electronics:
					@option = clause.getOption(0); option = "Electronics";
					@option = clause.getOption(1); option = floor(marginLevel * getResLowLevel(0.0025f));
					break;
				case DN_AdvParts:
					@option = clause.getOption(0); option = "AdvParts";
					@option = clause.getOption(1); option = floor(marginLevel * getResLowLevel(0.001f));
					break;
			}
		}
		else {
			@clause = treaty.addClause("research", true);
			@option = clause.getOption(0); option = floor(marginLevel * getResLowLevel(0.001f));
		}
		
		if(have != DN_Research) {
			@clause = treaty.addClause("trade", false);
			switch(have) {
				//default:
				case DN_Metals:
					@option = clause.getOption(0); option = "Metals";
					@option = clause.getOption(1); option = floor(getResLowLevel(0.005f));
					break;
				case DN_Electronics:
					@option = clause.getOption(0); option = "Electronics";
					@option = clause.getOption(1); option = floor(getResLowLevel(0.0025f));
					break;
				case DN_AdvParts:
					@option = clause.getOption(0); option = "AdvParts";
					@option = clause.getOption(1); option = floor(getResLowLevel(0.001f));
					break;
			}
		}
		else {
			@clause = treaty.addClause("research", false);
			@option = clause.getOption(0); option = floor(getResLowLevel(0.001f));
		}
		
		@clause = treaty.addClause("timeout", false);
		@option = clause.getOption(0); option = rand(1) == 0 ? 900.f : 600.f;
		
		factory.propose();
	}

	bool update(Empire@ emp, EmpireAIData@ data, float tick) {		
		bool endOfDiplomacy = false;
		
		if(thinkMode == DTM_FindWarTarget) {
			if(data.log)
				print("AI: Diplomacy Manager");
			thinkMode = DTM_RespondToEnemies;
			if(randomf(1.f) < 0.375f * data.focusDelay)
				considerWar(emp, data, tick);
		}
		else if(thinkMode == DTM_RespondToEnemies) {
			thinkMode = DTM_RespondToProposals;
			respondToEnemies(emp, data, tick);
		}
		else if(thinkMode == DTM_RespondToProposals) {
			thinkMode = DTM_ConsiderPeace;
			respondToProposals(emp, data, tick);
		}
		else if(thinkMode == DTM_ConsiderPeace) {
			thinkMode = DTM_ConsiderTrade;
			considerPeace(emp, data, tick);
		}
		else if(thinkMode == DTM_ConsiderTrade) {
			thinkMode = DTM_FindWarTarget;
			if(randomf(1.f) < 0.05f * data.focusDelay)
				considerTrade(emp, data, tick);
			endOfDiplomacy = true;
		}
		else { thinkMode = DTM_FindWarTarget; }
		
		if(endOfDiplomacy)
			data.switchFocusRand();
		else
			data.thinkDelay = 0.25f;
		return false;
	}
};
