827 lines
25 KiB
C#
827 lines
25 KiB
C#
$AI_PLAYER_ENABLED = true; //Are bots loaded when markers found on the map
|
|
$AI_PLAYER_FIREDELAY = 500; //Delay from end of one firing pulse to begin of next shot.
|
|
$AI_PLAYER_ENHANCED_FOV_TIME =3000; //Length of time bot is given 360deg vision when attacked.
|
|
$AI_PLAYER_FOV =160; //The bots field of vision 160 means the bot sees -80 to +80 degrees
|
|
//from it's eye vector.
|
|
$AI_PLAYER_DETECT_DISTANCE =200; //Sets how close a target has to be before a bot will start reacting
|
|
//to its presence.
|
|
$AI_PLAYER_IGNORE_DISTANCE = 150; //A bot will ignore and not shoot at a target beyond this distance.
|
|
$AI_PLAYER_SCANTIME =500; //This is the minimum time in milliseconds between scans for threats.
|
|
$AI_PLAYER_CREATION_DELAY =5000; //This delay controls how long a bot will wait to start scanning for
|
|
//threats upon creation.
|
|
$AI_PLAYER_TRIGGER_DOWN = 100; //Sets how long the bot holds down the trigger when firing. For single
|
|
//shot weapons a low value works fine, for continuous fire weapons the
|
|
//larger the number, the larget the burst of fire will last.
|
|
$AI_PLAYER_DEFAULTRESPAWN = true; //Sets whether bots respawn or not as a deault - can be overrode by setting
|
|
//a dynamic variable called respawn in the map editor on a per bot basis.
|
|
$AI_PLAYER_RESPAWN_DELAY = 20000; //Sets the delay in ms that a bot will wait before respawning.
|
|
$AI_PLAYER_MAX_ATTENTION = 10; //This number and $AI_PLAYER_SCANTIME are multiplied to set the delay in the
|
|
//thinking loop. Used to free up processor time on bots out of the mix.
|
|
//My default values of 500ms min and 10 for an attention level means that a bot will
|
|
//scan no more often than 500ms and no less than 5 seconds.
|
|
|
|
|
|
|
|
|
|
//The Orc Marker is a simple shape that is placed in the map during design time.
|
|
//The shape is replaced with an Orc at the shapes position, and the shape is hidden.
|
|
//DO NOT set the shape to be removed upon level loading. If you do so, when you go in to
|
|
//edit the map the shapes will have been deleted, and if you save the file your Orc markers will
|
|
//not be saved and your markers will be lost.
|
|
exec("~/data/shapes/Orc/Orc.cs");
|
|
datablock PlayerData(OrcBody)
|
|
{
|
|
renderFirstPerson = false;
|
|
emap = true;
|
|
|
|
className = Armor;
|
|
shapeFile = "~/data/shapes/Orc/Orc.dts";
|
|
cameraMaxDist = 3;
|
|
computeCRC = true;
|
|
|
|
canObserve = true;
|
|
cmdCategory = "Clients";
|
|
|
|
cameraDefaultFov = 90.0;
|
|
cameraMinFov = 5.0;
|
|
cameraMaxFov = 120.0;
|
|
|
|
debrisShapeName = "~/data/shapes/Orc/debris_Orc.dts";
|
|
debris = playerDebris;
|
|
|
|
aiAvoidThis = true;
|
|
|
|
minLookAngle = -1.4;
|
|
maxLookAngle = 1.4;
|
|
maxFreelookAngle = 3.0;
|
|
|
|
mass = 90;
|
|
drag = 0.3;
|
|
maxdrag = 0.4;
|
|
density = 10;
|
|
maxDamage = 100;
|
|
maxEnergy = 60;
|
|
repairRate = 0.33;
|
|
energyPerDamagePoint = 75.0;
|
|
|
|
rechargeRate = 0.256;
|
|
|
|
runForce = 48 * 90;
|
|
runEnergyDrain = 0;
|
|
minRunEnergy = 0;
|
|
maxForwardSpeed = 14;
|
|
maxBackwardSpeed = 13;
|
|
maxSideSpeed = 13;
|
|
|
|
maxUnderwaterForwardSpeed = 7.4;
|
|
maxUnderwaterBackwardSpeed = 6.8;
|
|
maxUnderwaterSideSpeed = 6.8;
|
|
|
|
jumpForce = 8.3 * 90;
|
|
jumpEnergyDrain = 0;
|
|
minJumpEnergy = 0;
|
|
jumpDelay = 15;
|
|
|
|
recoverDelay = 9;
|
|
recoverRunForceScale = 1.2;
|
|
|
|
minImpactSpeed = 45;
|
|
speedDamageScale = 0.4;
|
|
|
|
boundingBox = "1.2 1.2 2.3";
|
|
pickupRadius = 0.75;
|
|
|
|
// Damage location details
|
|
boxNormalHeadPercentage = 0.83;
|
|
boxNormalTorsoPercentage = 0.49;
|
|
boxHeadLeftPercentage = 0;
|
|
boxHeadRightPercentage = 1;
|
|
boxHeadBackPercentage = 0;
|
|
boxHeadFrontPercentage = 1;
|
|
|
|
// Foot Prints
|
|
decalData = PlayerFootprint;
|
|
decalOffset = 0.25;
|
|
|
|
footPuffEmitter = LightPuffEmitter;
|
|
footPuffNumParts = 10;
|
|
footPuffRadius = 0.25;
|
|
|
|
dustEmitter = LiftoffDustEmitter;
|
|
|
|
splash = PlayerSplash;
|
|
splashVelocity = 4.0;
|
|
splashAngle = 67.0;
|
|
splashFreqMod = 300.0;
|
|
splashVelEpsilon = 0.60;
|
|
bubbleEmitTime = 0.4;
|
|
splashEmitter[0] = PlayerFoamDropletsEmitter;
|
|
splashEmitter[1] = PlayerFoamEmitter;
|
|
splashEmitter[2] = PlayerBubbleEmitter;
|
|
mediumSplashSoundVelocity = 10.0;
|
|
hardSplashSoundVelocity = 20.0;
|
|
exitSplashSoundVelocity = 5.0;
|
|
|
|
// Controls over slope of runnable/jumpable surfaces
|
|
runSurfaceAngle = 70;
|
|
jumpSurfaceAngle = 80;
|
|
|
|
minJumpSpeed = 20;
|
|
maxJumpSpeed = 30;
|
|
|
|
horizMaxSpeed = 68;
|
|
horizResistSpeed = 33;
|
|
horizResistFactor = 0.35;
|
|
|
|
upMaxSpeed = 80;
|
|
upResistSpeed = 25;
|
|
upResistFactor = 0.3;
|
|
|
|
footstepSplashHeight = 0.35;
|
|
|
|
//NOTE: some sounds commented out until wav's are available
|
|
|
|
// Footstep Sounds
|
|
FootSoftSound = FootLightSoftSound;
|
|
FootHardSound = FootLightHardSound;
|
|
FootMetalSound = FootLightMetalSound;
|
|
FootSnowSound = FootLightSnowSound;
|
|
FootShallowSound = FootLightShallowSplashSound;
|
|
FootWadingSound = FootLightWadingSound;
|
|
FootUnderwaterSound = FootLightUnderwaterSound;
|
|
|
|
//FootBubblesSound = FootLightBubblesSound;
|
|
//movingBubblesSound = ArmorMoveBubblesSound;
|
|
//waterBreathSound = WaterBreathMaleSound;
|
|
|
|
//impactSoftSound = ImpactLightSoftSound;
|
|
//impactHardSound = ImpactLightHardSound;
|
|
//impactMetalSound = ImpactLightMetalSound;
|
|
//impactSnowSound = ImpactLightSnowSound;
|
|
|
|
//impactWaterEasy = ImpactLightWaterEasySound;
|
|
//impactWaterMedium = ImpactLightWaterMediumSound;
|
|
//impactWaterHard = ImpactLightWaterHardSound;
|
|
|
|
groundImpactMinSpeed = 10.0;
|
|
groundImpactShakeFreq = "4.0 4.0 4.0";
|
|
groundImpactShakeAmp = "1.0 1.0 1.0";
|
|
groundImpactShakeDuration = 0.8;
|
|
groundImpactShakeFalloff = 10.0;
|
|
|
|
//exitingWater = ExitingWaterLightSound;
|
|
|
|
observeParameters = "0.5 4.5 4.5";
|
|
|
|
// Allowable Inventory Items
|
|
maxInv[BulletAmmo] = 20;
|
|
maxInv[HealthKit] = 1;
|
|
maxInv[RifleAmmo] = 100;
|
|
maxInv[CrossbowAmmo] = 50;
|
|
maxInv[Crossbow] = 1;
|
|
maxInv[Rifle] = 1;
|
|
};
|
|
|
|
datablock ItemData(OrcMarker)
|
|
{
|
|
//This shape will be listed under the Shapes in the mission editor
|
|
category = "AIMarker";
|
|
|
|
// Basic Item properties
|
|
//Basic shape file to use as a marker in the editor.
|
|
shapeFile = "~/data/shapes/markers/octahedron.dts";
|
|
mass = 1;
|
|
friction = 1;
|
|
elasticity = 0.3;
|
|
|
|
};
|
|
|
|
datablock PlayerData(Orc : OrcBody)
|
|
{
|
|
shootingDelay = 100;
|
|
};
|
|
|
|
|
|
function Orc::onReachDestination(%this,%obj)
|
|
{
|
|
// Moves to the next node on the path.
|
|
// Override for all player. Normally we'd override this for only
|
|
// a specific player datablock or class of players.
|
|
if (%obj.path !$= "") {
|
|
if (%obj.currentNode == %obj.targetNode)
|
|
%this.onEndOfPath(%obj,%obj.path);
|
|
else
|
|
%obj.moveToNextNode();
|
|
}
|
|
}
|
|
|
|
function Orc::onEndOfPath(%this,%obj,%path)
|
|
{
|
|
%obj.nextTask();
|
|
}
|
|
|
|
function Orc::onEndSequence(%this,%obj,%slot)
|
|
{
|
|
echo("Sequence Done!");
|
|
%obj.stopThread(%slot);
|
|
%obj.nextTask();
|
|
}
|
|
|
|
|
|
function Orc::OnDamage(%this, %obj, %delta)
|
|
{
|
|
%obj.attentionlevel=1;
|
|
%obj.enhancefov(%obj);
|
|
|
|
if(%obj.getState() $= "Dead" && %obj.respawn == true)
|
|
{
|
|
%obj.delaybeforerespawn(%obj.botname, %obj.marker);
|
|
%this.player=0;
|
|
}
|
|
}
|
|
|
|
|
|
//The load entities function is called from game.cs
|
|
//The call takes the form of
|
|
// Orc::LoadEntities();
|
|
//It is placed in the game.cs file right after the part that reads
|
|
// Start the game timer
|
|
// if ($Game::Duration)
|
|
// $Game::Schedule = schedule($Game::Duration * 1000, 0, "onGameDurationEnd" );
|
|
// $Game::Running = true;
|
|
//The call uses a simple brute force method of finding all OrcMarkers
|
|
//within the given radius of the center of the map. It then inserts an AI player at the
|
|
//position of each marker, and then hides the marker from view.
|
|
function Orc::LoadEntities()
|
|
{
|
|
//checks to see if the entities are to be processed.
|
|
if ($AI_PLAYER_ENABLED == true)
|
|
{
|
|
echo("Loading Orc entities...");
|
|
%position = "0 0 0";
|
|
%radius = 100000.0;
|
|
InitContainerRadiusSearch(%position, %radius, $TypeMasks::ItemObjectType);
|
|
%i=0;
|
|
while ((%targetObject = containerSearchNext()) != 0)
|
|
{
|
|
if (%targetobject.getDataBlock().getName() $= "OrcMarker")
|
|
{
|
|
%i++;
|
|
%player = Orc::spawnAtMarker("" @ %i, %targetobject);
|
|
%targetobject.sethidden(true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
echo("Patrol entities disabled...");
|
|
}
|
|
}
|
|
|
|
|
|
//OpenFire - This function is called when the Orc has located a target.
|
|
|
|
function Orc::openfire(%this, %obj, %tgt)
|
|
{
|
|
//If the Orc is dead, or the target is dead, then the Orc
|
|
//will not shoot, and the Orc's target will be cleared.
|
|
if (%obj.getState() $= "Dead" || %tgt.player.getstate() $="Dead")
|
|
{
|
|
%firing = false;
|
|
%obj.clearaim();
|
|
}
|
|
else
|
|
{
|
|
//If the Orc was not firing, it will check the current distance to the target,
|
|
//if the target is within the Orc's ignore distance it will shoot, otherwise
|
|
//the Orc will not shoot.
|
|
if(!%firing)
|
|
{
|
|
%rtt=vectorDist(%obj.getposition(), %tgt.player.getposition());
|
|
|
|
if(%rtt < $AI_PLAYER_IGNORE_DISTANCE)
|
|
{
|
|
%firing = true;
|
|
//The following lines is added to keep the Orc stocked with ammo all the time.
|
|
//If you want the Orc to run out of ammunition then remove this line.
|
|
%obj.incinventory("Crossbowammo",100);
|
|
|
|
//This sets the Orcs target to shoot at. It uses a VectorAdd to
|
|
//help correct for the problem of the Orc shooting at the targets
|
|
//feet.
|
|
//I would recommend using a non-ballistic weapon for your Orcs
|
|
//otherwise you may have to do some additional math to calculate a good
|
|
//ballistics drop based on the Orcs range to the target.
|
|
%obj.setAimLocation(VectorAdd(%tgt.player.getPosition(),"0 0 2.0"));
|
|
//This actually starts the Orc shooting - equivalent to pulling and
|
|
//holding down the trigger.
|
|
%obj.setImageTrigger(0,true);
|
|
}
|
|
else
|
|
{
|
|
%obj.clearaim();
|
|
}
|
|
}
|
|
}
|
|
//This schedule is set to call the ceasefire method at the end of the desired time
|
|
//set for the Orc to hold down the trigger
|
|
%this.trigger = %this.schedule($AI_PLAYER_TRIGGER_DOWN,"ceasefire", %obj);
|
|
}
|
|
|
|
//CeaseFire - this makes the Orc lift off the trigger
|
|
function Orc::ceasefire(%this, %obj)
|
|
{
|
|
//Makes the Orc lift off the trigger
|
|
%obj.setImageTrigger(0,false);
|
|
//This schedule is created to implement a delay between the Orc's
|
|
//shots. This can be used to help set how aggressive the bot is.
|
|
%this.trigger = %this.schedule($AI_PLAYER_FIREDELAY,"delayfire", %obj);
|
|
}
|
|
|
|
function Orc::delayfire(%this, %obj)
|
|
{
|
|
//This clears the Orc's 'firing' flag.
|
|
//This allows the player to shoot again the next time it
|
|
//sees and selects a target within range.
|
|
%firing = false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Orc static functions
|
|
//-----------------------------------------------------------------------------
|
|
|
|
function Orc::spawn(%name, %obj)
|
|
{
|
|
// Create the demo player object
|
|
%player = new Orc() {
|
|
dataBlock = Orc;
|
|
//This value is set to true for Orcs, it is referenced in player.cs
|
|
//to help handle respawning bots properly.
|
|
isbot=true;
|
|
|
|
//These variables are set for each bot, this is done on a per bot basis
|
|
//because the fov and attention level for each bot will be adjusted during
|
|
//the game.
|
|
//FOV = Bot's field of vision in degrees. (180 deg. means everything in front of the bot)
|
|
//attentionlevel = The bot's attention level is raised or lowered based on how near targets
|
|
//are and whether targets are visible or not.
|
|
fov=$AI_PLAYER_FOV;
|
|
attentionlevel = $AI_PLAYER_MAX_ATTENTION/2;
|
|
|
|
//The following line allows gives access to the markers dynamic variables and it's spawn position.
|
|
marker = %obj;
|
|
|
|
//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;
|
|
botname = %name;
|
|
|
|
};
|
|
MissionCleanup.add(%player);
|
|
|
|
//The following set of if-then statements trap to see if the marker has a dynamic variable
|
|
//called respawn. (Set during map editing.)
|
|
//If it does then the players respawn flag is set accordingly true or false
|
|
//as stated by the variable.
|
|
//If there is no respawn variable set, then the flag is set to the default global value
|
|
|
|
if (%obj.respawn $= "" )
|
|
{
|
|
%player.respawn=$AI_PLAYER_DEFAULTRESPAWN;
|
|
}
|
|
else
|
|
{
|
|
if (%obj.respawn == true)
|
|
%player.respawn=true;
|
|
else
|
|
%player.respawn=false;
|
|
}
|
|
|
|
//Calls equipbot - to set the Orcs initial inventory
|
|
%player.EquipBot(%player);
|
|
|
|
// %player.setShapeName(%name);
|
|
%player.setTransform(%obj.gettransform());
|
|
%player.followPath(%obj.pathname,-1);
|
|
//This schedule is set start the bots thinking cycle.
|
|
//The creation delay is used so that the Orcs can be set to not start
|
|
//thinking for a long delay while the rest of the game loads and initializes.
|
|
%player.schedule($AI_PLAYER_CREATION_DELAY,"doscan", %player);
|
|
|
|
return %player;
|
|
}
|
|
|
|
|
|
//The delay before respawn function is set to wait a specified duration before
|
|
//respawning an Orc
|
|
function Orc::DelayBeforeRespawn(%this, %name, %obj)
|
|
{
|
|
%this.respawntrigger = %this.schedule($AI_PLAYER_RESPAWN_DELAY,"respawn", %name, %obj);
|
|
}
|
|
|
|
//Equipbot - this function is called by spawn and respawn and is used to set the bot's
|
|
//initial inventory.
|
|
function Orc::EquipBot(%this, %obj)
|
|
{
|
|
%obj.mountImage(CrossBowImage,0);
|
|
%obj.setInventory(CrossbowAmmo,1000);
|
|
}
|
|
|
|
// EnhanceFOV- This function sets the FOV for the Orc to 360 degrees
|
|
//This function is called from player.cs when the Orc takes damage.
|
|
//This has the same effect as having the Orc scan all around him when damaged.
|
|
//This reduces the need to perform a lot of extra checks to see which vector the damage has
|
|
//come from, etc...
|
|
function Orc::EnhanceFOV(%this, %obj)
|
|
{
|
|
if (%obj.fov != 360)
|
|
{
|
|
%obj.fov = 360;
|
|
%this.fovtrigger = %this.schedule($AI_PLAYER_ENHANCED_FOV_TIME,"restorefov", %obj);
|
|
}
|
|
}
|
|
|
|
//RestoreFOV - this function resets the Orc's FOV back to the default value for the bot
|
|
function Orc::restoreFOV(%this, %obj)
|
|
{
|
|
%obj.fov = $AI_PLAYER_FOV;
|
|
}
|
|
|
|
|
|
//This function is called when a bot is respawned, it is nearly identical to spawn
|
|
//With the exception of the parameters passed to it.
|
|
function Orc::respawn(%this, %name, %obj)
|
|
{
|
|
// Create the demo player object
|
|
|
|
%player = new Orc() {
|
|
dataBlock = PatrolPlayer;
|
|
isbot=true;
|
|
attentionlevel = $AI_PLAYER_MAX_ATTENTION/2;
|
|
fov=$AI_PLAYER_FOV;
|
|
|
|
marker = %obj;
|
|
path = %obj.pathname;
|
|
|
|
botname = %name;
|
|
};
|
|
MissionCleanup.add(%player);
|
|
%player.setShapeName(%name);
|
|
%player.EquipBot(%player);
|
|
%player.setTransform(%obj.gettransform());
|
|
%player.followPath(%obj.pathname,-1);
|
|
|
|
|
|
if (%obj.respawn $="" )
|
|
{
|
|
%player.respawn=$AI_PLAYER_DEFAULTRESPAWN;
|
|
}
|
|
else
|
|
{
|
|
if (%obj.respawn == true)
|
|
%player.respawn=true;
|
|
else
|
|
%player.respawn=false;
|
|
}
|
|
%player.schedule($AI_PLAYER_CREATION_DELAY,"doscan", %player);
|
|
return %player;
|
|
}
|
|
|
|
function Orc::spawnAtMarker(%name,%obj)
|
|
{
|
|
// Spawn a player and place him on the first node of the path
|
|
if (!isObject(%obj))
|
|
return;
|
|
%player = Orc::spawn(%name, %obj);
|
|
return %player;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Orc methods
|
|
//-----------------------------------------------------------------------------
|
|
|
|
function Orc::followPath(%this,%path,%node)
|
|
{
|
|
// Start the player following a path
|
|
%this.stopThread(0);
|
|
if (!isObject(%path)) {
|
|
%this.path = "";
|
|
return;
|
|
}
|
|
if (%node > %path.getCount() - 1)
|
|
%this.targetNode = %path.getCount() - 1;
|
|
else
|
|
%this.targetNode = %node;
|
|
if (%this.path $= %path)
|
|
%this.moveToNode(%this.currentNode);
|
|
else {
|
|
%this.path = %path;
|
|
%this.moveToNode(0);
|
|
}
|
|
}
|
|
|
|
function Orc::moveToNextNode(%this)
|
|
{
|
|
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 Orc::moveToNode(%this,%index)
|
|
{
|
|
// Move to the given path node index
|
|
%this.currentNode = %index;
|
|
%node = %this.path.getObject(%index);
|
|
%this.setMoveDestination(%node.getTransform(), %index == %this.targetNode);
|
|
}
|
|
|
|
//AITargeting
|
|
|
|
// Return the angle of a vector in relation to world origin
|
|
//This function comes from code taken from several resources on garagegames
|
|
//The only mods to the code I made are in the end of the code to ensure that variable
|
|
//returns the angle in degrees, and that the angle is within 0-360 degrees.
|
|
//I've seen sets of this code with the function returng %degangle rather than
|
|
//%angle - but in this script %angle is needed.
|
|
function Orc::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 Orc::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 DoScan function is the thinking part of the bot.
|
|
//The following code is commented heavily to help explain the logic behind
|
|
//the scripting.
|
|
|
|
function Orc::DoScan(%this, %obj)
|
|
{
|
|
//Cancels the ailoop schedule
|
|
cancel(%this.ailoop);
|
|
//if the %obj object is invalid then exit the DoScan Loop.
|
|
if (!%obj)
|
|
{
|
|
return;
|
|
}
|
|
//By cancelling the ailoop, then checking to see if the bot is valid,
|
|
//This should cancel any errant ailoops left hanging by bots killed or
|
|
//removed from the game.
|
|
|
|
|
|
//The next line calls a function to locate the closest human client that is
|
|
//both within the bot's range, within it's field of vision, and is not hidden by
|
|
//objects in the environment.
|
|
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
|
|
|
|
//Is the %tgtid valid target?
|
|
if(%tgtid >=0)
|
|
{
|
|
//If yes, then get the player object of the client
|
|
%tgt=ClientGroup.getobject(%tgtid);
|
|
|
|
if (%obj.getaimobject() != %tgt)
|
|
{
|
|
//Open fire on the current target
|
|
%this.openfire(%obj, %tgt);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Since there is no valid target, then make sure the Orc is
|
|
//not firing, and clear it's target so it will face in a normal direction,
|
|
//rather than tracking towards the target
|
|
%obj.setImageTrigger(0,false);
|
|
%obj.clearaim();
|
|
|
|
//The attention level is a value from 1 to MAX. The higher the number the
|
|
//less attention the bot is paying. A level of 1 means that the bot scans
|
|
//at it's fastest level.
|
|
//The attention level is adjusted in GetClosestHumanInSightandRange
|
|
|
|
//The next line ensures that the Orc's attention never goes above the default max
|
|
if (%this.attentionlevel == $AI_PLAYER_MAX_ATTENTION)
|
|
%obj.clearaim();
|
|
}
|
|
|
|
//Sets the bot's next "DoScan" call - based on the default scan time adjusted by the
|
|
//bot's attention level.
|
|
%this.ailoop=%this.schedule($AI_PLAYER_SCANTIME * %this.attentionlevel,"DoScan" , %obj);
|
|
}
|
|
|
|
//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 Orc to the target.
|
|
function Orc::CheckLOS(%this, %obj, %tgt)
|
|
{
|
|
%eyeTrans = %obj.getEyeTransform();
|
|
%eyeEnd = %tgt.player.getEyeTransform();
|
|
%searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::PlayerObjectType |
|
|
$TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType, %obj);
|
|
%foundObject = getword(%searchResult,0);
|
|
|
|
if(%foundObject.getType() & $TypeMasks::PlayerObjectType)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
function Orc::GetClosestHumanInSightandRange(%this, %obj)
|
|
{
|
|
|
|
%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)
|
|
return -1;
|
|
|
|
//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 <= $AI_PLAYER_DETECT_DISTANCE)
|
|
{
|
|
//Lower attentionlevel to increase response time...
|
|
%this.attentionlevel--;
|
|
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--;
|
|
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 no current target, then the index to the client/target
|
|
//and distance to the target are set. The distance is saved temporarily to run
|
|
//a comparison against the other clients to determine which is closest.
|
|
if(%i == 0)
|
|
{
|
|
%dist = %tempdist;
|
|
%index = %i;
|
|
}
|
|
else
|
|
{
|
|
//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(%dist > %tempdist)
|
|
{
|
|
%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_PLAYER_MAX_ATTENTION) %this.attentionlevel=$AI_PLAYER_MAX_ATTENTION;
|
|
}
|
|
}
|
|
return %index;
|
|
}
|
|
|
|
//This function checks to see if the target supplied is within the bots FOV
|
|
function Orc::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;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
function Orc::wait(%this,%time)
|
|
{
|
|
%this.schedule(%time * 1000,"nextTask");
|
|
}
|
|
|
|
function Orc::done(%this,%time)
|
|
{
|
|
%this.schedule(0,"delete");
|
|
}
|
|
|
|
function Orc::fire(%this,%bool)
|
|
{
|
|
if (%bool) {
|
|
cancel(%this.trigger);
|
|
%this.singleShot();
|
|
}
|
|
else
|
|
cancel(%this.trigger);
|
|
%this.nextTask();
|
|
}
|
|
|
|
|
|
|
|
function Orc::animate(%this,%seq)
|
|
{
|
|
//%this.stopThread(0);
|
|
//%this.playThread(0,%seq);
|
|
%this.setActionThread(%seq);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|