AI behaviour depending on player actions

From The DarkMod Wiki
Jump to navigationJump to search
 Attention: This approach is outdated. For a newer one see here

I am going to describe the setup of a mission, in which the AI is neutral to the player as default. The AI only gets hostile to the player, if the player is seen performing an illegal action. Such actions include: Being seen in an area where the player isn't supposed to be, stealing loot, or carrying a weapon.

The first thing you have to do is to put all affected AI on one team. Let's just take team 1. So set the spawnarg "team" "1" on all affected AI and make sure non-affected AI are on a different team. Create a target_setrelation entity and set the following spawnarg on it: "rel 1,0" "0". This will cause the AI on team 1 to see the player (team 0) as neutral (0). Target this entity from a worldspawn brush or trigger it via the command

 sys.trigger($nameof_target_setrelation);

included in your main() script function.

Being seen in an area where you aren't supposed to be

The first thing you need to do is to make use of the zone system. This approach is described here. This system allows you to call functions when the player passes from one zone to another. Here we'll make use of the call_on_entry script function. The following has to be applied to all allowed/disallowed zones neighboring each other. On the info_location entities belonging to the zones set the spawnarg "call_on_entry" "functionname", where functionname is:

setNeutralZone on allowed zones setHostileZone on disallowed zones

The functions used here have to have the form void functionname(entity entityname). The entity is the zone the player enters. As we don't need it here, but need the basic behavior for the weapon-based AI-player relation discussed below, they just call other functions with the same names without Zone at the end and no arguments passed. What they do is trigger the target_setrelation entities from the next section named ...

Caught stealing

This makes use of some triggers. You'll need two target_setrelation entities and one trigger_relay entity.

On the first target_setrelation entity set the spawnarg "rel 1,0" "-1". On the second, set "rel 1,0" "0". Let the trigger_relay target the second target_setrelation entity and set the spawnarg "delay" "T" on the trigger_relay, where T specifies the timespan in which the player is detected as hostile after stealing something (see description below).

Now let every item that the player is not allowed to take target both the first target_setrelation and the trigger_relay entity. The next thing you need to do is to set up a hidden objective with the content "If one AI of team 1 goes to alert state 5". In the success script field enter isHostile. This function will then destroy the second target_setrelation entity, so the AI stays hostile to the player.

Note: alert state 5 is the highest alert state and means that the player was seen by an AI.

What does the setup do?

Every time you steal something the AI from team 1 will be set hostile to you for a certain amount of time. After that period they will become neutral to you again, unless they see you within this amount of time. If so, they'll stay hostile. As you can see the amount of time (called T above) has an impact on how difficult it is for the player to get away with disallowed actions. Setting it to a higher value makes it more difficult and vice versa.

Being seen carrying a weapon

You'll need the following script:

 void CheckWeapon()
 {
       do
       {
             if ($player1.getCurrentWeapon() != "atdm:weapon_unarmed") {
                   setHostile_();
                   sys.wait(1);
                   if (!hostile) setNeutral_();       
             } 
             else {
                   sys.wait(1);
             }
       } while(true); 
 }

This function is started from the main method via

 thread CheckWeapon();

The functions setNeutral_ and setHostile_ are almost the same as above. For the differences see below. The script sets the AI to hostile as long as you carry a weapon. Again, being seen by any hostile AI will cause the hostility to be permanent.

Caught lockpicking

If we want the player to be threated as an enemy when caught while lockpicking, we just need to add the following lines of code into the checkWeapon function:

       t=$player1.getFrobbed();
       if(t!=$null) {
         if (t.IsLocked()) {
           classname = $player1.getCurInvItemEntity().getKey("classname");
           if ( classname == snake || classname == triangle) {
             if (t.GetDoorhandle().isRotating()) {
               setHostile_();
               sys.wait(1.0);
               if(!hostile) setNeutral_();
             } //endif rotate
           } //endif lockpick
         }//endif locked
       }//endif null
       else { 
         sys.wait(1.0);
       }

Beeing seen flashing an AI

If an AI is seeing you flashing another AI (but don't get flashed itself), you can be pretty sure he will engage you unless he really dislikes the flashed comrade. To achieve this add the following response via the stim/response menu to every AI. The stim type must be flash and the response is Run script. The script is named flashed and is as follows

 void flashed() {
   setHostile();
   sys.wait(3.0);
   setNetral();
 }

Beeing seen carrying a body

The last thing we want to add is that the AI reacts to the player when carrying an unconscious or dead body. The following script lines will also be added to the checkWeapon method and will make AI hostile if the player frobs an AI.

 if ($player1.heldEntity()!=$null) {
       if ($player1.heldEntity().getKey("spawnclass")=="idAI") {
         setHostile_();
         sys.wait(1.0);
         if(!hostile) setNeutral_();       
       }
 }

This method checks wether the player holds something in his hands and, if so, if it is an AI. The method will not identify the player as hostile when he has shouldered an AI. For this purpose we need to add the following two spawnargs on every AI: "equip_action_script" "setHostile" and "dequip_action_script" "setNeutral".


Summary

Now that we have all the pieces together, let's sum up how to set up the complete behavior. (We are starting from zero here; nothing has been done so far).

We need:

Two target_setrelation entities called makeHostile and makeNeutral

One trigger_relay entity called relay

A script file called mymapname.script, where mymapname is the name of your map.

An invisible objective "If one AI of team 1 get to alert level 5".


On all affected AI set "team" "1". Non-affected AI go to another team.

Also set the spawnargs "equip_action_script" "setHostile" and "dequip_action_script" "setNeutral" on them.

Give them a response to flash that runs the script flashed()

On makeHostile set "rel 1,0" "-1".

On makeNeutral set "rel 1,0" "0".

On relay set "target" "makeNeutral" and "delay" "1".

On the objective put stayHostile in the success script field.

On all objects that are not allowed to be taken by the player set "target" "makeHostile" and "target0" "relay".

Do the same with doors that are not allowed to be opened by the player.

On all info_location entities belonging to zones where the player is not allowed to be, set "call_on_entry" "setHostileZone".

Do the same for all other info_location entities but use the method "setNeutralZone".

Hint: The last two steps can be restricted to zones neighboring zones of the other kind.

On any worldspawn set "target" "makeNeutral" or add the line "sys.trigger($makeNeutral);" at the first line of your main method


In the script file include

 float hostile=0;
 void setHostile() {
   sys.trigger($makeHostile);
   hostile=1;
 }
 void setNeutral() {
   sys.trigger($makeNeutral);
   hostile=0;
 }
 void setHostile_() {
   sys.trigger($makeHostile);
 }
 void setNeutral_() {
   sys.trigger($makeNeutral);
 }
 void setHostileZone(entity zone) {
   setHostile();
 }
 void setNeutralZone(entity zone) {
   setNeutral();
 }
 void stayHostile() {
   setHostile();
   $makeNeutral.remove();
 }
 void flashed() {
   setHostile();
   sys.wait(3.0);
   setNeutral();
 }
 void CheckWeapon() {
   entity t;
   string classname,snake,triangle;
   snake="atdm:playertools_lockpick_snake";
   triangle="atdm:playertools_lockpick_triangle";
   do {
     if(!hostile) {
       if ($player1.getCurrentWeapon() != "atdm:weapon_unarmed") {
         setHostile_();
         sys.wait(1.0);
         if(!hostile) setNeutral_();       
       } //endif weapon
       else if ($player1.heldEntity()!=$null) {
         if ($player1.heldEntity().getKey("spawnclass")=="idAI") {
           setHostile_();
           sys.wait(1.0);
           if(!hostile) setNeutral_();       
         }
       } //endif held
       else  {
         t=$player1.getFrobbed();
         if(t!=$null) {
           if (t.IsLocked()) {
             classname = $player1.getCurInvItemEntity().getKey("classname");
             if ( classname == snake || classname == triangle) {
               if (t.GetDoorhandle().isRotating()) {
                 setHostile_();
                 sys.wait(1.0);
                 if(!hostile) setNeutral_();
               } //endif rotate
             } //endif lockpick
           }//endif locked
         }//endif null
         else { 
           sys.wait(1.0);
         }
       }
     }//endif hostile
     else {
       sys.wait(1.0);
     }
     sys.wait(0.1);
   } while(true);
 }
 void main() {
   setNeutral();
   thread CheckWeapon();
 } 

That's it. If you want to make the detection time for stealing things difficulty-dependent, add the following spawnargs on relay:

"diff_1_change_0" "delay" "diff_1_arg_0" "1.5" "diff_2_change_0" "delay" "diff_2_arg_0" "2.0"

Also, you could replace the times in the code by a variable that is set depending on the choosen difficulty. There is no need to choose the time for flashing higher then for the other hostilities, but it seemed more senseful to me while testing. Of course, you can use other times than mentioned here. The best idea is maybe to test this in game, as these things will hardly depend on the map layout, room sizes, patrol routes, lighting and so one.

Note, that this setup will alert the whole team at a time. So if the player is able to escape an alerted guard or is able to shut him down and hide him before support arrives, this won't be to much use for him. I had not much luck setting up such a behaviour. But what can be done is to use several teams for several areas, so the player can at least use the opportunity to escape. Obviously, this is only appropiate if the player is allowed to be identified.

Some notes to the code

The first thing that may draw attention in the code is the variable hostile and the two versions of setHostile and setNeutral. The problem was, that the code set the player back to neutral in some cases where he shouldn't be. If you, for example, draw your blackjack to knockout a guard, than put it away and pick up the guard, it is possible that you become neutral afterwards. What happened is, that after you drew the blackjack, the method chckweapon set you hostile in the weapon loop. While the thrad was waiting you put away your weapon and picked up the body. Now this would call the equip_action_script. Then the checkWeapon() thread is ready with waiting and sets you back to neutral, although you are still carrying a body. To prevent such a behaviour, to different methods are needed here. The last sys.wait command in the do-while loop is neccessary to prevent the thread of blocking the whole cpu in some situations, where none of the other sys.wait commands was executed (Very bad programming of the script language, by the way).

Feedback and information

Any kind of suggestions and/or questions can be posted here.

--Obsttorte (talk) 11:18, 2 January 2013 (EST)