$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 AIPlayer Marker is a simple shape that is placed in the map during design time. //The shape is replaced with an AIPlayer 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 AIPlayer markers will //not be saved and your markers will be lost. datablock ItemData(AIPlayerMarker) { //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(DemoPlayer : PlayerBody) { shootingDelay = 100; }; function DemoPlayer::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 DemoPlayer::onEndOfPath(%this,%obj,%path) { %obj.nextTask(); } function DemoPlayer::onEndSequence(%this,%obj,%slot) { echo("Sequence Done!"); %obj.stopThread(%slot); %obj.nextTask(); } function DemoPlayer::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 // AIPlayer::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 AIPlayerMarkers //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 AIPlayer::LoadEntities() { //checks to see if the entities are to be processed. if ($AI_PLAYER_ENABLED == true) { echo("Loading AIPlayer entities..."); %position = "0 0 0"; %radius = 100000.0; InitContainerRadiusSearch(%position, %radius, $TypeMasks::ItemObjectType); %i=0; while ((%targetObject = containerSearchNext()) != 0) { if (%targetobject.getDataBlock().getName() $= "AIPlayerMarker") { %i++; %player = AIPlayer::spawnAtMarker("Kork" @ %i, %targetobject); %targetobject.sethidden(true); } } } else { echo("Patrol entities disabled..."); } } //OpenFire - This function is called when the AIPlayer has located a target. function AIPlayer::openfire(%this, %obj, %tgt) { //If the AIPlayer is dead, or the target is dead, then the AIPlayer //will not shoot, and the AIPlayer's target will be cleared. if (%obj.getState() $= "Dead" || %tgt.player.getstate() $="Dead") { %firing = false; %obj.clearaim(); } else { //If the AIPlayer was not firing, it will check the current distance to the target, //if the target is within the AIPlayer's ignore distance it will shoot, otherwise //the AIPlayer 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 AIPlayer stocked with ammo all the time. //If you want the AIPlayer to run out of ammunition then remove this line. %obj.incinventory("Crossbowammo",100); //This sets the AIPlayers target to shoot at. It uses a VectorAdd to //help correct for the problem of the AIPlayer shooting at the targets //feet. //I would recommend using a non-ballistic weapon for your AIPlayers //otherwise you may have to do some additional math to calculate a good //ballistics drop based on the AIplayers range to the target. %obj.setAimLocation(VectorAdd(%tgt.player.getPosition(),"0 0 2.0")); //This actually starts the AIPlayer 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 AIPlayer to hold down the trigger %this.trigger = %this.schedule($AI_PLAYER_TRIGGER_DOWN,"ceasefire", %obj); } //CeaseFire - this makes the AIPlayer lift off the trigger function AIPlayer::ceasefire(%this, %obj) { //Makes the AIPlayer lift off the trigger %obj.setImageTrigger(0,false); //This schedule is created to implement a delay between the AIPlayer's //shots. This can be used to help set how aggressive the bot is. %this.trigger = %this.schedule($AI_PLAYER_FIREDELAY,"delayfire", %obj); } function AIPlayer::delayfire(%this, %obj) { //This clears the AIPlayer's 'firing' flag. //This allows the player to shoot again the next time it //sees and selects a target within range. %firing = false; } //----------------------------------------------------------------------------- // AIPlayer static functions //----------------------------------------------------------------------------- function AIPlayer::spawn(%name, %obj) { // Create the demo player object %player = new AIPlayer() { dataBlock = DemoPlayer; //This value is set to true for AIPlayers, 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 AIPlayers 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 AIPlayers 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 AIPlayer function AIPlayer::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 AIPlayer::EquipBot(%this, %obj) { %obj.mountImage(CrossBowImage,0); %obj.setInventory(CrossbowAmmo,1000); } // EnhanceFOV- This function sets the FOV for the AIPlayer to 360 degrees //This function is called from player.cs when the AIPlayer takes damage. //This has the same effect as having the AIPlayer 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 AIPlayer::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 AIPlayer's FOV back to the default value for the bot function AIPlayer::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 AIPlayer::respawn(%this, %name, %obj) { // Create the demo player object %player = new AIPlayer() { 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 AIPlayer::spawnAtMarker(%name,%obj) { // Spawn a player and place him on the first node of the path if (!isObject(%obj)) return; %player = AIPlayer::spawn(%name, %obj); return %player; } //----------------------------------------------------------------------------- // AIPlayer methods //----------------------------------------------------------------------------- function AIPlayer::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 AIPlayer::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 AIPlayer::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 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::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 AIPlayer::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); %this.setMoveDestination(%obj, %tgt); } } else { //Since there is no valid target, then make sure the AIPlayer 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 AIPlayer'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 AIPlayer to the target. function AIPlayer::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 AIPlayer::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 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; } } //----------------------------------------------------------------------------- function AIPlayer::wait(%this,%time) { %this.schedule(%time * 1000,"nextTask"); } function AIPlayer::done(%this,%time) { %this.schedule(0,"delete"); } function AIPlayer::fire(%this,%bool) { if (%bool) { cancel(%this.trigger); %this.singleShot(); } else cancel(%this.trigger); %this.nextTask(); } function AIPlayer::animate(%this,%seq) { //%this.stopThread(0); //%this.playThread(0,%seq); %this.setActionThread(%seq); }