Reduce var usage by removing kdtree

This commit is contained in:
ineedbots 2021-05-26 18:54:57 -06:00
parent fb6daae114
commit cc9c873ce7
5 changed files with 201 additions and 209 deletions

View File

@ -16,11 +16,6 @@ init()
if (!getDvarInt("bots_main"))
return;
if(getDvar("bots_main_lowmem") == "")
setDvar("bots_main_lowmem", false);//lower memory usage mode, more cpu
level.bots_lowmem = getDvarInt("bots_main_lowmem");
thread load_waypoints();
cac_init_patch();
thread hook_callbacks();

View File

@ -1695,7 +1695,7 @@ cleanUpAStar(team)
self waittill_any("death", "disconnect", "kill_goal");
for(i = self.bot.astar.size - 1; i >= 0; i--)
level.waypoints[self.bot.astar[i]].bots[team]--;
RemoveWaypointUsage(self.bot.astar[i], team);
}
/*
@ -1723,7 +1723,7 @@ removeAStar()
remove = self.bot.astar.size-1;
if(level.teamBased)
level.waypoints[self.bot.astar[remove]].bots[self.team]--;
RemoveWaypointUsage(self.bot.astar[remove], self.team);
self.bot.astar[remove] = undefined;

View File

@ -1096,47 +1096,6 @@ bots_watch_touch_obj(obj)
}
}
/*
Is bot near any of the given waypoints
*/
nearAnyOfWaypoints(dist, waypoints)
{
dist *= dist;
for (i = 0; i < waypoints.size; i++)
{
waypoint = waypoints[i];
if (DistanceSquared(waypoint.origin, self.origin) > dist)
continue;
return true;
}
return false;
}
/*
Returns nearest waypoint of waypoints
*/
getNearestWaypointOfWaypoints(waypoints)
{
answer = undefined;
closestDist = 2147483647;
for (i = 0; i < waypoints.size; i++)
{
waypoint = waypoints[i];
thisDist = DistanceSquared(self.origin, waypoint.origin);
if (isDefined(answer) && thisDist > closestDist)
continue;
answer = waypoint;
closestDist = thisDist;
}
return answer;
}
/*
Watches while the obj is being carried, calls 'goal' when complete
*/
@ -1714,16 +1673,7 @@ bot_think_camp()
if(randomInt(100) > self.pers["bots"]["behavior"]["camp"])
continue;
campSpots = [];
distSq = 1024*1024;
for (i = 0; i < level.waypointsCamp.size; i++)
{
if (DistanceSquared(self.origin, level.waypointsCamp[i].origin) > distSq)
continue;
campSpots[campSpots.size] = level.waypointsCamp[i];
}
campSpot = PickRandom(campSpots);
campSpot = getWaypointForIndex(random(self waypointsNear(getWaypointsOfType("camp"), 1024)));
if (!isDefined(campSpot))
continue;
@ -1976,18 +1926,9 @@ bot_use_tube_think()
loc = undefined;
if (!self nearAnyOfWaypoints(128, level.waypointsTube))
if (!self nearAnyOfWaypoints(128, getWaypointsOfType("tube")))
{
tubeWps = [];
distSq = 1024*1024;
for (i = 0; i < level.waypointsTube.size; i++)
{
if (DistanceSquared(self.origin, level.waypointsTube[i].origin) > distSq)
continue;
tubeWps[tubeWps.size] = level.waypointsTube[i];
}
tubeWp = PickRandom(tubeWps);
tubeWp = getWaypointForIndex(random(self waypointsNear(getWaypointsOfType("tube"), 1024)));
myEye = self GetEye();
if (!isDefined(tubeWp) || self HasScriptGoal() || self.bot_lock_goal)
@ -2025,7 +1966,7 @@ bot_use_tube_think()
}
else
{
tubeWp = self getNearestWaypointOfWaypoints(level.waypointsTube);
tubeWp = getWaypointForIndex(self getNearestWaypointOfWaypoints(getWaypointsOfType("tube")));
loc = tubeWp.origin + AnglesToForward(tubeWp.angles) * 2048;
}
@ -2105,18 +2046,9 @@ bot_use_equipment_think()
if (curWeap == "none" || !isWeaponDroppable(curWeap))
curWeap = self.lastDroppableWeapon;
if (!self nearAnyOfWaypoints(128, level.waypointsClay))
if (!self nearAnyOfWaypoints(128, getWaypointsOfType("claymore")))
{
clayWps = [];
distSq = 1024*1024;
for (i = 0; i < level.waypointsClay.size; i++)
{
if (DistanceSquared(self.origin, level.waypointsClay[i].origin) > distSq)
continue;
clayWps[clayWps.size] = level.waypointsClay[i];
}
clayWp = PickRandom(clayWps);
clayWp = getWaypointForIndex(random(self waypointsNear(getWaypointsOfType("claymore"), 1024)));
if (!isDefined(clayWp) || self HasScriptGoal() || self.bot_lock_goal)
{
@ -2144,7 +2076,7 @@ bot_use_equipment_think()
}
else
{
clayWp = self getNearestWaypointOfWaypoints(level.waypointsClay);
clayWp = getWaypointForIndex(self getNearestWaypointOfWaypoints(getWaypointsOfType("claymore")));
loc = clayWp.origin + AnglesToForward(clayWp.angles) * 2048;
}
@ -2219,18 +2151,9 @@ bot_use_grenade_think()
loc = undefined;
if (!self nearAnyOfWaypoints(128, level.waypointsGren))
if (!self nearAnyOfWaypoints(128, getWaypointsOfType("grenade")))
{
nadeWps = [];
distSq = 1024*1024;
for (i = 0; i < level.waypointsGren.size; i++)
{
if (DistanceSquared(self.origin, level.waypointsGren[i].origin) > distSq)
continue;
nadeWps[nadeWps.size] = level.waypointsGren[i];
}
nadeWp = PickRandom(nadeWps);
nadeWp = getWaypointForIndex(random(self waypointsNear(getWaypointsOfType("grenade"), 1024)));
myEye = self GetEye();
if (!isDefined(nadeWp) || self HasScriptGoal() || self.bot_lock_goal)
@ -2268,7 +2191,7 @@ bot_use_grenade_think()
}
else
{
nadeWp = self getNearestWaypointOfWaypoints(level.waypointsGren);
nadeWp = getWaypointForIndex(self getNearestWaypointOfWaypoints(getWaypointsOfType("grenade")));
loc = nadeWp.origin + AnglesToForward(nadeWp.angles) * 2048;
}

View File

@ -1013,7 +1013,6 @@ parseTokensIntoWaypoint(tokens)
childStr = tokens[1];
childToks = strtok(childStr, " ");
waypoint.childCount = childToks.size;
waypoint.children = [];
for( j=0; j<childToks.size; j++ )
waypoint.children[j] = int(childToks[j]);
@ -1102,6 +1101,9 @@ load_waypoints()
level.waypointCount = 0;
level.waypoints = [];
level.waypointUsage = [];
level.waypointUsage["allies"] = [];
level.waypointUsage["axis"] = [];
wps = readWpsFromFile(mapname);
@ -1200,11 +1202,6 @@ load_waypoints()
for(i = 0; i < level.waypointCount; i++)
{
level.waypoints[i].index = i;
level.waypoints[i].bots = [];
level.waypoints[i].bots["allies"] = 1;
level.waypoints[i].bots["axis"] = 1;
if (!isDefined(level.waypoints[i].children) || !isDefined(level.waypoints[i].children.size))
level.waypoints[i].children = [];
@ -1214,28 +1211,108 @@ load_waypoints()
if (!isDefined(level.waypoints[i].type))
level.waypoints[i].type = "crouch";
level.waypoints[i].childCount = level.waypoints[i].children.size;
level.waypoints[i].childCount = undefined;
}
if (!level.bots_lowmem)
}
/*
Is bot near any of the given waypoints
*/
nearAnyOfWaypoints(dist, waypoints)
{
dist *= dist;
for (i = 0; i < waypoints.size; i++)
{
level.waypointsKDTree = WaypointsToKDTree();
waypoint = level.waypoints[waypoints[i]];
if (DistanceSquared(waypoint.origin, self.origin) > dist)
continue;
return true;
}
level.waypointsCamp = [];
level.waypointsTube = [];
level.waypointsGren = [];
level.waypointsClay = [];
return false;
}
/*
Returns the waypoints that are near
*/
waypointsNear(waypoints, dist)
{
dist *= dist;
answer = [];
for (i = 0; i < waypoints.size; i++)
{
wp = level.waypoints[waypoints[i]];
if (DistanceSquared(wp.origin, self.origin) > dist)
continue;
answer[answer.size] = waypoints[i];
}
return answer;
}
/*
Returns nearest waypoint of waypoints
*/
getNearestWaypointOfWaypoints(waypoints)
{
answer = undefined;
closestDist = 2147483647;
for (i = 0; i < waypoints.size; i++)
{
waypoint = level.waypoints[waypoints[i]];
thisDist = DistanceSquared(self.origin, waypoint.origin);
if (isDefined(answer) && thisDist > closestDist)
continue;
answer = waypoints[i];
closestDist = thisDist;
}
return answer;
}
/*
Returns all waypoints of type
*/
getWaypointsOfType(type)
{
answer = [];
for(i = 0; i < level.waypointCount; i++)
if(level.waypoints[i].type == "crouch" && level.waypoints[i].childCount == 1)
level.waypointsCamp[level.waypointsCamp.size] = level.waypoints[i];
else if(level.waypoints[i].type == "tube")
level.waypointsTube[level.waypointsTube.size] = level.waypoints[i];
else if(level.waypoints[i].type == "grenade")
level.waypointsGren[level.waypointsGren.size] = level.waypoints[i];
else if(level.waypoints[i].type == "claymore")
level.waypointsClay[level.waypointsClay.size] = level.waypoints[i];
{
wp = level.waypoints[i];
if (type == "camp")
{
if (wp.type != "crouch")
continue;
if (wp.children.size != 1)
continue;
}
else if (type != wp.type)
continue;
answer[answer.size] = i;
}
return answer;
}
/*
Returns the waypoint for index
*/
getWaypointForIndex(i)
{
if (!isDefined(i))
return undefined;
return level.waypoints[i];
}
/*
@ -1827,6 +1904,23 @@ ReverseHeapAStar(item, item2)
return item.f < item2.f;
}
/*
Removes the waypoint usage
*/
RemoveWaypointUsage(wp, team)
{
if (!isDefined(level.waypointUsage))
return;
if (!isDefined(level.waypointUsage[team][wp+""]))
return;
level.waypointUsage[team][wp+""]--;
if (level.waypointUsage[team][wp+""] <= 0)
level.waypointUsage[team][wp+""] = undefined;
}
/*
Will linearly search for the nearest waypoint to pos that has a direct line of sight.
*/
@ -1845,7 +1939,7 @@ GetNearestWaypointWithSight(pos)
continue;
dist = curdis;
candidate = level.waypoints[i];
candidate = i;
}
return candidate;
@ -1866,7 +1960,7 @@ GetNearestWaypoint(pos)
continue;
dist = curdis;
candidate = level.waypoints[i];
candidate = i;
}
return candidate;
@ -1875,56 +1969,48 @@ GetNearestWaypoint(pos)
/*
Modified Pezbot astar search.
This makes use of sets for quick look up and a heap for a priority queue instead of simple lists which require to linearly search for elements everytime.
Also makes use of the KD tree to search for the nearest node to the goal. We only use the closest node from the KD tree if it has a direct line of sight, else we will have to linearly search for one that we have a line of sight on.
It is also modified to make paths with bots already on more expensive and will try a less congested path first. Thus spliting up the bots onto more paths instead of just one (the smallest).
*/
AStarSearch(start, goal, team, greedy_path)
{
open = NewHeap(::ReverseHeapAStar);//heap
openset = [];//set for quick lookup
closed = [];//set for quick lookup
startwp = undefined;
if (level.bots_lowmem)
startwp = getNearestWaypoint(start);
else
startwp = level.waypointsKDTree KDTreeNearest(start);//balanced kdtree, for nns
if(!isDefined(startwp))
startWp = getNearestWaypoint(start);
if(!isDefined(startWp))
return [];
_startwp = undefined;
if(!bulletTracePassed(start + (0, 0, 15), startwp.origin + (0, 0, 15), false, undefined))
if(!bulletTracePassed(start + (0, 0, 15), level.waypoints[startWp].origin + (0, 0, 15), false, undefined))
_startwp = GetNearestWaypointWithSight(start);
if(isDefined(_startwp))
startwp = _startwp;
startwp = startwp.index;
startWp = _startwp;
goalwp = undefined;
if (level.bots_lowmem)
goalwp = getNearestWaypoint(goal);
else
goalwp = level.waypointsKDTree KDTreeNearest(goal);
if(!isDefined(goalwp))
goalWp = getNearestWaypoint(goal);
if(!isDefined(goalWp))
return [];
_goalwp = undefined;
if(!bulletTracePassed(goal + (0, 0, 15), goalwp.origin + (0, 0, 15), false, undefined))
_goalWp = undefined;
if(!bulletTracePassed(goal + (0, 0, 15), level.waypoints[goalWp].origin + (0, 0, 15), false, undefined))
_goalwp = GetNearestWaypointWithSight(goal);
if(isDefined(_goalwp))
goalwp = _goalwp;
goalwp = goalwp.index;
goalorg = level.waypoints[goalWp].origin;
goalWp = _goalwp;
node = spawnStruct();
node.g = 0; //path dist so far
node.h = DistanceSquared(level.waypoints[startWp].origin, goalorg); //herustic, distance to goal for path finding
//node.f = node.h + node.g; // combine path dist and heru, use reverse heap to sort the priority queue by this attru
node.f = node.h;
node.index = startwp;
node.h = DistanceSquared(level.waypoints[startWp].origin, level.waypoints[goalWp].origin); //herustic, distance to goal for path finding
node.f = node.h + node.g; // combine path dist and heru, use reverse heap to sort the priority queue by this attru
node.index = startWp;
node.parent = undefined; //we are start, so we have no parent
//push node onto queue
openset[node.index] = node;
openset[node.index+""] = node;
open HeapInsert(node);
//while the queue is not empty
@ -1933,87 +2019,94 @@ AStarSearch(start, goal, team, greedy_path)
//pop bestnode from queue
bestNode = open.data[0];
open HeapRemove();
openset[bestNode.index] = undefined;
openset[bestNode.index+""] = undefined;
wp = level.waypoints[bestNode.index];
//check if we made it to the goal
if(bestNode.index == goalwp)
if(bestNode.index == goalWp)
{
path = [];
while(isDefined(bestNode))
{
if(isdefined(team))
level.waypoints[bestNode.index].bots[team]++;
if(isdefined(team) && isDefined(level.waypointUsage))
{
if (!isDefined(level.waypointUsage[team][bestNode.index+""]))
level.waypointUsage[team][bestNode.index+""] = 0;
level.waypointUsage[team][bestNode.index+""]++;
}
//construct path
path[path.size] = bestNode.index;
bestNode = bestNode.parent;
}
return path;
}
nodeorg = level.waypoints[bestNode.index].origin;
childcount = level.waypoints[bestNode.index].childCount;
//for each child of bestnode
for(i = 0; i < childcount; i++)
for(i = wp.children.size - 1; i >= 0; i--)
{
child = level.waypoints[bestNode.index].children[i];
childorg = level.waypoints[child].origin;
childtype = level.waypoints[child].type;
child = wp.children[i];
childWp = level.waypoints[child];
penalty = 1;
if(!greedy_path && isdefined(team))
if(!greedy_path && isdefined(team) && isDefined(level.waypointUsage))
{
temppen = level.waypoints[child].bots[team];//consider how many bots are taking this path
temppen = 1;
if (isDefined(level.waypointUsage[team][child+""]))
temppen = level.waypointUsage[team][child+""];//consider how many bots are taking this path
if(temppen > 1)
penalty = temppen;
}
// have certain types of nodes more expensive
if (childtype == "climb" || childtype == "prone")
if (childWp.type == "climb" || childWp.type == "prone")
penalty += 4;
//calc the total path we have took
newg = bestNode.g + DistanceSquared(nodeorg, childorg)*penalty;//bots on same team's path are more expensive
newg = bestNode.g + DistanceSquared(wp.origin, childWp.origin)*penalty;//bots on same team's path are more expensive
//check if this child is in open or close with a g value less than newg
inopen = isDefined(openset[child]);
if(inopen && openset[child].g <= newg)
inopen = isDefined(openset[child+""]);
if(inopen && openset[child+""].g <= newg)
continue;
inclosed = isDefined(closed[child]);
if(inclosed && closed[child].g <= newg)
inclosed = isDefined(closed[child+""]);
if(inclosed && closed[child+""].g <= newg)
continue;
node = undefined;
if(inopen)
node = openset[child];
node = openset[child+""];
else if(inclosed)
node = closed[child];
node = closed[child+""];
else
node = spawnStruct();
node.parent = bestNode;
node.g = newg;
node.h = DistanceSquared(childorg, goalorg);
node.h = DistanceSquared(childWp.origin, level.waypoints[goalWp].origin);
node.f = node.g + node.h;
node.index = child;
//check if in closed, remove it
if(inclosed)
closed[child] = undefined;
closed[child+""] = undefined;
//check if not in open, add it
if(!inopen)
{
open HeapInsert(node);
openset[child] = node;
openset[child+""] = node;
}
}
//done with children, push onto closed
closed[bestNode.index] = bestNode;
closed[bestNode.index+""] = bestNode;
}
return [];

View File

@ -54,7 +54,6 @@ init()
level.waypoints = [];
level.waypointCount = 0;
level.bots_lowmem = false;
level waittill( "connected", player);
@ -132,7 +131,7 @@ debug()
if(distance(level.waypoints[i].origin, self.origin) < getDvarFloat("bots_main_debug_distance") && (bulletTracePassed(myEye, wpOrg, false, self) || getDVarint("bots_main_debug_drawThrough")))
{
for(h = 0; h < level.waypoints[i].childCount; h++)
for(h = level.waypoints[i].children.size - 1; h >= 0; h--)
line(wpOrg, level.waypoints[level.waypoints[i].children[h]].origin + (0, 0, 25), (1,0,1));
if(getConeDot(wpOrg, myEye, myAngles) > getDvarFloat("bots_main_debug_cone"))
@ -148,7 +147,7 @@ debug()
if(closest != -1)
{
stringChildren = "";
for(i = 0; i < level.waypoints[closest].childCount; i++)
for(i = 0; i < level.waypoints[closest].children.size; i++)
{
if(i != 0)
stringChildren = stringChildren + "," + level.waypoints[closest].children[i];
@ -278,12 +277,11 @@ watchSaveWaypointsCommand()
logprint("*/waypoints["+i+"] = spawnstruct();\n/*");
logprint("*/waypoints["+i+"].origin = "+level.waypoints[i].origin+";\n/*");
logprint("*/waypoints["+i+"].type = \""+level.waypoints[i].type+"\";\n/*");
logprint("*/waypoints["+i+"].childCount = "+level.waypoints[i].childCount+";\n/*");
for(c = 0; c < level.waypoints[i].childCount; c++)
for(c = 0; c < level.waypoints[i].children.size; c++)
{
logprint("*/waypoints["+i+"].children["+c+"] = "+level.waypoints[i].children[c]+";\n/*");
}
if(isDefined(level.waypoints[i].angles) && (level.waypoints[i].type == "claymore" || level.waypoints[i].type == "tube" || (level.waypoints[i].type == "crouch" && level.waypoints[i].childCount == 1) || level.waypoints[i].type == "climb" || level.waypoints[i].type == "grenade"))
if(isDefined(level.waypoints[i].angles) && (level.waypoints[i].type == "claymore" || level.waypoints[i].type == "tube" || (level.waypoints[i].type == "crouch" && level.waypoints[i].children.size == 1) || level.waypoints[i].type == "climb" || level.waypoints[i].type == "grenade"))
logprint("*/waypoints["+i+"].angles = "+level.waypoints[i].angles+";\n/*");
}
logprint("*/return waypoints;\n}\n\n\n\n");
@ -301,11 +299,11 @@ watchSaveWaypointsCommand()
str += wp.origin[0] + " " + wp.origin[1] + " " + wp.origin[2] + ",";
for(h = 0; h < wp.childCount; h++)
for(h = 0; h < wp.children.size; h++)
{
str += wp.children[h];
if (h < wp.childCount - 1)
if (h < wp.children.size - 1)
str += " ";
}
str += "," + wp.type + ",";
@ -374,8 +372,8 @@ checkForWarnings()
continue;
}
if(level.waypoints[i].childCount <= 0)
self iprintln("WARNING: waypoint "+i+" childCount is "+level.waypoints[i].childCount);
if(level.waypoints[i].children.size <= 0)
self iprintln("WARNING: waypoint "+i+" childCount is "+level.waypoints[i].children.size);
else
{
if (!isDefined(level.waypoints[i].children) || !isDefined(level.waypoints[i].children.size))
@ -384,10 +382,7 @@ checkForWarnings()
}
else
{
if(level.waypoints[i].childCount != level.waypoints[i].children.size)
self iprintln("WARNING: waypoint "+i+" childCount is not "+level.waypoints[i].children.size);
for (h = 0; h < level.waypoints[i].childCount; h++)
for(h = level.waypoints[i].children.size - 1; h >= 0; h--)
{
child = level.waypoints[i].children[h];
@ -405,7 +400,7 @@ checkForWarnings()
continue;
}
if(!isDefined(level.waypoints[i].angles) && (level.waypoints[i].type == "claymore" || level.waypoints[i].type == "tube" || (level.waypoints[i].type == "crouch" && level.waypoints[i].childCount == 1) || level.waypoints[i].type == "climb" || level.waypoints[i].type == "grenade"))
if(!isDefined(level.waypoints[i].angles) && (level.waypoints[i].type == "claymore" || level.waypoints[i].type == "tube" || (level.waypoints[i].type == "crouch" && level.waypoints[i].children.size == 1) || level.waypoints[i].type == "climb" || level.waypoints[i].type == "grenade"))
self iprintln("WARNING: waypoint "+i+" angles is undefined");
}
}
@ -414,12 +409,6 @@ DeleteAllWaypoints()
{
level.waypoints = [];
level.waypointCount = 0;
level.waypointsKDTree = WaypointsToKDTree();
level.waypointsCamp = [];
level.waypointsTube = [];
level.waypointsGren = [];
level.waypointsClay = [];
self iprintln("DelAllWps");
}
@ -434,18 +423,16 @@ DeleteWaypoint(nwp)
level.wpToLink = -1;
for(i = 0; i < level.waypoints[nwp].childCount; i++)
for(i = level.waypoints[nwp].children.size - 1; i >= 0; i--)
{
child = level.waypoints[nwp].children[i];
level.waypoints[child].children = array_remove(level.waypoints[child].children, nwp);
level.waypoints[child].childCount = level.waypoints[child].children.size;
}
for(i = 0; i < level.waypointCount; i++)
{
for(h = 0; h < level.waypoints[i].childCount; h++)
for(h = level.waypoints[i].children.size - 1; h >= 0; h--)
{
if(level.waypoints[i].children[h] > nwp)
level.waypoints[i].children[h]--;
@ -490,7 +477,6 @@ addWaypoint(pos)
level.waypoints[level.waypointCount].angles = self getPlayerAngles();
level.waypoints[level.waypointCount].children = [];
level.waypoints[level.waypointCount].childCount = 0;
self iprintln(level.waypoints[level.waypointCount].type + " Waypoint "+ level.waypointCount +" Added at "+pos);
@ -527,9 +513,6 @@ UnLinkWaypoint(nwp)
level.waypoints[nwp].children = array_remove(level.waypoints[nwp].children, level.wpToLink);
level.waypoints[level.wpToLink].children = array_remove(level.waypoints[level.wpToLink].children, nwp);
level.waypoints[nwp].childCount = level.waypoints[nwp].children.size;
level.waypoints[level.wpToLink].childCount = level.waypoints[level.wpToLink].children.size;
self iprintln("Waypoint " + nwp + " Broken to " + level.wpToLink);
level.wpToLink = -1;
}
@ -551,7 +534,7 @@ LinkWaypoint(nwp)
}
weGood = true;
for(i = 0; i < level.waypoints[level.wpToLink].childCount; i++)
for(i = level.waypoints[level.wpToLink].children.size - 1; i >= 0; i--)
{
if(level.waypoints[level.wpToLink].children[i] == nwp)
{
@ -561,7 +544,7 @@ LinkWaypoint(nwp)
}
if(weGood)
{
for(i = 0; i < level.waypoints[nwp].childCount; i++)
for(i = level.waypoints[nwp].children.size - 1; i >= 0; i--)
{
if(level.waypoints[nwp].children[i] == level.wpToLink)
{
@ -578,10 +561,8 @@ LinkWaypoint(nwp)
return;
}
level.waypoints[level.wpToLink].children[level.waypoints[level.wpToLink].childcount] = nwp;
level.waypoints[level.wpToLink].childcount++;
level.waypoints[nwp].children[level.waypoints[nwp].childcount] = level.wpToLink;
level.waypoints[nwp].childcount++;
level.waypoints[level.wpToLink].children[level.waypoints[level.wpToLink].children.size] = nwp;
level.waypoints[nwp].children[level.waypoints[nwp].children.size] = level.wpToLink;
self iprintln("Waypoint " + nwp + " Linked to " + level.wpToLink);
level.wpToLink = -1;