Torque3D/Templates/Modules/AI_Guard/Scripts/aiPlayer.cs
Areloch f1777016b8 GFX card profile config file logging moved to debug only
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
2019-06-17 02:30:45 -05:00

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());
}