mirror of
https://github.com/Ragora/T2-DXAI.git
synced 2026-03-03 20:40:23 +00:00
Restructuring; PriorityQueue implementation; commander task weighting & assignment; some code documentation
This commit is contained in:
parent
0a77531cd9
commit
088d7fee8e
7 changed files with 537 additions and 264 deletions
213
scripts/DXAI/aicommander.cs
Normal file
213
scripts/DXAI/aicommander.cs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
//------------------------------------------------------------------------------------------
|
||||
// aicommander.cs
|
||||
// Source file for the DXAI commander AI implementation.
|
||||
// https://github.com/Ragora/T2-DXAI.git
|
||||
//
|
||||
// Copyright (c) 2014 Robert MacGregor
|
||||
// This software is licensed under the MIT license. Refer to LICENSE.txt for more information.
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
$DXAI::ActiveCommanderCount = 2;
|
||||
|
||||
$DXAI::Priorities::DefendGenerator = 1;
|
||||
$DXAI::Priorities::DefendFlag = 2;
|
||||
$DXAI::Priorities::ScoutBase = 3;
|
||||
//-----------------------------------------------
|
||||
$DXAI::Priorities::CaptureFlag = 4;
|
||||
$DXAI::Priorities::CaptureObjective = 5;
|
||||
$DXAI::Priorities::AttackTurret = 6;
|
||||
$DXAI::Priorities::Count = 3;
|
||||
|
||||
// # of bots assigned = mCeil(((totalPriorityValues / priorityCounts) * priority) / priorityCounts)
|
||||
|
||||
// totalPriorityValues = 3 + 2
|
||||
// priorityCounts = 2
|
||||
// Priority for Gen: 2
|
||||
// Priority for Flag: 3
|
||||
// flagBots = ((5.0 / 2.0) * 3.0) / 2.0
|
||||
// Gen bots = ((5.0 / 2.0) * 2.0) / 2.0
|
||||
$DXAI::Priorities::DefaultPriorityValue[$DXAI::Priorities::DefendGenerator] = 2;
|
||||
$DXAI::Priorities::DefaultPriorityValue[$DXAI::Priorities::DefendFlag] = 3;
|
||||
$DXAI::Priorities::DefaultPriorityValue[$DXAI::Priorities::ScoutBase] = 1;
|
||||
|
||||
function AICommander::setup(%this)
|
||||
{
|
||||
%this.botList = new SimSet();
|
||||
%this.idleBotList = new SimSet();
|
||||
|
||||
for (%iteration = 0; %iteration < ClientGroup.getCount(); %iteration++)
|
||||
{
|
||||
%currentClient = ClientGroup.getObject(%iteration);
|
||||
|
||||
if (%currentClient.isAIControlled() && %currentClient.team == %this.team)
|
||||
{
|
||||
%this.botList.add(%currentClient);
|
||||
%this.idleBotList.add(%currentClient);
|
||||
|
||||
%currentClient.commander = %this;
|
||||
}
|
||||
}
|
||||
|
||||
%this.setDefaultPriorities();
|
||||
|
||||
// Also set the assignment tracker
|
||||
for (%iteration = 0; %iteration < $DXAI::Priorities::Count; %iteration++)
|
||||
%this.assignments[%iteration] = 0;
|
||||
}
|
||||
|
||||
function AICommander::assignTasks(%this)
|
||||
{
|
||||
// Calculate how much priority we have total, first
|
||||
%totalPriority = 0.0;
|
||||
for (%iteration = 0; %iteration < $DXAI::Priorities::Count; %iteration++)
|
||||
{
|
||||
%totalPriority += %this.priorities[%iteration];
|
||||
%botAssignments[%iteration] = 0;
|
||||
}
|
||||
|
||||
// We create a priority queue preemptively so we can sort task priorities as we go and save a little bit of time
|
||||
%priorityQueue = PriorityQueue::create();
|
||||
|
||||
// Now calculate how many bots we need per objective, and count how many we will need in total
|
||||
%lostBots = false; // Used for a small optimization
|
||||
%botCountRequired = 0;
|
||||
for (%iteration = 0; %iteration < $DXAI::Priorities::Count; %iteration++)
|
||||
{
|
||||
%botAssignments[%iteration] = mCeil(((%totalPriority / $DXAI::Priorities::Count) * %this.priorities[%iteration]) / $DXAI::Priorities::Count);
|
||||
%botAssignments[%iteration] -= %this.botAssignments[%iteration]; // If we already have bots doing this, then we don't need to replicate them
|
||||
%botCountRequired += %botAssignments[%iteration];
|
||||
|
||||
if (%botAssignments[%iteration] < 0)
|
||||
%lostBots = true;
|
||||
else
|
||||
%priorityQueue.add(%iteration, %botAssignments[%iteration]);
|
||||
}
|
||||
|
||||
// Deassign from objectives we need less bots for now and put them into the idle list
|
||||
// When we lose bots, our %botAssignments[%task] value will be a negative, so we just need
|
||||
// to ditch mAbs(%botAssignments[%task]) bots from that given task.
|
||||
for (%taskIteration = 0; %lostBots && %taskIteration < $DXAI::Priorities::Count; %taskiteration++)
|
||||
// Need to ditch some bots
|
||||
if (%botAssignments[%taskIteration] < 0)
|
||||
%this.deassignBots(%taskIteration, mAbs(%botAssignments[%taskIteration]);
|
||||
|
||||
// Do we have enough idle bots to just shunt everyone into something?
|
||||
if (%this.idleBotList.getCount() >= %botCountRequired)
|
||||
{
|
||||
for (%taskIteration = 0; %taskIteration < $DXAI::Priorities::Count; %taskiteration++)
|
||||
for (%botIteration = 0; %botIteration < %botAssignments[%taskIteration]; %botIteration++)
|
||||
%this.assignTask(%taskIteration, %this.idleBotList.getObject(0));
|
||||
}
|
||||
// Okay, we don't have enough bots currently so we'll try to satisify the higher priority objectives first
|
||||
else
|
||||
while (!%priorityQueue.isEmpty() && %this.idleBotList.getCount() != 0)
|
||||
{
|
||||
%taskID = %priorityQueue.topKey();
|
||||
%requiredBots = %priorityQueue.topValue();
|
||||
%priorityQueue.pop();
|
||||
|
||||
for (%botIteration = 0; %botIteration < %requiredBots && %this.idleBotList.getCount() != 0; %botIteration++)
|
||||
%this.assignTask(%taskID, %this.idleBotList.getObject(0));
|
||||
}
|
||||
|
||||
// Regardless, we need to make sure we cleanup the queue
|
||||
// FIXME: Perhaps just create one per commander and reuse it?
|
||||
%priorityQueue.delete();
|
||||
}
|
||||
|
||||
function AICommander::deassignBots(%this, %taskID, %count)
|
||||
{
|
||||
// TODO: More efficient removal?
|
||||
for (%iteration = 0; %count > 0 && %iteration < %this.botList.getCount(); %iteration++)
|
||||
{
|
||||
%bot = %this.botList.getObject(%iteration);
|
||||
if (%bot.assignment == %taskID)
|
||||
{
|
||||
%bot.clearTasks();
|
||||
%this.idleBotList.add(%bot);
|
||||
%count--;
|
||||
}
|
||||
}
|
||||
|
||||
return %count == 0;
|
||||
}
|
||||
|
||||
function AICommander::assignTask(%this, %taskID, %bot)
|
||||
{
|
||||
// Don't try to assign if the bot is already assigned something
|
||||
if (!%this.idleBotList.contains(%this))
|
||||
return;
|
||||
|
||||
%this.idleBotList.remove(%this);
|
||||
|
||||
switch (%taskID)
|
||||
{
|
||||
case $DXAI::Priorities::DefendGenerator:
|
||||
break;
|
||||
case $DXAI::Priorities::DefendFlag:
|
||||
break;
|
||||
case $DXAI::Priorities::ScoutBase:
|
||||
break;
|
||||
}
|
||||
|
||||
%this.botAssignments[%taskID]++;
|
||||
%bot.assignment = %taskID;
|
||||
}
|
||||
|
||||
function AICommander::setDefaultPriorities(%this)
|
||||
{
|
||||
for (%iteration = 0; %iteration < $DXAI::Priorities::Count; %iteration++)
|
||||
%this.priorities[%iteration] = $DXAI::Priorities::DefaultPriorityValue[%iteration];
|
||||
}
|
||||
|
||||
function AICommander::cleanUp(%this)
|
||||
{
|
||||
%this.botList.delete();
|
||||
%this.idleBotList.delete();
|
||||
}
|
||||
|
||||
function AICommander::update(%this)
|
||||
{
|
||||
for (%iteration = 0; %iteration < %this.botList.getCount(); %iteration++)
|
||||
%this.botList.getObject(%iteration).update();
|
||||
}
|
||||
|
||||
function AICommander::removeBot(%this, %bot)
|
||||
{
|
||||
%this.botList.remove(%bot);
|
||||
%this.idleBotList.remove(%bot);
|
||||
|
||||
%bot.commander = -1;
|
||||
}
|
||||
|
||||
function AICommander::addBot(%this, %bot)
|
||||
{
|
||||
if (!%this.botList.isMember(%bot))
|
||||
%this.botList.add(%bot);
|
||||
|
||||
if (!%this.idleBotList.isMember(%bot))
|
||||
%this.idleBotList.add(%bot);
|
||||
|
||||
%bot.commander = %this;
|
||||
}
|
||||
|
||||
function AICommander::notifyPlayerDeath(%this, %killedClient, %killedByClient)
|
||||
{
|
||||
}
|
||||
|
||||
function AICommander::notifyFlagGrab(%this, %grabbedByClient)
|
||||
{
|
||||
%this.priority[$DXAI::Priorities::DefendFlag]++;
|
||||
|
||||
// ...well, balls, someone nipped me flag! Are there any bots sitting around being lazy?
|
||||
// TODO: We should also include nearby scouting bots into this, as well.
|
||||
if (%this.idleBotList.getCount() != 0)
|
||||
{
|
||||
// Go full-force and try to kill that jerk!
|
||||
for (%iteration = 0; %iteration < %this.idleBotList.getCount(); %iteration++)
|
||||
{
|
||||
%idleBot = %this.idleBotList.getObject(%iteration);
|
||||
%idleBot.attackTarget = %grabbedByClient.player;
|
||||
}
|
||||
}
|
||||
}
|
||||
178
scripts/DXAI/aiconnection.cs
Normal file
178
scripts/DXAI/aiconnection.cs
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
//------------------------------------------------------------------------------------------
|
||||
// aiconnection.cs
|
||||
// Source file declaring the custom AIConnection methods used by the DXAI experimental
|
||||
// AI enhancement project.
|
||||
// https://github.com/Ragora/T2-DXAI.git
|
||||
//
|
||||
// Copyright (c) 2014 Robert MacGregor
|
||||
// This software is licensed under the MIT license.
|
||||
// Refer to LICENSE.txt for more information.
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
function AIConnection::initialize(%this, %aiClient)
|
||||
{
|
||||
%this.fieldOfView = 3.14 / 2; // 90* View cone
|
||||
%this.viewDistance = 300;
|
||||
|
||||
if (!isObject(%aiClient))
|
||||
error("AIPlayer: Attempted to initialize with bad AI client connection!");
|
||||
|
||||
%this.client = %aiClient;
|
||||
}
|
||||
|
||||
function AIConnection::update(%this)
|
||||
{
|
||||
if (isObject(%this.player) && %this.player.getMoveState() $= "walk")
|
||||
{
|
||||
%this.updateLegs();
|
||||
%this.updateVisualAcuity();
|
||||
}
|
||||
}
|
||||
|
||||
function AIConnection::notifyProjectileImpact(%this, %data, %proj, %position)
|
||||
{
|
||||
if (!isObject(%proj.sourceObject) || %proj.sourceObject.client.team == %this.team)
|
||||
return;
|
||||
}
|
||||
|
||||
function AIConnection::isIdle(%this)
|
||||
{
|
||||
if (!isObject(%this.commander))
|
||||
return true;
|
||||
|
||||
return %this.commander.idleBotList.isMember(%this);
|
||||
}
|
||||
|
||||
function AIConnection::updateLegs(%this)
|
||||
{
|
||||
if (%this.isMoving && %this.getTaskID() != 0)
|
||||
{
|
||||
%this.setPath(%this.moveLocation);
|
||||
%this.stepMove(%this.moveLocation);
|
||||
|
||||
if (%this.aimAtLocation)
|
||||
%this.aimAt(%this.moveLocation);
|
||||
else if(%this.manualAim)
|
||||
%this.aimAt(%this.aimLocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
%this.stop();
|
||||
%this.clearStep();
|
||||
}
|
||||
}
|
||||
|
||||
function AIConnection::updateVisualAcuity(%this)
|
||||
{
|
||||
if (%this.enableVisualDebug)
|
||||
{
|
||||
if (!isObject(%this.originMarker))
|
||||
{
|
||||
%this.originMarker = new Waypoint(){ datablock = "WaypointMarker"; team = %this.team; name = %this.namebase SPC " Origin"; };
|
||||
%this.clockwiseMarker = new Waypoint(){ datablock = "WaypointMarker"; team = %this.team; name = %this.namebase SPC " Clockwise"; };
|
||||
%this.counterClockwiseMarker = new Waypoint(){ datablock = "WaypointMarker"; team = %this.team; name = %this.namebase SPC " Counter Clockwise"; };
|
||||
%this.upperMarker = new Waypoint(){ datablock = "WaypointMarker"; team = %this.team; name = %this.namebase SPC " Upper"; };
|
||||
%this.lowerMarker = new Waypoint(){ datablock = "WaypointMarker"; team = %this.team; name = %this.namebase SPC " Lower"; };
|
||||
}
|
||||
|
||||
%viewCone = %this.calculateViewCone();
|
||||
%coneOrigin = getWords(%viewCone, 0, 2);
|
||||
%viewConeClockwiseVector = getWords(%viewCone, 3, 5);
|
||||
%viewConeCounterClockwiseVector = getWords(%viewCone, 6, 8);
|
||||
|
||||
%viewConeUpperVector = getWords(%viewCone, 9, 11);
|
||||
%viewConeLowerVector = getWords(%viewCone, 12, 14);
|
||||
|
||||
// Update all the markers
|
||||
%this.clockwiseMarker.setPosition(%viewConeClockwiseVector);
|
||||
%this.counterClockwiseMarker.setPosition(%viewConeCounterClockwiseVector);
|
||||
%this.upperMarker.setPosition(%viewConeUpperVector);
|
||||
%this.lowerMarker.setPosition(%viewConeLowerVector);
|
||||
%this.originMarker.setPosition(%coneOrigin);
|
||||
}
|
||||
else if (isObject(%this.originMarker))
|
||||
{
|
||||
%this.originMarker.delete();
|
||||
%this.clockwiseMarker.delete();
|
||||
%this.counterClockwiseMarker.delete();
|
||||
%this.upperMarker.delete();
|
||||
%this.lowerMarker.delete();
|
||||
}
|
||||
|
||||
%result = %this.getObjectsInViewcone($TypeMasks::ProjectileObjectType | $TypeMasks::PlayerObjectType, %this.viewDistance, true);
|
||||
|
||||
// What can we see?
|
||||
for (%i = 0; %i < %result.getCount(); %i++)
|
||||
{
|
||||
%current = %result.getObject(%i);
|
||||
%this.awarenessTicks[%current]++;
|
||||
|
||||
if (%current.getType() & $TypeMasks::ProjectileObjectType)
|
||||
{
|
||||
// Did we "notice" the object yet?
|
||||
// We pick a random notice time between 700ms and 1200 ms
|
||||
// Obviously this timer runs on a 32ms tick, but it should help provide a little unpredictability
|
||||
%noticeTime = getRandom(700, 1200);
|
||||
if (%this.awarenessTicks[%current] < (%noticeTime / 32))
|
||||
continue;
|
||||
|
||||
%className = %current.getClassName();
|
||||
|
||||
// LinearFlareProjectile and LinearProjectile have linear properties, so we can easily determine if a dodge is necessary
|
||||
if (%className $= "LinearFlareProjectile" || %className $= "LinearProjectile")
|
||||
{
|
||||
//%this.setDangerLocation(%current.getPosition(), 20);
|
||||
|
||||
// Perform a raycast to determine a hitpoint
|
||||
%currentPosition = %current.getPosition();
|
||||
%rayCast = containerRayCast(%currentPosition, vectorAdd(%currentPosition, vectorScale(%current.initialDirection, 200)), -1, 0);
|
||||
%hitObject = getWord(%raycast, 0);
|
||||
|
||||
// We're set for a direct hit on us!
|
||||
if (%hitObject == %this.player)
|
||||
{
|
||||
%this.setDangerLocation(%current.getPosition(), 30);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is no radius damage, don't worry about it now
|
||||
if (!%current.getDatablock().hasDamageRadius)
|
||||
continue;
|
||||
|
||||
// How close is the hit loc?
|
||||
%hitLocation = getWords(%rayCast, 1, 3);
|
||||
%hitDistance = vectorDist(%this.player.getPosition(), %hitLocation);
|
||||
|
||||
// Is it within the radius damage of this thing?
|
||||
if (%hitDistance <= %current.getDatablock().damageRadius)
|
||||
%this.setDangerLocation(%current.getPosition(), 30);
|
||||
}
|
||||
// A little bit harder to detect.
|
||||
else if (%className $= "GrenadeProjectile")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
// See a player?
|
||||
else if (%current.getType() & $TypeMasks::PlayerObjectType)
|
||||
{
|
||||
%this.clientDetected(%current);
|
||||
%this.clientDetected(%current.client);
|
||||
|
||||
// ... if the moron is right there in our LOS then we probably should see them
|
||||
%start = %this.player.getPosition();
|
||||
%end = vectorAdd(%start, vectorScale(%this.player.getEyeVector(), %this.viewDistance));
|
||||
|
||||
%rayCast = containerRayCast(%start, %end, -1, %this.player);
|
||||
%hitObject = getWord(%raycast, 0);
|
||||
|
||||
if (%hitObject == %current)
|
||||
{
|
||||
%this.clientDetected(%current);
|
||||
%this.stepEngage(%current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
%result.delete();
|
||||
}
|
||||
9
scripts/DXAI/config.cs
Normal file
9
scripts/DXAI/config.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// config.cs
|
||||
// Configuration for the DXAI System
|
||||
// Copyright (c) 2014 Robert MacGregor
|
||||
|
||||
$DXAI::Commander::minimumFlagDefense = 1;
|
||||
$DXAI::Commander::minimumGeneratorDefense = 1;
|
||||
|
||||
$DXAI::Bot::DefaultFieldOfView = 3.14159 / 2; // 90*
|
||||
$DXAI::Bot::DefaultViewDistance = 300;
|
||||
188
scripts/DXAI/helpers.cs
Normal file
188
scripts/DXAI/helpers.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// helpers.cs
|
||||
// Helper functions for the AI System
|
||||
// Copyright (c) 2014 Robert MacGregor
|
||||
|
||||
function sameSide(%p1, %p2, %a, %b)
|
||||
{
|
||||
%cp1 = vectorCross(vectorSub(%b, %a), vectorSub(%p1, %a));
|
||||
%cp2 = vectorCross(vectorSub(%b, %a), vectorSub(%p2, %a));
|
||||
|
||||
if (vectorDot(%cp1, %cp2) >= 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function pointInTriangle(%point, %a, %b, %c)
|
||||
{
|
||||
if (sameSide(%point, %a, %b, %c) && sameSide(%point, %b, %a, %c) && sameSide(%point, %c, %a, %b))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function AIConnection::reset(%this)
|
||||
{
|
||||
AIUnassignClient(%this);
|
||||
|
||||
%this.stop();
|
||||
%this.clearTasks();
|
||||
%this.clearStep();
|
||||
%this.lastDamageClient = -1;
|
||||
%this.lastDamageTurret = -1;
|
||||
%this.shouldEngage = -1;
|
||||
%this.setEngageTarget(-1);
|
||||
%this.setTargetObject(-1);
|
||||
%this.pilotVehicle = false;
|
||||
%this.defaultTasksAdded = false;
|
||||
|
||||
if (isObject(%this.controlByHuman))
|
||||
aiReleaseHumanControl(%this.controlByHuman, %this);
|
||||
}
|
||||
|
||||
// TODO: Return in a faster-to-read format: Could try as static GVar names
|
||||
// as the game's scripting environment for the gameplay is single threaded
|
||||
// and it probably does a hash to store the values.
|
||||
// TODO: Mathematical optimizations, right now it's a hack because of no
|
||||
// reliable way of getting a player's X facing?
|
||||
function GameConnection::calculateViewCone(%this, %distance)
|
||||
{
|
||||
//%xFacing = %this.player.getXFacing();
|
||||
%halfView = %this.fieldOfView / 2;
|
||||
%coneOrigin = %this.player.getMuzzlePoint($WeaponSlot);
|
||||
|
||||
%forwardVector = %this.player.getForwardVector();
|
||||
%sideVector = vectorCross("0 0 1", %forwardVector);
|
||||
|
||||
// Clockwise
|
||||
//%viewConeClockwise = %xFacing - %halfView;
|
||||
|
||||
// %viewConeClockwisePoint = mCos(%viewConeClockwise) SPC mSin(%viewConeClockwise) SPC "0";
|
||||
%viewConeClockwisePoint = mCos(-%halfView) SPC mSin(-%halfView) SPC "0";
|
||||
%viewConeClockwisePoint = vectorScale(%viewConeClockwisePoint, %this.viewDistance);
|
||||
//%viewConeClockwisePoint = vectorAdd(%viewConeClockwisePoint, %coneOrigin);
|
||||
|
||||
// Counter Clockwise
|
||||
//%viewConeCounterClockwise = %xFacing + %halfView;
|
||||
|
||||
//%viewConeCounterClockwisePoint = mCos(%viewConeCounterClockwise) SPC mSin(%viewConeCounterClockwise) SPC "0";
|
||||
%viewConeCounterClockwisePoint = mCos(%halfView) SPC mSin(%halfView) SPC "0";
|
||||
%viewConeCounterClockwisePoint = vectorScale(%viewConeCounterClockwisePoint, %this.viewDistance);
|
||||
//%viewConeCounterClockwisePoint = vectorAdd(%viewConeCounterClockwisePoint, %coneOrigin);
|
||||
|
||||
// Offsets
|
||||
%halfDistance = vectorDist(%viewConeCounterClockwisePoint, %viewConeClockwisePoint) / 2;
|
||||
|
||||
%viewConeCounterClockwisePoint = vectorScale(%sideVector, %halfDistance);
|
||||
%viewConeCounterClockwisePoint = vectorAdd(%coneOrigin, %viewConeCounterClockwisePoint);
|
||||
|
||||
%viewConeClockwisePoint = vectorScale(vectorScale(%sideVector, -1), %halfDistance);
|
||||
%viewConeClockwisePoint = vectorAdd(%coneOrigin, %viewConeClockwisePoint);
|
||||
|
||||
// Translate the upper and lower points
|
||||
%viewForwardPoint = vectorScale(%forwardVector, %this.viewDistance);
|
||||
|
||||
%viewConeUpperPoint = vectorAdd(vectorScale("0 0 1", %halfDistance), %viewForwardPoint);
|
||||
%viewConeUpperPoint = vectorAdd(%coneOrigin, %viewConeUpperPoint);
|
||||
|
||||
%viewConeLowerPoint = vectorAdd(vectorScale("0 0 -1", %halfDistance), %viewForwardPoint);
|
||||
%viewConeLowerPoint = vectorAdd(%coneOrigin, %viewConeLowerPoint);
|
||||
|
||||
// Now cast them forward
|
||||
%viewConeClockwisePoint = vectorAdd(%viewConeClockwisePoint, vectorScale(%this.player.getForwardVector(), %this.viewDistance));
|
||||
%viewConeCounterClockwisePoint = vectorAdd(%viewConeCounterClockwisePoint, vectorScale(%this.player.getForwardVector(), %this.viewDistance));
|
||||
|
||||
return %coneOrigin SPC %viewConeClockwisePoint SPC %viewConeCounterClockwisePoint SPC %viewConeUpperPoint SPC %viewConeLowerPoint;
|
||||
}
|
||||
|
||||
// View cone simulation function
|
||||
function GameConnection::getObjectsInViewcone(%this, %typeMask, %distance, %performLOSTest)
|
||||
{
|
||||
// FIXME: Radians
|
||||
if (%this.fieldOfView < 0 || %this.fieldOfView > 3.14)
|
||||
{
|
||||
%this.fieldOfView = $DXAPI::Bot::DefaultFieldOfView;
|
||||
error("DXAI: Bad field of view value! (" @ %this @ ".fieldOfView > 3.14 || " @ %this @ ".fieldOfView < 0)");
|
||||
}
|
||||
|
||||
if (%this.viewDistance <= 0)
|
||||
{
|
||||
%this.viewDistance = $DXAPI::Bot::DefaultViewDistance;
|
||||
error("DXAI: Bad view distance value! (" @ %this @ ".viewDistance <= 0)");
|
||||
}
|
||||
|
||||
if (%distance $= "")
|
||||
%distance = %this.viewDistance;
|
||||
|
||||
%viewCone = %this.calculateViewCone(%distance);
|
||||
|
||||
// Extract the results: See TODO above ::calculateViewCone implementation
|
||||
%coneOrigin = getWords(%viewCone, 0, 2);
|
||||
%viewConeClockwiseVector = getWords(%viewCone, 3, 5);
|
||||
%viewConeCounterClockwiseVector = getWords(%viewCone, 6, 8);
|
||||
%viewConeUpperVector = getWords(%viewCone, 9, 11);
|
||||
%viewConeLowerVector = getWords(%viewCone, 12, 14);
|
||||
|
||||
%result = new SimSet();
|
||||
|
||||
// Doing a radius search should hopefully be faster than iterating over all objects in MissionCleanup.
|
||||
// Even if the game did that internally it's definitely faster than doing it in TS
|
||||
InitContainerRadiusSearch(%coneOrigin, %distance, %typeMask);
|
||||
while((%currentObject = containerSearchNext()) != 0)
|
||||
{
|
||||
if (%currentObject == %this || !isObject(%currentObject) || containerSearchCurrRadDamageDist() > %distance)
|
||||
continue;
|
||||
|
||||
// Check if the object is within both the horizontal and vertical triangles representing our view cone
|
||||
if (%currentObject.getType() & %typeMask && pointInTriangle(%currentObject.getPosition(), %viewConeClockwiseVector, %viewConeCounterClockwiseVector, %coneOrigin) && pointInTriangle(%currentObject.getPosition(), %viewConeLowerVector, %viewConeUpperVector, %coneOrigin))
|
||||
{
|
||||
if (!%performLOSTest)
|
||||
%result.add(%currentObject);
|
||||
else
|
||||
{
|
||||
%rayCast = containerRayCast(%coneOrigin, %currentObject.getWorldBoxCenter(), -1, 0);
|
||||
|
||||
%hitObject = getWord(%raycast, 0);
|
||||
|
||||
// Since the engine doesn't do raycasts against projectiles correctly, we just check if the bot
|
||||
// hit -nothing- when doing the raycast rather than checking for a hit against the object
|
||||
if (%hitObject == %currentObject || (%currentObject.getType() & $TypeMasks::ProjectileObjectType && !isObject(%hitObject)))
|
||||
%result.add(%currentObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return %result;
|
||||
}
|
||||
|
||||
function getRandomPosition(%position, %distance)
|
||||
{
|
||||
// First, we determine a random direction vector
|
||||
%direction = vectorNormalize(getRandom(0, 10000) SPC getRandom(0, 10000) SPC getRandom(0, 10000));
|
||||
// Return the scaled result
|
||||
return vectorAdd(%position, vectorScale(%direction, getRandom(0, %distance)));
|
||||
}
|
||||
|
||||
function getRandomPositionOnTerrain(%position, %distance)
|
||||
{
|
||||
%result = getRandomPosition(%position, %distance);
|
||||
return setWord(%result, 2, getTerrainHeight(%result));
|
||||
}
|
||||
|
||||
function vectorMultiply(%vec1, %vec2)
|
||||
{
|
||||
return (getWord(%vec1, 0) * getWord(%vec2, 0)) SPC
|
||||
(getWord(%vec1, 1) * getWord(%vec2, 1)) SPC
|
||||
(getWord(%vec1, 2) * getWord(%vec2, 2));
|
||||
}
|
||||
|
||||
// If the map editor was instantiated, this will prevent a little bit
|
||||
// of console warnings
|
||||
function Terraformer::getType(%this) { return 0; }
|
||||
|
||||
// Dummy ScriptObject methods to silence console warnings when testing the runtime
|
||||
// environment; this may not silence for all mods but it should help.
|
||||
$DXAI::System::RuntimeDummy = new ScriptObject(RuntimeDummy) { class = "RuntimeDummy"; };
|
||||
|
||||
function RuntimeDummy::addTask() { }
|
||||
function RuntimeDummy::reset() { }
|
||||
256
scripts/DXAI/main.cs
Normal file
256
scripts/DXAI/main.cs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
//------------------------------------------------------------------------------------------
|
||||
// main.cs
|
||||
// Main source file for the DXAI experimental AI enhancement project.
|
||||
// https://github.com/Ragora/T2-DXAI.git
|
||||
//
|
||||
// Copyright (c) 2014 Robert MacGregor
|
||||
// This software is licensed under the MIT license. Refer to LICENSE.txt for more information.
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
exec("scripts/DXAI/objectives.cs");
|
||||
exec("scripts/DXAI/helpers.cs");
|
||||
exec("scripts/DXAI/config.cs");
|
||||
exec("scripts/DXAI/aicommander.cs");
|
||||
exec("scripts/DXAI/aiconnection.cs");
|
||||
|
||||
// General DXAI API implementations
|
||||
function DXAI::cleanup()
|
||||
{
|
||||
$DXAI::System::Setup = false;
|
||||
|
||||
for (%iteration = 1; %iteration < $DXAI::ActiveCommanderCount + 1; %iteration++)
|
||||
{
|
||||
$DXAI::ActiveCommander[%iteration].cleanup();
|
||||
$DXAI::ActiveCommander[%iteration].delete();
|
||||
}
|
||||
|
||||
$DXAI::ActiveCommanderCount = 0;
|
||||
}
|
||||
|
||||
function DXAI::setup(%numTeams)
|
||||
{
|
||||
// Mark the environment as invalidated for each new run so that our hooks
|
||||
// can be verified
|
||||
$DXAI::System::InvalidatedEnvironment = true;
|
||||
|
||||
// Set our setup flag so that the execution hooks can behave correctly
|
||||
$DXAI::System::Setup = true;
|
||||
|
||||
for (%iteration = 1; %iteration < %numTeams + 1; %iteration++)
|
||||
{
|
||||
%commander = new ScriptObject() { class = "AICommander"; team = %iteration; };
|
||||
%commander.setup();
|
||||
|
||||
$DXAI::ActiveCommander[%iteration] = %commander;
|
||||
}
|
||||
|
||||
// And setup the default values
|
||||
for (%iteration = 0; %iteration < ClientGroup.getCount(); %iteration++)
|
||||
{
|
||||
%currentClient = ClientGroup.getObject(%iteration);
|
||||
|
||||
%currentClient.viewDistance = $DXAI::Bot::DefaultViewDistance;
|
||||
%currentClient.fieldOfView = $DXAI::Bot::DefaultFieldOfView;
|
||||
}
|
||||
|
||||
$DXAI::ActiveCommanderCount = %numTeams;
|
||||
}
|
||||
|
||||
function DXAI::validateEnvironment()
|
||||
{
|
||||
%gameModeName = $CurrentMissionType @ "Game";
|
||||
|
||||
%payloadTemplate = %payload = "function " @ %gameModeName @ "::<METHODNAME>() { return DefaultGame::<METHODNAME>($DXAI::System::RuntimeDummy); } ";
|
||||
if (game.AIChooseGameObjective($DXAI::System::RuntimeDummy) != 11595)
|
||||
{
|
||||
error("DXAI: Function 'DefaultGame::AIChooseGameObjective' detected to be overwritten by the current gamemode. Correcting ...");
|
||||
|
||||
eval(strReplace(%payloadTemplate, "<METHODNAME>", "AIChooseGameObjective"));
|
||||
|
||||
// Make sure the patch took
|
||||
if (game.AIChooseGameObjective($DXAI::System::RuntimeDummy) != 11595)
|
||||
error("DXAI: Failed to patch 'DefaultGame::AIChooseGameObjective'! DXAI may not function correctly.");
|
||||
}
|
||||
|
||||
if (game.onAIRespawn($DXAI::System::RuntimeDummy) != 11595)
|
||||
{
|
||||
error("DXAI: Function 'DefaultGame::onAIRespawn' detected to be overwritten by the current gamemode. Correcting ... ");
|
||||
|
||||
eval(strReplace(%payloadTemplate, "<METHODNAME>", "onAIRespawn"));
|
||||
|
||||
if (game.onAIRespawn($DXAI::System::RuntimeDummy) != 11595)
|
||||
error("DXAI: Failed to patch 'DefaultGame::onAIRespawn'! DXAI may not function correctly.");
|
||||
}
|
||||
|
||||
$DXAI::System::InvalidatedEnvironment = false;
|
||||
}
|
||||
|
||||
function DXAI::update()
|
||||
{
|
||||
if (isEventPending($DXAI::updateHandle))
|
||||
cancel($DXAI::updateHandle);
|
||||
|
||||
// Check if the bound functions are overwritten by the current gamemode, or if something
|
||||
// may have invalidated our hooks
|
||||
if ($DXAI::System::InvalidatedEnvironment && $DXAI::System::Setup)
|
||||
DXAI::validateEnvironment();
|
||||
|
||||
for (%iteration = 1; %iteration < $DXAI::ActiveCommanderCount + 1; %iteration++)
|
||||
$DXAI::ActiveCommander[%iteration].update();
|
||||
|
||||
// Apparently we can't schedule a bound function otherwise
|
||||
$DXAI::updateHandle = schedule(32,0,"eval", "DXAI::update();");
|
||||
}
|
||||
|
||||
function DXAI::notifyPlayerDeath(%killed, %killedBy)
|
||||
{
|
||||
for (%iteration = 1; %iteration < $DXAI::ActiveCommanderCount + 1; %iteration++)
|
||||
$DXAI::ActiveCommander[%iteration].notifyPlayerDeath(%killed, %killedBy);
|
||||
}
|
||||
|
||||
// Hooks for the AI System
|
||||
package DXAI_Hooks
|
||||
{
|
||||
function DefaultGame::gameOver(%game)
|
||||
{
|
||||
parent::gameOver(%game);
|
||||
|
||||
DXAI::cleanup();
|
||||
}
|
||||
|
||||
function DefaultGame::startMatch(%game)
|
||||
{
|
||||
parent::startMatch(%game);
|
||||
|
||||
DXAI::setup(%game.numTeams);
|
||||
DXAI::update();
|
||||
}
|
||||
|
||||
// Listen server fix
|
||||
function disconnect()
|
||||
{
|
||||
parent::disconnect();
|
||||
|
||||
DXAI::Cleanup();
|
||||
}
|
||||
|
||||
function DefaultGame::AIChangeTeam(%game, %client, %newTeam)
|
||||
{
|
||||
// Remove us from the old commander's control first
|
||||
$DXAI::ActiveCommander[%client.team].removeBot(%client);
|
||||
|
||||
parent::AIChangeTeam(%game, %client, %newTeam);
|
||||
|
||||
$DXAI::ActiveCommander[%newTeam].addBot(%client);
|
||||
}
|
||||
|
||||
function AIConnection::onAIDrop(%client)
|
||||
{
|
||||
if (isObject(%client.commander))
|
||||
%client.commander.removeBot(%client);
|
||||
|
||||
parent::onAIDrop(%client);
|
||||
}
|
||||
|
||||
// Hooks for AI System notification
|
||||
function DefaultGame::onClientKilled(%game, %clVictim, %clKiller, %damageType, %implement, %damageLocation)
|
||||
{
|
||||
parent::onClientKilled(%game, %clVictim, %clKiller, %damageType, %implement, %damageLocation);
|
||||
|
||||
DXAI::notifyPlayerDeath(%clVictim, %clKiller);
|
||||
}
|
||||
|
||||
function DefaultGame::onAIKilled(%game, %clVictim, %clKiller, %damageType, %implement)
|
||||
{
|
||||
parent::onAIKilled(%game, %clVictim, %clKiller, %damageType, %implement);
|
||||
|
||||
DXAI::notifyPlayerDeath(%clVictim, %clKiller);
|
||||
}
|
||||
|
||||
function ProjectileData::onExplode(%data, %proj, %pos, %mod)
|
||||
{
|
||||
parent::onExplode(%data, %proj, %pos, %mod);
|
||||
|
||||
// Look for any bots nearby
|
||||
InitContainerRadiusSearch(%pos, 100, $TypeMasks::PlayerObjectType);
|
||||
|
||||
while ((%targetObject = containerSearchNext()) != 0)
|
||||
{
|
||||
%currentDistance = containerSearchCurrRadDamageDist();
|
||||
|
||||
if (%currentDistance > 100 || !%targetObject.client.isAIControlled())
|
||||
continue;
|
||||
|
||||
// Get the projectile team
|
||||
%projectileTeam = -1;
|
||||
if (isObject(%proj.sourceObject))
|
||||
%projectileTeam = %proj.sourceObject.client.team;
|
||||
|
||||
// Determine if we should run based on team & Team damage
|
||||
%shouldRun = false;
|
||||
if (isObject(%proj.sourceObject) && %projectileTeam == %targetObject.client.team && $TeamDamage)
|
||||
%shouldRun = true;
|
||||
else if (isObject(%proj.sourceObject) && %projectileTeam != %targetObject.client.team)
|
||||
%shouldRun = true;
|
||||
|
||||
// Determine if we 'heard' it. The sound distance seems to be roughly 55m or less and we check the maxDistance
|
||||
// IIRC The 55m distance should scale with the min/max distances and volume but I'm not sure how those interact
|
||||
%heardHit = false;
|
||||
%hitDistance = vectorDist(%targetObject.getWorldBoxCenter(), %pos);
|
||||
|
||||
if (%hitDistance <= 20 && %hitDistance <= %data.explosion.soundProfile.description.maxDistance)
|
||||
%heardHit = true;
|
||||
|
||||
// If the thing has any radius damage (and we heard it), run around a little bit if we need to
|
||||
if (%data.indirectDamage != 0 && %heardHit)
|
||||
%targetObject.client.schedule(getRandom(250, 400), "setDangerLocation", %pos, 20);
|
||||
|
||||
// If we should care and it wasn't a teammate projectile, notify
|
||||
if (%shouldRun && %projectileTeam != %targetObject.client.team)
|
||||
%targetObject.client.notifyProjectileImpact(%data, %proj, %pos);
|
||||
}
|
||||
}
|
||||
|
||||
// The CreateServer function is hooked so that we can try and guarantee that the DXAI gamemode hooks still
|
||||
// exist in the runtime.
|
||||
function CreateServer(%mission, %missionType)
|
||||
{
|
||||
// Perform the default exec's
|
||||
parent::CreateServer(%mission, %missionType);
|
||||
|
||||
// Ensure that the DXAI is active.
|
||||
DXAI::validateEnvironment();
|
||||
}
|
||||
|
||||
// Make this do nothing so the bots don't ever get any objectives by default
|
||||
function DefaultGame::AIChooseGameObjective(%game, %client) { return 11595; }
|
||||
|
||||
function DefaultGame::onAIRespawn(%game, %client)
|
||||
{
|
||||
// Make sure the bot has no objectives
|
||||
// %client.reset();
|
||||
// %client.defaultTasksAdded = true;
|
||||
|
||||
return 11595;
|
||||
}
|
||||
|
||||
// We package hook the exec() and compile() functions to perform execution environment
|
||||
// checking because these can easily load code that overwrites methods that are otherwise
|
||||
// hooked by DXAI. This can happen with gamemode specific events because DXAI only hooks into
|
||||
// DefaultGame. This is mostly helpful for developers.
|
||||
function exec(%file)
|
||||
{
|
||||
$DXAI::System::InvalidatedEnvironment = true;
|
||||
parent::exec(%file);
|
||||
}
|
||||
|
||||
function compile(%file)
|
||||
{
|
||||
$DXAI::System::InvalidatedEnvironment = true;
|
||||
parent::compile(%file);
|
||||
}
|
||||
};
|
||||
|
||||
// Only activate the package if it isn't already active.
|
||||
if (!isActivePackage(DXAI_Hooks))
|
||||
activatePackage(DXAI_Hooks);
|
||||
159
scripts/DXAI/objectives.cs
Normal file
159
scripts/DXAI/objectives.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
//------------------------------------------------------------------------------------------
|
||||
// main.cs
|
||||
// Source file for the DXAI enhanced objective implementations.
|
||||
// https://github.com/Ragora/T2-DXAI.git
|
||||
//
|
||||
// Copyright (c) 2014 Robert MacGregor
|
||||
// This software is licensed under the MIT license. Refer to LICENSE.txt for more information.
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// +Param %bot.escortTarget: The ID of the object to escort. This can be literally
|
||||
// any object that exists in the game world.
|
||||
// +Description: The AIEnhancedDefendLocation does exactly as the name implies. The
|
||||
// behavior a bot will exhibit with this code is that the bot will attempt to first to
|
||||
// the location desiginated by %bot.defendLocation. Once the bot is in range, it will
|
||||
// idly step about near the defense location, performing a sort of short range scouting.
|
||||
// If the bot were to be knocked too far away, then this logic will simply start all over
|
||||
// again.
|
||||
//------------------------------------------------------------------------------------------
|
||||
function AIEnhancedEscort::initFromObjective(%task, %objective, %client) { }
|
||||
function AIEnhancedEscort::assume(%task, %client) { %task.setMonitorFreq(1); }
|
||||
function AIEnhancedEscort::retire(%task, %client) { %client.isMoving = false; %client.manualAim = false; }
|
||||
function AIEnhancedEscort::weight(%task, %client) { %task.setWeight(1000); }
|
||||
|
||||
function AIEnhancedEscort::monitor(%task, %client)
|
||||
{
|
||||
// Is our escort object even a thing?
|
||||
if (!isObject(%client.escortTarget))
|
||||
return;
|
||||
|
||||
%escortLocation = %client.escortTarget.getPosition();
|
||||
|
||||
// Pick a location near the target
|
||||
// FIXME: Only update randomly every so often, or perhaps update using the target's move direction & velocity?
|
||||
// TODO: Keep a minimum distance from the escort target, prevents crowding and accidental vehicular death.
|
||||
%client.isMoving = true;
|
||||
%client.manualAim = true;
|
||||
%client.aimLocation = %escortLocation;
|
||||
|
||||
%client.moveLocation = getRandomPositionOnTerrain(%escortLocation, 40);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// +Param %bot.defendLocation: The X Y Z coordinates of the location that this bot
|
||||
// must attempt to defend.
|
||||
// +Description: The AIEnhancedDefendLocation does exactly as the name implies. The
|
||||
// behavior a bot will exhibit with this code is that the bot will attempt to first to
|
||||
// the location desiginated by %bot.defendLocation. Once the bot is in range, it will
|
||||
// idly step about near the defense location, performing a sort of short range scouting.
|
||||
// If the bot were to be knocked too far away, then this logic will simply start all over
|
||||
// again.
|
||||
//------------------------------------------------------------------------------------------
|
||||
function AIEnhancedDefendLocation::initFromObjective(%task, %objective, %client) { }
|
||||
function AIEnhancedDefendLocation::assume(%task, %client) { %task.setMonitorFreq(1); }
|
||||
function AIEnhancedDefendLocation::retire(%task, %client) { %client.isMoving = false; }
|
||||
function AIEnhancedDefendLocation::weight(%task, %client) { %task.setWeight(1000); }
|
||||
|
||||
function AIEnhancedDefendLocation::monitor(%task, %client)
|
||||
{
|
||||
if (%client.getPathDistance(%client.defendLocation) <= 40)
|
||||
{
|
||||
// Pick a random time to move to a nearby location
|
||||
if (%client.defendTime == -1)
|
||||
{
|
||||
%client.nextDefendRotation = getRandom(5000, 10000);
|
||||
%client.isMoving = false;
|
||||
}
|
||||
|
||||
// If we're near our random point, just don't move
|
||||
if (%client.getPathDistance(%client.moveLocation) <= 10)
|
||||
%client.isMoving = false;
|
||||
|
||||
%client.defendTime += 32;
|
||||
if (%client.defendTime >= %client.nextDefendRotation)
|
||||
{
|
||||
%client.defendTime = 0;
|
||||
%client.nextDefendRotation = getRandom(5000, 10000);
|
||||
|
||||
// TODO: Replace with something that detects interiors as well
|
||||
%client.moveLocation = getRandomPositionOnTerrain(%client.defendLocation, 40);
|
||||
%client.isMoving = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
%client.defendTime = -1;
|
||||
%client.moveLocation = %client.defendLocation;
|
||||
%client.isMoving = true;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// +Param %bot.scoutLocation: The X Y Z coordinates of the location that this bot
|
||||
// must attempt to scout.
|
||||
// +Param %bot.scoutDistance: The maximum distance that this bot will attempt to scout
|
||||
// out around %bot.scoutLocation.
|
||||
// +Description: The AIEnhancedScoutLocation does exactly as the name implies. The
|
||||
// behavior a bot will exhibit with this code is that the bot will pick random nodes from
|
||||
// the navigation graph that is within %bot.scoutDistance of %bot.scoutLocation and head
|
||||
// to that chosen node. This produces a bot that will wander around the given location,
|
||||
// including into and through interiors & other noded obstructions.
|
||||
//------------------------------------------------------------------------------------------
|
||||
function AIEnhancedScoutLocation::initFromObjective(%task, %objective, %client) { }
|
||||
function AIEnhancedScoutLocation::assume(%task, %client) { %task.setMonitorFreq(1); %client.currentNode = -1; }
|
||||
function AIEnhancedScoutLocation::retire(%task, %client) { }
|
||||
function AIEnhancedScoutLocation::weight(%task, %client) { %task.setWeight(1000); }
|
||||
|
||||
function AIEnhancedScoutLocation::monitor(%task, %client)
|
||||
{
|
||||
// We can't really work without a NavGraph
|
||||
if (!isObject(NavGraph))
|
||||
return;
|
||||
|
||||
// We just received the task, so find a node within distance of our scout location
|
||||
if (%client.currentNode == -1)
|
||||
{
|
||||
%client.currentNode = NavGraph.randNode(%client.scoutLocation, %client.scoutDistance, true, true);
|
||||
|
||||
if (%client.currentNode != -1)
|
||||
{
|
||||
%client.moveLocation = NavGraph.nodeLoc(%client.currentNode);
|
||||
%client.isMoving = true;
|
||||
}
|
||||
}
|
||||
// We're moving, or are near enough to our target
|
||||
else
|
||||
{
|
||||
// Don't move if we're close enough to our next node
|
||||
if (%client.getPathDistance(%client.moveLocation) <= 40 && %client.isMoving)
|
||||
{
|
||||
%client.isMoving = false;
|
||||
%client.nextScoutRotation = getRandom(5000, 10000);
|
||||
%client.scoutTime += 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
%client.isMoving = true;
|
||||
%client.scoutTime = 0;
|
||||
}
|
||||
|
||||
// Wait a little bit at each node
|
||||
if (%client.scoutTime >= %client.nextScoutRotation)
|
||||
{
|
||||
%client.scoutTime = 0;
|
||||
%client.nextScoutRotation = getRandom(5000, 10000);
|
||||
|
||||
// Pick a new node
|
||||
%client.currentNode = NavGraph.randNode(%client.scoutLocation, %client.scoutDistance, true, true);
|
||||
|
||||
// Ensure that we found a node.
|
||||
if (%client.currentNode != -1)
|
||||
{
|
||||
%client.moveLocation = NavGraph.nodeLoc(%client.currentNode);
|
||||
%client.isMoving = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------------------
|
||||
95
scripts/DXAI/priorityqueue.cs
Normal file
95
scripts/DXAI/priorityqueue.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//------------------------------------------------------------------------------------------
|
||||
// priorityqueue.cs
|
||||
// Source file for the priority queue implementation.
|
||||
// https://github.com/Ragora/T2-DXAI.git
|
||||
//
|
||||
// Copyright (c) 2014 Robert MacGregor
|
||||
// This software is licensed under the MIT license. Refer to LICENSE.txt for more information.
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
function PriorityQueue::add(%this, %key, %value)
|
||||
{
|
||||
// Traverse the queue and discover our insertion point
|
||||
for (%iteration = 0; %iteration < %this.count; %iteration++)
|
||||
if (%key >= %this.keys[%iteration])
|
||||
{
|
||||
%this._shift(%iteration, false);
|
||||
%this.values[%iteration] = %value;
|
||||
%this.keys[%iteration] = %key;
|
||||
%this.keyIndex[%key] = %iteration;
|
||||
%this.hasKey[%key] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we never made an insertion, just stick our key and value at the end
|
||||
%this.values[%this.count] = %value;
|
||||
%this.keys[%this.count++] = %key;
|
||||
}
|
||||
|
||||
function PriorityQueue::remove(%this, %key)
|
||||
{
|
||||
if (!%this.hasKey[%key])
|
||||
return;
|
||||
|
||||
%this.hasKey[%key] = false;
|
||||
%this._shift(%this.keyIndex[%key], true);
|
||||
%this.count--;
|
||||
}
|
||||
|
||||
function PriorityQueue::_shift(%this, %index, %isRemoval)
|
||||
{
|
||||
if (%isRemoval)
|
||||
{
|
||||
for (%iteration = %index; %iteration < %this.count; %iteration++)
|
||||
{
|
||||
%this.values[%index] = %this.values[%index + 1];
|
||||
%this.keys[%index] = %this.keys[%index + 1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (%iteration = %index; %iteration < %this.count; %iteration++)
|
||||
{
|
||||
%this.values[%index + 1] = %this.values[%index];
|
||||
%this.keys[%index + 1] = %this.keys[%index];
|
||||
}
|
||||
}
|
||||
|
||||
function PriorityQueue::topValue(%this)
|
||||
{
|
||||
return %this.values[%this.count - 1];
|
||||
}
|
||||
|
||||
function PriorityQueue::topKey(%this)
|
||||
{
|
||||
return %this.keys[%this.count - 1];
|
||||
}
|
||||
|
||||
function PriorityQueue::pop(%this)
|
||||
{
|
||||
%this.hasKey[%this.keys[%this.count]] = false;
|
||||
%this.count--;
|
||||
}
|
||||
|
||||
function PriorityQueue::clear(%this)
|
||||
{
|
||||
for (%iteration = 0; %iteration < %this.count; %iteration++)
|
||||
%this.hasKey[%this.keys[%iteration]] = false;
|
||||
|
||||
%this.count = 0;
|
||||
}
|
||||
|
||||
function Priorityqueue::isEmpty(%this)
|
||||
{
|
||||
return %this.count == 0;
|
||||
}
|
||||
|
||||
function PriorityQueue::create(%name)
|
||||
{
|
||||
return new ScriptObject(%name)
|
||||
{
|
||||
class = "PriorityQueue";
|
||||
count = 0;
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue