/////////////////////////////////////////////////////////////////////
// general ai for skirmish game
/////////////////////////////////////////////////////////////////////
// Warzone2100, Pumpkin Studios,
// alex lee.98/99.
//
/////////////////////////////////////////////////////////////////////

//Tile in world units
#define	TILE					128
#define	NONE					(-1)

// These are final rules of the lexical parser
#define	R_REQUEST_HELP			"help me"
#define	R_REQUEST_BEACON		"drop a beacon"
#define	R_REPORT_SAFETY			"i'm ok"
#define R_REQUEST_ALLY			"ally me"

// These are our own messages - lexical parser should be able to handle them
#define M_REQUEST_HELP			"help me!!"
#define M_REQUEST_BEACON		"drop a beacon"
#define M_AFFIRMATIVE_OK		"ok"
#define M_AFFIRMATIVE_ROGER		"roger"
#define M_ANNOYED				"bug off"
#define M_HELPERS_KILLED		"that was all I had.."
#define M_HELP_NO_UNITS			"I don't have anything"

#define	MAX_PROBABILITY			100

// Base threat range in world units
#define	W_BASE_THREAT_RANGE		((17 + (mapWidth + mapHeight) / 2 / 35) * TILE)
#define ALL_ALLIES				-1

#define	BASE_DEFEND_DURATION	(3 * 60)

// Delay before we repeat our request, in seconds
#define HELP_REQUEST_INTERVAL	(3 * 60)

//in secs
#define BEACON_TIMEOUT			30

#define	MAX_DROIDS				150

//don't try to build on oil if there's threat within this range
#define	OIL_THREAT_RANGE		(9 * TILE)

#define	MAX_TRUCKS				9
#define	MIN_TRUCKS				5

//Enter power saving mode when lower than this
#define	LOW_POWER				250

// Start spending spree when above this
#define HIGH_POWER				1250

//Target type values
#define	NO_TARGET_VALUE			0
#define	DROID_TARGET_VALUE		1
#define	OTHER_TARGET_VALUE		2
#define	DEFENSE_TARGET_VALUE	3
#define	RESEARCH_TARGET_VALUE	4
#define	HQ_TARGET_VALUE			5
#define	OIL_TARGET_VALUE		6
#define	FACTORY_TARGET_VALUE	7

#define	UNLIMITED 				(-1)

#define	AA_THREAT_RANGE			(TILE * 12)

#define	MAX_DEFENDERS_RADIUS	(TILE * 40)

#define	MAX_VTOL_DEFEND_RADIUS	(TILE * 25)

// AI will remember max this number of structures
#define	MAX_REBUILD_STRUCT		100

//Total number of technology branches
#define	TECHS					2

//How many best templates to choose from when deciding what template to build
#define	MAX_RANDOM_TEMPLATES	4

private	int				me;			// player for this instance.
public	int				tileExpand;			// rate of exploration

public	int				numScouts,maxScouts;			// aim for...
public	int				numDefenders[TECHS],maxDefenders[TECHS];
public	int				numAttackers[TECHS],maxAttackers[TECHS];
public	int				numCyborgs,maxCyborgs;

public	BODY				bugBody, viperBody, cobraBody, scorpionBody, leopardBody, pythonBody, retributionBody, tigerBody, vengeanceBody, wyvernBody, dragonBody;
public	PROPULSION			vtolPropulsion, trackPropulsion, wheelPropulsion, hoverPropulsion, halftrackPropulsion;
public	WEAPON				atWeapon[5], asWeapon[5], mdWeapon[5];
public	int				numAtWeapons, numAsWeapons, numMdWeapons;

public	int				branchDefault,branchVTOL,techCount[TECHS],maxVtolFacs[TECHS],maxIdleRes,
						maxVTOLs[TECHS],numVtolTargets,vtolTargetWeight[10],numRebuildStat;
public	RESEARCHSTAT	tech[TECHS][30];		//technology for different research branches
public	STRUCTURESTAT	vtolTarget[10],rebuildStat[4];

// structures
private int				baseX,baseY,minx,miny,maxx,maxy;
public	int				numStructs,numIncendrys,numDefStructs,numWallWeaps,numBaseStruct,numLightCyborgs,numFundamental;
private	STRUCTURESTAT	structChoice[5];
public	STRUCTURESTAT	incendiaries[20],structs[13],defStructs[26],wallWeaps[11];
public	STRUCTURESTAT	sensorTower,wall,cornerWall,resLab,powGen,playerHQ,lassat,factory,derrick,cybFactory,
						vtolDefStruct[6],vtolPad,vtolFactory,uplink,baseStruct[8];
public	STRUCTURESTAT	powModule,facModule,resModule,vtolModule;
public	int				extraStruct;

// unit templates
public	int				numTemplates;
public	TEMPLATE		tmpl[70];
private	TEMPLATE		tmplChoice[5];

public	TEMPLATE		cybTempl[10],superCyb[4],cybMechanic,cybEngineer,hovertruck;

public	TEMPLATE		sense[11];
public	int				numSenseTemplates;

public	TEMPLATE		constructor,repairUnit;
public	int				numRepairUnits;

//defend
private GROUP			defendGroup;
private bool			defendbusy;
private BASEOBJ			defendObj;

public  RESEARCHSTAT		nexusDefence;
private  RESEARCHSTAT		research;

//build
private GROUP			buildGroup, rushGroup;
private int			buildX, buildY;
public	FEATURESTAT		oilRes;

// scout
private GROUP			scoutGroup;
private int				scoutX,scoutY;
private int				scoutTLX,scoutTLY,scoutW,scoutH;

// attack
private GROUP			attackGroup;
private BASEOBJ			attackObj,allOutAttack,vtolGrAttackObj[10];

// vtols
private GROUP			vtolDefendGr,vtolAttackGr[10];

// generic
private STRUCTURE		structure,rebuildObj[100];
private DROID			droid;
private FEATURE			feature;
private BASEOBJ			baseobj,baseobj2;
private int				count,count2,result;

private bool			powerSave,_DEBUG,bRunning;
// Hopefully this will be at least as large as MAX_PLAYERS... Why can't I just use MAX_PLAYERS as the array size?!
#define MAX_PLAYERS_HACK 17
private	int				allianceTime[MAX_PLAYERS_HACK];
private int				sender,x,y,beaconX[MAX_PLAYERS_HACK],beaconY[MAX_PLAYERS_HACK],tBeacon[MAX_PLAYERS_HACK],
						tLastHelpRequest,lastHelpPlayer,tHelp,tHelpTimeout,helpX,helpY;
private string			message;

private	int				defendX,defendY,__defendRadiusUnused,tDefendStart,tDefendTimeout,
						defendMoveType,curTech,numVtolAttackGroups,numAttackVtols,
						numDefendVtols,rebuildStructX[MAX_REBUILD_STRUCT],rebuildStructY[MAX_REBUILD_STRUCT],countRebuildStruct;
private	STRUCTURESTAT	rebuildStructStat[MAX_REBUILD_STRUCT];

private int			order;	// callback global
private bool			seenAnyAA;

/////////////////////////////////////////////////////////////////////
// triggers.

#region triggers
trigger reachedTr				(CALL_DROID_REACH_LOCATION, me, ref droid, ref order);
trigger fortifyTr					(every,	1000);
trigger conDroidsTr					(every, 900);	// was 1400
trigger repairDroidsTr				(every, 2600);
trigger basedetailsTr				(every, 600	);
trigger droidBuiltTr				(CALL_NEWDROID,me, ref droid,ref structure);
trigger	structBuiltTr				(CALL_STRUCTBUILT, me, ref droid, ref structure);
trigger droidDestroyedTr			(CALL_DROID_DESTROYED,  me, ref droid);
trigger	structureDestroyedTr		(CALL_STRUCT_DESTROYED, me, ref structure);
trigger	rebuildStructureTr			(every, 50);
trigger consolidateEventTr			(every,	3100);
trigger factoryEventTr				(every, 170	);
trigger cyborgFactoryEventTr		(every, 170	);
trigger chooseScoutAreaTr			(every,	200	);
trigger expandScoutAreaTr			(every,	600	);
trigger scoutMainTr					(every,	150	);
trigger newObjectReportTr			(CALL_OBJ_SEEN, me, ref baseobj, ref baseobj2);
trigger attackStuffTr				(every, 300	);
trigger allOutAttackTr				(every, 4000);
trigger defendWatchTr				(CALL_STRUCT_ATTACKED, me, ref structure, ref baseobj);
trigger defendReturnTr				(every,	500	);
trigger doResearchTr				(CALL_RESEARCHCOMPLETED, ref research, ref structure, me);
trigger vtolDefendTr				(CALL_STRUCT_ATTACKED, me, ref structure, ref baseobj);
trigger vtolStructsTr				(every, 290);
trigger buildVtolsTr				(every, 360);
trigger vtolAttackTr				(every, 150);
trigger vtolEnablerTr				(every, 700);
trigger takeoverTr					(CALL_UNITTAKEOVER, ref droid);
trigger useLassatTr					(every, 3000);
trigger reassignTr					(CALL_PLAYERLEFT,ref count);
trigger formAllianceEventTr			(every,170);
trigger breakAllianceEventTr		(every,3000);
trigger humanAllianceTr				(CALL_ALLIANCEOFFER,ref count, ref count2);
trigger multiMsgTr					(CALL_AI_MSG, me, ref sender, ref message);
trigger	beaconTr					(CALL_BEACON, me, ref sender, ref x, ref y, ref message);
trigger consoleTr					(CALL_CONSOLE, ref sender, ref message);
trigger watchBaseThreatTr			(every, 120);
trigger manageAllyHelpTr			(every, 80);
trigger everySec					(every, 10);
trigger manageDefendLocationTr		(every, 70);
trigger startLevelTr				(CALL_START_NEXT_LEVEL);
trigger chainloadTr				(wait, 1);
trigger slowloadTr				(wait, 13);
trigger checkActivitiesTr			(every, 400);
trigger selectedDroidTr				(CALL_DROID_SELECTED, ref droid);
trigger hostileAATr				(every, 600);	// check for AA each minute
trigger vtolRetargetTr				(CALL_VTOL_RETARGET, me, ref droid);

/* Events */
event	checkForHostileAA;
event	manicWalls;
event	buildFundamentals;
event	newfortify;
event	buildOilDefenseOrRetreat;
event	buildPowerGenerators;
event	conDroids;
event	multiMsgEv;
event	beaconEv;
event	watchBaseThreat;
event	manageAllyHelp;
event	everySecEv;
event	manageDefendLocationEv;
event	structureDestroyed;
event	rebuildStructureEv;
event	doResearch;
event	buildDerrick;
event	buildExpand;

/* Function prototypes */
function int numUnusedDerricks();
function bool haveBeacon(int _player);
function bool beaconTimeout(int _player);
function void processCommand(string _message, int _sender, bool _bBlipMessage);
function bool haveHelpers();
function bool attemptToHelp(int _playerToHelp, int _x, int _y);
function void helpPlayer(int _playerToHelp, int _helpX, int _helpY);
function bool canStopHelpingAlly();
function void stopHelpingAlly();
function bool helpingAlly();
function bool helpAllyTimeout();
function void requestHelp(int _helpX, int _helpY);
function void doRequestHelp(int _helpX, int _helpY);
function bool allyBaseAtLoc(int _ally, int _x, int _y);
function void messagePlayer(int _playerToMessage, string _message, int _probability);
function void messagePlayerAddressed(int _playerToMessage, int _playersToAddress, string _message);
function bool canSeeAllies();
function bool baseInTrouble();
function string m_affirmative();
function void defendLocation(int _defendX, int _defendY, int _tDefendTimeout, bool _bMove);
function void stopDefendingLocation();
function bool defendingLocation();
function bool defendLocationTimeout();
function bool friendlyPlayer(int _playerToCheck);
function void factoryBuildDroid(STRUCTURE _factory);
function void cybFactorBuildCyborg(STRUCTURE _factory);
function void vtolFactoryBuildVtol(STRUCTURE _factory);
function bool insideBase(int _x, int _y);
function int numAlliesInBase(bool _bVtols);
function int numEnemiesInBase(bool _bVtols);
function bool defendingOwnBase();
function int targetTypeValue(BASEOBJ _target);
function int numBitsSet(int _integer);
function int findResearch(int _searchStart, int _techTree);
function void buildRearmPads();
function int numEnemyAAInRange(int _x, int _y, int _range);
function BASEOBJ chooseVtolTarget(bool bExclusiveTarget);
function int getVtolTargetWeight(BASEOBJ _target);
function bool vtolTargetAssigned(BASEOBJ _target);
function int numBuildSameBuilding(STRUCTURESTAT _checkStat, int _x, int _y);
function int totalVtols();
function bool needTank();
function void setTechBranch(int _tech);
function DROID closestIdleTruck(int _x, int _y);
function void buildOnExactLocation(DROID _truck, int _x, int _y, STRUCTURESTAT _stat);
function void rebuildStructures();
function BASEOBJ chooseVtolDefenceTarget(int _x, int _y, int _range, bool bExclusiveTarget);
function int numGroupSameOrder(GROUP _group, int _orderIndex);
function void rearrangeAttackVtols();
function int numStructBusyByType(STRUCTURESTAT _busyStructType);
function bool aiResponsibleForPlayer(int _player);
function void reassignAI();
function void shutDownAI();
function bool buildUnit(TEMPLATE _tankTemplate, STRUCTURE _factory, STRUCTURESTAT _factoryType, bool _bIdleOnly);
function STRUCTURE findIdleStructure(STRUCTURESTAT _structType, bool _bIdleOnly);
#endregion triggers

/////////////////////////////////////////////////////////////////////
// HouseKeeping
event initialisedEvent(CALL_GAMEINIT)
{
	local int player;
	// initialise
	me = getPlayer("Semperfi");
	_DEBUG = FALSE;
	dbgMsgOn(me, _DEBUG);

	extraStruct = 0;
	numRepairUnits = 0;
	allOutAttack = NULLOBJECT;

	tLastHelpRequest = -1;	//when we requested help for the last time
	lastHelpPlayer = -1;		//we are not currently helping anyone
	tHelp = -1;					//when we started helping last time
	tHelpTimeout = -1;			//time when help times out
	helpX = -1;
	helpY = -1;
	defendX = -1;
	defendY = -1;
	tDefendStart = -1;
	tDefendTimeout = -1;
	defendMoveType = -1;		//move or scout
	seenAnyAA = false;

	// set current research branch
	setTechBranch(-1);

	numVtolAttackGroups = 10;
	numAttackVtols = 10;	//num vtols in an attack group
	numDefendVtols = 5;	//num vtols in an attack group

	// setup build group - all initial droids are in buildgroup!
	groupAddArea(buildGroup, me, 0, 0, (mapWidth*128), (mapHeight*128));

	// note where our base is.
	getPlayerStartPosition(me, ref baseX, ref baseY);

	// defence.
	defendbusy = FALSE;

	// setup scouts
	structure = getStructure(factory, me);
	if(structure != NULLOBJECT)
	{
		scoutTLX		= structure.x;
		scoutTLY		= structure.y;
	}
	else
	{
		scoutTLX		= baseX;
		scoutTLY		= baseY;
	}
	scoutW			= 256;
	scoutH			= 256;
	scoutX			= scoutTLX;
	scoutY			= scoutTLY;

	// clear the alliance array...
	player = 0;
	while (player != MAX_PLAYERS)
	{
		allianceTime[player] = 0;
		player = player + 1;
	}

	if(aiResponsibleForPlayer(me))
	{
		bRunning = true;
	}
	else
	{
		bRunning = false;
		shutDownAI();
	}
}

// check whether we have at least one structure of that type
function bool haveStructure(STRUCTURESTAT type)
{
	return getStructure(type, me) != NULLOBJECT;
}

// check if we are getting any income
function bool havePowerSource()
{
	// we don't check buildings being finished here
	return haveStructure(powGen) and haveStructure(derrick);
}

function void dbgPlr(string message)
{
	if (me == selectedPlayer)
	{
		console(message);
	}
}

function void dbgObj(DROID obj, string message)
{
	if (obj.selected)
	{
		console(message);
	}
}

event retargetVtol(vtolRetargetTr)
{
	local	BASEOBJ _obj;

	if (droid != NULLOBJECT)
	{
		_obj = getClosestEnemy(droid.x, droid.y, MAX_VTOL_DEFEND_RADIUS, false, false, me);
		if (_obj != NULLOBJECT)
		{
			orderDroidObj(droid, DORDER_ATTACK, _obj);
		}
	}
}

// for debugging
event selectedDroid(selectedDroidTr)
{
	local int groupcount;

	if (!bRunning or me != selectedPlayer)
	{
		exit;
	}
	if (!hasGroup(droid))
	{
		console("Not in any group");
	}
	if (groupMember(buildGroup, droid))
	{
		console("In build group");
	}
	if (groupMember(rushGroup, droid))
	{
		console("In rush (forward defense construction) group");
	}
	if (groupMember(defendGroup, droid))
	{
		console("In defend group");
	}
	if (groupMember(attackGroup, droid))
	{
		console("In attack group");
	}
	if (groupMember(scoutGroup, droid))
	{
		console("In scout group");
	}
	if (groupMember(vtolDefendGr, droid))
	{
		console("In VTOL defend group");
	}
	groupcount = 0;
	while (groupcount < 10)
	{
		if (groupMember(vtolAttackGr[groupcount], droid))
		{
			console("In VTOL attack group " & groupcount);
		}
		groupcount = groupcount + 1;
	}
}

function bool conCanHelp(DROID mydroid, int bx, int by)
{
	return (mydroid.order != DORDER_HELPBUILD and mydroid.order != DORDER_BUILD and mydroid.order != DORDER_LINEBUILD and droidCanReach(mydroid, bx, by));
}

// I am not sure why we need this hack, but the AI can still end up not researching anything at times
// Also add hack in case trucks get idle
event checkActivities(checkActivitiesTr)
{
	setEventTrigger(doResearch, chainloadTr);
	setEventTrigger(buildFundamentals, slowloadTr);
}

// Count factories. TODO: Cache this value.
function int numFactories()
{
	initEnumStruct(FALSE, factory, me, me);
	structure = enumStruct();
	count = 0;
	while (structure != NULLOBJECT)
	{
		count = count + 1;
		structure = enumStruct();
	}
	return count;
}

// Count VTOL factories. TODO: Cache this value.
function int numVtolFactories()
{
	initEnumStruct(FALSE, vtolFactory, me, me);
	structure = enumStruct();
	count = 0;
	while (structure != NULLOBJECT)
	{
		count = count + 1;
		structure = enumStruct();
	}
	return count;
}

// Count cyborg factories. TODO: Cache this value.
function int numCyborgFactories()
{
	initEnumStruct(FALSE, cybFactory, me, me);
	structure = enumStruct();
	count = 0;
	while (structure != NULLOBJECT)
	{
		count = count + 1;
		structure = enumStruct();
	}
	return count;
}

// Count research labs. TODO: Cache this value.
function int numResearchLabs()
{
	initEnumStruct(FALSE, resLab, me, me);
	structure = enumStruct();
	count = 0;
	while (structure != NULLOBJECT)
	{
		count = count + 1;
		structure = enumStruct();
	}
	return count;
}

// Build something on specific position, grab trucks to do it within tiles range
function bool grabTrucksAndBuildAt(int bx, int by, int range, STRUCTURESTAT bstats)
{
	local DROID	mydroid, closestDroid;
	local int	closestDist, currDist, numHelpDroids, tilerange;

	initIterateGroup(buildGroup);				// find idle droids in build group.
	mydroid = iterateGroup(buildGroup);
	closestDist = 99999;
	closestDroid = NULLOBJECT;
	numHelpDroids = 0;
	tilerange = range * TILE;
	while (mydroid != NULLOBJECT)
	{
		currDist = distBetweenTwoPoints(bx, by, mydroid.x, mydroid.y);
		if (conCanHelp(mydroid, bx, by))
		{
			if (currDist < tilerange)
			{
				orderDroidStatsLoc(mydroid, DORDER_BUILD, bstats, bx, by);	// close, so help build it
				numHelpDroids = numHelpDroids + 1;
				dbgObj(mydroid, "Asked to do short distance construction at (" & bx & ", " & by & ")");
			}
			else if (currDist < closestDist)
			{
				closestDroid = mydroid;	// record this droid as being closest so far
				closestDist = currDist;
			}
		}
		mydroid = iterateGroup(buildGroup);
	}
	if (numHelpDroids == 0 and closestDroid != NULLOBJECT)	// found none within help radius, so force someone to go long distance traveling
	{
		dbgObj(closestDroid, "Asked to do long distance construction work at (" & bx & ", " & by & ")");
		orderDroidStatsLoc(closestDroid, DORDER_BUILD, bstats, bx, by);	// you, book a plane ticket and go!
		return true;
	}
	return (numHelpDroids > 0);
}

// Build something in main base, grab trucks to do it within tiles range
function bool grabTrucksAndBuild(int range, STRUCTURESTAT bstats, int maxBlockingTiles)
{
	local DROID	mydroid, closestDroid;
	local int	closestDist, currDist, numHelpDroids, tilerange, bx, by;

	initIterateGroup(buildGroup);				// find idle droids in build group.
	mydroid = iterateGroup(buildGroup);
	closestDist = 99999;
	closestDroid = NULLOBJECT;
	numHelpDroids = 0;
	tilerange = range * TILE;
	while (mydroid != NULLOBJECT)
	{
		if (conCanHelp(mydroid, baseX, baseY))
		{
			bx = baseX;
			by = baseY;
			if (pickDroidStructLocation(mydroid, bstats, ref bx, ref by, me, maxBlockingTiles))
			{
				currDist = distBetweenTwoPoints(bx, by, mydroid.x, mydroid.y);
				if (currDist < tilerange)
				{
					orderDroidStatsLoc(mydroid, DORDER_BUILD, bstats, bx, by);	// close, so help build it
					numHelpDroids = numHelpDroids + 1;
					dbgObj(mydroid, "Asked to do short distance construction at (" & bx & ", " & by & ")");
				}
				else if (currDist < closestDist)
				{
					closestDroid = mydroid;	// record this droid as being closest so far
					closestDist = currDist;
				}
			}
		}
		mydroid = iterateGroup(buildGroup);
	}
	if (numHelpDroids == 0 and closestDroid != NULLOBJECT)	// found none within help radius, so force someone to go long distance traveling
	{
		dbgObj(closestDroid, "Asked to do long distance construction work at (" & bx & ", " & by & ")");
		orderDroidStatsLoc(closestDroid, DORDER_BUILD, bstats, bx, by);	// you, book a plane ticket and go!
		return true;
	}
	return (numHelpDroids > 0);
}

event arrived(reachedTr)
{
	local bool found;
	local STRUCTURESTAT myChoice;

	if (groupMember(rushGroup, droid))
	{
		dbgObj(droid, "Failed to build where we should - (re)start wall building");
		setEventTrigger(manicWalls, chainloadTr);
		exit;
	}

	if (droid.droidType == DROID_CONSTRUCT or droid.droidType == DROID_CYBORG_CONSTRUCT && getDifficulty(me) > EASY)
	{
		dbgObj(droid, "Failed to build where we should - attempt to screw up enemy oil derrick");

		// Check if at oil well, and it was taken by enemy
		structure = structureBuiltInRange(derrick, droid.x, droid.y, (5 * 128), -1);
		if (structure != NULLOBJECT)
		{
			if (not friendlyPlayer(structure.player) and droid.health == 100)
			{
				// Ok, at enemy derrick, and nobody has hurt us yet. Start being nasty.
				count = 0;
				found = false;
				// find simplest/cheapest one available to build
				while (count < numDefStructs and not found)
				{
					if (isStructureAvailable(defStructs[count], me))
					{
						found = true;
					}
					else
					{
						count++;
					}
				}
				if (found)
				{
					buildX = droid.x;
					buildY = droid.y;
					if (pickDroidStructLocation(droid, defStructs[count], ref buildX, ref buildY, me, -1))
					{
						dbgObj(droid, "Building defense at enemy oil derrick - just to be nasty");
						orderDroidStatsLoc(droid, DORDER_BUILD, defStructs[count], buildX, buildY);
					}
					else
					{
						dbgObj(droid, "Wanted to be nasty, but found nowhere to build defense");
						orderDroid(droid, DORDER_RTB);		// nothing more to do here.
					}
				}
				else
				{
					dbgObj(droid, "Wanted to be nasty, but had nothing nasty to build - returning to base");
					orderDroid(droid, DORDER_RTB);		// oh, well. nothing more to do here.
				}
				exit;
			}
			else if (droid.health < 100 and !insideBase(droid.x, droid.y))
			{
				dbgObj(droid, "Under fire - bolting back to repair!");
				orderDroid(droid, DORDER_RTR);		// bolt back to base now!
				exit;
			}
		}
	}
}

function bool tryUpgrade()
{
	local	STRUCTURE	struct;

	if (not havePowerSource())
	{
		return false;
	}

	if (isStructureAvailable(powModule, me))
	{
		initEnumStruct(FALSE, powGen, me, me);
		struct = enumStruct();
		while (struct != NULLOBJECT)
		{
			if (not testStructureModule(me, struct, 0))
			{
				return grabTrucksAndBuildAt(struct.x, struct.y, 8, powModule);
			}
			struct = enumStruct();
		}
	}
	if (isStructureAvailable(facModule, me))
	{
		initEnumStruct(FALSE, factory, me, me);
		struct = enumStruct();
		while (struct != NULLOBJECT)
		{
			if (not testStructureModule(me, struct, 0))
			{
				return grabTrucksAndBuildAt(struct.x, struct.y, 10, facModule);
			}
			struct = enumStruct();
		}

		initEnumStruct(FALSE, vtolFactory, me, me);
		struct = enumStruct();
		while (struct != NULLOBJECT)
		{
			if (not testStructureModule(me, struct, 0))
			{
				return grabTrucksAndBuildAt(struct.x, struct.y, 10, facModule);
			}
			struct = enumStruct();
		}
	}
	if (isStructureAvailable(resModule, me))
	{
		initEnumStruct(FALSE, resLab, me, me);
		struct = enumStruct();
		while (struct != NULLOBJECT)
		{
			if (not testStructureModule(me, struct, 0))
			{
				return grabTrucksAndBuildAt(struct.x, struct.y, 6, resModule);
			}
			struct = enumStruct();
		}
	}
	return false;	// did not succeed in getting ANY droids busy!
}

// Manage trucks and other constructors
event buildFundamentals(inactive)
{
	local FEATURE	_oil;
	local bool	needPwGen;
	local STRUCTURE	iterstruct, closeststruct;
	local int	closest, proximity;

	needPwGen = false;
	setEventTrigger(buildFundamentals, inactive);

	// Do we need power generators?
	if ((playerPower(me) < HIGH_POWER * 2 or numFactories() > 1) and numUnusedDerricks() > 0)
	{
		needPwGen = true;
		dbgPlr("More power generators needed");
	}

	// Do we need to get oil?
	if (not needPwGen and playerPower(me) < LOW_POWER)
	{
		// Any available?
		initGetFeature(oilRes, -1, me);
		_oil = getFeatureB(me);
		if (_oil != NULLOBJECT)
		{
			dbgPlr("More oil needed - candidate found");
			setEventTrigger(buildDerrick, chainloadTr);
			exit;
		}
	}

	// Help build unfinished buildings
	initIterateGroup(buildGroup);				// find idle droids in build group.
	droid = iterateGroup(buildGroup);
	while (droid != NULLOBJECT)
 	{
		initEnumUnbuilt(me);
		iterstruct = enumUnbuilt();
		closeststruct = NULLOBJECT;
		closest = 99999;
		while (iterstruct != NULLOBJECT)
		{
			proximity = distBetweenTwoPoints(droid.x, droid.y, iterstruct.x, iterstruct.y);
			if (conCanHelp(droid, iterstruct.x, iterstruct.y)
			    and (!needPwGen or iterstruct.stattype == REF_POWER_GEN or iterstruct.stattype == REF_POWER_MODULE)
			    and (proximity < 50 * TILE)
			    and (proximity < closest))
			{
				closeststruct = iterstruct;
				closest = proximity;
			}
			iterstruct = enumUnbuilt();
		}
		if (closeststruct != NULLOBJECT)
 		{
			orderDroidObj(droid, DORDER_HELPBUILD, closeststruct);
			dbgObj(droid, "Aid construction at (" & closeststruct.x & ", " & closeststruct.y & ")");
			if (closeststruct.stattype == REF_POWER_GEN || closeststruct.stattype == REF_POWER_MODULE)
 			{
				needPwGen = false;	// in progress
 			}
 		}
		droid = iterateGroup(buildGroup);
 	}

	if (idleGroup(buildGroup) == 0)
	{
		exit;
	}

	if (needPwGen)
	{
		setEventTrigger(buildPowerGenerators, chainloadTr);
		exit;
	}

	// If we have power, build up base basics
	if (playerPower(me) >= LOW_POWER)
	{
		count = 0;
		while (count < numFundamental)
		{
			// check that struct.
			structure = getStructure(structs[count], me);
			if (structure == NULLOBJECT)				// if missing build it.
			{
				if (isStructureAvailable(structs[count], me))
				{
					if (grabTrucksAndBuild(100, structs[count], 0))
					{
						exit;
					}
				}
			}
			count = count + 1;
		}
	}

	if (playerPower(me) < LOW_POWER)
	{
		exit;	// not much more we can (or should) do - except maybe upgrade?
	}
	else if (playerPower(me) < HIGH_POWER or numFactories() > 1)
	{
		// Ok, basics are done. Now go offensive! We first check for oil derricks to build on, to deny
		// them to the enemy. Then that calls for building defenses there. Failing that, we expand our base.
		// TODO - build at nearby, undefended enemy oil wells
		setEventTrigger(buildDerrick, chainloadTr);
		exit;
	}
	else	// if lots of power or too few factories to replace high-risk oil hunting trucks
	{
		dbgPlr("Too much power - EXPAND!");
		setEventTrigger(buildExpand, chainloadTr);
		exit;
	}
}

event truckRush(inactive)
{
	local int	plr, target, curdist, mindist, checkX, checkY, startX, startY;
	local FEATURE	_oil, _closestOil;
	local bool	found;

	setEventTrigger(truckRush, inactive);

	// Find closest enemy
	plr = 0;
	target = -1;
	mindist = 999999;
	while (plr < MAX_PLAYERS)
	{
		if (!friendlyPlayer(plr))
		{
			if (getPlayerStartPosition(plr, ref checkX, ref checkY))
			{
				curdist = distBetweenTwoPoints(baseX, baseY, checkX, checkY);
				if (curdist < mindist)
				{
					startX = checkX;
					startY = checkY;
					mindist = curdist;
					target = plr;
				}
			}
		}
		plr = plr + 1;
	}
	if (target == -1)
	{
		dbgPlr("Failed to find an enemy!");
		exit;	// oops.
	}
	dbgPlr("Rushing player " & target);

	// Reserve droids
	groupAddGroup(rushGroup, buildGroup);

	// Find oil well closest to closest enemy
	mindist = 999999;
	_closestOil = NULLOBJECT;
	initIterateGroup(rushGroup);
	droid = iterateGroup(rushGroup);
	initGetFeature(oilRes, -1, me);
	_oil = getFeatureB(me);
	while (droid != NULLOBJECT and _oil != NULLOBJECT)
	{
		curdist = distBetweenTwoPoints(startX, startY, _oil.x, _oil.y);
		// Find the closest oil to target player that we can reach safely, and that he can reach safely as well
		if (curdist < mindist and droidCanReach(droid, _oil.x, _oil.y) and safeDest(me, _oil.x, _oil.y) && safeDest(target, _oil.x, _oil.y))
		{
			mindist = curdist;
			_closestOil = _oil;
		}
		_oil = getFeatureB(me);
	}
	if (_closestOil == NULLOBJECT)
	{
		dbgPlr("No hostile oil found to rush (or no trucks available)");
		groupAddGroup(buildGroup, rushGroup); // reverse
		exit;
	}

	// Pick best defense
	mindist = numDefStructs - 1;	// reusing this var to find best def
	found = false;
	while (mindist > 0 and not found)
	{
		if (isStructureAvailable(defStructs[mindist], me))
		{
			found = true;
		}
		else
		{
			mindist = mindist - 1;
		}
	}

	// Pick location for defensive building near for our trucks
	buildX = _closestOil.x;
	buildY = _closestOil.y;
	if (!found || !pickDroidStructLocation(droid, defStructs[mindist], ref buildX, ref buildY, me, -1))
	{
		dbgPlr("Failed to find defense, or failed to find placement for it");
		groupAddGroup(buildGroup, rushGroup); // reverse
		exit;
	}

	// Build hostilities
	while (droid != NULLOBJECT)
	{
		orderDroidStatsLoc(droid, DORDER_BUILD, defStructs[mindist], buildX, buildY);
		droid = iterateGroup(rushGroup);
	}
}

// Try to wall in the enemy with wall defenses at random gateways
event manicWalls(inactive)
{
	local int weap, tempx, tempy;
	local bool found;

	setEventTrigger(manicWalls, inactive);

	// choose best hardpoint type
	weap = numWallWeaps - 1;
	found = false;
	while (weap > 0 and !found)
	{
		if (isStructureAvailable(wallWeaps[weap], me))
		{
			found = true;
		}
		else
		{
			weap = weap - 1;
		}
	}
	if (!found)
	{
		dbgPlr("Had no wall weapons to build to wall in enemy");
		groupAddGroup(buildGroup, rushGroup); // reverse
		exit;
	}

	// Go build on gateways near enemy base
	initIterateGroup(rushGroup);
	droid = iterateGroup(rushGroup);
	while (droid != NULLOBJECT)
	{
		tempx = droid.x;
		tempy = droid.y;
		if (!skDefenseLocationB(ref tempx, ref tempy, wall, wallWeaps[weap], droid, me))
		{
			dbgPlr("Manic wall building ran into an error - stopped");
			groupAddGroup(buildGroup, rushGroup); // reverse
			exit;
		}
		droid = iterateGroup(rushGroup);
	}
}

event startLevel(startLevelTr)
{
	setEventTrigger(conDroids, chainloadTr);
	setEventTrigger(doResearch, chainloadTr);
	if (numFactories() > 1 and isStructureAvailable(defStructs[0], me) and getDifficulty(me) > MEDIUM)
	{
		dbgPlr("TRUCK RUSH!");
		setEventTrigger(truckRush, chainloadTr);
	}
	else
	{
		setEventTrigger(buildFundamentals, slowloadTr);
	}
	setEventTrigger(startLevel, inactive);
}

// decide what technology branch we will use
function void setTechBranch(int _tech)
{
	local float		_y2,_y1,_x2,_x1,_a,_y,_m,_rnd,_mapSize;

	_mapSize = (float)((mapWidth + mapHeight) / 2);

	if(_tech != -1)
	{
		curTech = _tech;
	}
	else
	{
		//probability to choose vtol branch for map size 90 = 0; probability for map size 200 = 45
		//build a linear function: y = ((y2 - y1) / (x2 - x1)) * x + a depending on two values given (short: y = mx+a)
		_x1 = 90.0; _y1 = 0.0;
		_x2 = 200.0; _y2 = 45.0;
		_m = ((_y2 - _y1) / (_x2 - _x1));
		_a = -(_m * _x1);

		//calculate probability for the current map
		_y = _m * _mapSize + _a;

		dbg("_m = " & _m & ", a = " & _a, me);

		_rnd = (float)random(100);
		if(_rnd  < _y)
		{
			curTech = branchVTOL;
			dbgPlr("going air (" & _y & "/" & _rnd & ")");
		}
		else
		{
			curTech = branchDefault;
			dbgPlr("going land (" & _y & "/" & _rnd & ")");
		}
	}
}

/* returns TRUE if AI is responsible for the _player */
function bool aiResponsibleForPlayer(int _player)
{
	if(not _DEBUG and ((_player == selectedPlayer) or not myResponsibility(_player)))
	{
		return FALSE;
	}

	return TRUE;
}

/////////////////////////////////////////////////////////////////////
// keep details about the size and position of the ai players base
event basedetails(basedetailsTr)
{
	local int result, baseRange;

	// clear old extremities.
	maxy = 0;
	maxx = 0;
	miny = (mapHeight*128);
	minx = (mapWidth*128);

	baseRange = 4 * TILE;

	// now find the extremities of our vital structures.
	count = 0;
	while(count < numBaseStruct)
	{
		initEnumStruct(FALSE,baseStruct[count],me,me);
		structure= enumStruct();
		while(structure != NULLOBJECT)
		{
			if(structure.x < minx)
			{
				minx = structure.x;
			}
			if(structure.x > maxx)
			{
				maxx = structure.x;
			}
			if(structure.y < miny)
			{
				miny = structure.y;
			}
			if(structure.y > maxy)
			{
				maxy = structure.y;
			}

			result = distBetweenTwoPoints(baseX, baseY, structure.x, structure.y);

			if(result > baseRange){
				baseRange = result;
			}

			structure= enumStruct();
		}

		count = count + 1;
	}

	result = 3 * 128;
	minx = minx - result;
	maxx = maxx + result;
	miny = miny - result;
	maxy = maxy + result;

	baseRange = baseRange + (4 * 128);
}

// If positive, there are oil derricks that unused due to lack of power generators.
// If negative, we have too many power generator (usually not a problem in itself).
function int numUnusedDerricks()
{
	initEnumStruct(FALSE, derrick, me, me);					// count = numderricks
	structure = enumStruct();
	count = 0;
	while (structure != NULLOBJECT)
	{
		count = count + 1;
		structure = enumStruct();
	}
	initEnumStruct(FALSE, powGen, me, me);					// count2 = numpowgens
	structure = enumStruct();
	count2 = 0;
	while (structure != NULLOBJECT)
	{
		count2 = count2 + 1;
		structure = enumStruct();
	}
	return (count - (count2 * 4));
}

/////////////////////////////////////////////////////////////////////
// structure building rules

// build derricks on oil.
event buildDerrick(inactive)
{
	local bool	foundOne, _same;
	local FEATURE	_oil, _closestOil;
	local int	_bestDist, _newDist;
	local DROID	_search;

	// what if we can't waste power on building derricks because we don't have a gen yet?
	if (playerPower(me) < 300 and haveStructure(derrick) and not haveStructure(powGen))
	{
		setEventTrigger(buildDerrick, slowloadTr);
		exit;
	}

	_bestDist = 99999;
	_closestOil = NULLOBJECT;
	foundOne = false;
	initIterateGroup(buildGroup);				// find all units in build group
	droid = iterateGroup(buildGroup);
	while (droid != NULLOBJECT && !foundOne)
	{
		if (droid.order != DORDER_BUILD and droid.order != DORDER_LINEBUILD and droid.order != DORDER_HELPBUILD)
		{
			foundOne = true;
		}
		else
		{
			droid = iterateGroup(buildGroup);
		}
	}
	if (droid != NULLOBJECT)
	{
		initGetFeature(oilRes, -1, me);
		_oil = getFeatureB(me);
		while (_oil != NULLOBJECT)
		{
			_newDist = distBetweenTwoPoints(droid.x, droid.y, _oil.x, _oil.y);
			_same = false;

			if (_newDist < _bestDist and droidCanReach(droid, _oil.x, _oil.y) and safeDest(me, _oil.x, _oil.y))	// this one is closer
			{
				initIterateGroup(buildGroup);				// find all units in build group.
				_search = iterateGroup(buildGroup);
				foundOne = false;
				while (_search != NULLOBJECT && !foundOne)
				{
					if (_search.orderx == _oil.x and _search.ordery == _oil.y and _search != droid)
					{
						_same = true;
						foundOne = true;
					}
					_search = iterateGroup(buildGroup);
				}
				if (!_same)	// do not go to same spot as another droid
				{
					_bestDist = _newDist;
					_closestOil = _oil;
				}
			}
			_oil = getFeatureB(me);
		}
		if (_closestOil != NULLOBJECT)
		{
			orderDroidStatsLoc(droid, DORDER_BUILD, derrick, _closestOil.x, _closestOil.y); // build a derrick
			dbgObj(droid, "Off to build derrick at ("& _closestOil.x & ", " & _closestOil.y & ")");
			if (idleGroup(buildGroup) > 0)
			{
				setEventTrigger(buildDerrick, slowloadTr);	// do it again for next droid
				exit;
			}
		}
		else
		{
			if (!tryUpgrade())	// try upgrading buildings
			{
				setEventTrigger(buildOilDefenseOrRetreat, chainloadTr);	// need defense?
			}
		}
	}
	setEventTrigger(buildDerrick, inactive);
}

/////////////////////////////////////////////////////////////////////
// if idle and derrick in range and no defense then build defense, else ret to base .
event buildOilDefenseOrRetreat(inactive)
{
	local	int		_numBuilders,_maxBuilders;

	_maxBuilders = 1;

	// check idle.
	initIterateGroup(buildGroup);					// find idle droids in build group.
	droid = iterateGroup(buildGroup);
	while(droid != NULLOBJECT)
	{
		if (droid.order != DORDER_BUILD and droid.order != DORDER_LINEBUILD and droid.order != DORDER_HELPBUILD)
		{
			// if in range of a derrick
			structure = structureBuiltInRange(derrick, droid.x, droid.y, (5*128), me);

			// if inside base limits then presume ok..
			if( structure != NULLOBJECT)
			{
				if((structure.x > minx) and (structure.y > miny) and (structure.x < maxx) and (structure.y <maxy))
				{
					structure = NULLOBJECT;
				}
			}

			if(structure != NULLOBJECT)
			{
				buildX = structure.x;
				buildY = structure.y;

				// not many defenses nearby
				if(numFriendlyWeapStructsInRange(me, buildX, buildY, (3*128), FALSE) < 2)
				{
					count = numDefStructs - 1;							//pick a struct to build..
					count2 = 0;
					while( (count2 < 5) and (count >= 0) )
					{
						if( isStructureAvailable(defStructs[count],me))
						{
							structChoice[count2] = defStructs[count];
							count2 = count2 + 1;
						}
						count = count - 1;
					}
					count =0;
					if(count2 > 0)
					{
						count = random(count2);						 //count = choice!

						// pick a location
						if (pickDroidStructLocation(droid, structChoice[count], ref buildX, ref buildY, me, 1))
						{
							_numBuilders = numBuildSameBuilding(NULLSTRUCTURESTAT, buildX, buildY);

							if (_numBuilders < _maxBuilders)
							{
								// build it.
								orderDroidStatsLoc(droid, DORDER_BUILD,structChoice[count], buildX,buildY);
								_numBuilders++;
							}
						}
					}
				}
				else
				{
					structure = structureBuiltInRange(playerHQ, droid.x, droid.y, (5*128), me);
					if(structure == NULLOBJECT)
					{
						if(!insideBase(droid.x, droid.y))
						{
							orderDroid(droid,DORDER_RTB);				// return to base;
						}
					}
				}
			}
			else
			{
				structure = structureBuiltInRange(playerHQ, droid.x, droid.y, (5*128), me);
				if(structure == NULLOBJECT)
				{
					if(!insideBase(droid.x, droid.y))
					{
						orderDroid(droid,DORDER_RTB);					// return to base;
					}
				}
			}
		}
		droid = iterateGroup(buildGroup);
	}
	if (idleGroup(buildGroup) > 0)
	{
		// Fundamental base already built, so start expanding it if we have too much power. It will call all kinds
		// of wasteful spending routines. Nothing is worse than losing with the coffers full! This event will
		// allow us to spend more, and failing that will call the build defense event instead.
		setEventTrigger(buildExpand, chainloadTr);	// not enough to do, start expanding in depth instead
	}
	setEventTrigger(buildOilDefenseOrRetreat, inactive);
}

/////////////////////////////////////////////////////////////////////
//mortar etc.. rules. build sensor towers and emplacements.
event incendiary(inactive)
{
	local int result;
	local bool boolResult;
	local STRUCTURE structure2;

	if (not isStructureAvailable(sensorTower, me))
	{
		exit;
	}

	initEnumStruct(FALSE,sensorTower,me,me);

	count = 0;
	structure = enumStruct();
	while(structure != NULLOBJECT)
	{
		count = count + 1;
		structure = enumStruct();
	}

	if(count < (gameTime/4200) )		// every 7 mins
	{
		// if not found build a sensor tower.
		// find a place to build.
		buildX = 0;
		buildY = 0;
		initEnumStruct(FALSE,derrick,me,me);
		structure= enumStruct();
		while(structure != NULLOBJECT)
		{
			count = 0;
			result = 0;
			while(count < numDefStructs)
			{
				structure2 = structureBuiltInRange(defStructs[count], structure.x, structure.y,(4*128), me);
				if(structure2 != NULLOBJECT)
				{
					result = result + 1;
				}
				count = count + 1;
			}

			// check for sensor nearby,
			structure2 = structureBuiltInRange(sensorTower, structure.x, structure.y,(5*128), me);
			if(structure2 != NULLOBJECT)
			{
				result = 4;
			}

			if(result < 3)
			{
				buildX = structure.x;
				buildY = structure.y;
				structure = NULLOBJECT;
			}
			else
			{
				structure = enumStruct();
			}
		}

		if(buildX != 0)
		{
			boolResult = pickStructLocation(sensorTower, ref buildX, ref buildY,me);	// pick spot.
			if(boolResult == TRUE)
			{
				// find unit
				initIterateGroup(buildGroup);
				droid = iterateGroup(buildGroup);
				while(droid != NULLOBJECT)
				{
					if ((droid.order == DORDER_NONE or droid.order == DORDER_RTB) and droidCanReach(droid, buildX, buildY))
					{
						orderDroidStatsLoc(droid, DORDER_BUILD, sensorTower, buildX, buildY);
						droid = NULLOBJECT;
					}
					else
					{
						droid = iterateGroup(buildGroup);
					}
				}
			}
		}
	}
	else
	{
		// find a sensor tower with least incendiary structs around it..
		buildX = 0;
		buildY = 0;

		initEnumStruct(FALSE,sensorTower,me,me);
		structure= enumStruct();
		count = 999;
		while(structure != NULLOBJECT)
		{
			// count incendiaries near this tower.
			result = 0;
			count2 = 0;
			while(count2 < numIncendrys)
			{
				structure2 = structureBuiltInRange(incendiaries[count2], structure.x, structure.y,(4*128), me);
				if(structure2 != NULLOBJECT)
				{
					result = result + 1;
				}
				count2 = count2 + 1;
			}

			if((result < 6) and (result < count))		// lowest found yet. only sites with <6 too.
			{
				buildX = structure.x;
				buildY = structure.y;
				count  = result;
			}
			structure = enumStruct();
		}

		if(buildX != 0)
		{
			// choose a device
			count = numIncendrys - 1;
			result = 99;
			while(count >= 0 )
			{
				if(isStructureAvailable(incendiaries[count],me))
				{
					result = count;
					count = -1;
				}
				else
				{
					count = count - 1;
				}
			}

			// find a unit and build an incendiary device.
			if(result != 99)
			{
				boolResult = pickStructLocation(incendiaries[result], ref buildX, ref buildY,me);	// pick spot.
				if(boolResult == TRUE)
				{
					initIterateGroup(buildGroup);
					droid = iterateGroup(buildGroup);

					boolResult = (numBuildSameBuilding(incendiaries[result], buildX, buildY) > 0);	//anyone building there already?

					while(droid != NULLOBJECT and (not boolResult))
					{
						if ((droid.order == DORDER_NONE or droid.order == DORDER_RTB) and droidCanReach(droid, buildX, buildY))
						{
							orderDroidStatsLoc(droid, DORDER_BUILD,incendiaries[result], buildX,buildY);
							boolResult = TRUE;	//only 1 truck
						}
						droid = iterateGroup(buildGroup);
					}
				}
			}
		}
	}
	setEventTrigger(incendiary, inactive);
}

/////////////////////////////////////////////////////////////////////
// build a power gen for every 4 derricks. VITAL!
event buildPowerGenerators(inactive)
{
	if (isStructureAvailable(powGen, me))
	{
		if (not grabTrucksAndBuild(20, powGen, 1))
		{
			dbgPlr("Needed power generator but could not build one");
		}
	}
	setEventTrigger(buildPowerGenerators, inactive);
}

/////////////////////////////////////////////////////////////////////
//  build other stuff, grow the base slowly...
event buildExpand(inactive)
{
	local int factories, reslabs, vtolfacs, cybfacs, total;

	setEventTrigger(buildExpand, inactive);

	if (playerPower(me) < LOW_POWER)
	{
		exit;	// do not expand base with low power
	}

	// Try to keep some balance to the madness
	factories = numFactories();
	reslabs = numResearchLabs();
	vtolfacs = numVtolFactories();
	cybfacs = numCyborgFactories();
	total = factories + reslabs + vtolfacs + cybfacs;

	if (playerPower(me) > LOW_POWER * total)
	{
		dbgPlr("EXPAND! factories=" & factories & " vtolfacs=" & vtolfacs & " cybfacs=" & cybfacs & " reslabs=" & reslabs);
		buildX = baseX;
		buildY = baseY;
		if (isStructureAvailable(cybFactory, me) and (cybfacs < factories))
		{
			if (grabTrucksAndBuild(10, cybFactory, 0))
			{
				dbgPlr("Building more cyborg factories");
			}
			exit;
		}
		if (isStructureAvailable(resLab, me) and (reslabs < factories))
		{
			if (grabTrucksAndBuild(10, resLab, 0))
			{
				dbgPlr("Building more research labs");
			}
			exit;
		}
		if (isStructureAvailable(vtolFactory, me) and (vtolfacs < factories))
		{
			if (grabTrucksAndBuild(10, vtolFactory, 0))
			{
				dbgPlr("Building more VTOL factories");
			}
			exit;
		}
		if (isStructureAvailable(factory, me) and (factories < vtolfacs + cybfacs + reslabs + 1))
		{
			if (grabTrucksAndBuild(25, factory, 0))
			{
				dbgPlr("Building more factories");
			}
			exit;
		}
	}

	if (idleGroup(buildGroup) > 0)
	{
		setEventTrigger(newfortify, slowloadTr);
	}

	extraStruct = extraStruct + 1;
}

/////////////////////////////////////////////////////////////////////
//  fortify base by builiding defensive structs on the edge of the base.
// rewrote fortify to use scrSkDefenseLocation(baseX,baseY,me);
event newfortify(inactive)
{
	local int _numBuilders,_maxBuilders, tempx, tempy;
	local bool boolResult;

	_maxBuilders = 1;
	setEventTrigger(newfortify, inactive);

	if (numGroupSameOrder(buildGroup, DORDER_LINEBUILD) < _maxBuilders)	// only gateway fortifications use line build
	{
		boolResult = FALSE;
		initIterateGroup(buildGroup);				// find idle an idle veh.in build group.
		droid = iterateGroup(buildGroup);
		while((boolResult == FALSE) and (droid != NULLOBJECT))
		{
			if (droid.order != DORDER_BUILD and droid.order != DORDER_LINEBUILD and droid.order != DORDER_HELPBUILD)
			{
				boolResult = TRUE;					// don't do this again!

				tempx = baseX;
				tempy = baseY;

				// choose a suitable turret.
				count = numWallWeaps - 1;
				count2 = 0;
				while ((count2 < 3) and (count >= 0))
				{
					if (isStructureAvailable(wallWeaps[count], me))
					{
						structChoice[count2] = wallWeaps[count];
						count2 = count2 + 1;
					}
					count = count - 1;
				}
				count = 0;
				if ((count2 > 0) and (_numBuilders < _maxBuilders))
				{
					count = random(count2);
					skDefenseLocationB(ref tempx, ref tempy, wall, structChoice[count], droid, me);
					_numBuilders++;
				}

			}
			droid = iterateGroup(buildGroup);
		}
	}
	if (idleGroup(buildGroup) > 0 and !tryUpgrade())
	{
		dbgPlr("No more base defenses to build - trying to build incendiary");
		setEventTrigger(incendiary, slowloadTr);
	}
}


/////////////////////////////////////////////////////////////////////
// droid building rules
/////////////////////////////////////////////////////////////////////
// deal with a droid being built
event droidBuiltAssign(droidBuiltTr)
{
	if(isVtol(droid))
	{
		if(vtolDefendGr.members < numDefendVtols)
		{
			groupAddDroid(vtolDefendGr, droid);
		}
		else
		{
			count = 0;
			while(count < numVtolAttackGroups)
			{
				if(vtolAttackGr[count].members < numAttackVtols)
				{
					dbg("added new vtol to group " & count, me);
					groupAddDroid(vtolAttackGr[count], droid);
					count = numVtolAttackGroups;
				}
				count++;
			}
		}
	}
	else if((droid.droidType != DROID_TRANSPORTER) and (droid.droidType != DROID_COMMAND))
	{

		if((droid.droidType == DROID_REPAIR)
			or (droid.droidType == DROID_CYBORG_REPAIR))
		{
			numRepairUnits = numRepairUnits + 1;
		}

		if((droid.droidType == DROID_CONSTRUCT)
			or (droid.droidType == DROID_CYBORG_CONSTRUCT))				// if constructor droid
		{
			groupAddDroid(buildGroup, droid);
		}
		else
		{
			if(droid.droidType == DROID_CYBORG)
			{
				if (random(2) == 1)
				{
					groupAddDroid(attackGroup, droid);
				}
				else
				{
					groupAddDroid(defendGroup, droid);
				}
			}
			else
			{
				if(scoutGroup.members < numScouts)
				{
					groupAddDroid(scoutGroup, droid);
				}
				else if(attackGroup.members < numAttackers[curTech])
				{
					groupAddDroid(attackGroup, droid);
				}
				else if( defendGroup.members < numDefenders[curTech])
				{
					groupAddDroid(defendGroup, droid);
				}
				else
				{
					if(scoutGroup.members < maxScouts)
					{
						groupAddDroid(scoutGroup, droid);
					}
					else if(attackGroup.members < maxAttackers[curTech])
					{
						groupAddDroid(attackGroup, droid);
					}
					else if( defendGroup.members < maxDefenders[curTech])
					{
						groupAddDroid(defendGroup, droid);
					}
					else	//make them attack
					{
						groupAddDroid(attackGroup, droid);
					}
				}
			}
		}
	}
}

//When droid built: check emergency jobs, start building next droid
event droidBuilt(droidBuiltTr)
{
	local STRUCTURE _fundie;

	if (droid.droidType == DROID_CONSTRUCT)
	{
		setEventTrigger(buildFundamentals, slowloadTr);	// activate
	}

	/* Start building next droid */
	if(structure != NULLOBJECT)
	{
		if (droid.droidType == DROID_CONSTRUCT && structure.stattype == REF_FACTORY)
		{
			setEventTrigger(conDroids, chainloadTr);	// consider building more
		}
		// Continue building new droids right away
		else if(structure.stattype == REF_FACTORY)
		{
			factoryBuildDroid(structure);
		}
		else if(structure.stattype == REF_CYBORG_FACTORY)
		{
			cybFactorBuildCyborg(structure);
		}
		else if(structure.stattype == REF_VTOL_FACTORY)
		{
			vtolFactoryBuildVtol(structure);
		}
	}
}

/* Gets triggered when structure was built */
event structBuilt(structBuiltTr)
{
	local	FEATURE	_oilResource;
	local	int		_count,_count2;

	if (structure == NULLOBJECT || droid == NULLOBJECT)
	{
		exit;
	}

	// Try to do more nasty things with rush group
	if (groupMember(rushGroup, droid))
	{
		if (isStructureAvailable(wallWeaps[0], me))
		{
			dbgPlr("Manic wall code triggered");
			setEventTrigger(manicWalls, chainloadTr);	// try to wall in enemy
			exit;
		}
		else
		{
			dbgPlr("Nothing more to do for rush group - disbanding it!");
			groupAddGroup(buildGroup, rushGroup);
			setEventTrigger(buildFundamentals, slowloadTr);
			exit;
		}
	}

	/* factory or factory module */
	if(structure.stattype == REF_FACTORY)
	{
		if (isStructureAvailable(facModule, me) and (skGetFactoryCapacity(structure) < 2 ) and (getDroidCount(me) > 4))
		{
			orderDroidStatsLoc(droid, DORDER_BUILD,facModule, structure.x,structure.y);	// upgrade it.
		}
		else
		{
			setEventTrigger(conDroids, chainloadTr);
		}
	}
	/* vtol factory or vtol factory module */
	else if(structure.stattype == REF_VTOL_FACTORY)
	{
		if( isStructureAvailable(facModule,me) and (skGetFactoryCapacity(structure) < 2 ))
		{
			orderDroidStatsLoc(droid, DORDER_BUILD,facModule, structure.x,structure.y);	// upgrade it.
		}
	}
	else if(structure.stattype == REF_RESOURCE_EXTRACTOR)
	{
		setEventTrigger(buildDerrick, chainloadTr);
		exit;
	}
	else if (structure.stattype == REF_RESEARCH)
	{
		if (isStructureAvailable(resModule, me))
		{
			orderDroidStatsLoc(droid, DORDER_BUILD, resModule, structure.x, structure.y);	// upgrade it.
		}
		else
		{
			setEventTrigger(doResearch, chainloadTr);
		}
	}
	else if (structure.stattype == REF_POWER_GEN)
	{
		if (isStructureAvailable(powModule, me))
		{
			orderDroidStatsLoc(droid, DORDER_BUILD, powModule, structure.x, structure.y);	// upgrade it.
		}
	}

	// Check if available trucks need to build more absolute necessities right away. We need a trigger here because
	// droids involved in building have not yet come out of their build orders.
	setEventTrigger(buildFundamentals, slowloadTr);

	//see if we have just rebuilt a destroyed structure
	_count = 0;
	while(_count < countRebuildStruct)
	{
		if(structure.x == rebuildStructX[_count] and
		   structure.y == rebuildStructY[_count] and
		   ( (structure.stat == rebuildStructStat[_count]) or //walls can end up as corner walls
		   ( (structure.stat == wall or structure.stat == cornerWall) and
		   (rebuildStructStat[_count] == wall or rebuildStructStat[_count] == cornerWall))
		   ))
		{
			dbg("finished rebuilding destroyed structure - " & _count, me);

			//resort destroyed structures
			_count2 = _count;
			while(_count2 < (countRebuildStruct - 1))
			{
				rebuildStructX[_count2] = rebuildStructX[_count2 + 1];
				rebuildStructY[_count2] = rebuildStructY[_count2 + 1];
				rebuildStructStat[_count2] = rebuildStructStat[_count2 + 1];

				_count2++;
			}

			//clear last entry
			rebuildStructX[countRebuildStruct - 1] = 0;
			rebuildStructY[countRebuildStruct - 1] = 0;
			rebuildStructStat[countRebuildStruct - 1] = NULLSTRUCTURESTAT;

			countRebuildStruct--;	//we just built one structure

			//_count = countRebuildStruct;	//exit outer loop
		}
		_count++;
	}
}

/////////////////////////////////////////////////////////////////////
// deal with attacks.
event droidDestroyed(droidDestroyedTr)
{

	if(droid.droidType == DROID_REPAIR)
	{
		numRepairUnits = numRepairUnits - 1;
	}

	if(droid.droidType == DROID_CONSTRUCT)					// if constructor droid
	{
		initEnumStruct(FALSE,factory,me,me);
		structure= enumStruct();							// find factory.

		if( (structure != NULLOBJECT) and (getDroidCount(me) < MAX_DROIDS) )
		{
			buildDroid(constructor, structure, me, 1);	// build constructor
		}

	}
}


/////////////////////////////////////////////////////////////////////
// build more con droids.
event conDroids(conDroidsTr)
{
	local 	int			_maxTrucks;
	local 	STRUCTURE	_factory;
	local	int 		_numBuilding,_haveTrucks,_maxTruckFactories,_totalTrucks;
	local	bool		_bStartedBuilding;

	_maxTrucks = MAX_TRUCKS;
	if (playerPower(me) < LOW_POWER)
	{
		_maxTrucks = MIN_TRUCKS;
	}

	_maxTruckFactories = 3; 		//max factories to use for truck production

	_haveTrucks = buildGroup.members;

	//Find out how many trucks and combat engineers are already in production
	_numBuilding = numTemplatesInProduction(constructor,me);	//trucks
	_numBuilding = _numBuilding + numTemplatesInProduction(cybEngineer,me);	//engineers

	_totalTrucks = _numBuilding + _haveTrucks;

	initEnumStruct(FALSE,factory,me,me);
	_factory = enumStruct();

	while ((_factory != NULLOBJECT) and (_numBuilding < _maxTruckFactories) and (_totalTrucks < _maxTrucks))
	{
		//Try to build a truck
		if (skCanBuildTemplate(me, _factory, hovertruck))
		{
			_bStartedBuilding = buildUnit(hovertruck, _factory, factory, FALSE);	//build truck even if not idle
		}
		else
		{
			_bStartedBuilding = buildUnit(constructor, _factory, factory, FALSE);	//build truck even if not idle
		}

		//Update statistics if started building a truck
		if(_bStartedBuilding)
		{
			_numBuilding++;
			_totalTrucks++;
		}

		_factory = enumStruct();
	}

	//build cyborg engineers if needed, no building structure limit here
	initEnumStruct(FALSE,cybFactory,me,me);
	_factory = enumStruct();
	while((_factory != NULLOBJECT) and (_totalTrucks < _maxTrucks))
	{
		//Try to build a truck
		if( skCanBuildTemplate(me,_factory, cybEngineer) )	//make sure we have researched cyb engineer
		{
			_bStartedBuilding = buildUnit(cybEngineer, _factory, cybFactory, FALSE);	//build a cyb eng even if not idle

			//Update statistics if started building a cyborg engineer
			if(_bStartedBuilding)
			{
				_numBuilding++;
				_totalTrucks++;
			}
		}

		_factory = enumStruct();
	}
	setEventTrigger(conDroids, conDroidsTr);
}

//Build a droid
function bool buildUnit(TEMPLATE _tankTemplate, STRUCTURE _factory, STRUCTURESTAT _factoryType, bool _bIdleOnly)
{
	//Factory was not provided, find an factory
	if(_factory == NULLOBJECT)
	{
		_factory = findIdleStructure(_factoryType, _bIdleOnly);
	}

	//Build if got a factory
	if(_factory != NULLOBJECT)
	{
		if(structureComplete(_factory) and (getDroidCount(me) < MAX_DROIDS))
		{
			if( !(_bIdleOnly and !structureIdle(_factory)) )	//don't build if only allowed to build whe idle and fac is not idle
			{
				buildDroid(_tankTemplate, _factory, me, 1);	// build a tank
				return TRUE;	//success
			}
		}
	}

	return FALSE;		//failed
}

//Returns an idle structure of the provided type or NULLOBJECT if none found
function STRUCTURE findIdleStructure(STRUCTURESTAT _structType, bool _bIdleOnly)
{
	local	STRUCTURE	_structure;

	initEnumStruct(FALSE,_structType,me,me);
	_structure = enumStruct();
	while(_structure != NULLOBJECT)
	{
		if(structureComplete(_structure))
		{
			if( !(_bIdleOnly and !structureIdle(_structure)) )
			{
				return _structure;
			}
		}

		_structure = enumStruct();
	}

	return NULLOBJECT;	//none found
}

/////////////////////////////////////////////////////////////////////
// build repair droids.
event repairDroids(repairDroidsTr)
{
	// if we're running low on repair droids, build some..
	if(numRepairUnits <3)
	{
		initEnumStruct(FALSE,factory,me,me);
		structure= enumStruct();							// find factory.
		if (structure != NULLOBJECT)
		{
			if ((getDroidCount(me) < MAX_DROIDS) and (skCanBuildTemplate(me, structure, repairUnit)))
			{
				buildDroid(repairUnit, structure, me, 1);	// build repairunit.
			}
		}
	}
}


/////////////////////////////////////////////////////////////////////
event factoryEvent(factoryEventTr)
{
	// for each factory....
	initEnumStruct(FALSE,factory,me,me);
	structure = enumStruct();								// find factory.
	if(getDroidCount(me) < MAX_DROIDS)
	{
		while(structure != NULLOBJECT)
		{
			if( structureIdle(structure) )
			{
				factoryBuildDroid(structure);
			}

			structure = enumStruct();
		}
	}
}

function bool needTank()
{
	if(not havePowerSource())
	{
		return FALSE;
	}

	if((defendGroup.members < maxDefenders[curTech]) or (maxDefenders[curTech] == UNLIMITED))
	{
		return TRUE;
	}

	if((scoutGroup.members < maxScouts) or (maxScouts == UNLIMITED))
	{
		return TRUE;
	}

	if((attackGroup.members < maxAttackers[curTech]) or (maxAttackers[curTech] == UNLIMITED))
	{
		return TRUE;
	}

	return FALSE;
}

function void factoryBuildDroid(STRUCTURE _factory)
{
	local int _count,_count2;

	if(_factory == NULLOBJECT){
		dbgPlr("factoryBuildDroid: factory is NULLOBJECT");
		return;
	}

	if(not needTank())
	{
		dbgPlr("factoryBuildDroid: NEED NO TANKS!! limit=" & maxDefenders[curTech]);
		return;
	}

	if( structureIdle(_factory) )
	{
		_count = numTemplates - 1;
		_count2 = 0;
		while( (_count2 < MAX_RANDOM_TEMPLATES) and (_count >= 0) )
		{
			if( skCanBuildTemplate(me,_factory, tmpl[_count]) )
			{
				tmplChoice[_count2] = tmpl[_count];
				_count2 = _count2 + 1;
			}
			_count = _count - 1;
		}

		if(_count2 > 0)
		{
			buildDroid(tmplChoice[random(_count2)],_factory,me,1);
		}
	}
	else
	{
		dbgPlr("factoryBuildDroid: Factory is busy!");
	}
}

/////////////////////////////////////////////////////////////////////
// put cyborg factories to work
event cyborgFactoryEvent(cyborgFactoryEventTr)
{
	if(not ((defendGroup.members < maxCyborgs) or (maxCyborgs == UNLIMITED)))
	{
		exit;		//we need no cyborgs
	}

	initEnumStruct(FALSE,cybFactory,me,me);
	structure= enumStruct();								// find factory.

	while(structure != NULLOBJECT)
	{
		if( structureIdle(structure) == TRUE)
		{
			cybFactorBuildCyborg(structure);
		}
		structure= enumStruct();							// find factory.
	}
}

function void cybFactorBuildCyborg(STRUCTURE _factory)
{
	if(_factory == NULLOBJECT){
		dbg("cybFactorBuildCyborg: factory is NULLOBJECT", me);
		return;
	}

	if( structureIdle(_factory) )
	{
		if( (defendGroup.members < maxCyborgs) and (getDroidCount(me) < MAX_DROIDS) )
		{
			if(random(5) == 1)
			{
				if(skCanBuildTemplate(me, _factory, cybMechanic))
				{
					buildDroid(cybMechanic, _factory, me, 1);
				}
			}
			else
			{
				count = 3;
				count2 = 0;
				while( count >= 0 )
				{
					if( skCanBuildTemplate(me,_factory, superCyb[count]) )
					{
						tmplChoice[count2] = superCyb[count];
						count2 = count2 + 1;
					}
					count = count - 1;
				}

				if(count2 > 0)
				{
					buildDroid(tmplChoice[random(count2)],_factory,me,1);
				}
				else	//try light cyborgs
				{
					count = numLightCyborgs - 1;
					count2 = 0;
					while( (count >= 0) and (count2 < 2) )
					{
						if( skCanBuildTemplate(me,_factory, cybTempl[count]) )
						{
							tmplChoice[count2] = cybTempl[count];
							count2++;
						}
						count--;
					}
					if(count2 > 0)
					{
						buildDroid(tmplChoice[random(count2)], _factory, me, 1);
					}
				}
			}

		}
	}
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// scouting rules

// scout an area
event chooseScoutArea(chooseScoutAreaTr)
{
	scoutX = scoutTLX + random(scoutW);
	scoutY = scoutTLY + random(scoutH);
}

/////////////////////////////////////////////////////////////////////
// visit new places

event expandScoutArea(expandScoutAreaTr)
{
	//expand the scouting area slightly
	scoutTLX	= scoutTLX - ((mapWidth*128)/ tileExpand);
	scoutTLY	= scoutTLY - ((mapHeight*128)/ tileExpand);
	scoutW		= scoutW + (2*((mapWidth*128)/ tileExpand));
	scoutH		= scoutH + (2*((mapHeight*128)/ tileExpand));

	// check & restrain.
	if(scoutTLX <1)
	{
		scoutTLX = 1;
	}
	if(scoutTLY <1)
	{
		scoutTLY = 1;
	}

	if(scoutTLX >(mapWidth*128))
	{
		scoutTLX = (mapWidth*128) - 128;
	}
	if(scoutTLY >(mapHeight*128))
	{
		scoutTLY = (128*mapHeight) - 128;
	}

	if( (scoutTLX + scoutW) > (128 * mapWidth) )
	{
		scoutW = ( (128 * mapWidth) - scoutTLX) - 128;
	}
	if( (scoutTLY + scoutH) > (128 *mapHeight) )
	{
		scoutH = ( (128*mapHeight) - scoutTLY) - 128;
	}
}

/////////////////////////////////////////////////////////////////////
// order scouts

event scoutMain(scoutMainTr)
{
	// find any new scouts
	// if scouts aren't busy, send them to a new spot.
	if( idleGroup(scoutGroup) >= (scoutGroup.members /2) )
	{
		orderGroupLoc(scoutGroup, DORDER_MOVE,scoutX,scoutY);
	}
}

/////////////////////////////////////////////////////////////////////
// process new visibility reports
event newObjectReport(newObjectReportTr)
{
	if(!friendlyPlayer(baseobj.player))
	{
		if(targetTypeValue(baseobj) > targetTypeValue(attackObj))
		{
			attackObj = baseobj;// got a new unseen  target from a scout.

			if(	attackObj.type == OBJ_STRUCTURE)
			{
				if(not allianceExistsBetween(attackObj.player,me))	// an enemy
				{
					structure = objToStructure(attackObj);
					if(structure.stat == factory)
					{
						allOutAttack = attackObj;
					}
				}
			}
		}
	}
}

function int targetTypeValue(BASEOBJ _target)
{
	local STRUCTURE _strTarget;

	if(_target == NULLOBJECT){
		return NO_TARGET_VALUE;
	}

	if(_target.type == OBJ_DROID)
	{
		return DROID_TARGET_VALUE;
	}
	else if(_target.type == OBJ_STRUCTURE)
	{
		_strTarget = objToStructure(_target);

		if(_strTarget.stattype == REF_DEFENSE)
		{
			return DEFENSE_TARGET_VALUE;
		}
		else if(_strTarget.stattype == REF_RESEARCH or
				_strTarget.stattype == REF_POWER_GEN)
		{
			return RESEARCH_TARGET_VALUE;
		}
		else if(_strTarget.stattype == REF_HQ or
				_strTarget.stattype == REF_COMMAND_CONTROL)
		{
			return HQ_TARGET_VALUE;
		}
		else if(_strTarget.stattype == REF_RESOURCE_EXTRACTOR)
		{
			return OIL_TARGET_VALUE;
		}
		else if(_strTarget.stattype == REF_FACTORY or
				_strTarget.stattype == REF_CYBORG_FACTORY or
				_strTarget.stattype == REF_VTOL_FACTORY)
		{
			return FACTORY_TARGET_VALUE;
		}
		else	//walls, rearm pads etc
		{
			return OTHER_TARGET_VALUE;
		}
	}

	return NO_TARGET_VALUE;
}


/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// spy technologies
//event takeover( CALL_UNITTAKEOVER , ref droid  )

event takeover(takeoverTr)
{
	if( droid.player == me )
	{
		if(droid.droidType == DROID_CONSTRUCT or
				droid.droidType == DROID_CYBORG_CONSTRUCT)
		{
			groupAddDroid( buildGroup, droid );
		}
		else if (droid.droidType != DROID_TRANSPORTER
		      && droid.droidType != DROID_COMMAND)
		{
			groupAddDroid( attackGroup, droid );
		}
	}
}

event takeoverDefend(takeoverTr)
{
	if( droid.player != me )
	{
		completeResearch(nexusDefence,me);
		setEventTrigger(takeoverDefend,	inactive);
	}
}


event useLassat(useLassatTr)
{
	// find my lassat
	// fire it at my attack objective.
	if(allOutAttack != NULLOBJECT)
	{
		initEnumStruct(FALSE,lassat,me,me);
		structure= enumStruct();
		while(structure != NULLOBJECT)
		{
			if(structureComplete(structure) == TRUE)
			{
				skFireLassat(me,allOutAttack);
			}
			structure= enumStruct();
		}
	}
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// attack rules

event findEnemy(attackStuffTr)
{
	if(attackObj == NULLOBJECT)
	{
		count = random(8);
		count2 = 100;
		while( friendlyPlayer(count) && (count2 > 0) )
		{
			count = random(8);
			count2--;
		}

		if(!friendlyPlayer(count))
		{
			baseobj = skLocateEnemy(count);
			if(baseobj != NULLOBJECT)
			{
				attackObj =	baseobj;

				// set allOutAttack to attackObj only if attackObj is a more valuable target than allOutAttack
				if(targetTypeValue(attackObj) > targetTypeValue(allOutAttack))
				{
					allOutAttack = attackObj;
				}
			}
		}
	}
}

/////////////////////////////////////////////////////////////////////
// send attack team out to cause trouble near things scout found.
event attackStuff(attackStuffTr)
{
	local int i, x, y;
	local BASEOBJ result, best;

	// Attacks of opportunity
	i = 0;
	result = getDerrick(i);
	best = NULLOBJECT;
	while (result != NULLOBJECT)
	{
		if (result.type == OBJ_STRUCTURE and !allianceExistsBetween(result.player, me) and safeDest(me, result.x, result.y))
		{
			best = result;	// easy pickings
		}
		i++;
		result = getDerrick(i);
	}
	if (best != NULLOBJECT)
	{
		dbgPlr("MOVEMENT OF OPPORTUNITY to " & best.x & ", " & best.y);
		orderGroupLoc(attackGroup, DORDER_SCOUT, best.x, best.y);
		exit;
	}

	// Regular attacks
	if (idleGroup(attackGroup) >= (attackGroup.members / 2))
	{
		if( (attackObj != NULLOBJECT) and (not helpingAlly()) )
		{
			if (not allianceExistsBetween(me, attackObj.player))
			{
				if(attackGroup.members > (6 + random(6)) )
				{
					orderGroupLoc(attackGroup, DORDER_SCOUT, attackObj.x, attackObj.y);
				}

				// make scouts attack too
				if( idleGroup(scoutGroup) >= (scoutGroup.members / 2) )
				{
					orderGroupLoc(scoutGroup, DORDER_SCOUT, attackObj.x, attackObj.y);
				}
			}
		}
	}
}

/////////////////////////////////////////////////////////////////////
event doAllOutAttack(allOutAttackTr)
{
	if (idleGroup(attackGroup) >= (attackGroup.members / 3))	// make sure at least 30% are idle
	{
		if ((allOutAttack != NULLOBJECT) and (not helpingAlly()))
		{
			if (!friendlyPlayer(allOutAttack.player))
			{
				if (getDroidCount(me) > 40)	// plenty of units.
				{
					dbgPlr("ALL OUT ATTACK!");
					orderGroupObj(attackGroup, DORDER_ATTACK, allOutAttack);
					orderGroupObj(defendGroup, DORDER_ATTACK, allOutAttack);
					orderGroupLoc(scoutGroup, DORDER_SCOUT, allOutAttack.x, allOutAttack.y);
				}
			}
			else
			{
				allOutAttack = NULLOBJECT;
			}
		}
	}
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// defending rules

// defend attacked objects.
event defendWatch(defendWatchTr)
{
	if (baseobj != NULLOBJECT)
	{
		if (baseobj.type == OBJ_DROID)
		{
			if (isVtol(objToDroid(baseobj)))
			{
				exit;	// avoid getting caught in the blast from incendiary bombs... no way to check if building is burning
			}
		}
		if (!friendlyPlayer(baseobj.player))
		{
			if(distBetweenTwoPoints(baseobj.x, baseobj.y, baseX, baseY) <= MAX_DEFENDERS_RADIUS)	//don't go too far away from the base
			{
				defendObj = baseobj;
				defendbusy = TRUE;
				// if not too busy, attack.
				if (idleGroup(defendGroup) >= (defendGroup.members / 2))
				{
					orderGroupLoc(defendGroup, DORDER_MOVE, defendObj.x, defendObj.y);	//cyborg mechanics can't attack (won't move)
				}

				if (idleGroup(scoutGroup) >= (scoutGroup.members / 2))
				{
					orderGroupLoc(scoutGroup, DORDER_MOVE, scoutX, scoutY);
				}
			}
		}
	}
}

/////////////////////////////////////////////////////////////////////
// defenders  return after they are finished.
event defendReturn(defendReturnTr)
{
	if( defendbusy and (idleGroup(defendGroup) == (defendGroup.members - defendGroup.members / 12)))
	{
		// find a place with no structures nearby
		buildX = baseX;
		buildY = baseY;
		pickStructLocationB(powGen, ref buildX, ref buildY, me, 0);

		orderGroupLoc(defendGroup, DORDER_MOVE,buildX,buildY);
		defendbusy = FALSE;
	}
}

//returns number of non-idle structures of a certain type
function int numStructBusyByType(STRUCTURESTAT _busyStructType)
{
	local	int _result;

	initEnumStruct(FALSE,_busyStructType,me,me);
	structure = enumStruct();
	_result = 0;
	while(structure != NULLOBJECT)
	{
		if(structureComplete(structure))
		{
			if(not structureIdle(structure))
			{
				_result++;
			}
		}
		structure = enumStruct();
	}

	return _result;
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// Research Rules Now does true research.
// do research

event doResearch(doResearchTr)
{
	local	int		_techIndex,_numResearching;
	local bool boolResult;

	// don't throw in half of your money on research in T3 no bases when we don't have any income yet
	if (isStructureAvailable(facModule,me) and not havePowerSource())
	{
		setEventTrigger(doResearch, doResearchTr);
		exit;
	}

	_techIndex = 0;	//research start
	_numResearching = numStructBusyByType(resLab);

	// for every research lab do this..
	initEnumStruct(FALSE,resLab,me,me);
	structure= enumStruct();
	count = 0;
	while(structure != NULLOBJECT)
	{
		boolResult = FALSE;		//haven't started research for the current resFac

		if(structureIdle(structure))
		{
			if(structureComplete(structure))
			{
				// first research all technologies necessary for the current research branch
				while(not boolResult and _techIndex != NONE)	//not started researching and still branch tech left to try
				{
					_techIndex = findResearch(_techIndex, curTech);
					if(_techIndex > NONE)
					{
						boolResult = pursueResearch(structure,me,tech[curTech][_techIndex]);

						_techIndex++;	//try nect research next time if needed
						_numResearching++;
					}
				}

				// do common research
				if(not boolResult)	//didn't start branch research
				{
					if((maxIdleRes == UNLIMITED) or (_numResearching < maxIdleRes))
					{
						skDoResearch(structure,me,0);
						_numResearching++;
					}
				}
			}
		}

		structure = enumStruct();
	}
	setEventTrigger(doResearch, doResearchTr);
}

// find next available research of our research branch
function int findResearch(int _searchStart, int _techTree)
{
	local	int	_result;

	ASSERT(_searchStart >= 0, "findResearch: _searchStart < 0", me);
	ASSERT(_techTree >= 0, "findResearch: _techTree < 0", me);

	_result = _searchStart;
	while(_result < techCount[_techTree])
	{
		if((not researchFinished(tech[_techTree][_result], me)) and (not researchStarted(tech[_techTree][_result], me)))
		{
			return _result;		//found research
		}
		_result++;
	}

	return NONE;		//not found
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// Alliance Rules

// form alliances
event formAllianceEvent(formAllianceEventTr)
{
	count = 0;
	while(count < MAX_PLAYERS)
	{
		if( count != me )												// if not the only other player and rand2
		{
			if((getDroidCount(me) > 1) and (getDroidCount(count) > 1) )	// not dead
			{
				if(random(28) == 1)											// bit random
				{
					if(not isHumanPlayer(count))							// not human
					{
						createAlliance(me,count);
						allianceTime[count] = gameTime;
					}
				}
			}
		}
		count = count + 1;
	}
}

/////////////////////////////////////////////////////////////////////
// break the alliance too.
event breakAllianceEvent(breakAllianceEventTr)
{
	count = 0;
	while(count<multiPlayerMaxPlayers)
	{
		if( count != me)
		{
			if((getDroidCount(me) > 1) and (getDroidCount(count) > 1) )	// not dead
			{
				if(allianceExistsBetween(me,count) )
				{
					// check if we're in alliance with any other players.
					if( (random(30) == 1) and ( (gameTime - allianceTime[count]) > 6000) )	// rand and more than 10 minutes.
					{
						allianceTime[count] = gameTime;
						breakAlliance(me,count);
					}

					// rules for breaking alliances with humans.
					// built within my base
					if(numStructsInArea(count,minx,miny,maxx,maxy) > 1)
					{
						allianceTime[count] = gameTime;
						breakAlliance(me,count);
					}

					// you've got lots of units in my area.
					if(numDroidsInArea(count,minx,miny,maxx,maxy) > 3)
					{
						allianceTime[count] = gameTime;
						breakAlliance(me,count);
					}

					// you've wiped out one of my allies ??.
				}
			}
		}
		count = count + 1;
	}
}

/////////////////////////////////////////////////////////////////////
event formHumanAlliances(humanAllianceTr)
{
	local int result, result2;

	if(count2 == me) // offered to me.
	{
		result = 0;
		result2 = 0;
		while(result < multiPlayerMaxPlayers)
		{
			if(allianceExistsBetween(count,result))
			{
				result2 = result2 + 1;
			}
			result = result + 1;
		}
		if( result2 < ((multiPlayerMaxPlayers / 2) - 1) )	// not too many already
		{
			//not too soon.
			if((allianceTime[count] == 0) or (gameTime - allianceTime[count] > 1200))
			{
				result = 0;								// check forming wont end the game
				result2 = 0;
				while(result < multiPlayerMaxPlayers)
				{
					while(result2 < multiPlayerMaxPlayers)
					{
						if((not allianceExistsBetween(result,result2)) and (getDroidCount(result) > 0) and (getDroidCount(result2) > 0) and (result != result2) )
						{
							if( ((result == count and result2 == count2) or (result2 == count2 and result == count)) )	// ignore the outcome of this alliance
							{
								createAlliance(me,count);
								allianceTime[count] = gameTime;
							}
						}
						result2 = result2 + 1;
					}
					result = result + 1;
				}
			}

		}
	}
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// Consolidate Rules

// bring forces back together to consolidate attacks
event consolidateEvent(consolidateEventTr)
{
	if(not helpingAlly())
	{
		if(random(3) == 1)				// order all droids home to rejoin forces.!
		{
			// find a place with no structures nearby
			buildX = baseX;
			buildY = baseY;
			pickStructLocationB(powGen, ref buildX, ref buildY, me, 0);

			orderGroupLoc(scoutGroup, DORDER_MOVE,buildX,buildY);
			orderGroupLoc(defendGroup, DORDER_MOVE,buildX,buildY);
		}
	}
}

/////////////////////////////////////////////////////////////////////
// vtols.
/////////////////////////////////////////////////////////////////////
// build vtol structures.
event vtolStructs(inactive)
{
	local	int		_numVtolFacs,_numRearmPads;

	// got any idle trucks?
	if (idleGroup(buildGroup) < 1)
	{
		exit;
	}

	// see how many vtol factories we already have
	_numVtolFacs = getNumStructures(vtolFactory,me);
	_numRearmPads = getNumStructures(vtolPad,me);

	//see if we have enough rearm pads
	if( _numRearmPads * 4 / 3 <= totalVtols() )
	{
		dbg("NEED REARM PADS", me);
		buildRearmPads();
	}

	if (_numVtolFacs > 0 and playerPower(me) < LOW_POWER)
	{
		exit;	// Throttle expansion to avoid waste
	}

	if (isStructureAvailable(vtolFactory, me) and _numVtolFacs < maxVtolFacs[curTech])			// if not enough
	{
		if (not grabTrucksAndBuild(8, vtolFactory, 1))
		{
			dbgPlr("Wanted to build VTOL Factory but could not");
		}
	}
}

//counts vtols
function int totalVtols()
{
	local	int		_vtolGroup,_totalVtols;

	_totalVtols = 0;
	_vtolGroup = 0;
	while(_vtolGroup < numVtolAttackGroups)
	{
		_totalVtols = _totalVtols + vtolAttackGr[_vtolGroup].members;
		_vtolGroup++;
	}

	_totalVtols = _totalVtols + vtolDefendGr.members;

	return _totalVtols;
}

function void buildRearmPads()
{
	if (isStructureAvailable(vtolPad, me))
	{
		if (not grabTrucksAndBuild(4, vtolPad, -1))
		{
			dbgPlr("Wanted to build VTOL rearm pad, but could not");
		}
	}
}


/////////////////////////////////////////////////////////////////////
// build vtols.

event buildVtols(inactive)
{
	// got enough vtols?
	if((vtolDefendGr.members >= maxVTOLs[curTech]) or (getDroidCount(me) >= MAX_DROIDS)){
		dbg("CAN'T BUILD VTOLS - TOO MANY UNITS", me);
		exit;
	}

	// build vtols
	initEnumStruct(FALSE,vtolFactory,me,me);
	structure = enumStruct();
	while(structure != NULLOBJECT)
	{
		if(structureIdle(structure))	// if factory idle
		{
			vtolFactoryBuildVtol(structure);
		}

		structure = enumStruct();
	}
}

function void vtolFactoryBuildVtol(STRUCTURE _factory)
{
	local	TEMPLATE	_newTemplate;
	local	BODY		_bestBody;
	local	WEAPON		_bestWeapon;
	local	int		_rand, _idx;

	if (_factory == NULLOBJECT or not structureIdle(_factory))
	{
		return;
	}

	// choose branch: AT, AS or splash
	_rand = random(3);
	_idx = 0;
	_bestWeapon = NULLSTAT;
	if (_rand == 0)	// anti-tank
	{
		_idx = numAtWeapons - 1;
		while (_idx >= 0 and _bestWeapon == NULLSTAT)
		{
			if (isComponentAvailable(me, atWeapon[_idx]))
			{
				_bestWeapon = atWeapon[_idx];
			}
			_idx--;
		}
	}
	if (_rand == 1)	// anti-structure
	{
		_idx = numAsWeapons - 1;
		while (_idx >= 0 and _bestWeapon == NULLSTAT)
		{
			if (isComponentAvailable(me, asWeapon[_idx]))
			{
				_bestWeapon = asWeapon[_idx];
			}
			_idx--;
		}
	}
	if (_rand < 2 and _bestWeapon == NULLSTAT)
	{
		_rand = 2;	// try this instead
	}
	if (_rand == 2)	// indiscriminate mass destruction
	{
		_idx = numMdWeapons - 1;
		while (_idx >= 0 and _bestWeapon == NULLSTAT)
		{
			if (isComponentAvailable(me, mdWeapon[_idx]))
			{
				_bestWeapon = mdWeapon[_idx];
			}
			_idx--;
		}
	}
	if (_bestWeapon == NULLSTAT)
	{
		dbgPlr("Failed to find a weapon for VTOL design");
		return;
	}
	_bestBody = NULLSTAT;
	if (not seenAnyAA)
	{
		// no AA seen, so go for the cheapest bodies -- no need for defense yet!
		if (isComponentAvailable(me, bugBody))
		{
			_bestBody = bugBody;
		}
		else if (isComponentAvailable(me, viperBody))
		{
			_bestBody = viperBody;
		}
	}
	else
	{
		// build sturdiest body available with a decent engine
		if (isComponentAvailable(me, retributionBody))
		{
			_bestBody = retributionBody;
		}
		else
		{
			if (isComponentAvailable(me, scorpionBody))
			{
				_bestBody = scorpionBody;
			}
			else if (isComponentAvailable(me, cobraBody))
			{
				_bestBody = cobraBody;
			}
		}
	}
	if (_bestBody == NULLSTAT or getBodySize(_bestBody) > skGetFactoryCapacity(_factory))
	{
		dbgPlr("Failed to find a viable body for VTOL design");
		return;
	}

	_newTemplate = assembleWeaponTemplate(me, _bestBody, vtolPropulsion, _bestWeapon);

	if (_newTemplate == NULLTEMPLATE or not skCanBuildTemplate(me, _factory, _newTemplate))
	{
		dbgPlr("Failed to construct viable VTOL template");
		return;
	}
	buildDroid(_newTemplate, _factory, me, 1);
}

/////////////////////////////////////////////////////////////////////
// attack with vtols.

event vtolAttack(inactive)
{
	local		int			_groupIndex,_newTargetWeight,_oldTargetWeight;
	local		BASEOBJ		_newTarget;
	local		bool		_bHaveDefendTarget;
	local		DROID		_droid;

	// if vtol group is not busy..
	if(  (idleGroup(vtolDefendGr) >= (vtolDefendGr.members / 2)) and (vtolDefendGr.members >= 2) )
	{
		if(attackObj != NULLOBJECT)
		{
			if(!friendlyPlayer(attackObj.player))
			{
				orderGroupObj(vtolDefendGr, DORDER_ATTACK, attackObj);	// get the attack target.
			}
			else
			{
				attackObj = NULLOBJECT;
			}
		}
		else
		{
			if(defendObj != NULLOBJECT)
			{
				if(not isHumanPlayer(defendObj.player) )	// new in wdg1 	//TODO:is this check needed?
				{
					orderGroupObj(vtolDefendGr, DORDER_ATTACK,defendObj);		// get the defend target
				}
			}
		}
	}

	//make sure attack vtol groups are not cluttered
	rearrangeAttackVtols();

	//attack vtols
	_groupIndex = 0;
	while(_groupIndex < numVtolAttackGroups)
	{
		if(vtolAttackGr[_groupIndex].members > 0)
		{
			// if our base is in trouble see if we have a target in the base
			// don't choose new target if already have one
			_bHaveDefendTarget = FALSE;
			if((vtolGrAttackObj[_groupIndex] != NULLOBJECT))
			{
				if(defendingOwnBase() and
					(distBetweenTwoPoints(baseX, baseY, vtolGrAttackObj[_groupIndex].x, vtolGrAttackObj[_groupIndex].y) <= MAX_VTOL_DEFEND_RADIUS))
				{
					_bHaveDefendTarget = TRUE;
				}
				else	//reset target if it's not worth it
				{
					//we don't want to attack enemy heavy tanks outside of our base
					if(vtolGrAttackObj[_groupIndex].type == OBJ_DROID)
					{
						_droid = objToDroid(vtolGrAttackObj[_groupIndex]);
						if(_droid.droidType != DROID_CONSTRUCT)
						{
							vtolGrAttackObj[_groupIndex] = NULLOBJECT;	//reset target
							_bHaveDefendTarget = FALSE;
						}
					}
				}
			}

			//find target in our base if our base is in trouble
			if(!_bHaveDefendTarget and defendingOwnBase())
			{
				vtolGrAttackObj[_groupIndex] = chooseVtolDefenceTarget(baseX, baseY, MAX_VTOL_DEFEND_RADIUS, FALSE);

				if(vtolGrAttackObj[_groupIndex] != NULLOBJECT)
				{
					dbg("assigned new defence target for group " & _groupIndex, me);
					_bHaveDefendTarget = TRUE;
				}
			}

			//attack rules
			if(!_bHaveDefendTarget and (idleGroup(vtolAttackGr[_groupIndex]) >= (vtolAttackGr[_groupIndex].members / 2)) and
			(vtolAttackGr[_groupIndex].members >= (numAttackVtols * 2 / 3)))
			{
				_newTarget = NULLOBJECT;

				//reset attack targets once in a while
				if(vtolGrAttackObj[_groupIndex] != NULLOBJECT)
				{
					if(random(10) > 7)
					{
						vtolGrAttackObj[_groupIndex] = NULLOBJECT;
					}
				}

				//now try to find an attack target if we have no defence target
				//find best attack target
				_newTarget = chooseVtolTarget(TRUE);	//search for an exclusive target
				if(_newTarget == NULLOBJECT)
				{
					_newTarget = chooseVtolTarget(FALSE);	//search for any target
				}

				_newTargetWeight = getVtolTargetWeight(_newTarget);
				_oldTargetWeight = getVtolTargetWeight(vtolGrAttackObj[_groupIndex]);

				// new one or a much better one
				if((_newTargetWeight >= (_oldTargetWeight + 20)) or
					(vtolGrAttackObj[_groupIndex] == NULLOBJECT ))
				{
					dbg("choosing new attack object for " & _groupIndex, me);
					vtolGrAttackObj[_groupIndex] = _newTarget;
				}
			}

			//see if this group has something to attack
			if(vtolGrAttackObj[_groupIndex] != NULLOBJECT)
			{
				if(!friendlyPlayer(vtolGrAttackObj[_groupIndex].player))
				{
					dbg("VTOL Group " & _groupIndex & " attacking", me);

					if(_DEBUG and (_groupIndex == 0))
					{
						dropBeacon(getPlayerName(me), me, me, vtolGrAttackObj[_groupIndex].x, vtolGrAttackObj[_groupIndex].y, 0);
					}

					orderGroupObj(vtolAttackGr[_groupIndex], DORDER_ATTACK, vtolGrAttackObj[_groupIndex]);
				}
				else
				{
					vtolGrAttackObj[_groupIndex] = NULLOBJECT;
				}
			}
		}

		_groupIndex++;
	}
}

//make sure vtol groups are not cluttered
function void rearrangeAttackVtols()
{
	local	int		_emptyGr,_fillGr;
	local	DROID	_droid;

	_emptyGr = 0;
	while(_emptyGr < numVtolAttackGroups)
	{
		if((vtolAttackGr[_emptyGr].members < numAttackVtols) and	//this group is not full
			(vtolAttackGr[_emptyGr].members > 0))
		{
			//try to make full groups by moving vtols from the last groups
			_fillGr = _emptyGr + 1;	//start from the next group
			while((_fillGr < numVtolAttackGroups) and
				(vtolAttackGr[_emptyGr].members < numAttackVtols))
			{
				initIterateGroup(vtolAttackGr[_fillGr]);
				_droid = iterateGroup(vtolAttackGr[_fillGr]);
				while((_droid != NULLOBJECT) and
					(vtolAttackGr[_emptyGr].members < numAttackVtols))
				{
					groupAddDroid(vtolAttackGr[_emptyGr], _droid);	//refill the group
					_droid = iterateGroup(vtolAttackGr[_fillGr]);
				}

				_fillGr++;
			}
		}
		_emptyGr++;
	}
}

function BASEOBJ chooseVtolDefenceTarget(int _x, int _y, int _range, bool bExclusiveTarget)
{
	local	BASEOBJ		_target;

	_target = getClosestEnemy(_x, _y, _range, FALSE, FALSE, me);

	// make sure no one else is targeting it already if we want exclusive targets
	if(bExclusiveTarget and vtolTargetAssigned(_target))
	{
		_target = NULLOBJECT;
	}

	return _target;
}

function BASEOBJ chooseVtolTarget(bool bExclusiveTarget)
{
	local 	int				_structTypeIndex,_enemy;
	local	int				_bestScore,_tempScore;
	local	BASEOBJ			_bestTarget;
	local	STRUCTURE		_structure;

	_bestScore = 0;
	_bestTarget = NULLOBJECT;

	_enemy = 0;
	while(_enemy < MAX_PLAYERS)
	{
		if(!friendlyPlayer(_enemy))
		{
			_structTypeIndex = 0;
			while(_structTypeIndex < numVtolTargets)
			{
				initEnumStruct(FALSE,vtolTarget[_structTypeIndex],_enemy,me);
				_structure = enumStruct();
				while(_structure != NULLOBJECT)
				{

					// in case we don't want all groups to attack the same target
					if(not (bExclusiveTarget and vtolTargetAssigned(_structure)) )
					{
						_tempScore = getVtolTargetWeight(_structure);

						//see if this one is better
						if(_tempScore > _bestScore)
						{
							_bestScore = _tempScore;
							_bestTarget = _structure;
						}
					}

					_structure = enumStruct();
				}

				_structTypeIndex++;
			}
		}
		_enemy++;
	}

	return _bestTarget;
}

function int getVtolTargetWeight(BASEOBJ _target)
{
	local 	int 		_AAPenalty,_numAA,_range,_targetWeight;
	local 	int 		_targetType;
	local	STRUCTURE	_structTarget;

	if(_target == NULLOBJECT)
	{
		return 0;
	}

	if(_target.type != OBJ_STRUCTURE)
	{
		return 0;
	}

	_structTarget = objToStructure(_target);

	_targetWeight = 0;

	_AAPenalty = 9;			//penalty per aa defense
	_range = AA_THREAT_RANGE;

	// count enemy AA
	_numAA = numEnemyAAInRange(_structTarget.x, _structTarget.y, _range);

	// find tyrget type from stats
	_targetType = 0;
	while(_targetType < numVtolTargets)
	{
		if(_structTarget.stat == vtolTarget[_targetType])
		{
			_targetWeight = vtolTargetWeight[_targetType] - (_numAA * _AAPenalty);

			_targetType = numVtolTargets;	//exit loop
		}
		_targetType++;
	}

	// incomplete structures get lower weight
	if(!structureComplete(_structTarget))
	{
		_targetWeight = _targetWeight / 10;
	}

	return _targetWeight;
}

event checkForHostileAA(hostileAATr)
{
	local	int	_enemy, _numAA;

	_numAA = 0;
	_enemy = 0;
	while (_enemy < MAX_PLAYERS)
	{
		if (!friendlyPlayer(_enemy))
		{
			_numAA = _numAA + numAAinRange(_enemy, me, baseX, baseY, -1);
			if (_numAA > 0)
			{
				seenAnyAA = true;
				exit;
			}
		}
		_enemy++;
	}
	if (_numAA == 0)
	{
		seenAnyAA = false;
	}
}

function int numEnemyAAInRange(int _x, int _y, int _range)
{
	local int _enemy,_numAA;

	_numAA = 0;
	_enemy = 0;
	while(_enemy < MAX_PLAYERS)
	{
		if(!friendlyPlayer(_enemy))
		{
			_numAA = _numAA + numAAinRange(_enemy, me, _x, _y, _range);
		}
		_enemy++;
	}

	return _numAA;
}

// see if a particular target is already assigned to one of the VTOL attack groups
function bool vtolTargetAssigned(BASEOBJ _target)
{
	local int _groupIndex;

	if(_target == NULLOBJECT)
	{
		return FALSE;
	}

	_groupIndex = 0;
	while(_groupIndex < numVtolAttackGroups)
	{
		if(_target == vtolGrAttackObj[_groupIndex])
		{
			return TRUE;
		}

		_groupIndex++;
	}

	return FALSE;
}

/////////////////////////////////////////////////////////////////////
// watch for incoming vtols
event vtolDefend(vtolDefendTr)
{
	local	int		_numBuilders,_maxBuilders;
	local bool boolResult;

	if(baseobj != NULLOBJECT)
	{
		if(baseobj.type == OBJ_DROID)
		{
			if(isVtol(objToDroid(baseobj)))
			{
				_numBuilders = 0;
				_maxBuilders = 2;

				// build defenses.
				initIterateGroup(buildGroup);		// find idle droids in build group.
				droid = iterateGroup(buildGroup);
				while( droid != NULLOBJECT)
				{
					if( (structure != NULLOBJECT) and (droid.order != DORDER_BUILD) )
					{
						buildX = structure.x;
						buildY = structure.y;

						//find best defense we can build.
						count = 0;
						count2 = -1;
						while (count < 6)
						{
							if(isStructureAvailable(vtolDefStruct[count],me))
							{
								count2 = count;
							}
							count = count + 1;
						}
						if(count2 != (-1) )
						{
							boolResult = pickDroidStructLocation(droid, vtolDefStruct[count2], ref buildX, ref buildY, me, 1);
							if (boolResult == TRUE and (_numBuilders < _maxBuilders))	// build a vtol defense near the attacked struct...
							{
								orderDroidStatsLoc(droid, DORDER_BUILD,vtolDefStruct[count2],buildX,buildY);
								_numBuilders++;
							}
						}

					}
					droid = iterateGroup(buildGroup);
				}
			}
		}
	}
}

/////////////////////////////////////////////////////////////////////
event vtolEnabler(vtolEnablerTr)
{
	if( skVtolEnableCheck(me) )					// check to see if we have vtol technologies.
	{
		setEventTrigger(vtolEnabler,inactive);		// turn off this event.
		setEventTrigger(vtolStructs,vtolStructsTr);	// activate other vtol functions..
		setEventTrigger(buildVtols,	buildVtolsTr);
		setEventTrigger(vtolAttack,	vtolAttackTr);
		dbg("----I CAN USE VTOLS----", me);
	}
}

/////////////////////////////////////////////////////////////////////
// HouseKeeping

function void shutDownAI()
{
	bRunning = false;

	setEventTrigger(basedetails,	inactive);
	setEventTrigger(buildDerrick,	inactive);
	setEventTrigger(buildOilDefenseOrRetreat,	inactive);
	setEventTrigger(incendiary,	inactive);
	setEventTrigger(buildPowerGenerators,	inactive);
	setEventTrigger(buildExpand,inactive);
	setEventTrigger(newfortify,	inactive);
	setEventTrigger(droidBuiltAssign, inactive);
	setEventTrigger(droidBuilt,	inactive);
	setEventTrigger(structBuilt,inactive);
	setEventTrigger(droidDestroyed,inactive);
	setEventTrigger(conDroids,inactive);
	setEventTrigger(repairDroids,inactive);
	setEventTrigger(factoryEvent,	inactive);
	setEventTrigger(cyborgFactoryEvent,	inactive);
	setEventTrigger(chooseScoutArea,	inactive);
	setEventTrigger(expandScoutArea,	inactive);
	setEventTrigger(scoutMain,	inactive);
	setEventTrigger(newObjectReport,	inactive);
	setEventTrigger(takeover,	inactive);
	setEventTrigger(takeoverDefend,	inactive);
	setEventTrigger(useLassat,	inactive);
	setEventTrigger(findEnemy,	inactive);
	setEventTrigger(attackStuff,	inactive);
	setEventTrigger(defendWatch,	inactive);
	setEventTrigger(defendReturn,	inactive);
	setEventTrigger(doResearch,	inactive);
	setEventTrigger(formAllianceEvent,	inactive);
	setEventTrigger(breakAllianceEvent,	inactive);
	setEventTrigger(formHumanAlliances,	inactive);
	setEventTrigger(consolidateEvent,	inactive);
	setEventTrigger(vtolStructs,inactive);
	setEventTrigger(buildVtols,inactive);
	setEventTrigger(vtolAttack,inactive);
	setEventTrigger(vtolDefend,inactive);
	setEventTrigger(vtolEnabler,inactive);
	setEventTrigger(beaconEv,	inactive);
	setEventTrigger(multiMsgEv,	inactive);
	setEventTrigger(manageAllyHelp,	inactive);
	setEventTrigger(everySecEv,	inactive);
	setEventTrigger(watchBaseThreat, inactive);
	setEventTrigger(manageDefendLocationEv,	inactive);
	setEventTrigger(structureDestroyed,inactive);
	setEventTrigger(rebuildStructureEv,inactive);
	setEventTrigger(startLevel, inactive);
	setEventTrigger(arrived, inactive);
	setEventTrigger(checkActivities, inactive);
}

function void reassignAI()
{
	bRunning = true;

	setEventTrigger(basedetails,basedetailsTr);
	setEventTrigger(buildDerrick, inactive);
	setEventTrigger(buildOilDefenseOrRetreat, inactive);
	setEventTrigger(incendiary, inactive);
	setEventTrigger(buildPowerGenerators, inactive);
	setEventTrigger(buildExpand, inactive);
	setEventTrigger(newfortify, inactive);
	setEventTrigger(droidBuiltAssign,droidBuiltTr);
	setEventTrigger(droidBuilt,droidBuiltTr);
	setEventTrigger(droidDestroyed,droidDestroyedTr);
	setEventTrigger(conDroids,conDroidsTr);
	setEventTrigger(repairDroids,repairDroidsTr);
	setEventTrigger(factoryEvent,factoryEventTr);
	setEventTrigger(cyborgFactoryEvent,cyborgFactoryEventTr);
	setEventTrigger(chooseScoutArea,chooseScoutAreaTr);
	setEventTrigger(expandScoutArea,expandScoutAreaTr);
	setEventTrigger(scoutMain,scoutMainTr);
	setEventTrigger(newObjectReport,newObjectReportTr);
	setEventTrigger(takeover,takeoverTr);
	setEventTrigger(useLassat,useLassatTr);
	setEventTrigger(findEnemy,attackStuffTr);
	setEventTrigger(attackStuff,attackStuffTr);
	setEventTrigger(doAllOutAttack,allOutAttackTr);
	setEventTrigger(defendWatch,defendWatchTr);
	setEventTrigger(defendReturn,defendReturnTr);
	setEventTrigger(doResearch,doResearchTr);
	setEventTrigger(formAllianceEvent,formAllianceEventTr);
	setEventTrigger(breakAllianceEvent,breakAllianceEventTr);
	setEventTrigger(consolidateEvent,consolidateEventTr);
	setEventTrigger(vtolStructs,inactive);
	setEventTrigger(buildVtols,inactive);
	//setEventTrigger(vtolAttack,inactive);
	setEventTrigger(vtolAttack,	vtolAttackTr);
	setEventTrigger(vtolDefend,vtolDefendTr);
	setEventTrigger(vtolEnabler,vtolEnablerTr);
	setEventTrigger(formHumanAlliances,humanAllianceTr);
	setEventTrigger(multiMsgEv, multiMsgTr);
	setEventTrigger(beaconEv, beaconTr);
	setEventTrigger(watchBaseThreat, watchBaseThreatTr);
	setEventTrigger(manageAllyHelp, manageAllyHelpTr);
	setEventTrigger(everySecEv, everySec);
	//setEventTrigger(manageDefendLocationEv, manageDefendLocationTr);
	setEventTrigger(structBuilt, structBuiltTr);
	setEventTrigger(structureDestroyed, structureDestroyedTr);
	setEventTrigger(rebuildStructureEv, rebuildStructureTr);
	setEventTrigger(startLevel, startLevelTr);
	setEventTrigger(arrived, reachedTr);
	setEventTrigger(checkActivities, checkActivitiesTr);
}

/* Returns true if we just received a beacon from a certain player */
function bool haveBeacon(int _player)
{
		if((tBeacon[_player] > 0) and (not beaconTimeout(_player)))
		{
			return TRUE;	//have beacon for this player
		}

		return FALSE;
}

/* See if last beacon was placed long ago */
function bool beaconTimeout(int _player)
{
	if((tBeacon[_player] > 0) and
		((tBeacon[_player] + BEACON_TIMEOUT) < (gameTime / 10)))	//not too long ago
	{
		return TRUE;	//this beacon is still 'fresh'
	}

	return FALSE;
}

/* Deal with beacons */
event beaconEv(beaconTr)
{
	local	int			_players;
	local	string		_processedString;

	if(_DEBUG)
		debug(me & ") beaconEv: from " & sender);

	ASSERT(sender >= 0 and sender < MAX_PLAYERS,
		"beaconEv: sender out of bounds: " & sender , me);

	beaconX[sender] = x;
	beaconY[sender] = y;
	tBeacon[sender] = gameTime / 10;

	processCommand(message, sender, TRUE);
}

/* Deal with a chat message */
event multiMsgEv(multiMsgTr)
{
	if(_DEBUG)
		debug(me & ") multiMsgEv: from " & sender);

	if(not allianceExistsBetween(me, sender)){
		exit;
	}

	if(sender == me){
		exit;
	}

	processCommand(message, sender, FALSE);
}

/* Process multiplayer messages */
function void processCommand(string _message, int _sender, bool _bBlipMessage)
{
	local	int 		_numMsgs,_curMsg,_addressedPlayers,_x,_y;
	local	string		_msg,_processedString;

	/* Extract semantic information */
	_curMsg = 0;
	_numMsgs = processChatMsg(_message);

	debug(me & ") processCommand: '" & _message & "' from " & _sender);
	dbg("processCommand: '" & _message & "' from " & _sender, me);
	dbg("got " & _numMsgs & " commands", me);

	/* Process all messages */
	while(_curMsg < _numMsgs)
	{
		if(chatCmdIsPlayerAddressed(_curMsg, me))
		{
			dbg("i'm addressed", me);
			_msg = getChatCmdDescription(_curMsg);

			/* Someone requested help */
			if(_msg == R_REQUEST_HELP)
			{
				dbg("'help' command", me);

				if(haveBeacon(_sender))
				{
					dbg("got beacon", me);

					_x = beaconX[_sender];
					_y = beaconY[_sender];

					if(attemptToHelp(_sender, _x, _y))
					{
						messagePlayer(ALL_ALLIES, m_affirmative(), MAX_PROBABILITY);
					}
				}
				else
				{
					/* Ask player to drop a beacon so we would know where to help */
					_addressedPlayers = setBit(0, _sender, TRUE);
					messagePlayerAddressed(ALL_ALLIES, _addressedPlayers, R_REQUEST_BEACON);
				}
			}
			/* Someone requested a beacon from us -
			 * did we request help and our beacon timed out??
			 */
			else if(_msg == M_REQUEST_BEACON)
			{
				/* If our base is really in trouble drop a beacon for the requester again */
				if(baseInTrouble()){
					dropBeacon(getPlayerName(me), _sender, me, baseX, baseY, 0);
				}
			}
			else if(_msg == R_REPORT_SAFETY)
			{
				dbg("helping " & lastHelpPlayer, me);

				/* Make sure we were helping him */
				if(helpingAlly() and (lastHelpPlayer == _sender))
				{
					stopHelpingAlly();
					messagePlayer(ALL_ALLIES, m_affirmative(), MAX_PROBABILITY);
				}
				else if(defendingOwnBase())	//if we are in trouble re-request help
				{
					requestHelp(baseX, baseY);
				}
			}
			else
			{
				dbg("unknown message", me);
			}

		}
		else
		{
			dbg("i'm not addressed", me);
		}

		_curMsg++;
	}
}

function bool attemptToHelp(int _playerToHelp, int _x, int _y)
{
	local bool _bHelpingMyself;

	if(_playerToHelp < 0 or _playerToHelp >= MAX_PLAYERS){
		return FALSE;
	}

	if(_x <= 0 or _y <= 0){
		return FALSE;
	}

	dbg("attemptToHelp - checking", me);

	_bHelpingMyself = (_playerToHelp == me);

	/* Can only help allies and myself */
	if(not friendlyPlayer(_playerToHelp)){
		return FALSE;
	}

	if(_bHelpingMyself or !helpingAlly() or (lastHelpPlayer == _playerToHelp) )	//if not helping any other ally or it's me who needs help
	{
		dbg("not busy helping", me);

		if(haveHelpers() or _DEBUG)
		{
			dbg("got attackers", me);
			helpPlayer(_playerToHelp, _x, _y);
			return TRUE;
		}
		else
		{
			messagePlayer(ALL_ALLIES, M_HELP_NO_UNITS, MAX_PROBABILITY);
		}
	}
	else if((lastHelpPlayer >= 0) and (lastHelpPlayer < MAX_PLAYERS))
	{
		if(!_bHelpingMyself){
			messagePlayer(ALL_ALLIES, "helping " & getPlayerName(lastHelpPlayer) & " already", MAX_PROBABILITY);
		}
	}

	return FALSE;
}

/* Start helping player */
function void helpPlayer(int _playerToHelp, int _helpX, int _helpY)
{
	local int _tTravelTime;

	dbg("helping " & _playerToHelp, me);

	if(_DEBUG)
		debug(me & ") helpPlayer: '" & _playerToHelp);

	/* Move scoutes to attackers */
	groupAddGroup(attackGroup, scoutGroup);

	//Calculate travel time, assume ~ 150 tiles in 4 minutes
	if(attackGroup.members == 0){
		_tTravelTime = (int)((float)(distBetweenTwoPoints(baseX, baseY, _helpX, _helpY) / 128 ) * 1.7);
	}else{
		_tTravelTime = (int)((float)(distBetweenTwoPoints(attackGroup.x, attackGroup.y, _helpX, _helpY) / 128 ) * 1.7);
	}

	tHelp = gameTime / 10;
	tHelpTimeout = (gameTime / 10) + BASE_DEFEND_DURATION + _tTravelTime;
	lastHelpPlayer = _playerToHelp;
	helpX = _helpX;
	helpY = _helpY;

	/* Scouts and attackers go to help */
	defendLocation(_helpX, _helpY, tHelpTimeout, (_playerToHelp == me));
}

/* Returns a random affirmative response */
function string m_affirmative()
{
	local int _rnd;

	_rnd = random(4);
	if(_rnd == 3)
	{
		return M_AFFIRMATIVE_ROGER;
	}

	return M_AFFIRMATIVE_OK;
}

/* See if there are any base structures belonging to ally at a certain location */
function bool allyBaseAtLoc(int _ally, int _x, int _y)
{
	local int _structIndex;

	if(_x <= 0 or _y <= 0){
		return FALSE;
	}

	if(_ally < 0 or _ally >= MAX_PLAYERS){
		return FALSE;
	}

	_structIndex = 0;
	while(_structIndex < numBaseStruct)
	{
		if(numStructsByStatInRange(baseStruct[_structIndex], _x, _y, (7 * 128), me, _ally) > 0 )
		{
			return TRUE;
		}

		_structIndex++;
	}

	return FALSE;
}

event manageAllyHelp(manageAllyHelpTr)
{
	if(helpingAlly())
	{
		if(canStopHelpingAlly())
		{
			stopHelpingAlly();
		}
	}
}

event everySecEv(everySec)
{
	/* Check if we were helping long enough */
	if(helpingAlly())
	{
		if(helpAllyTimeout())
		{
			stopHelpingAlly();
		}
	}

	if(defendingLocation())
	{
		if(defendLocationTimeout())
		{
			stopDefendingLocation();
		}
	}
}

/* Do we have any units we can send to help ally ? */
function bool haveHelpers()
{
	if(attackGroup.members == 0){
		return FALSE;
	}

	return TRUE;
}

function bool helpingAlly()
{
	if(lastHelpPlayer >= 0){
		return TRUE;
	}

	return FALSE;
}

/* Returns true if we were helping long enough */
function bool helpAllyTimeout()
{
	if(tHelpTimeout < (gameTime / 10) ){
		return TRUE;
	}

	return FALSE;
}

function bool canStopHelpingAlly()
{
	if(lastHelpPlayer < 0)
	{
		ASSERT(FALSE, "canStopHelpingAlly: lastHelpPlayer < 0", me);
		return TRUE;
	}

	/* Were helping long enough or someone's backstabbing */
	if(!friendlyPlayer(lastHelpPlayer)){
		return TRUE;
	}

	return FALSE;
}

function void stopHelpingAlly()
{
	dbg("stopped helping", me);

	tHelp = -1;
	tHelpTimeout = -1;
	lastHelpPlayer = -1;
	helpX = -1;
	helpY = -1;

	stopDefendingLocation();
}

/* Send a multiplayer message to an ally */
function void messagePlayer(int _playerToMessage, string _message, int _probability)
{
	local int	_player;

	ASSERT(_playerToMessage >= -1 && _playerToMessage < MAX_PLAYERS,
		"messagePlayer: player out of bounds: " & _playerToMessage, me);

	// throw the dice
	if( random(MAX_PROBABILITY) >= _probability ){
		return;
	}

	_player = 0;
	if(_playerToMessage == ALL_ALLIES)	//everyone
	{
		while(_player < MAX_PLAYERS)
		{
			/* Send message (allies only)) */
			if(allianceExistsBetween(me, _player))
			{
				msg(_message, me, _player);
			}

			_player++;
		}
	}
	else	//a certain player
	{
		/* Send message (allies only)) */
		if(allianceExistsBetween(me, _playerToMessage))
		{
			msg(_message, me, _playerToMessage);
		}
	}
}

function int numBitsSet(int _integer)
{
	local	int		_position,_result;

	_position = 0;
	_result = 0;
	while(_position < 8)
	{
		if(getBit(_integer, _position))
		{
			_result++;
		}
		_position++;
	}

	return _result;
}

/* Send a multiplayer message, addressing some player(s) */
function void messagePlayerAddressed(int _playerToMessage, int _playersToAddress, string _message)
{
	local	int		_player,_totalAddressed,_curAddressed;
	local	string	_adrMessage;

	_totalAddressed = numBitsSet(_playersToAddress);

	ASSERT(_totalAddressed > 0, "messagePlayerAddressed: no players addressed", me);

	_adrMessage = " ";

	_player = 0;
	_curAddressed = 0;
	while(_player < MAX_PLAYERS)
	{
		if(getBit(_playersToAddress, _player))
		{
			_curAddressed++;

			_adrMessage = _adrMessage & getPlayerName(_player);

			//if(_totalAddressed == 1){	//one only
			//	_adrMessage = getPlayerName(_player);
			//}else

			if(_totalAddressed > 1)
			{
				if(_curAddressed == _totalAddressed){		//end
					_adrMessage = _adrMessage & " and " & getPlayerName(_player);
				}else{
					_adrMessage = _adrMessage & ", " & getPlayerName(_player);
				}
			}

		}
		_player++;
	}

	_message = _adrMessage &  " " & _message;

	//Now send the message to all players addressed
	messagePlayer(_playerToMessage, _message, MAX_PROBABILITY);
}

/* Returns true if we can see our allies on the map */
function bool canSeeAllies()
{
	local STRUCTURE _uplink;

	/* Can see allies when team mode is on */
	if(multiPlayerAlliancesType == ALLIANCES_TEAMS)
	{
		return TRUE;
	}

	/* Can see whole map if we have uplink */
	_uplink = getStructure(uplink, me);
	if(_uplink != NULLOBJECT)
	{
		/* Make sure finished building */
		if(structureComplete(_uplink))
		{
			return TRUE;
		}
	}

	return FALSE;
}

function bool defendingOwnBase()
{
	if(helpingAlly() && lastHelpPlayer == me){
		return TRUE;
	}

	return FALSE;
}

/* Call for help when our base is in danger */
event watchBaseThreat(watchBaseThreatTr)
{
	/* See if we can stop defending */
	if(defendingOwnBase())
	{
		if(numEnemiesInBase(FALSE) == 0)
		{
			stopHelpingAlly();	//stop defending our own base

			/* Let allies know we don't need their help anymore */
			messagePlayer(ALL_ALLIES, R_REPORT_SAFETY, MAX_PROBABILITY);
			exit;
		}
	}

	/* See if our base is in trouble and we need help */
	if(baseInTrouble())
	{
		if(!defendingOwnBase())	//make sure not already defending the base
		{
			dbgPlr("Our base is in trouble!");

			/* Bring our forces back if needed */
			if(helpingAlly())
			{
				stopHelpingAlly();
			}

			/* Defend my own base */
			helpPlayer(me, baseX, baseY);
		}

		/* Request help once in a while */
		requestHelp(baseX, baseY);
		exit;
	}
}

function int numAlliesInBase(bool _bVtols)
{
	local int _numAllies;

	_numAllies = numFriendlyWeapDroidsInRange(me, baseX, baseY, W_BASE_THREAT_RANGE, _bVtols);
	_numAllies = _numAllies +
					numFriendlyWeapStructsInRange(me, baseX, baseY, W_BASE_THREAT_RANGE, true) / 3;

	return _numAllies;
}

function int numEnemiesInBase(bool _bVtols)
{
	local int _numEnemies;

	_numEnemies = numEnemyWeapDroidsInRange(me, baseX, baseY, W_BASE_THREAT_RANGE, _bVtols);
	_numEnemies = _numEnemies +
				numEnemyWeapStructsInRange(me, baseX, baseY, W_BASE_THREAT_RANGE, true) / 4;

	return _numEnemies;
}

/* Returns true if our base is in trouble */
function bool baseInTrouble()
{
	local int _enemyForce,_friendlyForce;

	_friendlyForce = numAlliesInBase(FALSE);
	_enemyForce = numEnemiesInBase(FALSE);

	/* See if we are in trouble */
	if((_enemyForce > 0) && (_enemyForce >= _friendlyForce)){
		dbg("baseInTrouble: " & _enemyForce & " >= " & _friendlyForce, me);
		return TRUE;
	}

	return FALSE;
}

/* Request help from allies */
function void requestHelp(int _helpX, int _helpY)
{
	/* Don't do this too frequently */
	if(tLastHelpRequest + HELP_REQUEST_INTERVAL > (gameTime / 10) ){
		return;
	}

	doRequestHelp(_helpX, _helpY);
}

function void doRequestHelp(int _helpX, int _helpY)
{
	local int _ally;

	/* Remember when we requested help last time */
	tLastHelpRequest = gameTime / 10;

	/* Drop beacon for all allies so they would know where to help */
	_ally = 0;
	while(_ally < MAX_PLAYERS)
	{
		if(allianceExistsBetween(me, _ally)){
			if(_DEBUG)
				debug(me & ") requestHelp: " & _ally);

			dropBeacon(getPlayerName(me), _ally, me, _helpX, _helpY, 0);
		}
		_ally++;
	}

	/* Now send message with help request */
	messagePlayer(ALL_ALLIES, M_REQUEST_HELP, MAX_PROBABILITY);
}

function void defendLocation(int _defendX, int _defendY, int _tDefendTimeout, bool _bMove)
{
	local int defendRadius;

	defendRadius = 15 * TILE;

	dbg("starting defending for " & _tDefendTimeout - (gameTime / 10) & " secs, with defend radius " & defendRadius, me);

	defendX = _defendX;
	defendY = _defendY;

	tDefendStart = gameTime / 10;

	/* Should already include travel time */
	tDefendTimeout = _tDefendTimeout;

	/* See if we have to move or scout */
	defendMoveType = DORDER_SCOUT;
	if(_bMove){
		defendMoveType = DORDER_MOVE;
	}

	/* Send attackers */
	if(attackGroup.members > 0)
	{
		if(distBetweenTwoPoints(attackGroup.x, attackGroup.y, _defendX, _defendY) > defendRadius)
		{
			orderGroupLoc(attackGroup, defendMoveType, _defendX, _defendY);
		}
	}

	setEventTrigger(manageDefendLocationEv,	manageDefendLocationTr);
}

function void stopDefendingLocation()
{
	dbg("stopped defending location", me);

	defendX = -1;
	defendY = -1;

	tDefendStart = -1;
	tDefendTimeout = -1;

	defendMoveType = -1;

	setEventTrigger(manageDefendLocationEv,	inactive);

	orderGroupLoc(attackGroup, DORDER_SCOUT,baseX,baseY);
}

function bool defendingLocation()
{
	if(defendX > 0 and defendY > 0){
		return TRUE;
	}

	return FALSE;
}

event manageDefendLocationEv(inactive)
{
	local int defendRadius;
	local DROID _droid;

	if (not defendingLocation())
	{
		exit;
	}

	defendRadius = 15 * TILE;

	dbg("manageDefendLocationEv", me);

	ASSERT(defendMoveType == DORDER_MOVE || defendMoveType == DORDER_SCOUT,
		"manageDefendLocationEv: wrong move order:" & defendMoveType, me);

	ASSERT(defendX > 0 && defendY > 0,
		"manageDefendLocationEv: x/y coordinates:" & defendX & "/" & defendY, me);

	if(!(defendX > 0 && defendY > 0)){
		dbg("not defending???????", me);
		exit;		//not defending?
	}

	/* Collect attackers */
	initIterateGroup(attackGroup);
	_droid = iterateGroup(attackGroup);
	while(_droid != NULLOBJECT)
	{
		if(distBetweenTwoPoints(_droid.x,_droid.y,defendX,defendY) > defendRadius)	//too far from defend location
		{
			if(distBetweenTwoPoints(_droid.orderx,_droid.ordery,defendX,defendY) > defendRadius)	//not already on its way to the defend location
			{
				orderDroidLoc(_droid, defendMoveType, defendX, defendY);
			}
		}
		_droid = iterateGroup(attackGroup);
	}
}

function bool defendLocationTimeout()
{
	if(tDefendTimeout < (gameTime / 10) ){
		return TRUE;
	}

	return FALSE;
}

/* Returns true if player in question is my ally or if it's me */
function bool friendlyPlayer(int _playerToCheck)
{
	if(allianceExistsBetween(_playerToCheck, me) or (_playerToCheck == me)){
		return TRUE;
	}

	return FALSE;
}

function bool insideBase(int _x, int _y)
{
	if(_x < minx){
		return FALSE;
	}

	if(_x > maxx){
		return FALSE;
	}

	if(_y < miny){
		return FALSE;
	}

	if(_y > maxy){
		return FALSE;
	}

	return TRUE;
}

//---------------------------------------------------------------
//Returns how many droids are already on the way to build the
//same structure on the same spot (like helpbuild)
//---------------------------------------------------------------
function int numBuildSameBuilding(STRUCTURESTAT _checkStat, int _x, int _y)
{
	local	int		_numSameBuilding;
	local	DROID	_truck;

	_numSameBuilding = 0;

	initIterateGroup(buildGroup);
	_truck = iterateGroup(buildGroup);
	while(_truck != NULLOBJECT)
	{
		if((_truck.order == DORDER_BUILD) or (_truck.order == DORDER_HELPBUILD) or (_truck.order == DORDER_LINEBUILD))
		{
			if((_checkStat == NULLSTRUCTURESTAT) or (_truck.stat == _checkStat))	//Same struct type
			{
				//Within some range
				if((_x < 0) or (distBetweenTwoPoints(_x, _y, _truck.orderx , _truck.ordery) <= TILE))
				{
					_numSameBuilding++;
				}
			}
		}
		_truck = iterateGroup(buildGroup);
	}

	return _numSameBuilding;
}

// returns number of droids in a certain group with the same order
function int numGroupSameOrder(GROUP _group, int _orderIndex)
{
	local		int 		_numDroids;
	local		DROID		_droid;

	_numDroids = 0;

	initIterateGroup(_group);
	_droid = iterateGroup(_group);
	while(_droid != NULLOBJECT)
	{
		if(_droid.order == _orderIndex)	//right order type
		{
			_numDroids++;
		}
		_droid = iterateGroup(_group);
	}

	return _numDroids;
}

// Remember certain destroyed structures so we can rebuild them later
event structureDestroyed(structureDestroyedTr)
{
	local	int	_count;

	// add certain structures to the rebuild list
	_count = 0;
	while(_count < numRebuildStat)
	{
		if(structure.stat == rebuildStat[_count])
		{
			if(countRebuildStruct < MAX_REBUILD_STRUCT)
			{
				rebuildStructX[countRebuildStruct] = structure.x;
				rebuildStructY[countRebuildStruct] = structure.y;
				rebuildStructStat[countRebuildStruct] = structure.stat;
				countRebuildStruct++;

				dbg("remembered structure (" & countRebuildStruct & ") - " & structure.x & "/" & structure.y, me);

				exit;
			}
		}
		_count++;
	}
}

// Rebuild structures that were destroyed
event rebuildStructureEv(rebuildStructureTr)
{
	rebuildStructures();
}

function void rebuildStructures()
{
	local	int				_count,_threatRange,_x,_y;
	local	DROID			_truck;
	local	STRUCTURESTAT	_stat;

	_threatRange = (TILE * 8);

	_count = 0;
	while(_count < countRebuildStruct)
	{
		if(!threatInRange(me, rebuildStructX[_count], rebuildStructY[_count], _threatRange, FALSE))
		{
			if(getTileStructure(_x / TILE, _y  / TILE) == NULLOBJECT)
			{
				_stat = rebuildStructStat[_count];
				_x = rebuildStructX[_count];
				_y = rebuildStructY[_count];

				if (isStructureAvailable(_stat, me))
				{
					_truck = closestIdleTruck(_x, _y);

					if (_truck != NULLOBJECT)
					{
						if (numBuildSameBuilding(_stat, _x, _y) == 0)	//make sure no one is building already
						{
							buildOnExactLocation(_truck, _x, _y, _stat);
						}
					}
				}
			}
		}
		_count++;
	}
}

function void buildOnExactLocation(DROID _truck, int _x, int _y, STRUCTURESTAT _stat)
{
	local	int	_newX,_newY;

	if(_truck == NULLOBJECT)
	{
		return;
	}

	_newX = _x;
	_newY = _y;

	if (pickDroidStructLocation(_truck, _stat, ref _newX, ref _newY, me, -1))
	{
		dbg("trying to rebuild on (" & _newX & "/" & _newY & ")", me);

		if ((_x != _newX) or (_y != _newY))
		{
			return;
		}

		orderDroidStatsLoc(_truck, DORDER_BUILD, _stat, _x, _y);
	}
}

// Get idle truck closest to some location
function DROID closestIdleTruck(int _x, int _y)
{
	local DROID _closestTruck, _tempTruck;
	local int _closestDist, _tempDist;

	_closestTruck = NULLOBJECT;
	_closestDist = 99999;

	initIterateGroup(buildGroup);
	_tempTruck = iterateGroup(buildGroup);
	while(_tempTruck != NULLOBJECT)
	{
		if((_tempTruck.order == DORDER_NONE) or (_tempTruck.order == DORDER_RTB))
		{
			_tempDist = distBetweenTwoPoints(_x, _y, _tempTruck.x, _tempTruck.y);
			if(_tempDist < _closestDist)
			{
				_closestDist = _tempDist;
				_closestTruck = _tempTruck;
			}
		}
		_tempTruck = iterateGroup(buildGroup);
	}

	return _closestTruck;
}

event consoleEv(consoleTr)
{
	//turn on 'autogame'
	if(message == "autogame on" && (sender == me))
	{
		if(debugModeEnabled())
		{
			if(myResponsibility(me))
			{
				if(not bRunning)		//make sure current machine is responsible for this AI and it's not already active
				{
					console(getPlayerName(me) & " is active");
					reassignAI();
					setEventTrigger(startLevel, chainloadTr);
				}
			}
		}
	}

	//turn off 'autogames'
	if(message == "autogame off" && debugModeEnabled()  && (sender == me))
	{
		if(bRunning)		//make sure this AI is active
		{
			console(getPlayerName(me) & " is deactivated");

			shutDownAI();
		}
	}

	if(message == "aidebug on")
	{
		console(getPlayerName(me) & " ai debug is on");
		_DEBUG = TRUE;
		dbgMsgOn(me, _DEBUG);
	}
	else if(message == "aidebug off")
	{
		console(getPlayerName(me) & " ai debug is off");
		_DEBUG = FALSE;
		dbgMsgOn(me, _DEBUG);
	}
}
