mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-02-14 12:13:46 +00:00
WIP mode of guiSliderCtrl to be a filled rectangle instead of a textured UI Fixed bug with guiTextEditCtrl losing focus updating history passing malformed strings Updated WIP options menu Editor/Project settings WIP Updated editor theme to be consistent, and feed off the editor settings Updated popup menus to reference renamed profiles Added more in-progress modules for examples/stress testing
1639 lines
60 KiB
C#
1639 lines
60 KiB
C#
//The aiPlayer.cs file creates a guard-style bot that can also be pathed.
|
|
//The guard uses a simple state machine to control it's actions.
|
|
|
|
//The bots actions are as follows:
|
|
//Dead: The bot stops all activity and is dead.
|
|
//Guarding: When guarding the bot scans for new targets, and when one is found it switches to 'Attacking'
|
|
//Attacking: The guard tries to close with the target while firing and checking for target updates.
|
|
//Holding: When the bot loses a target it will go into a holding pattern. While holding the bot's FOV
|
|
// is enhanced. The bot holds for a set number of cycles before changing it's action state to
|
|
// 'Returning'
|
|
//Returning: The bot tries to return to it's original position. While returning the guard looks for new targets
|
|
// and checks it motion relattive to it's last movement to determine if it is stuck.
|
|
// If it is stuck the bot tries to move is a random direction to try and clear the obstacle.
|
|
// (Not always a foolproof solution, but in a simple environment it works well enough.)
|
|
//Defending: When a bot takes damage it's status is set to defending. The bot sidesteps and then
|
|
// goes into a holding pattern. This does two things. It enhances the bots FOV and it scans for
|
|
// targets. Plus it will have the bot return to it's original position if there is no
|
|
// perceivable threat in range.
|
|
//NoTarget This is set when the bot loses sight or perception of it's targets. This is used to help
|
|
// clear the bots aim and other housekeeping functions
|
|
|
|
//The following are global variables used to set the guards basic settings.
|
|
$AI_GUARD_ENABLED = true; //Whether Guard bots are loaded during mission loading.
|
|
$AI_GUARD_MARKER_HIDE = true; //Turns marker hiding on or off - useful when editing maps.
|
|
$AI_GUARD_WEAPON = "Lurker"; //Which weapon do you want the guard to use
|
|
$AI_GUARD_ENDLESS_AMMO = true; //When set to true the guard will replenish its ammo perpetually
|
|
$AI_GUARD_WEAPON_USES_AMMO = true; //Set this to false for energy weapons that do not use ammo
|
|
$AI_GUARD_SIDESTEP = 20; //This value helps determine how far a bot sidesteps when he is stuck.
|
|
//The computer picks a random number between 1 and $AI_GUARD_SIDESTEP
|
|
//The value is then subtracted by half it's value to create a left/right
|
|
//and forward/back component. So the effective range is really -10 to +10
|
|
//with the default setting of 20
|
|
$AI_GUARD_DETECT_ITEM_RANGE = 50; //Sets how far around itself a bot will look for items to pick up
|
|
$AI_GUARD_HOLDCNT_MAX = 10; //The number of think cycles that the bot will 'hold' for before trying to
|
|
//return to his post.
|
|
$AI_GUARD_FIREDELAY = 1000; //How long the bot waits between firing bursts.
|
|
$AI_GUARD_ENHANCED_FOV_TIME = 2000; //How long the bots field of vision is enhanced to 360 for.
|
|
$AI_GUARD_FOV = 200; //The guards field of vision
|
|
$AI_GUARD_ENHANCED_DEFENDING_TIME = 5000; //How long the bot gets a 360 FOV and a longer detect distance for after being sniped.
|
|
$AI_GUARD_ENHANCED_DEFENDING_DISTANCE = 100; //Detect distance after being sniped.
|
|
$AI_GUARD_DETECT_DISTANCE = 50; //The range at which a guardbot will start reacting to a client target
|
|
$AI_GUARD_IGNORE_DISTANCE = 40; //The range at which the bot ignores a client and will not fire on it.
|
|
$AI_GUARD_MAX_DISTANCE = 5; //The bot will stop and try to stay at this distance or less from the player.
|
|
$AI_GUARD_RANGED_MAX_DISTANCE = 15; //Bots flagged as ranged will stop and try to stay at this distance or less from the player.
|
|
$AI_GUARD_MAX_PACE = 12; //The maximum range the mobs pace away from their guard point. (works like AI_GUARD_SIDESTEP)
|
|
$AI_GUARD_MIN_PACE = 1.5; //The minimum range the mobs pace away from their guard point.
|
|
$AI_GUARD_PACE_SPEED = 0.5; //Set the speed of the mob while pacing (1.0 is 100%, 0.5 is 50%)
|
|
$AI_GUARD_PACE_TIME = 4; //Sets how many think cycles the bot has to travel to it's location (or stand at
|
|
//it's location if it's already there) before getting another one to move to, random between 1 and this number.
|
|
$AI_GUARD_LOS_TIME = 100; //The amount of time after the bot loses sight of player that it will get their position.
|
|
//This helps the bot turn sharp corners. Set it to 1 or 0 if you don't want the bot to cheat.
|
|
$AI_GUARD_LOS_BYPASS = 3; //The distance at which positions will not have line of sight tests done on them.
|
|
//This is needed because the bot can not see the area around its feet.
|
|
$AI_GUARD_CORNERING = 0.8; //How close the bot will attempt to take corners. If the bot is having problems with corners,
|
|
//adjust this value, $AI_GUARD_LOS_TIME and $AI_GUARD_LOS_BYPASS as needed (will vary based on run speed).
|
|
$AI_GUARD_SCANTIME = 500; //The quickest time between think cycles.
|
|
$AI_GUARD_MAX_ATTENTION = 10; //This number and $AI_GUARD_SCANTIME are multiplied to set the delay in the
|
|
//thinking loop. Used to free up processor time on bots out of the mix.
|
|
$AI_GUARD_CREATION_DELAY = 3000; //How long a bot waits after creation before his think cycles are controlled by
|
|
//his attention rate. (Used to help free up think cycles on bots while misison
|
|
//finishes loading.
|
|
$AI_GUARD_TRIGGER_DOWN = 100; //How long the bot holds down the trigger when firing. Use longer pulses for
|
|
//pray and spray type weapons.
|
|
$AI_GUARD_DEFAULTRESPAWN = true; //Controls whether guards respawn automatically or not if their marker does not have
|
|
//dynamic 'respawn' variable set in it.
|
|
$AI_GUARD_RESPAWN_DELAY = 18000; //Determines how long a bot goes in between death and respawning.
|
|
$AI_GUARD_ENHANCEFOV_CHANCE = 25; //There is a 1 in x chance that guard will see 360 deg vision to prevent it
|
|
//from being snuck up on.
|
|
$AI_GUARD_SEEK_HEALTH_LVL = 65; //This sets at what damage level a bot will attempt to look for a health pack nearby.
|
|
$AI_GUARD_CHAR_TYPE = DemoPlayer; //This is the default datablock that is spawned as the bot unless another is specified on the node
|
|
|
|
//The onReachDestination function is responsible for setting the bots 'action'
|
|
//state to the appropriate setting depending on what action the bot was following
|
|
//to reach the destination.
|
|
function DemoPlayer::onReachDestination(%this, %obj)
|
|
{
|
|
//Picks an appropriate set of actions based on the bots 'action' variable
|
|
switch$(%obj.action)
|
|
{
|
|
//If the bot is attacking when it reaches it's target it will go into a hold.
|
|
case "Attacking":
|
|
%obj.action="Holding";
|
|
//If the bot is returning it has two possible scenarios for reaching a destination
|
|
//The first case is the the bot sidestepped and has reached it's sidestep location.
|
|
//If that is the case, then the bot goes into a quick hold. (Which sets the bot to
|
|
//only hold for 1 cycle before returning to his post.)
|
|
//The other alternative is that the bot has returned as is back at it's original position.
|
|
//If this is the case, then the bot's transform is set to match that of it's marker's
|
|
//transformation.
|
|
//This will cause a snapping into position - but it ensures that your guard always faces the
|
|
//direction you want it to when it returns to it's post.
|
|
//(It also helps to make sure that your markers are set as close to the ground as possible.
|
|
//Otherwise your bots will hop up and drop from the sky when they return to post.)
|
|
case "Returning":
|
|
//If the bot is pathed have it move to the next node on its path
|
|
if (%obj.path !$= "")
|
|
{
|
|
//Check if the bot's guarding
|
|
if (%obj.doesGuard $= "guard")
|
|
{
|
|
if (%obj.returningPos == %obj.marker.getposition())
|
|
{
|
|
%obj.moveToNextNode(%this.returningPath);
|
|
}
|
|
else
|
|
{
|
|
%obj.path = "";
|
|
%obj.doesGuard = "";
|
|
}
|
|
}
|
|
else
|
|
%obj.moveToNextNode(%this.returningPath);
|
|
}
|
|
else
|
|
{
|
|
if (%obj.doesGuard $= "guard")
|
|
%basedist = vectorDist(%obj.getposition(), %obj.marker.getposition());
|
|
else
|
|
%basedist = vectorDist(%obj.getposition(), %obj.returningPos);
|
|
//if the bot is close to his original position then set it's action to
|
|
//Guarding and set it to it's original facing and position.
|
|
if(%basedist < 1.0)
|
|
{
|
|
%obj.action = "Guarding";
|
|
//Set the bots returning position to its marker if it's guarding
|
|
if (%obj.doesGuard $= "guard")
|
|
%obj.settransform(%obj.marker.gettransform());
|
|
else
|
|
%obj.settransform(%obj.returningTrans);
|
|
%obj.clearaim();
|
|
}
|
|
//if the bot is away from his post, then he must have gotten here
|
|
//as a result of sidestepping so set him to do a quick hold to scan
|
|
//for targets then return to post.
|
|
else
|
|
{
|
|
//Sets holdcnt to 1 less than the max. Ensures that the bot only holds for 1 cycle.
|
|
//before trying to return.
|
|
%obj.holdcnt=$AI_GUARD_HOLDCNT_MAX-1;
|
|
%obj.action="Holding";
|
|
}
|
|
}
|
|
//The bot was defending and sidestepped. So set him to 'hold' to check for targets
|
|
//and to prepare to return to post if no targets are found.
|
|
case "Defending":
|
|
%obj.action = "Holding";
|
|
|
|
case "RetrievingItem":
|
|
%obj.holdcnt=$AI_GUARD_HOLDCNT_MAX-1;
|
|
%obj.action="Holding";
|
|
}
|
|
}
|
|
|
|
//The OnDamage function sets the bots action to 'Dead' and starts the respawn process
|
|
//if called for.
|
|
function DemoPlayer::OnDamage(%this, %obj, %delta)
|
|
{
|
|
if (%obj.action !$="GetHealth")
|
|
{
|
|
if (%obj.action !$= "Attacking" && %obj.action !$= "Defending" && %obj.getstate() !$="Dead")
|
|
{
|
|
%obj.enhancedefending(%obj);
|
|
}
|
|
%obj.action = "Defending";
|
|
}
|
|
|
|
if(%obj.getstate() $="Dead")
|
|
%obj.action="Dead";
|
|
|
|
if(%obj.getState() $= "Dead" && %obj.respawn == true)
|
|
{
|
|
//%obj.delaybeforerespawn(%obj.botname, %obj.marker);
|
|
%this.player = 0;
|
|
}
|
|
}
|
|
|
|
//The delay before respawn function is set to wait a specified duration before
|
|
//respawning an AIPlayer
|
|
function AIPlayer::DelayBeforeRespawn(%this, %name, %marker)
|
|
{
|
|
%this.respawntrigger = %this.schedule($AI_GUARD_RESPAWN_DELAY,"spawn", %name, %marker);
|
|
}
|
|
|
|
//The LoadEntities function replaces the markers placed in the map with the AI bots during the
|
|
//mission loading.
|
|
function AIPlayer::LoadEntities()
|
|
{
|
|
//Check to see if the AIPlayers are to be loaded.
|
|
if ($AI_GUARD_ENABLED == true)
|
|
{
|
|
echo("Loading Guard entities...");
|
|
//This performs a search for all items within the radius from the starting point.
|
|
//All of the items that match "AIPlayerMarker" trigger a bot to be placed at the
|
|
//position of the marker found.
|
|
%position = "0 0 0";
|
|
%radius = 100000.0;
|
|
InitContainerRadiusSearch(%position, %radius, $TypeMasks::StaticObjectType);
|
|
%i=0;
|
|
while ((%targetObject = containerSearchNext()) != 0)
|
|
{
|
|
if(%targetobject.getclassname() $= "StaticShape")
|
|
{
|
|
if (%targetobject.getDataBlock().getName() $= "AIPlayerMarker")
|
|
{
|
|
%i++;
|
|
|
|
// Let's check to see if the marker specifies a datablock.
|
|
// if so, we spawn that datablock model instead of the default
|
|
if (%targetObject.block $= "")
|
|
{
|
|
%block = $AI_GUARD_CHAR_TYPE;
|
|
}
|
|
else
|
|
{
|
|
%block = %targetObject.block;
|
|
}
|
|
%player = AIPlayer::spawnAtMarker("Guard" @ %i, %targetobject, %block);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
echo("Guard entities disabled...");
|
|
}
|
|
|
|
//This determines whether to hide or not hide the markers during mission loading.
|
|
//It's helpful to have the markers visible when editing the map and fine tuning the bot
|
|
//placement.
|
|
//This search is identical to the one above, only it hides the markers if found.
|
|
if ($AI_GUARD_MARKER_HIDE == true)
|
|
{
|
|
echo("Hiding Guard markers...");
|
|
%position = "0 0 0";
|
|
%radius = 100000.0;
|
|
InitContainerRadiusSearch(%position, %radius, $TypeMasks::StaticObjectType);
|
|
while ((%targetObject = containerSearchNext()) != 0)
|
|
{
|
|
if(%targetobject.getclassname() $= "StaticShape")
|
|
{
|
|
if (%targetobject.getDataBlock().getName() $= "AIPlayerMarker")
|
|
%targetobject.setAllMeshesHidden(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function AIPlayer::spawnByGroup(%spawnGroup)
|
|
{
|
|
echo ("spawning group " @ %spawnGroup);
|
|
|
|
//echo("Loading soldiers!");
|
|
|
|
//This performs a search for all items within the radius from the starting point.
|
|
//All of the items that match "AIPlayerMarker" trigger a bot to be placed at the
|
|
//position of the marker found.
|
|
|
|
%position = "0 0 0";
|
|
%radius = 100000.0;
|
|
InitContainerRadiusSearch(%position, %radius, $TypeMasks::StaticObjectType);
|
|
%i=0;
|
|
while ((%targetObject = containerSearchNext()) != 0)
|
|
{
|
|
if(%targetobject.getclassname() $= "StaticShape")
|
|
{
|
|
if (%targetobject.getDataBlock().getName() $= "AIPlayerMarker")
|
|
{
|
|
%i++;
|
|
echo("target's spawn is " @ %targetObject.spawnGroup);
|
|
if (%targetObject.spawnGroup $= %spawnGroup)
|
|
{
|
|
// we're in the correct spawn group!
|
|
|
|
// Let's check to see if the marker specifies a datablock.
|
|
// if so, we spawn that datablock model instead of the default
|
|
if (%targetObject.block $= "")
|
|
{
|
|
%block = $AI_GUARD_CHAR_TYPE;
|
|
}
|
|
else
|
|
{
|
|
%block = %targetObject.block;
|
|
}
|
|
|
|
// let's spawn some bad guys!
|
|
%player = AIPlayer::spawnAtMarker("Guard" @ %i, %targetobject, %block);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//This determines whether to hide or not hide the markers during mission loading.
|
|
//It's helpful to have the markers visible when editing the map and fine tuning the bot
|
|
//placement.
|
|
//This search is identical to the one above, only it hides the markers if found.
|
|
if ($AI_GUARD_MARKER_HIDE == true)
|
|
{
|
|
echo("Hiding Guard markers...");
|
|
%position = "0 0 0";
|
|
%radius = 100000.0;
|
|
InitContainerRadiusSearch(%position, %radius, $TypeMasks::StaticObjectType);
|
|
while ((%targetObject = containerSearchNext()) != 0)
|
|
{
|
|
if(%targetobject.getclassname() $= "StaticShape")
|
|
{
|
|
if (%targetobject.getDataBlock().getName() $= "AIPlayerMarker")
|
|
{
|
|
if (%targetObject.spawnGroup $= %spawnGroup)
|
|
%targetobject.setAllMeshesHidden(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//This function sets the bots aim to the current target, and 'pulls' the trigger
|
|
//of the weapon of the bot to begin the firing sequence.
|
|
function AIPlayer::openfire(%this, %obj, %tgt)
|
|
{
|
|
//If the bot is dead or the target is dead then let's bail out of here.
|
|
if (%obj.getState() $= "Dead" || %tgt.player.getstate() $="Dead")
|
|
{
|
|
%obj.firing = false;
|
|
%obj.NoTarget();
|
|
}
|
|
else
|
|
{
|
|
//We've got two live ones. So let's kill something.
|
|
//The firing variable is set while firing and is cleared at the end of the delay cycle.
|
|
//This is done to allow the use of a firing delay - and prevent a bot from firing again
|
|
//prematurely.
|
|
if(!%obj.firing)
|
|
{
|
|
//Gets the range to target - rtt
|
|
%rtt=vectorDist(%obj.getposition(), %tgt.player.getposition());
|
|
|
|
//If the target is within our ignore distance then we will attack.
|
|
if(%rtt < $AI_GUARD_IGNORE_DISTANCE)
|
|
{
|
|
if(%obj.fireLater <= 0 && %obj.getAimLocation() != %tgt.player.getposition()) //Fix for premature firing
|
|
{
|
|
%obj.fireLater++;
|
|
return;
|
|
}
|
|
//Sets the firing variable to true
|
|
%obj.firing = true;
|
|
|
|
if($AI_GUARD_WEAPON_USES_AMMO)
|
|
{
|
|
if($AI_GUARD_ENDLESS_AMMO == true)
|
|
{
|
|
%obj.incinventory(%obj.botWeapon @"Ammo",100);
|
|
}
|
|
}
|
|
|
|
//'Pulls' the trigger on the bot gun.
|
|
%obj.setImageTrigger(0,true);
|
|
//This sets a delay of $AI_GUARD_TRIGGER_DOWN length to hold the trigger down for.
|
|
%this.trigger = %this.schedule($AI_GUARD_TRIGGER_DOWN,"ceasefire", %obj);
|
|
}
|
|
else
|
|
{
|
|
//There was a target when openfire was called, but now they're out of range so
|
|
//we have no target. Call NoTarget to clear the bots aim.
|
|
%obj.NoTarget(%obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//This simply clears the bots aim to have it look forward relative to it's movement.
|
|
function AIPlayer::NoTarget(%this, %obj)
|
|
{
|
|
%obj.clearaim();
|
|
}
|
|
|
|
//Ceasefire is called by the openfire function after the set delay to
|
|
//hold the trigger down is met.
|
|
function AIPlayer::ceasefire(%this, %obj)
|
|
{
|
|
//Turns off the trigger, or lets off of it.
|
|
%obj.setImageTrigger(0,false);
|
|
//This sets the delay between when we let off the trigger and how soon it will
|
|
//be before we allow the bot to fire again.
|
|
%this.ceasefiretrigger = %this.schedule($AI_GUARD_FIREDELAY,"delayfire", %obj);
|
|
}
|
|
|
|
//delayfire is called to clear the firing variable. Clearing this allows
|
|
//the bot to fire again in the openfire function.
|
|
function AIPlayer::delayfire(%this, %obj)
|
|
{
|
|
//this is the end of the firing cycle
|
|
%obj.firing = false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// AIPlayer static functions
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//This is the spawn function for the bot.
|
|
function AIPlayer::spawn(%this, %name, %obj, %block)
|
|
{
|
|
if (%obj.block $= "")
|
|
{
|
|
%block = $AI_GUARD_CHAR_TYPE;
|
|
}
|
|
else
|
|
{
|
|
%block = %obj.block;
|
|
}
|
|
|
|
// Create the demo player object
|
|
%player = new AIPlayer() {
|
|
dataBlock = %block;
|
|
|
|
//The marker is the AIPlayer marker object that the guard is associated with.
|
|
//The marker object is kept with the player data because it's location, and
|
|
//dynamic variable values are used in several functions. This also allows the addition
|
|
//of future dynamic variables without having to change the spawn/respawn functions to
|
|
//access them.
|
|
marker = %obj;
|
|
botname = %name;
|
|
//Sets the bot's field of vision
|
|
fov = $AI_GUARD_FOV;
|
|
//Sets the bot's detect distance
|
|
detdis = $AI_GUARD_DETECT_DISTANCE;
|
|
//Sets the bot's returning position and transform
|
|
returningPos = %obj.getposition();
|
|
returningTrans = %obj.gettransform();
|
|
//Sets the bot not to return to a path as soon as it is loaded
|
|
//The pathed bots will go to there paths at another point
|
|
returningPath = 0;
|
|
//Fix for premature firing
|
|
fireLater = 0;
|
|
//Sets the bot's pacing
|
|
pace = $AI_GUARD_PACE_TIME;
|
|
//The pathname variable is a dynamic variable set during map editing.
|
|
//This allows the designer to attach each bot to a seperate path
|
|
path = %obj.pathname;
|
|
//Is the bot using a ranged weapon
|
|
weapRange = %obj.range;
|
|
//Does the bot return to its spawn point
|
|
doesGuard = %obj.doesReturn;
|
|
//Sets whether the bot is AI or not
|
|
isbot=true;
|
|
//Thinking variables
|
|
//Firing tells whether or not we're in the midst of a firing sequence.
|
|
firing = false;
|
|
//The 'action' variable holds the state of the bot - which controls how it
|
|
//thinks.
|
|
holdcnt = $AI_GUARD_HOLDCNT_MAX-1;
|
|
action = "Holding";
|
|
//The bots starting attention level is set to half of it's range.
|
|
attentionlevel = $AI_GUARD_MAX_ATTENTION/2;
|
|
|
|
//Oldpos holds the position of the bot at the end of it's last 'think' cycle
|
|
//This is used to help determine if a bot is stuck or not.
|
|
oldpos = %obj.getposition();
|
|
//Added for bots use different weapons
|
|
botWeapon = $AI_GUARD_WEAPON;
|
|
};
|
|
|
|
MissionCleanup.add(%player);
|
|
|
|
// if the field is not blank, set the weapon variable to the weapon
|
|
// otherwise, use default.
|
|
if (%obj.Weapon !$= "")
|
|
{
|
|
%player.botWeapon = %obj.Weapon;
|
|
}
|
|
|
|
//Sets the name displayed in the hud above the bot. Commented out be default.
|
|
//%player.setShapeName(%name);
|
|
//is called to set the bots beginning inventory.
|
|
%player.EquipBot(%player);
|
|
//Sets the bot's initial position to that of it's marker.
|
|
%player.setTransform(%obj.gettransform());
|
|
|
|
//The following cluster of if-thens sets whether the bot will respawn or not
|
|
//based on it's markers dynamic variable - or the default respawn variable setting.
|
|
if (%obj.respawn $= "" )
|
|
{
|
|
%player.respawn=$AI_GUARD_DEFAULTRESPAWN;
|
|
}
|
|
else
|
|
{
|
|
if (%obj.respawn == true)
|
|
%player.respawn=true;
|
|
else
|
|
%player.respawn=false;
|
|
}
|
|
|
|
if (%obj.pathname !$= "")
|
|
{
|
|
%player.schedule($AI_GUARD_CREATION_DELAY,"followPath", %obj.pathname, -1);
|
|
}
|
|
|
|
//Sets the bot to begin thinking after waiting the length of $AI_GUARD_CREATION_DELAY
|
|
%player.schedule($AI_GUARD_CREATION_DELAY,"Think", %player);
|
|
|
|
return %player;
|
|
}
|
|
|
|
//This sets the bots beginning equipment and inventory
|
|
function AIPlayer::EquipBot(%this, %obj)
|
|
{
|
|
echo("equipingBot");
|
|
//This adds a weapon to the bots inventory.
|
|
%obj.incinventory(%obj.botWeapon,1);
|
|
//This mounts the weapon on the bot.
|
|
%obj.mountImage(%obj.botWeapon @ "Image",0);
|
|
echo(%obj.botWeapon);
|
|
%obj.use(%obj.botWeapon);
|
|
if($AI_GUARD_WEAPON_USES_AMMO == true)
|
|
{
|
|
//This sets the bots beginning inventory of ammo.
|
|
%obj.setInventory(%obj.botWeapon @ "Ammo",100);
|
|
}
|
|
}
|
|
|
|
//The EnhanceFOV function temporarily gives the bot a 360 degree field of vision
|
|
//This is used to emulate the bot looking around at different times. Namely when
|
|
//'Holding'.
|
|
function AIPlayer::EnhanceFOV(%this, %obj)
|
|
{
|
|
//Is the botFOV already 360 degrees? If not then we'll set it, and set the schedule to
|
|
//turn it back off.
|
|
if (%obj.fov != 360)
|
|
{
|
|
//Sets the field of vision to 360 deg.
|
|
%obj.fov = 360;
|
|
//Starts the timer to disable the enhanced FOV
|
|
%this.fovtrigger = %this.schedule($AI_GUARD_ENHANCED_FOV_TIME, "restorefov", %obj);
|
|
}
|
|
}
|
|
|
|
//Restore FOV sets the bot's FOV back to it's regular default setting.
|
|
function AIPlayer::restoreFOV(%this, %obj)
|
|
{
|
|
%obj.fov = $AI_GUARD_FOV;
|
|
}
|
|
|
|
//Enhances the defending mob's FOV and detect distance after being hit.
|
|
function AIPlayer::EnhanceDefending(%this, %obj)
|
|
{
|
|
if (%obj.detdis == $AI_GUARD_DETECT_DISTANCE)
|
|
{
|
|
%obj.detdis = $AI_GUARD_ENHANCED_DEFENDING_DISTANCE;
|
|
%this.distancetrigger = %this.schedule($AI_GUARD_ENHANCED_DEFENDING_TIME, "restoreDefending", %obj);
|
|
}
|
|
|
|
%obj.fov = 360;
|
|
%this.fovtrigger = %this.schedule($AI_GUARD_ENHANCED_DEFENDING_TIME, "restorefov", %obj);
|
|
}
|
|
|
|
//Restores the defending mob's detect distance.
|
|
function AIPlayer::restoreDefending(%this, %obj)
|
|
{
|
|
%obj.detdis = $AI_GUARD_DETECT_DISTANCE;
|
|
}
|
|
|
|
//Spawn at marker is called by LoadEntities, and calls the spawn function to
|
|
//create the bots and place them at their starting positions.
|
|
function AIPlayer::spawnAtMarker(%name, %obj, %block)
|
|
{
|
|
if (!isObject(%obj))
|
|
return;
|
|
%player = AIPlayer::spawn(%this, %name, %obj, %block);
|
|
return %player;
|
|
}
|
|
|
|
//AITargeting
|
|
|
|
//Return the angle of a vector in relation to world origin
|
|
function AIPlayer::getAngleofVector(%this, %vec)
|
|
{
|
|
%vector = VectorNormalize(%vec);
|
|
%vecx = getWord(%vector,0);
|
|
%vecy = getWord(%vector,1);
|
|
if(%vecx >= 0 && %vecy >= 0)
|
|
%quad = 1;
|
|
else
|
|
if(%vecx >= 0 && %vecy < 0)
|
|
%quad = 2;
|
|
else
|
|
if(%vecx < 0 && %vecy < 0)
|
|
%quad = 3;
|
|
else
|
|
%quad = 4;
|
|
%angle = mATan(%vecy/%vecx, -1);
|
|
%degangle = mRadToDeg(%angle);
|
|
switch(%quad)
|
|
{
|
|
case 1:
|
|
%angle = %degangle-90;
|
|
case 2:
|
|
%angle = %degangle+270;
|
|
case 3:
|
|
%angle = %degangle+90;
|
|
case 4:
|
|
%angle = %degangle+450;
|
|
}
|
|
if (%angle < 0) %angle = %angle + 360;
|
|
return %angle;
|
|
}
|
|
|
|
//This is another function taken from code off of garagegames.
|
|
//The only mods I made to it was to add the extra check to ensure that the
|
|
//angle is within the 0-360 range.
|
|
function AIPlayer::check2DAngletoItem(%this, %obj, %item)
|
|
{
|
|
%eyeVec = VectorNormalize(%this.getEyeVector());
|
|
%eyeangle = %this.getAngleofVector(%eyeVec);
|
|
%posVec = VectorSub(%item.getPosition(), %obj.getPosition());
|
|
%posangle = %this.getAngleofVector(%posVec);
|
|
%angle = %posangle - %eyeAngle;
|
|
%angle = %angle ? %angle : %angle * -1;
|
|
if (%angle < 0) %angle = %angle + 360;
|
|
return %angle;
|
|
}
|
|
|
|
//This is another function taken from code off of garagegames.
|
|
//The only mods I made to it was to add the extra check to ensure that the
|
|
//angle is within the 0-360 range.
|
|
function AIPlayer::check2DAngletoTarget(%this, %obj, %tgt)
|
|
{
|
|
%eyeVec = VectorNormalize(%this.getEyeVector());
|
|
%eyeangle = %this.getAngleofVector(%eyeVec);
|
|
%posVec = VectorSub(%tgt.player.getPosition(), %obj.getPosition());
|
|
%posangle = %this.getAngleofVector(%posVec);
|
|
%angle = %posangle - %eyeAngle;
|
|
%angle = %angle ? %angle : %angle * -1;
|
|
if (%angle < 0) %angle = %angle + 360;
|
|
return %angle;
|
|
}
|
|
|
|
//The 'Think' function is the brains of the bot.
|
|
//The bot performs certain actions based on what it's current 'action' state is.
|
|
//The bot thinks on a scheduled basis. How fast the bot 'thinks' is determined by
|
|
//the bots attention level and its default scan time. (There are a few cases in the think
|
|
//function below where the schedule is shortened - but only to make the 'thinking' more
|
|
//realistic and to cut down on duplicating chunks of code.
|
|
|
|
function AIPlayer::Think(%this, %obj)
|
|
{
|
|
//This cancels the current schedule - just to make sure that things are kept neat and tidy.
|
|
cancel(%this.ailoop);
|
|
|
|
//If the bot is dead, then there's no need to think or do anything. So let's bail out.
|
|
if (!%obj || %obj.getstate() $="Dead")
|
|
return;
|
|
|
|
%prevaction=%obj.action;
|
|
|
|
if (%obj.action !$="RetrievingItem" && %obj.action !$="Dead")
|
|
{
|
|
if (%obj.getdamagelevel() > $AI_GUARD_SEEK_HEALTH_LVL)
|
|
{
|
|
%this.enhancefov(%obj);
|
|
%hlth= %this.getclosestiteminsightandrange(%obj, "HealthPatch");
|
|
|
|
if(%hlth > 0)
|
|
{
|
|
%obj.action="GetHealth";
|
|
}
|
|
|
|
if($AI_GUARD_WEAPON_USES_AMMO == true)
|
|
{
|
|
if(%obj.getInventory(%obj.botWeapon @ "Ammo") == 0)
|
|
{
|
|
%this.enhancefov(%obj);
|
|
%ammostr = %obj.botWeapon @ "Ammo";
|
|
%i_ammo= %this.getclosestiteminsightandrange(%obj, %ammostr );
|
|
if(%i_ammo > 0)
|
|
{
|
|
%obj.action="GetAmmo";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//The switch$ takes the value of the bots action variable and then chooses what code to run
|
|
//according to what value it is.
|
|
switch$(%obj.action)
|
|
{
|
|
//The bot is 'dead' so lets clear his aim, and turn off his firing variable.
|
|
case "Dead":
|
|
%obj.clearaim();
|
|
%obj.firing = false;
|
|
|
|
//This is the bots default position. While guarding the bot will only do 2 things.
|
|
//The first is that the bot will run a random check to see if it can enhance it's fov.
|
|
//This is thrown in to prevent bots from having a perpetual blind spot, but still limits
|
|
//their field of vision for the majority of the time.
|
|
//The other thing the bot does is to check for nearby targets. If found the bot goes into attack mode.
|
|
case "Guarding":
|
|
//The bot will enhance it's FOV if it picks a 1 from a range of 1 to $AI_GUARD_ENHANCEFOV_CHANCE
|
|
%chance = getRandom(($AI_GUARD_ENHANCEFOV_CHANCE-1)) +1;
|
|
if (%chance == 1 )
|
|
%this.enhancefov(%obj);
|
|
|
|
%obj.fireLater = 0;
|
|
%obj.lostest = 0;
|
|
|
|
//The bot checks for the nearest valid target if any.
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
//If %tgtid >= 0 then a target is in sight and range.
|
|
if(%tgtid >= 0)
|
|
{
|
|
//Set the bots action to 'Attacking' and set it to attack quickly.
|
|
%obj.action = "Attacking";
|
|
//This is one instance where the bots thinking is sped up to enable the bot
|
|
//to react more quickly as seems appropriate.
|
|
%this.ailoop=%this.schedule(100,"Think" , %obj);
|
|
}
|
|
else
|
|
{
|
|
//Check if the bot's pathed and if not, pace if it's time to pace
|
|
if (%obj.path $= "")
|
|
{
|
|
if(%obj.pace == 0)
|
|
{
|
|
%obj.pace = getRandom(($AI_GUARD_PACE_TIME-1)) +1;
|
|
%this.pacing(%obj);
|
|
}
|
|
else
|
|
{
|
|
%obj.pace--;
|
|
}
|
|
}
|
|
//There are no targets so continue guarding and call the scheduler to have the bot think
|
|
//at it's regular interval
|
|
%this.ailoop=%this.schedule($AI_GUARD_SCANTIME * %obj.attentionlevel ,"Think" , %obj);
|
|
}
|
|
|
|
//The bot has been told that there is a target in sight and range and is set to attack it.
|
|
//While attacking the bot's attention level is kept at it's lowest value (Quickest thinking)
|
|
//The bot looks for the nearest target in sight. If the target is found the bot will aim at the
|
|
//target, set it's move destination to the position of the target, and then openfire on the target.
|
|
case "Attacking":
|
|
//Set the bot's move speed back to normal
|
|
%obj.setMoveSpeed(1.0);
|
|
//Maintain a low attention value to keep the bot thinking quickly while attacking.
|
|
%obj.attentionlevel=1;
|
|
//Get the id of the nearest valid target
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
//If %tgtid>0 then there is a valid target
|
|
if(%tgtid >=0)
|
|
{
|
|
//Make sure that we keep ourself in attack mode since we have a target in sight.
|
|
%obj.action = "Attacking";
|
|
//Get the current player object from the client value set in %tgtid
|
|
%tgt = ClientGroup.getobject(%tgtid);
|
|
//Set the bot to aim at the target.
|
|
//(The code uses the VectorAdd to adjust the aim of the bot to correct for the
|
|
//bot trying to shoot at the targets feet.)
|
|
%obj.setAimObject(%tgt.player, "0 0 1");
|
|
|
|
%dest = %tgt.player.getposition();
|
|
|
|
%basedist = vectorDist(%obj.getposition(), %dest);
|
|
|
|
//Check if the bot is flagged as using a ranged weapon, then check if the bot is already close
|
|
//enough to the target or needs to be closer
|
|
if (%obj.weapRange $= "ranged")
|
|
{
|
|
if(%basedist > $AI_GUARD_RANGED_MAX_DISTANCE)
|
|
{
|
|
%this.moveDestinationA = %dest;
|
|
%this.dontMoveAlongTheWall(%obj);
|
|
}
|
|
}
|
|
//Check if the bot is already close enough to the target or needs to be closer
|
|
else
|
|
{
|
|
if(%basedist > $AI_GUARD_MAX_DISTANCE)
|
|
{
|
|
%this.moveDestinationA = %dest;
|
|
%this.dontMoveAlongTheWall(%obj);
|
|
}
|
|
}
|
|
//Tells the bot to start shooting the target.
|
|
%obj.openfire(%obj, %tgt);
|
|
//Tells the scheduler to have us think again
|
|
%this.ailoop=%this.schedule($AI_GUARD_SCANTIME * %obj.attentionlevel ,"Think" , %obj);
|
|
}
|
|
else
|
|
{
|
|
//There was no target found, so set our action to NoTarget.
|
|
%obj.action="NoTarget";
|
|
//Again this sets the scheduler to have us think quickly to have the bot
|
|
//react to the loss of it's attack target
|
|
%this.ailoop=%this.schedule(100 ,"Think" , %obj);
|
|
}
|
|
|
|
//When a bot loses it's target, or when the bot reaches it's destination as the result of
|
|
//a sidestep the bot will go into a 'hold'
|
|
//During a hold the bot will have enhanced FOV (to emulate scanning around for targets.)
|
|
//The bot will look for targets in range and attack if found.
|
|
//If no target is found the bot will increase it's holdcnt by 1. When the bot reaches it's
|
|
//maximum holdcnt value it will attempt to return to it's base position.
|
|
case "Holding":
|
|
//Set the bot's move speed back to normal
|
|
%obj.setMoveSpeed(1.0);
|
|
//Enhance the bot's FOV
|
|
%this.enhancefov(%obj);
|
|
//Checks for targets - (See the above code for full details of this section of code)
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
if(%tgtid >=0)
|
|
{
|
|
%obj.holdcnt=0;
|
|
%obj.action = "Attacking";
|
|
%this.ailoop=%this.schedule(100,"Think" , %obj);
|
|
}
|
|
else
|
|
{
|
|
//There was no target found, so we need to have the bot continue to 'hold'
|
|
//for a little bit before doing anything else.
|
|
|
|
//Increase the holdcnt variable by one
|
|
%obj.holdcnt++;
|
|
%obj.fireLater = 0;
|
|
|
|
%basedist = vectorDist(%this.getposition(), %this.moveDestinationA);
|
|
if (%basedist > 0.5)
|
|
%this.dontMoveAlongTheWall(%obj);
|
|
|
|
//Check to see if we've passed our threshold of waiting
|
|
if (%obj.holdcnt > $AI_GUARD_HOLDCNT_MAX)
|
|
{
|
|
//Set holdcnt back to 0 for the next time we need it.
|
|
%obj.holdcnt=0;
|
|
|
|
//Set the bot to return to where it last saw the player if it's not pathed
|
|
if (%obj.path $= "")
|
|
{
|
|
//Reset returning positions for guard bots
|
|
if (%obj.doesGuard $= "guard")
|
|
{
|
|
%obj.returningPos = %obj.marker.getposition();
|
|
%obj.returningTrans = %obj.marker.gettransform();
|
|
}
|
|
|
|
%this.moveDestinationA = %obj.returningPos;
|
|
%this.dontMoveAlongTheWall(%obj);
|
|
}
|
|
//Set the bot to return to its path since it is pathed
|
|
else
|
|
{
|
|
if (%obj.returningPath != 0)
|
|
{
|
|
if (%obj.doesGuard $= "guard")
|
|
{
|
|
%this.moveDestinationA = %obj.returningPos;
|
|
%this.dontMoveAlongTheWall(%obj);
|
|
}
|
|
else
|
|
{
|
|
%this.movtrigg = %this.schedule(100, "followPath", %obj.path, -1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
%obj.returningPath = 1;
|
|
}
|
|
}
|
|
|
|
//Set the bot action to 'Returning'
|
|
%obj.action="Returning";
|
|
//Sets the bots oldpos to that of the position it's returning to
|
|
//This is done this way due to the fact that we've been holding
|
|
//and our position hasn't been changing. So we want to be sure that
|
|
//our bot doesn't think that it's stuck as soon as it tries to return.
|
|
%obj.oldpos = %obj.returningPos;
|
|
//We've waited long enough, so let's quickthink and go into 'Return' mode
|
|
%this.ailoop=%this.schedule(100, "Think" , %obj);
|
|
}
|
|
else
|
|
{
|
|
//Start the bot moving to its return point while it's still in holding mode
|
|
%this.moveDestinationA = %obj.returningPos;
|
|
%this.dontMoveAlongTheWall(%obj);
|
|
|
|
%obj.clearaim();
|
|
%this.ailoop=%this.schedule($AI_GUARD_SCANTIME * %obj.attentionlevel ,"Think" , %obj);
|
|
}
|
|
}
|
|
|
|
//In Return mode the bot will do the following.
|
|
//It looks for the nearest target in sight and will attack it.
|
|
//It does not check for people sneaking up behind it, nor does it enhance it's FOV.
|
|
//If a target is found the bot will attack.
|
|
//If no target is found, the bot is still in the process of returning so we check to see
|
|
//if the bot is stuck. Stuck in the case means that the bot hase moved a distance of less than
|
|
//1 unit since the last time it thought.
|
|
//If the bot is stuck, sidestep is called to have the bot try to move a different direction
|
|
//The bot is then set to go into 'Holding' but with it's holdcnt set to 1 less than it's maximum.
|
|
//This essentially means that the bot will sidestep, and go into hold for one cycle in which to check
|
|
//targets and then try to return again if there is nothing to attack.
|
|
//If the bot is not stuck and there are no targets, then the bots aim is set to point towards it's
|
|
//destination of it's spawn point. (This is done to prevent the bot from pointing to the position
|
|
//of it's last sidestep while returning.)
|
|
case "Returning":
|
|
//Set the bot's move speed back to normal
|
|
%obj.setMoveSpeed(1.0);
|
|
//The next line can be commented out if desired. I chose to put it in so that the
|
|
//bots would try to return in a timely manner rather than having them wait too long
|
|
//between thinks to see if they were stuck.
|
|
%obj.attentionlevel=$AI_GUARD_MAX_ATTENTION/2;
|
|
|
|
//The next few lines again have the bot check for a target and attack if need be.
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
if(%tgtid >=0)
|
|
{
|
|
%obj.action = "Attacking";
|
|
%this.ailoop=%this.schedule(100,"Think" , %obj);
|
|
}
|
|
else
|
|
{
|
|
//There was no target so we're still returning. So now check for a pathed or stuck bot
|
|
//This gets a value depicting the distance from the bots last known move point
|
|
%movedist=vectorDist(%obj.getposition(), %obj.oldpos);
|
|
//If the bot hasn't moved more than 1 unit we're probably stuck.
|
|
//Remember - this is only checked for while returning - not guarding
|
|
if (%movedist <1.0)
|
|
{
|
|
//Set our holdcnt to 1 less than the maximum so we only hold for 1 cycle
|
|
%obj.holdcnt=$AI_GUARD_HOLDCNT_MAX-1;
|
|
//Call sidestep to pick a new move destination near the bot
|
|
%this.sidestep(%obj);
|
|
}
|
|
else
|
|
{
|
|
//Check to make sure the bot is not pathed
|
|
if (%obj.path $= "")
|
|
{
|
|
//We're returning and we're not stuck. So make sure we're looking the direction we're running.
|
|
//Check if the bot is guarding
|
|
if (%obj.doesGuard $= "guard")
|
|
{
|
|
%obj.setAimLocation(%obj.marker.getposition());
|
|
}
|
|
else
|
|
{
|
|
%obj.setAimLocation(%obj.returningPos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//Set our oldpos to match our current position so that next time we cycle through
|
|
//we'll know if we're going anywhere or not
|
|
%obj.oldpos = %obj.getposition();
|
|
//Scedhule ourselves to think at our regular interval
|
|
%this.ailoop = %this.schedule($AI_GUARD_SCANTIME * %obj.attentionlevel, "Think", %obj);
|
|
|
|
//When a bot takes damage his state is set to defending.
|
|
//A bot that is defending will have it's attention set to it's lowest level
|
|
//It will sidestep to try to avoid the danger, and to throw some randomness into it's
|
|
//movement. The bot will then go into a quick hold of 1 count.
|
|
case "Defending":
|
|
//Set the bot's move speed back to normal
|
|
%obj.setMoveSpeed(1.0);
|
|
//Set the hldcnt to 1 less than the max
|
|
%obj.holdcnt=$AI_GUARD_HOLDCNT_MAX-1;
|
|
//Set the bot to it's highest awareness
|
|
%obj.attentionlevel=1;
|
|
//Sidestep to a random position
|
|
%this.sidestep(%obj);
|
|
//Set our action to 'Holding'
|
|
%obj.action="Holding";
|
|
//Set a quick think schedule to start us looking for targets quickly.
|
|
%this.ailoop=%this.schedule(100 ,"Think" , %obj);
|
|
|
|
//NoTarget is set when a bot loses it's target while attacking.
|
|
//It causes a bot's firing variable to be reset, sets the holdcnt to 0
|
|
//so that when we go into a hold we will do so for the full duration
|
|
case "NoTarget":
|
|
//Clear the firing variable
|
|
%obj.firing = false;
|
|
//Clear holdcnt
|
|
%obj.holdcnt=0;
|
|
//Set our action to 'Holding'
|
|
%obj.action = "Holding";
|
|
//Quick think to start us looking for our lost target.
|
|
%this.ailoop=%this.schedule(100 ,"Think" , %obj);
|
|
|
|
case "GetHealth":
|
|
%hlth= %this.getclosestiteminsightandrange(%obj, "HealthPatch");
|
|
if(%hlth > 0)
|
|
{
|
|
%obj.action="RetrievingItem";
|
|
%dest=%hlth.getposition();
|
|
%obj.setmovedestination(%dest);
|
|
%this.enhancefov(%obj);
|
|
}
|
|
else
|
|
{
|
|
%obj.action=%prevaction;
|
|
}
|
|
%this.ailoop=%this.schedule(100 ,"Think" , %obj);
|
|
|
|
case "GetAmmo":
|
|
%ammostr = %obj.botWeapon @"Ammo";
|
|
%i_ammo= %this.getclosestiteminsightandrange(%obj, %ammostr );
|
|
if(%i_ammo > 0)
|
|
{
|
|
%obj.action="RetrievingItem";
|
|
%dest=%i_ammo.getposition();
|
|
%obj.setmovedestination(%dest);
|
|
%this.enhancefov(%obj);
|
|
}
|
|
else
|
|
{
|
|
%obj.action=%prevaction;
|
|
}
|
|
|
|
%this.ailoop=%this.schedule(100 ,"Think" , %obj);
|
|
|
|
case "RetrievingItem":
|
|
%obj.setMoveSpeed(1.0);
|
|
%obj.attentionlevel=$AI_GUARD_MAX_ATTENTION/2;
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
if(%tgtid >=0)
|
|
{
|
|
%obj.action = "RetrievingItem";
|
|
%obj.attentionlevel=1;
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
if(%tgtid >=0)
|
|
{
|
|
%tgt = ClientGroup.getobject(%tgtid);
|
|
%obj.setAimObject(%tgt.player, "0 0 1");
|
|
%obj.openfire(%obj, %tgt);
|
|
}
|
|
else
|
|
{
|
|
%obj.firing = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
%movedist=vectorDist(%obj.getposition(), %obj.oldpos);
|
|
if (%movedist <1.0)
|
|
{
|
|
%obj.holdcnt=$AI_GUARD_HOLDCNT_MAX-1;
|
|
|
|
%this.sidestep(%obj);
|
|
}
|
|
else
|
|
{
|
|
%obj.setaimlocation(%obj.getmovedestination());
|
|
}
|
|
}
|
|
%obj.oldpos=%obj.getposition();
|
|
%this.ailoop=%this.schedule($AI_GUARD_SCANTIME * %obj.attentionlevel ,"Think" , %obj);
|
|
|
|
default:
|
|
%obj.action="Holding";
|
|
%this.ailoop=%this.schedule(100 ,"Think" , %obj);
|
|
}
|
|
}
|
|
//If you want to see the bots thinking processes in action then uncomment the
|
|
//line below. It will then set the hud above the bot to show it's current
|
|
//action/attention level/damage/ammo
|
|
//(Used during testing, but kind of fun to watch when you have
|
|
//several bots on the map at a time to see how things are working out.)
|
|
|
|
//%objname= %obj.action @ ":"@ %this.attentionlevel @ ":" @ %obj.getdamagelevel() @ ":" @ %obj.getInventory(%obj.botWeapon @ "Ammo") ;
|
|
//%obj.setshapename(%objname);
|
|
|
|
//Clear aim if attention hits max.
|
|
if (%this.attentionlevel == $AI_GUARD_MAX_ATTENTION)
|
|
%obj.clearaim();
|
|
}
|
|
|
|
//Causes AIPlayer to slowly pace around their current location
|
|
function AIPlayer::Pacing(%this, %obj)
|
|
{
|
|
//%xrand and %yrand are set to be a random number that is equal to -1/2$AI_GUARD_MAX_PACE and +1/2$AI_GUARD_MAX_PACE
|
|
%xrand = getRandom(1,$AI_GUARD_MAX_PACE)-$AI_GUARD_MAX_PACE/2;
|
|
%yrand = getRandom(1,$AI_GUARD_MAX_PACE)-$AI_GUARD_MAX_PACE/2;
|
|
|
|
while(%xrand > -$AI_GUARD_MIN_PACE && %xrand < $AI_GUARD_MIN_PACE)
|
|
{
|
|
%xrand = getRandom(1,$AI_GUARD_MAX_PACE)-$AI_GUARD_MAX_PACE/2;
|
|
}
|
|
while(%yrand > -$AI_GUARD_MIN_PACE && %yrand < $AI_GUARD_MIN_PACE)
|
|
{
|
|
%yrand = getRandom(1,$AI_GUARD_MAX_PACE)-$AI_GUARD_MAX_PACE/2;
|
|
}
|
|
|
|
//%newloc is first set to the bots current position
|
|
%newLoc = %obj.getTransform();
|
|
|
|
//Set the bots returning position to its marker if it's guarding
|
|
if (%obj.doesGuard $= "guard")
|
|
%obj.returningPos = %obj.marker.getposition();
|
|
|
|
//If the is away from its returning position, go back to it so it doesn't wander too far away
|
|
%basedist = vectorDist(%obj.getposition(), %obj.returningPos);
|
|
if(%basedist > $AI_GUARD_MIN_PACE)
|
|
{
|
|
%newLoc = %obj.returningTrans;
|
|
}
|
|
else
|
|
{
|
|
//Word(0) of %newloc (which is the x value) is set to equal it's original value plus the value
|
|
//of %xrand. The -/+ aspect of this equivalates to a left/right direction.
|
|
%newLoc = setWord(%newLoc, 0, (getWord(%newLoc, 0) + (%xrand)));
|
|
//Word(1) of %newloc (which is the y value) is set to equal it's original value plus the value
|
|
//of %yrand. The -/+ aspect of this equivalates to a forward/back direction.
|
|
%newLoc = setWord(%newLoc, 1, (getWord(%newLoc, 1) + (%yrand)));
|
|
|
|
%basedist = vectorDist(%obj.getposition(), %newLoc);
|
|
//If the target location is very close, don't preform a line of sight test
|
|
if(%basedist > $AI_GUARD_LOS_BYPASS)
|
|
{
|
|
//Line of sight test for the position the bot wants to pace to
|
|
%eyeTrans = %obj.getEyeTransform();
|
|
%eyeEnd = %newLoc;
|
|
%searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::PlayerObjectType | $TypeMasks::StaticTSObjectType |
|
|
$TypeMasks::TerrainObjectType | $TypeMasks::ItemObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::StaticObjectType, %obj);
|
|
%foundObject = getword(%searchResult,0);
|
|
|
|
if (%foundObject > 0)
|
|
{
|
|
%this.pacing(%obj);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//Set the bot to move at a different speed than normal while pacing
|
|
%obj.setMoveSpeed($AI_GUARD_PACE_SPEED);
|
|
//Set the bot to look in the direction that it is moving.
|
|
%obj.setaimlocation(%newLoc);
|
|
//Set the bot to move towards the new position.
|
|
%obj.setMoveDestination(%newLoc);
|
|
}
|
|
|
|
//Sidestep is used to find a random spot near the bot and attempt to have them move towards it.
|
|
function AIPlayer::SideStep(%this, %obj)
|
|
{
|
|
//%xrand and %yrand are set to be a random number that is equal to -1/2$AI_GUARD_SIDESTEP and +1/2$AI_GUARD_SIDESTEP
|
|
%xrand = getRandom(1,$AI_GUARD_SIDESTEP)-$AI_GUARD_SIDESTEP/2;
|
|
%yrand = getRandom(1,$AI_GUARD_SIDESTEP)-$AI_GUARD_SIDESTEP/2;
|
|
//%newloc is first set to the bots current position
|
|
%newLoc = %obj.getTransform();
|
|
//Word(0) of %newloc (which is the x value) is set to equal it's original value plus the value
|
|
//of %xrand. The -/+ aspect of this equivalates to a left/right direction.
|
|
%newLoc = setWord(%newLoc, 0, (getWord(%newLoc, 0) + (%xrand)));
|
|
//Word(1) of %newloc (which is the y value) is set to equal it's original value plus the value
|
|
//of %yrand. The -/+ aspect of this equivalates to a forward/back direction.
|
|
%newLoc = setWord(%newLoc, 1, (getWord(%newLoc, 1) + (%yrand)));
|
|
|
|
//If the bot is pathed, get ready to move to the correct node
|
|
if (%obj.path !$= "")
|
|
{
|
|
if (%this.returningPath == 1)
|
|
{
|
|
%this.returningPath = 2;
|
|
}
|
|
}
|
|
|
|
//If there's a target, keep aiming at it while sidestepping
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
if(%tgtid >= 0)
|
|
{
|
|
%tgt = ClientGroup.getobject(%tgtid);
|
|
%obj.setAimObject(%tgt.player, "0 0 1");
|
|
|
|
%basedist = vectorDist(%obj.getposition(), %newLoc);
|
|
//If the target location is very close and we have a target player, don't preform a line of sight test
|
|
if(%basedist > $AI_GUARD_LOS_BYPASS)
|
|
{
|
|
//Line of sight test for the position the bot wants to sidestep to
|
|
%eyeTrans = %obj.getEyeTransform();
|
|
%eyeEnd = %newLoc;
|
|
%searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::PlayerObjectType | $TypeMasks::StaticTSObjectType |
|
|
$TypeMasks::TerrainObjectType | $TypeMasks::ItemObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::StaticObjectType, %obj);
|
|
%foundObject = getword(%searchResult,0);
|
|
|
|
if (%foundObject > 0)
|
|
{
|
|
%this.sidestep(%obj);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//There is no target
|
|
else
|
|
{
|
|
//Line of sight test for the position the bot wants to sidstep to
|
|
%eyeTrans = %obj.getEyeTransform();
|
|
%eyeEnd = %newLoc;
|
|
%searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::PlayerObjectType | $TypeMasks::StaticTSObjectType |
|
|
$TypeMasks::TerrainObjectType | $TypeMasks::ItemObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::StaticObjectType, %obj);
|
|
%foundObject = getword(%searchResult,0);
|
|
|
|
if (%foundObject > 0)
|
|
{
|
|
%this.sidestep(%obj);
|
|
return;
|
|
}
|
|
//Set the bot to look in the direction that it is moving.
|
|
else
|
|
{
|
|
%obj.setaimlocation(%newloc);
|
|
}
|
|
}
|
|
//Set the bot to move towards the new position.
|
|
%obj.setMoveDestination(%newLoc);
|
|
}
|
|
|
|
function AIPlayer::CheckLOStoItem(%this, %obj, %item)
|
|
{
|
|
%basedist = vectorDist(%obj.getposition(), %item.getposition());
|
|
//If the target item is very close, don't preform a line of sight test
|
|
if(%basedist > $AI_GUARD_LOS_BYPASS)
|
|
{
|
|
%eyeTrans = %obj.getEyeTransform();
|
|
//%eyeEnd = %item.getposition();
|
|
%eyeEnd = %item.getWorldBoxCenter();
|
|
%searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::TerrainObjectType |
|
|
$TypeMasks::InteriorObjectType | $TypeMasks::ItemObjectType | $TypeMasks::PlayerObjectType |
|
|
$TypeMasks::StaticTSObjectType | $TypeMasks::StaticObjectType , %obj);
|
|
%foundObject = getword(%searchResult,0);
|
|
|
|
if(%foundObject == %item)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//This is another function taken from code found on garagegames.
|
|
//It checks to see if there are any static objects blocking the view
|
|
//from the AIPlayer to the target.
|
|
function AIPlayer::CheckLOS(%this, %obj, %tgt)
|
|
{
|
|
%eyeTrans = %obj.getEyeTransform();
|
|
%eyeEnd = %tgt.player.getEyeTransform();
|
|
%searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::PlayerObjectType | $TypeMasks::StaticTSObjectType |
|
|
$TypeMasks::TerrainObjectType | $TypeMasks::ItemObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::StaticObjectType, %obj);
|
|
%foundObject = getword(%searchResult,0);
|
|
|
|
if (%foundObject > 0)
|
|
{
|
|
if(%foundObject.getType() & $TypeMasks::PlayerObjectType)
|
|
{
|
|
//Get the target's location and set it as the bot's return point
|
|
%obj.returningPos = %tgt.player.getposition();
|
|
%obj.returningTrans = %tgt.player.gettransform();
|
|
%obj.lostest = 1;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//If the bot just lost sight of the player, get the player's position a short time after that
|
|
if(%obj.lostest == 1)
|
|
{
|
|
%obj.lostest = 0;
|
|
%this.lostrigger = %this.schedule($AI_GUARD_LOS_TIME,"getnewguardposition", %obj, %tgt);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Get the player's position a short time after sight is lost
|
|
function AIPlayer::GetNewGuardPosition(%this, %obj, %tgt)
|
|
{
|
|
%obj.returningPos = %tgt.player.getposition();
|
|
%obj.returningTrans = %tgt.player.gettransform();
|
|
}
|
|
|
|
function AIPlayer::GetClosestHumanInSightandRange(%this, %obj)
|
|
{
|
|
%dist=0;
|
|
%index = -1; //sets the initial index value to -1 The index is the id number of the nearest
|
|
//human target found
|
|
%botpos = %this.getposition(); //The bots current position
|
|
%count = ClientGroup.getCount(); //The number of clients to check
|
|
|
|
//The for-next loop cycles through all of the valid clients
|
|
for(%i=0; %i < %count; %i++)
|
|
{
|
|
%client = ClientGroup.getobject(%i); //Get the client info for the client at index %i
|
|
|
|
//If the client is invalid then the function bails out returning a -1 value, for no target found.
|
|
if (%client.player !$= "" && %client.player > 0)
|
|
{
|
|
//The following line just changes the %client to %tgt to make it easier to follow the code below
|
|
%tgt = %client;
|
|
|
|
%playpos = %client.player.getposition(); //Assigns the player position to a variable
|
|
|
|
%tempdist = vectorDist(%playpos, %botpos); //Determine the distance from the bot to the target
|
|
|
|
//The first test we perform is to see if the target is within the bots range
|
|
//Is target in range? If not bail out of checking to see if its in view.
|
|
if (%tempdist <= %obj.detdis)
|
|
{
|
|
|
|
//Lower attentionlevel to increase response time...
|
|
%this.attentionlevel--;
|
|
|
|
//Prevent the attention level from dropping below 1
|
|
if(%this.attentionlevel < 1) %this.attentionlevel = 1;
|
|
|
|
//The second check is to see if the target is within the FOV of the bot.
|
|
//Is the target within the fov field of vision of the bot?
|
|
if(%this.Istargetinview(%obj, %tgt, %obj.fov))
|
|
{
|
|
|
|
//Lower attentionlevel to increase response time...
|
|
%this.attentionlevel--;
|
|
|
|
//Prevent the attention level from dropping below 1
|
|
if(%this.attentionlevel < 1) %this.attentionlevel = 1;
|
|
|
|
//The third check we run is to see if there is anything blocking the
|
|
//target from the bot.
|
|
if(%this.CheckLOS(%obj, %tgt))
|
|
{
|
|
//We lower the bots attention level again, to further increase it's
|
|
//response time, this effectively means that the bot will respnd faster to
|
|
//objects that are both in range and in plain sight.
|
|
%this.attentionlevel--;
|
|
|
|
//Prevent the attention level from dropping below 1
|
|
if(%this.attentionlevel < 1) %this.attentionlevel = 1;
|
|
|
|
//If there is a current target, then check the distance to the new target as
|
|
//compared to the current set target. If the new target is closest, then set
|
|
//the index and tempdistance to the new target.
|
|
if(%tempdist < %dist || %dist == 0)
|
|
{
|
|
%dist = %tempdist;
|
|
%index = %i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//If there are no targets in view, then the bots attention will slowly lapse and increase
|
|
//This will slow down how fast the bot thinks and how often it checks for threats.
|
|
%this.attentionlevel = %this.attentionlevel + 0.5;
|
|
if(%this.attentionlevel > $AI_GUARD_MAX_ATTENTION) %this.attentionlevel = $AI_GUARD_MAX_ATTENTION;
|
|
}
|
|
}
|
|
|
|
return %index;
|
|
}
|
|
|
|
function AIPlayer::GetClosestItemInSightandRange(%this, %obj, %itemname)
|
|
{
|
|
%dist=0;
|
|
%index = -1;
|
|
%botpos = %this.getposition();
|
|
InitContainerRadiusSearch(%botpos, $AI_GUARD_DETECT_ITEM_RANGE, $TypeMasks::ItemObjectType);
|
|
while ((%item = containerSearchNext()) != 0)
|
|
{
|
|
if (%item.getDataBlock().getName() $= %itemname)
|
|
{
|
|
%itempos = %item.getposition();
|
|
%tempdist = vectorDist(%itempos, %botpos);
|
|
|
|
if(%this.IsIteminview(%obj, %item, %obj.fov))
|
|
{
|
|
if(%this.CheckLOStoItem(%obj, %item))
|
|
{
|
|
if(%tempdist < %dist || %dist == 0)
|
|
{
|
|
%dist = %tempdist;
|
|
%index = %item;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return %index;
|
|
}
|
|
|
|
//This function checks to see if the target supplied is within the bots FOV
|
|
function AIPlayer::IsItemInView(%this, %obj, %item, %fov)
|
|
{
|
|
%ang = %this.check2dangletoitem(%obj, %item);
|
|
%visleft = 360 - (%fov/2);
|
|
%visright = %fov/2;
|
|
if (%ang > %visleft || %ang < %visright)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//This function checks to see if the target supplied is within the bots FOV
|
|
function AIPlayer::IsTargetInView(%this, %obj, %tgt, %fov)
|
|
{
|
|
%ang = %this.check2dangletotarget(%obj, %tgt);
|
|
%visleft = 360 - (%fov/2);
|
|
%visright = %fov/2;
|
|
if (%ang > %visleft || %ang < %visright)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if the location the bot is moving to is in sight.
|
|
//And if it's not, move somwhere that is in sight (if there's a better place).
|
|
function AIPlayer::dontMoveAlongTheWall(%this, %obj)
|
|
{
|
|
//Save the original destination to another variable for later use
|
|
%this.moveDestinationB = %this.moveDestinationA;
|
|
|
|
if (%this.checkMovementLos(%obj))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//Word(0) of %this.moveDestinationB (which is the x value) is set to equal the value of %this.moveDestinationA's Word(0).
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 0, (getWord(%this.moveDestinationA, 0)));
|
|
//Word(1) of %this.moveDestinationB (which is the y value) is set to equal the value of %this.getposition()'s Word(1).
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 1, (getWord(%this.getposition(), 1)));
|
|
|
|
if (%this.checkMovementLos(%obj))
|
|
{
|
|
//Add AI_GUARD_CORNERING's value to the destination's value
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 0, (getWord(%this.moveDestinationA, 0) + $AI_GUARD_CORNERING));
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 1, (getWord(%this.getposition(), 1) + $AI_GUARD_CORNERING));
|
|
|
|
if (%this.checkMovementLos(%obj))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//Or else subtracts AI_GUARD_CORNERING's value from the destination's value
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 0, (getWord(%this.moveDestinationA, 0) - $AI_GUARD_CORNERING));
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 1, (getWord(%this.getposition(), 1) - $AI_GUARD_CORNERING));
|
|
%obj.setmovedestination(%this.moveDestinationB);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Word(0) of %this.moveDestinationB (which is the x value) is set to equal the value of %this.getposition()'s Word(0).
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 0, (getWord(%this.getposition(), 0)));
|
|
//Word(1) of %this.moveDestinationB (which is the y value) is set to equal the value of %this.moveDestinationA's Word(1).
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 1, (getWord(%this.moveDestinationA, 1)));
|
|
|
|
if (%this.checkMovementLos(%obj))
|
|
{
|
|
//Add AI_GUARD_CORNERING's value to the destination's value
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 0, (getWord(%this.getposition(), 0) + $AI_GUARD_CORNERING));
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 1, (getWord(%this.moveDestinationA, 1) + $AI_GUARD_CORNERING));
|
|
|
|
if (%this.checkMovementLos(%obj))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//Or else subtracts AI_GUARD_CORNERING's value from the destination's value
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 0, (getWord(%this.getposition(), 0) - $AI_GUARD_CORNERING));
|
|
%this.moveDestinationB = setWord(%this.moveDestinationB, 1, (getWord(%this.moveDestinationA, 1) - $AI_GUARD_CORNERING));
|
|
%obj.setmovedestination(%this.moveDestinationB);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
%obj.setmovedestination(%this.moveDestinationA);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Line of sight test for the position the bot wants to move to
|
|
function AIPlayer::checkMovementLos(%this, %obj)
|
|
{
|
|
%eyeTrans = %obj.getEyeTransform();
|
|
%eyeEnd = %this.moveDestinationB;
|
|
%searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::StaticTSObjectType | $TypeMasks::TerrainObjectType |
|
|
$TypeMasks::ItemObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::StaticObjectType, %obj);
|
|
%foundObject = getword(%searchResult,0);
|
|
|
|
if (%foundObject == 0)
|
|
{
|
|
//Check to make sure the bot isn't already extremly close to its dstination
|
|
%basedist = vectorDist(%obj.getposition(), %this.moveDestinationB);
|
|
|
|
if (%basedist > 0.5)
|
|
{
|
|
%obj.setmovedestination(%this.moveDestinationB);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Gets the closest player to object. Used by the trigger to determine if a player triggered it.
|
|
function AIPlayer::GetClosestPlayer(%this, %obj)
|
|
{
|
|
%dist=0;
|
|
%index = -1; //sets the initial index value to -1 The index is the id number of the nearest
|
|
//human target found
|
|
%botpos = %this.getposition(); //The bots current position
|
|
%count = ClientGroup.getCount(); //The number of clients to check
|
|
|
|
//The for-next loop cycles through all of the valid clients
|
|
for(%i=0; %i < %count; %i++)
|
|
{
|
|
%client = ClientGroup.getobject(%i); //Get the client info for the client at index %i
|
|
|
|
//If the client is invalid then the function bails out returning a -1 value, for no
|
|
//target found.
|
|
if (%client.player !$= "" && %client.player > 0)
|
|
{
|
|
%index = %client.player;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Pathed AI Functions
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//Start the bot following a path
|
|
function AIPlayer::followPath(%this, %path, %node, %obj)
|
|
{
|
|
//Check if the bot is pathed
|
|
if (!isObject(%path))
|
|
{
|
|
%this.path = "";
|
|
return;
|
|
}
|
|
|
|
%dist = 0;
|
|
%tempdist = 0;
|
|
%index = -1;
|
|
%botpos = %this.getposition();
|
|
%count = %path.getCount();
|
|
//Cycle through all nodes on this path and set the closest node as the bot's current location
|
|
while ((%node = %count) != 0)
|
|
{
|
|
%nodepos = %this.path.getObject(%count - 1).getposition();
|
|
%tempdist = vectorDist(%nodepos, %botpos);
|
|
|
|
if(%tempdist < %dist || %dist == 0)
|
|
{
|
|
%dist = %tempdist;
|
|
%index = %node;
|
|
}
|
|
%count--;
|
|
}
|
|
%index = %index - 1;
|
|
%this.moveToNode(%index);
|
|
|
|
if (%index > %path.getCount() - 1)
|
|
{
|
|
%this.targetNode = %path.getCount() - 1;
|
|
}
|
|
else
|
|
{
|
|
%this.targetNode = %index;
|
|
}
|
|
}
|
|
|
|
function AIPlayer::moveToNextNode(%this, %obj)
|
|
{
|
|
//See if the bot just sidesteped
|
|
if (%this.returningPath == 2)
|
|
{
|
|
//Set returningPath back to 1 for other functions
|
|
%this.returningPath = 1;
|
|
%this.moveToNode(%this.currentNode);
|
|
return;
|
|
}
|
|
|
|
//See where the bot is and where it should be going
|
|
if (%this.targetNode < 0 || %this.currentNode < %this.targetNode)
|
|
{
|
|
if (%this.currentNode < %this.path.getCount() - 1)
|
|
{
|
|
%this.moveToNode(%this.currentNode + 1);
|
|
}
|
|
else
|
|
{
|
|
%this.moveToNode(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (%this.currentNode == 0)
|
|
{
|
|
%this.moveToNode(%this.path.getCount() - 1);
|
|
}
|
|
else
|
|
{
|
|
%this.moveToNode(%this.currentNode - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function AIPlayer::moveToNode(%this, %index, %obj)
|
|
{
|
|
//Move to the given path node index
|
|
%this.currentNode = %index;
|
|
%node = %this.path.getObject(%index);
|
|
%this.setMoveDestination(%node.getTransform());
|
|
%this.targetNode = %this.currentNode + 1;
|
|
|
|
//Make the bot face the node it's moving to
|
|
%this.setAimLocation(%this.path.getObject(%this.currentNode).getposition());
|
|
}
|