Initial attempts at getting bots unstuck on broken paths; AI visual acuity is now the primary system of picking targets, improving performance

This commit is contained in:
Robert MacGregor 2015-10-07 03:16:32 -04:00
parent 657545aaac
commit ec3b86cdf6
6 changed files with 260 additions and 103 deletions

View file

@ -34,7 +34,6 @@ function AICommander::setup(%this)
for (%iteration = 0; %iteration < ClientGroup.getCount(); %iteration++)
{
%currentClient = ClientGroup.getObject(%iteration);
%currentClient.updateVisualAcuity();
if (%currentClient.isAIControlled() && %currentClient.team == %this.team)
{
@ -42,6 +41,11 @@ function AICommander::setup(%this)
%this.idleBotList.add(%currentClient);
%currentClient.commander = %this;
%currentClient.initialize();
%currentClient.visibleHostiles = new SimSet();
%currentClient.updateVisualAcuity();
%currentClient.stuckCheck();
}
}
@ -74,9 +78,7 @@ function AICommander::_skimObjectiveGroup(%this, %group)
case "AIODefendLocation":
// FIXME: Console spam from .targetObjectID not being set?
%datablockName = %current.targetObjectID.getDatablock().getName();
echo(%datablockName);
// Defending the flag?
if (%datablockName $= "FLAG")
%this.objectiveCycles[$DXAI::Priorities::DefendFlag].add(%current);
@ -243,14 +245,24 @@ function AICommander::assignTask(%this, %taskID, %bot)
%objective = %this.objectiveCycles[%taskID].next();
// Set the bot to defend the location
%bot.defendLocation = %objective.location;
%bot.defendTargetLocation = %objective.location;
%datablockName = %objective.targetObjectID.getDatablock().getName();
switch$(%datablockName)
{
case "FLAG":
%bot.defenseDescription = "flag";
case "GeneratorLarge":
%bot.defenseDescription = "generator";
}
%bot.addTask("AIEnhancedDefendLocation");
case $DXAI::Priorities::ScoutBase:
%objective = %this.objectiveCycles[%taskID].next();
// Set the bot to defend the location
%bot.scoutLocation = %objective.location;
%bot.scoutTargetLocation = %objective.location;
%bot.scoutDistance = %objective.distance;
%bot.addTask("AIEnhancedScoutLocation");
}
@ -267,6 +279,13 @@ function AICommander::setDefaultPriorities(%this)
function AICommander::cleanUp(%this)
{
for (%iteration = 0; %iteration < %this.botList.getCount(); %iteration++)
{
%current = %this.botList.getObject(%iteration);
cancel(%current.visualAcuityTick);
cancel(%current.stuckCheckTick);
}
%this.botList.delete();
%this.idleBotList.delete();
}

View file

@ -9,15 +9,10 @@
// Refer to LICENSE.txt for more information.
//------------------------------------------------------------------------------------------
function AIConnection::initialize(%this, %aiClient)
function AIConnection::initialize(%this)
{
%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)
@ -77,6 +72,9 @@ function AIConnection::setMoveTarget(%this, %position)
%this.isFollowingTarget = false;
%this.setPath(%position);
%this.stepMove(%position);
%this.minimumPathDistance = 9999;
%this.maximumPathDistance = -9999;
}
function AIConnection::setFollowTarget(%this, %target, %minDistance, %maxDistance, %hostile)
@ -97,14 +95,72 @@ function AIConnection::setFollowTarget(%this, %target, %minDistance, %maxDistanc
%this.stepEscort(%target);
}
function AIConnection::stuckCheck(%this)
{
if (isEventPending(%this.stuckCheckTick))
cancel(%this.stuckCheckTick);
%targetDistance = %this.pathDistRemaining(9000);
if (!%this.isMovingToTarget || !isObject(%this.player) || %this.player.getState() !$= "Move" || %targetDistance <= 5)
{
%this.stuckCheckTick = %this.schedule(5000, "stuckCheck");
return;
}
if (!%this.isPathCorrecting && %targetDistance >= %this.minimumPathDistance && %this.minimumPathDistance != 9999)
%this.isPathCorrecting = true;
if (%targetDistance > %this.maximumPathDistance)
%this.maximumPathDistance = %targetDistance;
if (%targetDistance < %this.minimumPathDistance)
%this.minimumPathDistance = %targetDistance;
%this.stuckCheckTick = %this.schedule(5000, "stuckCheck");
}
function AIConnection::updateLegs(%this)
{
%now = getSimTime();
%delta = %now - %this.lastUpdateLegs;
%this.lastUpdateLegs = %now;
if (%this.isMovingToTarget)
{
if (%this.aimAtLocation)
%this.aimAt(%this.moveTarget);
else if(%this.manualAim)
%this.aimAt(%this.moveTarget);
%targetDistance = %this.pathDistRemaining(9000);
if (%targetDistance > %this.maximumPathDistance)
%this.maximumPathDistance = %targetDistance;
if (%targetDistance < %this.minimumPathDistance)
%this.minimumPathDistance = %targetDistance;
// Bots follow a set of lines drawn between nodes to slowly decrement the path distance,
// so bots that are stuck usually get their remaining distance stuck in some range of
// arbitrary values, so we monitor the minimum and maximum values over a period of 5 seconds
// Test...
%pathDistance = %this.getPathDistance(%this.moveTarget);
if(%pathDistance > 10 && %this.moveTravelTime < 10000)
%this.moveTravelTime += %delta;
else if (%pathDistance < 10)
%this.moveTravelTime = 0;
else if (%this.moveTravelTime >= 10000)
{
// We appear to be stuck, so pick a random nearby node and try to run to it
%this.moveTravelTime = 0;
%this.isPathCorrecting = true;
if (isObject(NavGraph))
{
%randomNode = NavGraph.randNode(%this.player.getPosition(), 200, true, true);
if (%randomNode != -1)
%this.setMoveTarget(NavGraph.nodeLoc(%randomNode));
}
}
}
else if (%this.isFollowingTarget)
{
@ -119,7 +175,24 @@ function AIConnection::updateLegs(%this)
function AIConnection::updateWeapons(%this)
{
if (isObject(%this.engageTarget))
{
%player = %this.player;
%targetDistance = vectorDist(%player.getPosition(), %this.engageTarget.getPosition());
// Firstly, just aim at them for now
%this.aimAt(%this.engageTarget.getWorldBoxCenter());
// What is our current best weapon? Right now we just check target distance and weapon spread.
%bestWeapon = 0;
for (%iteration = 0; %iteration < %player.weaponSlotCount; %iteration++)
{
// Weapons with a decent bit of spread should be used <= 20m
}
%player.selectWeaponSlot(%bestWeapon);
}
}
function AIConnection::updateVisualAcuity(%this)
@ -127,7 +200,7 @@ function AIConnection::updateVisualAcuity(%this)
if (isEventPending(%this.visualAcuityTick))
cancel(%this.visualAcuityTick);
if (!isObject(%this.player) || %this.player.getState() !$= "Move")
if (%this.visibleDistance = 0 || !isObject(%this.player) || %this.player.getState() !$= "Move")
{
%this.visualAcuityTick = %this.schedule(getRandom(230, 400), "updateVisualAcuity");
return;
@ -167,24 +240,26 @@ function AIConnection::updateVisualAcuity(%this)
%this.upperMarker.delete();
%this.lowerMarker.delete();
}
%now = getSimTime();
%deltaTime = %now - %this.lastVisualAcuityUpdate;
%this.lastVisualAcuityUpdate = %now;
%result = %this.getObjectsInViewcone($TypeMasks::ProjectileObjectType | $TypeMasks::PlayerObjectType, %this.viewDistance, true);
// What can we see?
for (%i = 0; %i < %result.getCount(); %i++)
%visibleObjects = %this.getObjectsInViewcone($TypeMasks::ProjectileObjectType | $TypeMasks::PlayerObjectType, %this.viewDistance, true);
for (%iteration = 0; %iteration < %visibleObjects.getCount(); %iteration++)
{
%current = %result.getObject(%i);
%this.awarenessTicks[%current]++;
%current = %visibleObjects.getObject(%iteration);
%this.awarenessTime[%current] += %deltaTime;
// Did we "notice" the object yet?
%noticeTime = getRandom(700, 1200);
if (%this.awarenessTime[%current] < %noticeTime)
continue;
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 trajectories, so we can easily determine if a dodge is necessary
@ -223,26 +298,43 @@ function AIConnection::updateVisualAcuity(%this)
}
}
// See a player?
else if (%current.getType() & $TypeMasks::PlayerObjectType)
else if (%current.getType() & $TypeMasks::PlayerObjectType && %current.client.team != %this.team)
{
%this.clientDetected(%current);
%this.clientDetected(%current.client);
%this.visibleHostiles.add(%current);
//%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));
// %start = %this.player.getPosition();
// %end = vectorAdd(%start, vectorScale(%this.player.getEyeVector(), %this.viewDistance));
%rayCast = containerRayCast(%start, %end, -1, %this.player);
%hitObject = getWord(%raycast, 0);
// %rayCast = containerRayCast(%start, %end, -1, %this.player);
// %hitObject = getWord(%raycast, 0);
if (%hitObject == %current)
{
%this.clientDetected(%current);
%this.stepEngage(%current);
}
// if (%hitObject == %current)
// {
// %this.clientDetected(%current);
// %this.stepEngage(%current);
// }
}
}
%result.delete();
%this.visualAcuityTick = %this.schedule(getRandom(230, 400), "updateVisualAcuity");
// Now we run some logic on some things that we no longer can see.
for (%iteration = 0; %iteration < %this.visibleHostiles.getCount(); %iteration++)
{
%current = %this.visibleHostiles.getObject(%iteration);
if (%this.visibleHostiles.isMember(%current) && !%visibleObjects.isMember(%current))
{
%this.awarenessTime[%current] -= %deltaTime;
if (%this.awarenessTime[%current] < 200)
{
%this.visibleHostiles.remove(%current);
continue;
}
}
}
%visibleObjects.delete();
%this.visualAcuityTick = %this.schedule(getRandom($DXAI::Bot::MinimumVisualAcuityTime, $DXAI::Bot::MaximumVisualAcuityTime), "updateVisualAcuity");
}

View file

@ -13,3 +13,6 @@ $DXAI::Commander::minimumGeneratorDefense = 1;
$DXAI::Bot::DefaultFieldOfView = 3.14159 / 2; // 90*
$DXAI::Bot::DefaultViewDistance = 300;
$DXAI::Bot::MinimumVisualAcuityTime = 200;
$DXAI::Bot::MaximumVisualAcuityTime = 400;

View file

@ -188,7 +188,7 @@ function GameConnection::getObjectsInViewcone(%this, %typeMask, %distance, %perf
%result.add(%currentObject);
else
{
%rayCast = containerRayCast(%coneOrigin, %currentObject.getWorldBoxCenter(), -1, 0);
%rayCast = containerRayCast(%coneOrigin, %currentObject.getWorldBoxCenter(), $TypeMasks::AllObjectType, %this.player);
%hitObject = getWord(%raycast, 0);
@ -224,6 +224,16 @@ function vectorMultiply(%vec1, %vec2)
(getWord(%vec1, 2) * getWord(%vec2, 2));
}
function listStuckBots()
{
for (%iteration = 0; %iteration < ClientGroup.getCount(); %iteration++)
{
%client = ClientGroup.getObject(%iteration);
if (%client.isAIControlled() && %client.isPathCorrecting)
error(%client);
}
}
// If the map editor was instantiated, this will prevent a little bit
// of console warnings
function Terraformer::getType(%this) { return 0; }

View file

@ -245,6 +245,9 @@ package DXAI_Hooks
%client.shouldRearm = true;
%client.targetLoadout = 1;
%client.engageTargetLastPosition = "";
%client.engageTarget = -1;
return 11595;
}
@ -270,10 +273,26 @@ package DXAI_Hooks
//switch$ (%eventTag)
//{
schedule(250, %targetClient, "AIPlayAnimSound", %targetClient, %clientPos, "cmd.decline", $AIAnimSalute, $AIAnimSalute, 0);
schedule(2000, %targetClient, "AIPlayAnimSound", %targetClient, %clientPos, ObjectiveNameToVoice(%targetClient.getTaskName()), $AIAnimSalute, $AIAnimSalute, 0);
schedule(2000, %targetClient, "AIPlayAnimSound", %targetClient, %clientPos, ObjectiveNameToVoice(%targetClient), $AIAnimSalute, $AIAnimSalute, 0);
schedule(3700, %targetClient, "AIPlayAnimSound", %targetClient, %clientPos, "vqk.sorry", $AIAnimSalute, $AIAnimSalute, 0);
}
function AISystemEnabled(%enabled)
{
parent::AISystemEnabled(%enabled);
echo(%enabled);
$DXAI::AISystemEnabled = %enabled;
}
function AIConnection::onAIDrop(%client)
{
parent::onAIDrop(%client);
if (isObject(%client.visibleHostiles))
%client.visibleHostiles.delete();
}
function Station::stationTriggered(%data, %obj, %isTriggered)
{
parent::stationTriggered(%data, %obj, %isTriggered);

View file

@ -20,7 +20,8 @@ $DXAI::Task::NoPriority = 0;
$DXAI::Task::LowPriority = 100;
$DXAI::Task::MediumPriority = 200;
$DXAI::Task::HighPriority = 500;
$DXAI::Task::VeryHighPriority = 1000;
$DXAI::Task::VeryHighPriority = 1000;
$DXAI::Task::ReservedPriority = 5000;
//------------------------------------------------------------------------------------------
// +Param %bot.escortTarget: The ID of the object to escort. This can be literally
@ -119,7 +120,10 @@ function AIEnhancedScoutLocation::retire(%task, %client) { }
function AIEnhancedScoutLocation::weight(%task, %client) { %task.setWeight($DXAI::Task::MediumPriority); }
function AIEnhancedScoutLocation::monitor(%task, %client)
{
{
if (%client.engageTarget)
return AIEnhancedScoutLocation::monitorEngage(%task, %client);
// We can't really work without a NavGraph
if (!isObject(NavGraph))
return;
@ -127,7 +131,7 @@ function AIEnhancedScoutLocation::monitor(%task, %client)
// 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);
%client.currentNode = NavGraph.randNode(%client.scoutTargetLocation, %client.scoutDistance, true, true);
if (%client.currentNode != -1)
%client.setMoveTarget(NavGraph.nodeLoc(%client.currentNode));
@ -138,7 +142,7 @@ function AIEnhancedScoutLocation::monitor(%task, %client)
%pathDistance = %client.getPathDistance(%client.moveTarget);
// Don't move if we're close enough to our next node
if (%pathDistance <= 40 && %client.isMoving)
if (%pathDistance <= 40 && %client.isMovingToTarget)
{
%client.setMoveTarget(-1);
%client.nextScoutRotation = getRandom(5000, 10000);
@ -159,7 +163,7 @@ function AIEnhancedScoutLocation::monitor(%task, %client)
%client.nextScoutRotation = getRandom(5000, 10000);
// Pick a new node
%client.currentNode = NavGraph.randNode(%client.scoutLocation, %client.scoutDistance, true, true);
%client.currentNode = NavGraph.randNode(%client.scoutTargetLocation, %client.scoutDistance, true, true);
// Ensure that we found a node.
if (%client.currentNode != -1)
@ -167,6 +171,11 @@ function AIEnhancedScoutLocation::monitor(%task, %client)
}
}
}
function AIEnhancedScoutLocation::monitorEngage(%task, %client)
{
}
//------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------
@ -182,40 +191,24 @@ function AIEnhancedEngageTarget::retire(%task, %client) { }
function AIEnhancedEngageTarget::weight(%task, %client)
{
if (!isObject(%client.engageTarget))
// Blow through seen targets
%chosenTarget = -1;
%chosenTargetDistance = 9999;
%botPosition = %client.player.getPosition();
for (%iteration = 0; %iteration < %client.visibleHostiles.getCount(); %iteration++)
{
%visibleObjects = %client.getObjectsInViewcone($TypeMasks::PlayerObjectType, %client.viewDistance, true);
%current = %client.visibleHostiles.getObject(%iteration);
// Choose the closest target
// TODO: Choose on more advanced metrics like HP
%chosenTarget = -1;
%chosenTargetDistance = 9999;
for (%iteration = 0; %iteration < %visibleObjects.getCount(); %iteration++)
%targetDistance = vectorDist(%current.getPosition(), %botPosition);
if (%targetDistance < %chosenTargetDistance)
{
%potentialTarget = %visibleObjects.getObject(%iteration);
%potentialTargetDistance = vectorDist(%potentialTarget.getPosition(), %client.player.getPosition());
if (%potentialTarget.client.team != %client.team && %potentialTargetDistance < %chosenTargetDistance)
{
%chosenTargetDistance = %potentialTargetDistance;
%chosenTarget = %potentialTarget;
}
%chosenTargetDistance = %targetDistance;
%chosenTarget = %current;
}
%visibleObjects.delete();
%client.engageTarget = %chosenTarget;
}
else
{
// Can we still see them?
%rayCast = containerRayCast(%client.player.getWorldBoxCenter(), %client.engageTarget.getWorldBoxCenter(), -1, %client.player);
%hitObject = getWord(%raycast, 0);
// TODO: Go to the last known position.
if (%hitObject != %client.engageTarget)
%client.engageTarget = -1;
}
%client.engageTarget = %chosenTarget;
if (!isObject(%client.engageTarget) && %client.engageTargetLastPosition $= "")
%task.setWeight($DXAI::Task::NoPriority);
else
@ -225,26 +218,16 @@ function AIEnhancedEngageTarget::weight(%task, %client)
function AIEnhancedEngageTarget::monitor(%task, %client)
{
if (isObject(%client.engageTarget))
{
%player = %client.player;
%targetDistance = vectorDist(%player.getPosition(), %client.engageTarget.getPosition());
// Firstly, just aim at them for now
%client.aimAt(%client.engageTarget.getWorldBoxCenter());
// What is our current best weapon? Right now we just check target distance and weapon spread.
%bestWeapon = 0;
for (%iteration = 0; %iteration < %player.weaponSlotCount; %iteration++)
{
if (%client.engageTarget.getState() !$= "Move")
{
// Weapons with a decent bit of spread should be used <= 20m
%client.engageTarget = -1;
%client.engageTargetLastPosition = "";
return;
}
%player.selectWeaponSlot(%bestWeapon);
%client.engageTargetLastPosition = %client.engageTarget.getWorldBoxCenter();
%client.setMoveTarget(getRandomPositionOnTerrain(%client.engageTargetLastPosition, 40));
%client.engageTargetLastPosition = %client.engageTarget.getWorldBoxCenter();
%client.setMoveTarget(getRandomPositionOnTerrain(%client.engageTargetLastPosition, 40));
%client.pressFire();
}
else if (%client.engageTargetLastPosition !$= "")
@ -258,6 +241,7 @@ function AIEnhancedEngageTarget::monitor(%task, %client)
}
}
}
//------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------
@ -275,6 +259,8 @@ function AIEnhancedRearmTask::weight(%task, %client)
%task.setWeight($DXAI::Task::HighPriority);
else
%task.setWeight($DXAI::Task::NoPriority);
%task.setMonitorFreq(getRandom(10, 32));
}
function AIEnhancedRearmTask::monitor(%task, %client)
@ -322,26 +308,44 @@ function AIEnhancedReturnFlagTask::monitor(%task, %client)
{
if (!isObject(%client.returnFlagTarget))
return;
%client.setMoveTarget(%client.returnFlagTarget.getPosition());
if (isObject(%client.engageTarget) && %client.engageTarget.getState() $= "Move")
AIEnhancedReturnFlagTask::monitorEngage(%task, %client);
else
%client.setMoveTarget(%client.returnFlagTarget.getPosition());
}
function AIEnhancedReturnFlagTask::monitorEngage(%task, %client)
{
}
//------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------
// Description: A task that performs path correction.
//------------------------------------------------------------------------------------------`
function AIEnhancedPathCorrectionTask::initFromObjective(%task, %objective, %client) { }
function AIEnhancedPathCorrectionTask::assume(%task, %client) { %task.setMonitorFreq(32); }
function AIEnhancedPathCorrectionTask::assume(%task, %client) { %task.setMonitorFreq(2); }
function AIEnhancedPathCorrectionTask::retire(%task, %client) { }
function AIEnhancedPathCorrectionTask::weight(%task, %client)
{
%task.setWeight(0);
if (%client.isPathCorrecting)
%task.setWeight($DXAI::Task::VeryHighPriority);
else
%task.setWeight($DXAI::Task::NoPriority);
}
function AIEnhancedPathCorrectionTask::monitor(%task, %client)
{
{
if (%client.isPathCorrecting)
{
if (%client.player.getEnergyPercent() >= 1)
%client.isPathCorrecting = false;
else
%client.setMoveTarget(-1);
}
}
//------------------------------------------------------------------------------------------
@ -383,8 +387,10 @@ function AIEnhancedFlagCaptureTask::monitor(%task, %client)
}
//------------------------------------------------------------------------------------------
function ObjectiveNameToVoice(%objective)
function ObjectiveNameToVoice(%bot)
{
%objective = %bot.getTaskName();
%result = "avo.grunt";
switch$(%objective)
{
@ -397,7 +403,15 @@ function ObjectiveNameToVoice(%objective)
case "AIEnhancedScoutLocation":
%result = "slf.def.defend";
case "AIEnhancedDefendLocation":
%result = "slf.def.defend";
switch$(%bot.defenseDescription)
{
case "flag":
%result = "slf.def.flag";
case "generator":
%result = "slf.def.generator";
default:
%result = "slf.def.defend";
}
case "AIEnhancedEscort":
%result = "slf.tsk.cover";
}