T2-DXAI/scripts/DXAI/aiconnection.cs

340 lines
12 KiB
C#

//------------------------------------------------------------------------------------------
// 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)
{
%this.fieldOfView = 3.14 / 2; // 90* View cone
%this.viewDistance = 300;
}
function AIConnection::update(%this)
{
if (isObject(%this.player) && %this.player.getState() $= "Move")
{
%this.updateLegs();
%this.updateWeapons();
}
}
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::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);
}
function AIConnection::setMoveTarget(%this, %position)
{
if (%position == -1)
{
%this.reset();
%this.isMovingToTarget = false;
%this.isFollowingTarget = false;
return;
}
%this.moveTarget = %position;
%this.isMovingToTarget = true;
%this.isFollowingTarget = false;
%this.setPath(%position);
%this.stepMove(%position);
%this.minimumPathDistance = 9999;
%this.maximumPathDistance = -9999;
}
function AIConnection::setFollowTarget(%this, %target, %minDistance, %maxDistance, %hostile)
{
if (!isObject(%target))
{
%this.reset();
%this.isMovingToTarget = false;
%this.isFollowingTarget = false;
return;
}
%this.followTarget = %target;
%this.isFollowingTarget = true;
%this.followMinDistance = %minDistance;
%this.followMaxDistance = %maxDistance;
%this.followHostile = %hostile;
%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)
{
}
else
{
%this.stop();
%this.clearStep();
}
}
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)
{
if (isEventPending(%this.visualAcuityTick))
cancel(%this.visualAcuityTick);
if (%this.visibleDistance = 0 || !isObject(%this.player) || %this.player.getState() !$= "Move")
{
%this.visualAcuityTick = %this.schedule(getRandom(230, 400), "updateVisualAcuity");
return;
}
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();
}
%now = getSimTime();
%deltaTime = %now - %this.lastVisualAcuityUpdate;
%this.lastVisualAcuityUpdate = %now;
%visibleObjects = %this.getObjectsInViewcone($TypeMasks::ProjectileObjectType | $TypeMasks::PlayerObjectType, %this.viewDistance, true);
for (%iteration = 0; %iteration < %visibleObjects.getCount(); %iteration++)
{
%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)
{
%className = %current.getClassName();
// LinearFlareProjectile and LinearProjectile have linear trajectories, 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 && %current.client.team != %this.team)
{
%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));
// %rayCast = containerRayCast(%start, %end, -1, %this.player);
// %hitObject = getWord(%raycast, 0);
// if (%hitObject == %current)
// {
// %this.clientDetected(%current);
// %this.stepEngage(%current);
// }
}
}
// 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");
}