//=====================================================================
// NemesisWINGSvX.
//      ""(§)FrÅgÊd(§)""
//               **Monarch**
//
//              555 ---===CrAzYBoNeS===--- 555
//			Boppolis_The_Dog
//
//
// Thank you Al McElrath for SLV2.
//=====================================================================
/** SkyNet. Spawned on the server to build the sky node network. Built
	slowly over time in latent code so we don't choke.
*/
class SkyNet extends SLInfo config (NemesisWINGSvX);


// DATA
//

// When we started.
var float buildStamp;

// Set based on gametype in postBeginPlay().
var bool bCTF, bAss;

// Tag given to auto-spawned nodes.
var() name autotag;

// Used during auto-spawning.
var NavigationPoint autonp;
var SkyNode firstsn, autosn;
var int i;

/** Map type. 0 == no sky nodes. 1 == tagged path nodes. 2 == sky
	nodes.
*/
var int type;

var byte goalnum;
var Actor goals[16];
var SkyNode goalNodes[16];
var float maxGoalDist[16];
var int nodeSum;

var() float weldRadius;
var() float linkRadius;

// The angle between a launch node and the next one that is "launchable". See goodNode().
var() float goodAngle;

// Set when we've finished building.
var bool bBuilt;

// For visual debugging.
var() config bool bShowNodes;
var() config bool bShowLinks;

// So we can do debugging just for the sky net.
var() config bool bDebug;

var() bool bNoAutoWeightNodes;
var() float randomLinkAdjust;
var() int laps;

var Util u;


// METHODS
//

function postBeginPlay() {
	u = spawn(Class'Util', self);
	u.setDebugLevel(DL_Verbose);
	if (bDebug)
		u.setOutputLevel(DL_Verbose);

	if (!properGame()) {
		u.err("postBeginPlay(): Only usable in CTF or Assault. Destroying.");
		destroy();
	}

	if (bAss)
		// 16 possible fort standards.
		goalnum = Assault(level.game).numForts;
	else
		// Only 4 flags.
		goalnum = 4;

	setMapType();

	u.debug("postBeginPlay(): map type: " $ type);
}


function setMapType() {
	local SkyNode sn;
	local NavigationPoint np;

	foreach allActors(Class'SkyNode', sn) {
		type = 3;
		return;
	}

	foreach allActors(Class'NavigationPoint', np, 'SKYNODE') {
		type = 2;
		return;
	}

	// No SL support.
	type = 1;
}


/** Currently only works with CTF and Assault.
*/
function bool properGame() {
	if (level.game.isA('CTFGame'))
		bCTF = true;

	if (level.game.isA('Assault'))
		bAss = true;

	return (bCTF || bAss);
}


/** Pick a random goal. The Optional args are for excluding a specific
	goal. Not currently used.
*/
function byte randomGoal(optional bool bEx, optional byte exgoal) {
	local int i, pnum;
	local int poss[16];

	for (i = 0; i < goalnum; i++) {
		if (goals[i] != none && !(bEx && i == exgoal)) {
			poss[pnum] = i;
			pnum++;
		}
	}
	
	if (pnum > 0) {
		return poss[rand(pnum)];
	} else {
		u.err("randomGoal(): no possible goals!");
		return 0;
	}
}


/** Returns the first node near us that has a positive launch record.
*/
function SkyNode findLaunchNode(Bot b) {
	local int team;
	local SkyNode sn, ln;
	local byte goal;
	local float cw, w;

	// Init.
	goal = -1;

	// If we are close to where we want to be, don't launch!
	if (getGoal(b, goal)) {
		if (vsize(goalNodes[goal].location - b.location) < linkRadius * 2.0) {
			u.debug("findLaunchNode(): in range of goal");
			return none;
		}
	}

	// Find the heaviest, good node.
	foreach b.radiusActors(Class'SkyNode', sn, linkRadius) {
		if (sn.launchWeight() > 0.50) {
			// Don't use this one if we'll have to do a 180 once we reach it.
			if (goodNode(sn, b)) {
				if (goal == -1) {
					// No goal? Just use the first one.
					ln = sn;
					break;
				} else {
					cw = sn.getWeight(goal);

					if (ln == none) {
						ln = sn;
						w = cw;
					} else {
						if (cw > w) {
							ln = sn;
							w = cw;
						}
					}
				}
			}
		}
	}


	if (ln != none) {
		u.debug("findLaunchNode(): node: " $ u.sname(ln));
		return ln;
	} else {
		u.debug("findLaunchNode(): no good nodes");
		return none;
	}
}


/** This sets the goal index. We may set bEject, so this needs to
	happen just before we test for that in pilotBehavior(). Returns
	true if we have a valid goal.
*/
function bool getGoal(Bot bot, out byte goal) {
	local byte team;
	local FortStandard fort;
	local int i;

	team = bot.playerReplicationInfo.team;

	// Set our goal!
	if (bCTF) {
		// Are we carrying a flag?
		if (bot.playerReplicationInfo.hasFlag != none) {
			goal = team;

		} else {
			// Go for an enemy flag!
			//goal = randomGoal(true, team);
			goal = findEnemyFlagGoal(team);
		} 

	} else if (bAss) {
		fort = Assault(level.game).bestFort;
		if (fort != none) {
			if (FortStandard(goals[goal]) != fort) {
				// Find the new fort index.
				for (i = 0; i < goalnum; i++) {
					if (goals[i] == fort) {
						goal = i;
						break;
					}
				}
			}
		} else {
			u.err("setGoal(): No best fort. Ejecting.");
			return false;
		}
	}

	return true;
}


/** FIX this.
*/
function byte findEnemyFlagGoal(byte team) {
	local int i;

	for (i = 0; i < goalnum; i++) {
		if (i != team) { // && flagAtBase(i)) {
			return i;
		}
	}
}


/** Returns true if the flag is safe and sound.
*/
function bool flagAtBase(byte team) {
	return CTFReplicationInfo(level.game.gameReplicationInfo).flagList[team].bHome;
}


/** Return true if there is a node after n which is roughly in a
	continuous direction from the bot and not blocked.
*/
function bool goodNode(SkyNode n, Bot b) {
	local vector ton;

	if (n != none) {
		u.debug("goodNode(" $ u.sname(n) $ ", " $ u.sname(b) $ ")");

		ton = normal(n.location - b.location);

		for (i = 0; i < arrayCount(n.links); i++) {
			if (n.links[i] != none && fastTrace(n.links[i].location, n.location) && (ton dot normal(n.location - n.links[i].location)) > goodAngle) {
				return true;
			}
		}
	}

	return false;
}


function SkyNode closestNode(vector loc, float radius) {
	local SkyNode sn, closest;
	local float dist;

	foreach radiusActors(Class'SkyNode', sn, radius, loc) {
		if (closest == none || vsize(sn.location - closest.location) < dist)
			closest = sn;
	}

	return closest;
}


/** Build our sky network.
*/
auto state BuildSkyNet {


begin:
	u.debug("BuildSkyNet:begin: Building sky network.", DL_Normal);
	buildStamp = level.timeseconds;

	// Need to auto-spawn sky nodes?
	if (type == 1 || type == 2) {
		u.debug("BuildSkyNet:begin: Auto-spawning nodes.", DL_Normal);
		autonp = level.navigationPointList;
		while (autonp != none) {
			// Catch our CTF goals here.
			if (bCTF && autonp.isA('FlagBase'))
				goals[FlagBase(autonp).team] = autonp;

			// If type 2, only spawn nodes for tagged path nodes.
			if (type == 1 || (type == 2 && autosn.tag == 'SKYNODE')) {
				autosn = autoSkyNode(autonp, autosn);
				nodeSum++;
			}
			autonp = autonp.nextNavigationPoint;
			sleep(0.0);
		}

		// Keep track of the last (which will be first).
		firstsn = autosn;
		u.debug("BuildSkyNet:begin: firstsn: " $ u.sname(firstsn));

		u.debug("BuildSkyNet:begin: Rising sky nodes.", DL_Normal);
		autosn = firstsn;
		while (autosn != none) {
			autosn.setWeldLoc();
			riseSkyNode(autosn);
			autosn = autosn.nextSkyNode;
			sleep(0.0);
		}
	}

	// Assault goals done separately here. Copy them over.
	if (bAss) {
		for (i = 0; i < goalnum; i++) {
			goals[i] = Assault(level.game).fort[i];
		}
	}

linkNodes:
	u.debug("BuildSkyNet:linkNodes: Linking sky nodes.", DL_Normal);
	autosn = firstsn;
	while (autosn != none) {
		// Destroy it if it has no links.
		if (!autosn.linkUp(linkRadius)) {
			// Need to keep a first one.
			if (autosn == firstsn)
				firstsn = autosn.nextSkyNode;

			autosn.destroy();
		}
	
		autosn = autosn.nextSkyNode;
		sleep(0.0);
	}


weightNodes:
	// Weigh the links, depending on gametype.
	if (!bNoAutoWeightNodes) {
		u.debug("BuildSkyNet:weightNodes: Weighting sky nodes.", DL_Normal);

		// Assign goal nodes.
		for (i = 0; i < goalnum; i++) {
			if (goals[i] != none)
				goalNodes[i] = closestNode(goals[i].location, linkRadius);
				u.debug("BuildSkyNet:weightNodes: new goal node: " $ u.sname(goalNodes[i]) $ " for: " $ u.sname(goals[i]));
		}

		// Set the weight to the distance first.
		autosn = firstsn;
		while (autosn != none) {
			for (i = 0; i < goalnum; i++) {
				if (goalNodes[i] != none) {
					autosn.weight[i] = vsize(goalNodes[i].location - autosn.location);
					u.debug("BuildSkyNet:weightNodes: " $ u.sname(autosn) $ ": distance to goal node " $ i $ ": " $ autosn.weight[i]);
					if (autosn.weight[i] > maxGoalDist[i])
						maxGoalDist[i] = autosn.weight[i];
				}
			}

			sleep(0.0);
			autosn = autosn.nextSkyNode;
		}

		// Now divide by the max and subtract from 1.0 to get the weight.
		u.debug("BuildSkyNet:weightNodes: dividing distances: firstsn: " $ u.sname(firstsn));
		autosn = firstsn;
		while (autosn != none) {
			for (i = 0; i < goalnum; i++) {
				if (goalNodes[i] != none) {
					autosn.weight[i] = 1.0 - (autosn.weight[i] / maxGoalDist[i]);
					u.debug("BuildSkyNet:weightNodes: " $ u.sname(autosn) $ ": goal: " $ i $ " weight: " $ autosn.weight[i]);
				}
			}
			sleep(0.0);
			autosn = autosn.nextSkyNode;
		}
	}

	// End.

	u.debug("BuildSkyNet:begin: Built network in " $ u.sf(level.timeseconds - buildStamp) $ " seconds.", DL_Normal);
	bBuilt = true;

} // End BuildSkyNet.


function SkyNode autoSkyNode(NavigationPoint np, SkyNode lastsn) {
	local SkyNode n;

	// Don't weld goal nodes.
	if (!isGoalNavPoint(np)) {
		foreach np.radiusActors(Class'SkyNode', n, weldRadius) {
			u.debug("autoSkyNode(): welded " $ u.sname(np) $ " to " $ u.sname(n));
			n.weld(np.location);
			// Return the last one, for setting firstsn.
			return lastsn;
		}
	}

	n = spawn(Class'SkyNode',, 'AutoSkyNode', np.location);

	if (n == none) {
		u.err("autoSkyNode(): spawn failed for " $ u.sname(np));
		return lastsn;
	}

	if (bShowNodes)
		n.bHidden = false;

	n.navPoint = np;
	n.nextSkyNode = lastsn;
	if (lastsn != none)
		lastsn.prevSkyNode = n;

	u.debug("autoSkyNode(): new skynode for " $ u.sname(np));

	return n;
}


function bool isGoalNavPoint(NavigationPoint np) {
	return ((bASS && np.isA('FortStandard')) || (bCTF && np.isA('FlagBase')));
}


function linkNodes() {
	local SkyNode last, n;

	foreach allActors(Class'SkyNode', n) {
		n.nextSkyNode = last;
		last = n;
	}
}		


function riseSkyNode(SkyNode n) {
	local vector down, up;

	// Don't rise goal nodes.
	if (!isGoalNavPoint(n.navPoint)) {
		down = roof(n.navPoint, -500);
		up = roof(n.navPoint, 500);

		n.setLocation(down + (up - down) * 0.5);
	}
}


function vector roof(Actor a, float dist) {
	local Actor hit;
	local vector end, hl, hn;

	end = a.location + vect(0, 0, 1) * dist;
	hit = a.trace(hl, hn, end, a.location, false);
	if (hit != none)
		return hl;
	else
		return end;
}



// end

defaultproperties
{
     autotag=autoSkyNode
     weldRadius=300.000000
     linkRadius=1800.000000
     goodAngle=0.200000
     randomLinkAdjust=0.050000
}
