mirror of
https://github.com/Ragora/T2-DXAI.git
synced 2026-01-19 18:14:45 +00:00
Restructuring; PriorityQueue implementation; commander task weighting & assignment; some code documentation
This commit is contained in:
parent
0a77531cd9
commit
088d7fee8e
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,47 @@
|
||||||
// DXAI_Objectives.cs
|
//------------------------------------------------------------------------------------------
|
||||||
// Objectives for the AI system
|
// 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
|
// 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)
|
||||||
// The AIVisualAcuity task is a complementary task for the AI grunt systems
|
{
|
||||||
// to perform better at recognizing things visually with reasonably
|
%this.fieldOfView = 3.14 / 2; // 90* View cone
|
||||||
// Human perception capabilities.
|
%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)
|
function AIConnection::updateLegs(%this)
|
||||||
{
|
{
|
||||||
|
|
@ -17,6 +52,8 @@ function AIConnection::updateLegs(%this)
|
||||||
|
|
||||||
if (%this.aimAtLocation)
|
if (%this.aimAtLocation)
|
||||||
%this.aimAt(%this.moveLocation);
|
%this.aimAt(%this.moveLocation);
|
||||||
|
else if(%this.manualAim)
|
||||||
|
%this.aimAt(%this.aimLocation);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -129,8 +166,6 @@ function AIConnection::updateVisualAcuity(%this)
|
||||||
%rayCast = containerRayCast(%start, %end, -1, %this.player);
|
%rayCast = containerRayCast(%start, %end, -1, %this.player);
|
||||||
%hitObject = getWord(%raycast, 0);
|
%hitObject = getWord(%raycast, 0);
|
||||||
|
|
||||||
// echo(%hitObject);
|
|
||||||
// echo(%current);
|
|
||||||
if (%hitObject == %current)
|
if (%hitObject == %current)
|
||||||
{
|
{
|
||||||
%this.clientDetected(%current);
|
%this.clientDetected(%current);
|
||||||
|
|
@ -141,155 +176,3 @@ function AIConnection::updateVisualAcuity(%this)
|
||||||
|
|
||||||
%result.delete();
|
%result.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
function AIConnection::enhancedLogicLoop(%this)
|
|
||||||
{
|
|
||||||
cancel(%this.enhancedLogicHandle);
|
|
||||||
|
|
||||||
if (isObject(%this.player))
|
|
||||||
{
|
|
||||||
%this.updateVisualAcuity();
|
|
||||||
%this.updateLegs();
|
|
||||||
}
|
|
||||||
|
|
||||||
%this.enhancedLogicHandle = %this.schedule(32, "enhancedLogicLoop");
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------------------
|
|
||||||
function AIEnhancedDefendLocation::initFromObjective(%task, %objective, %client)
|
|
||||||
{
|
|
||||||
// Called to initialize from an objective object
|
|
||||||
}
|
|
||||||
|
|
||||||
function AIEnhancedDefendLocation::assume(%task, %client)
|
|
||||||
{
|
|
||||||
// Called when the bot starts the task
|
|
||||||
%task.setMonitorFreq(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AIEnhancedDefendLocation::retire(%task, %client)
|
|
||||||
{
|
|
||||||
// Called when the bot stops the task
|
|
||||||
}
|
|
||||||
|
|
||||||
function AIEnhancedDefendLocation::weight(%task, %client)
|
|
||||||
{
|
|
||||||
%task.setWeight(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AIEnhancedDefendLocation::monitor(%task, %client)
|
|
||||||
{
|
|
||||||
// echo(%task.getMonitorFreq());
|
|
||||||
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
|
|
||||||
%randomPosition = getRandomPosition(%client.defendLocation, 40);
|
|
||||||
%randomPosition = getWords(%randomPosition, 0, 1) SPC getTerrainHeight(%randomPosition);
|
|
||||||
|
|
||||||
%client.moveLocation = %randomPosition;
|
|
||||||
%client.isMoving = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
%client.defendTime = -1;
|
|
||||||
%client.moveLocation = %client.defendLocation;
|
|
||||||
%client.isMoving = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------------------
|
|
||||||
function AIEnhancedScoutLocation::initFromObjective(%task, %objective, %client)
|
|
||||||
{
|
|
||||||
// Called to initialize from an objective object
|
|
||||||
}
|
|
||||||
|
|
||||||
function AIEnhancedScoutLocation::assume(%task, %client)
|
|
||||||
{
|
|
||||||
// Called when the bot starts the task
|
|
||||||
%task.setMonitorFreq(1);
|
|
||||||
|
|
||||||
%client.currentNode = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AIEnhancedScoutLocation::retire(%task, %client)
|
|
||||||
{
|
|
||||||
// Called when the bot stops the task
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------------------
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// DXAI_Config.cs
|
// config.cs
|
||||||
// Configuration for the DXAI System
|
// Configuration for the DXAI System
|
||||||
// Copyright (c) 2014 Robert MacGregor
|
// Copyright (c) 2014 Robert MacGregor
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// DXAI_Helpers.cs
|
// helpers.cs
|
||||||
// Helper functions for the AI System
|
// Helper functions for the AI System
|
||||||
// Copyright (c) 2014 Robert MacGregor
|
// Copyright (c) 2014 Robert MacGregor
|
||||||
|
|
||||||
|
|
@ -163,6 +163,12 @@ function getRandomPosition(%position, %distance)
|
||||||
return vectorAdd(%position, vectorScale(%direction, getRandom(0, %distance)));
|
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)
|
function vectorMultiply(%vec1, %vec2)
|
||||||
{
|
{
|
||||||
return (getWord(%vec1, 0) * getWord(%vec2, 0)) SPC
|
return (getWord(%vec1, 0) * getWord(%vec2, 0)) SPC
|
||||||
|
|
@ -1,70 +1,17 @@
|
||||||
// DXAI_Main.cs
|
//------------------------------------------------------------------------------------------
|
||||||
// Experimental AI System for ProjectR3
|
// main.cs
|
||||||
|
// Main source file for the DXAI experimental AI enhancement project.
|
||||||
|
// https://github.com/Ragora/T2-DXAI.git
|
||||||
|
//
|
||||||
// Copyright (c) 2014 Robert MacGregor
|
// Copyright (c) 2014 Robert MacGregor
|
||||||
|
// This software is licensed under the MIT license. Refer to LICENSE.txt for more information.
|
||||||
|
//------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
exec("scripts/Server/DXAI_Objectives.cs");
|
exec("scripts/DXAI/objectives.cs");
|
||||||
exec("scripts/Server/DXAI_Helpers.cs");
|
exec("scripts/DXAI/helpers.cs");
|
||||||
exec("scripts/Server/DXAI_Config.cs");
|
exec("scripts/DXAI/config.cs");
|
||||||
|
exec("scripts/DXAI/aicommander.cs");
|
||||||
$DXAI::ActiveCommanderCount = 2;
|
exec("scripts/DXAI/aiconnection.cs");
|
||||||
|
|
||||||
// AICommander
|
|
||||||
// This is a script object that exists for every team in a given
|
|
||||||
// gamemode and performs the coordination of bots in the game.
|
|
||||||
|
|
||||||
function AICommander::notifyPlayerDeath(%this, %killed, %killedBy)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
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.team == %this.team && %currentClient.isAIControlled())
|
|
||||||
{
|
|
||||||
%this.botList.add(%currentClient);
|
|
||||||
%this.idleBotList.add(%currentClient);
|
|
||||||
|
|
||||||
%currentClient.commander = %this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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::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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// General DXAI API implementations
|
// General DXAI API implementations
|
||||||
function DXAI::cleanup()
|
function DXAI::cleanup()
|
||||||
|
|
@ -161,39 +108,6 @@ function DXAI::notifyPlayerDeath(%killed, %killedBy)
|
||||||
$DXAI::ActiveCommander[%iteration].notifyPlayerDeath(%killed, %killedBy);
|
$DXAI::ActiveCommander[%iteration].notifyPlayerDeath(%killed, %killedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AIPlayer
|
|
||||||
// This is a script object that contains DXAI functionality on a per-soldier
|
|
||||||
// basis
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Hooks for the AI System
|
// Hooks for the AI System
|
||||||
package DXAI_Hooks
|
package DXAI_Hooks
|
||||||
{
|
{
|
||||||
|
|
@ -297,13 +211,15 @@ package DXAI_Hooks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
function CreateServer(%mission, %missionType)
|
||||||
{
|
{
|
||||||
// Perform the default exec's
|
// Perform the default exec's
|
||||||
parent::CreateServer(%mission, %missionType);
|
parent::CreateServer(%mission, %missionType);
|
||||||
|
|
||||||
// Ensure that the DXAI is active.
|
// Ensure that the DXAI is active.
|
||||||
$DXAI::System::InvalidatedEnvironment = true;
|
DXAI::validateEnvironment();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make this do nothing so the bots don't ever get any objectives by default
|
// Make this do nothing so the bots don't ever get any objectives by default
|
||||||
|
|
@ -335,5 +251,6 @@ package DXAI_Hooks
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only activate the package if it isn't already active.
|
||||||
if (!isActivePackage(DXAI_Hooks))
|
if (!isActivePackage(DXAI_Hooks))
|
||||||
activatePackage(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…
Reference in a new issue