Deus Ex AI: Documentation of the Labels of State Seeking

A refuge for those migrating from the fallen DXEditing.com and a place for general discussion relating to Deus Ex editing (coding, mapping, etc).
Post Reply
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Deus Ex AI: Documentation of the Labels of State Seeking

Post by Cybernetic pig »

This is to help me, and others, understand State Seeking found in ScriptedPawn. I've spent a few days in the code trying to decipher it and ultimately failing, but I did manage to fix an exploit in there, which was my primary goal.
Even if it's no use to anybody else, it's documentation for me to return to if I want to improve core AI behaviour further, and my actual code and comments in GMDX's scripted pawn can remain intact also.
Thing is, I am NOT a programmer. I don't like coding. I've had little schooling in the subject. OK, I find it somewhat interesting but I know it's not for me and knew from the start. Ideally I should have an actual programmer doing these things, but the only actual programmers (about 3) that frequent this place have their own projects. Where the fuck are all the Deus Ex fans?

Question: when the code is running through the labels, it goes from top to bottom through the whole lot (all labels) unless otherwise specified with GoTo right?

The following sheds some light on the labels of state seeking (all vanilla code, my comments):

Code: Select all

Begin:                                   //We start from here unless otherwise specified.
	WaitForLanding();               //Calls this function which waits for the pawn to land before executing the code 
	PlayWaiting();                   //plays a standing animation where the pawns appear to breathe. 
	if ((Weapon != None) && bKeepWeaponDrawn && (Weapon.CockingSound != None) && !bSeekPostCombat) //checks conditions
	{
		if(DeusExPlayer(GetPlayerPawn()).DrugEffectTimer < 0)                         //Nested check
			PlaySound(Weapon.CockingSound, SLOT_None,,, 1024, 0.5);       //play sound at half pitch because player is drugged?
		else
			PlaySound(Weapon.CockingSound, SLOT_None,,, 1024);           //normal pitch if not drugged
	}
	Acceleration = vect(0,0,0);                                                                 //No idea why this changes a pawn's acceleration
	if (!PickNextDestination())                                                                  //check 
		Goto('DoneSeek');                                                                    //Go to the DoneSeek label

GoToLocation:                                                                              //label for making pawns go to a certain location
	bInterruptSeek = true;                                             //bInterruptSeek is a bool used for checking for whether or not the state can be interrupted
	Acceleration = vect(0,0,0);                                    //Again, Why?

	if ((DeusExWeapon(Weapon) != None) && DeusExWeapon(Weapon).CanReload() && !Weapon.IsInState('Reload')) //checks
		DeusExWeapon(Weapon).ReloadAmmo();           //Reload weapon before or whilst moving to location?

	if (bSeekPostCombat)                     //bSeekPostCombat is a bool for checking whether or not the pawn is seeking post 'attacking' state
		PlayPostAttackSearchingSound();   //self explanatory
	else if (SeekType == SEEKTYPE_Sound)   //SEEKTYPES are self explanatory
		PlayPreAttackSearchingSound();    //"" ""
	else if (SeekType == SEEKTYPE_Sight)   //"" ""
	{
		if (ReactionLevel > 0.5)              //Another check. ReactionLevel. Not sure what this var is EXACTLY for. 
			PlayPreAttackSightingSound();  //playsound
	}
	else if ((SeekType == SEEKTYPE_Carcass) && bSeekLocation) //check
		PlayCarcassSound();                                                //if spotted a carcass, play carcass react sound

	StopBlendAnims();                                 //stop all pawn anims in progress I think                      

//THIS CODE BELOW HANDLES WHEN A PAWN HAS SPOTTED THE PLAYER BUT NOT CONFIRMED FRIEND OR FOE

	if ((SeekType == SEEKTYPE_Sight) && bSeekLocation)  //Check
		Goto('TurnToLocation');              //If looking for player based on sight, go to TTL label.  

//IF WE ARE NOT SEEKING BASED ON SIGHT WE RUN THROUGH THIS CODE

	EnableCheckDestLoc(true);              //Not sure what this function is for, but it gets enabled
	while (GetNextLocation(useLoc)) //While loops. Basic stuff but I don't know the exact workings. DO this WHILE this is active i think
	{
		if (ShouldPlayWalk(useLoc))   //not sure what this function is for. 
			PlayRunning();            //Play running handles running anim
                        MoveTo(useLoc, MaxDesiredSpeed);  //moveto useLoc at max class speed. useLoc is a var assigned the vector of the last known location of the player I believe.
		CheckDestLoc(useLoc);       //dont know. Will update when I check out this function
	}
	EnableCheckDestLoc(false);        //This was enabled before the while loop. Now it is disabled once the while loop is finished. Time for me to figure out GetNextLocation and EnableCheckDestination functions...   

//THIS CODE BELOW HANDLES SOUND EVENTS I BELIEVE 

	if ((SeekType == SEEKTYPE_Guess) && bSeekLocation) //check. Seektype guess I am not 100% on
	{
		MoveTarget = GetOvershootDestination(0.5); //GetOverShootDestination function makes pawns overshoot their destination
		if (MoveTarget != None)  //check
		{
			if (ShouldPlayWalk(MoveTarget.Location)) //ShouldPlayWalk is a function to check if pawns should move I think!!???
				PlayRunning(); //start running anim. 
			MoveToward(MoveTarget, MaxDesiredSpeed); //Moves Toward MoveTarget at max desired speed
		}

//OK, THIS IF STATEMENT BELOW IS FOR RANDOMIZED RUNNING AROUND CALLED IN THE LABEL 'FindAnotherPlace:'

		if (AIPickRandomDestination(CollisionRadius*2, 200+FRand()*200, Rotation.Yaw, 0.75, Rotation.Pitch, 0.75, 2,
		                            0.4, useLoc)) //This is a very specific check. WTF?
		{
			if (ShouldPlayWalk(useLoc))            //only do this if passed the insanely specific check above   
				PlayRunning();                      //only do this if passed the insanely specific check above   
			MoveTo(useLoc, MaxDesiredSpeed); //only do this if passed the insanely specific check above  
		}
	}



TurnToLocation:                                          //Label that turns the pawn to look at the source of the disturbance
	Acceleration = vect(0,0,0);                   //Again, why is acceleration modified? to make sure they cannot move?
	PlayTurning();                                     //play turning animation
	if ((SeekType == SEEKTYPE_Guess) && bSeekLocation)  //check
		destLoc = Location + Vector(Rotation+(rot(0,1,0)*(Rand(16384)-8192)))*1000; //formula for rotating the pawn to location which is then stored inside destLoc                                                                            
	if (bCanTurnHead)    //Check. 
	{
		Sleep(0);  // needed to turn head                                 
		LookAtVector(destLoc, true, false, true);  //Make pawn look (with it's head only I think) at a specific vector, which in this case is destLoc
		TurnTo(Vector(DesiredRotation)*1000+Location); //PlayTurning() handles the anim. This however handles the vector of the pawn
	}
	else
		TurnTo(destLoc); //else If is robot or anyhting else that cannot move it's head, just turn the robot's vector.
	bSeekLocation = false;   //used often for checks. fucks with me.
	bInterruptSeek = false;  //same as above. GRRR

	PlayWaiting();             //Play the standing breathing anim again.
	Sleep(FRand()*1.5+3.0);  //Randomized time frame for the state code to stop executing right here momentarily. 

LookAround:     //Just for handling looking around anims for human. I am going to skip "documenting" it because it's all self explanatory.
	if (bCanTurnHead)
	{
		if (FRand() < 0.5)
		{
			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Left, 1.0, 1.0);
				Sleep(1.0);
			}
			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Forward, 1.0, 1.0);
				Sleep(0.5);
			}
			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Right, 1.0, 1.0);
				Sleep(1.0);
			}
		}
		else
		{
			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Right, 1.0, 1.0);
				Sleep(1.0);
			}
			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Forward, 1.0, 1.0);
				Sleep(0.5);
			}
			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Left, 1.0, 1.0);
				Sleep(1.0);
			}
		}
		PlayTurnHead(LOOK_Forward, 1.0, 1.0);
		Sleep(0.5);
		StopBlendAnims();
	}
	else
	{
		if (!bSeekLocation)
			Sleep(1.0);
	}

FindAnotherPlace:           //Label for making pawns run to partially-randomized location. 
	SeekLevel--;          //No idea what this is
	if (PickNextDestination())   //????? If this function is executing, or if it is true, or what?
		Goto('GoToLocation'); //Causes the code to Goto the GoToLocation label back up near the top.

DoneSeek:                    //label for handling the ending of the state.
	if (bSeekPostCombat)            //check 
		PlayTargetLostSound();  //playsound if post combat
	else
		PlaySearchGiveUpSound();  //playsound if not post combat
	bSeekPostCombat = false;      //sets the var to false
	SeekPawn = None;                //sets the var to none
	if (Orders != 'Seeking')           //check    
		FollowOrders();            //if no longer seeking, go back to default orders I think
	else                                    //check
		GotoState('Wandering'); //if orders are seeking, GottoState wandering

ContinueSeek:     //what the fuck? just an unused label I guess. The code will run through and ignore?
ContinueFromDoor: //Continue from door?
	FinishAnim();   //Waits for the anim to finish before execution
	Goto('FindAnotherPlace'); //Go to find another place label above. How come the code doesn't get stuck in an infinite loop if right at the end it is told to go back up to FindAnotherPlace again? 

}
Feel free to to clarify things, by all means it needs it. I'm not a fucking programmer. I need an actual programmer to come in and do this shit while I work on other aspects of the mod.
But again, this will come in handy if I have to go in fucking scripted fucking pawn again, and perhaps will help someone else understand the code easier. Maybe even mislead them, who knows? I'm not a fucking programmer.

Where is G-Flex? He'd get a kick out of correcting all this.

If I return to it I will update it as I make progress, or update it if someone provides clarity to certain lines.
Last edited by Cybernetic pig on Fri Jul 11, 2014 8:48 pm, edited 7 times in total.
Hanfling
MJ12
Posts: 406
Joined: Sun Oct 04, 2009 6:54 pm

Re: Documentation of the Labels of State Seeking

Post by Hanfling »

Code: Select all

     if(DeusExPlayer(GetPlayerPawn()).DrugEffectTimer < 0)                         //Nested check
         PlaySound(Weapon.CockingSound, SLOT_None,,, 1024, 0.5);       //play sound at half pitch because player is drugged?
      else
         PlaySound(Weapon.CockingSound, SLOT_None,,, 1024);           //normal pitch if not drugged
Other way around. Oh wait.. seems like a bug. If drug effect timer is set to zero (which should corospond to not drugged) the wrong branch is executed. Probably the DrugEffectTimer will never be less then 0.

Please post the exploit and maybe the fix. Or tell me you already posted it. I just read the first few lines.

/edit: Jep, drug effect timer will never be less then zero. So always the seond line is excuted. Yeah, just another bug. Feels like DX code.
I demand my DXE User ID back. Launcher for DeusEx, Rune, Nerf, Unreal & Botpack. HX on Mod DB. Revision on Steam.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Documentation of the Labels of State Seeking

Post by Cybernetic pig »

The exploit I mentioned was when they are distracted by a sound event (fire a dart in a nearby wall for example) they didn't react to further sound events for a good few seconds. So you could fire a dart into a nearby wall, then run up behind them, jump around, run around a bit more, then smack em with the baton, just to show how ridiculous it was.
A good five seconds they would stare at the source of the sound before reacting to further sound events.

I didn't fix it per se, well, I half fixed it by cutting down on Sleep() time, but this alone wasn't enough, so I added another label immediately after 'TurnToLocation:' which was a duplicate of 'FindAnotherPlace:', which made them do more unpredictable randomized running around rather than standing there for ages completely exploitable. During the running around they do process more sound events the player makes, so it worked out nicely.

So with that worked around I decided to give improving the AI seeking behaviour another shot. I wanted to have pawns actively hunt down the sources of disturbances as I mentioned in the other thread. Vanilla they do actively seek sources of disturbances, but only for some types of disturbances (footstep sounds only I think).

If SEEKTYPE == Sight (spotted the player but hasn't confirmed is a threat) I wanted to have it be randomized as well, so if (Frand() < 0.1 && bcanNavigateToUseLoc), they walk to the place they spotted the player, if (FRand() < 0.3 && bcsnNavigateToUseLoc) they run to the place they spotted the player, else if (FRand() < 1.0) they simply turn to the place they spotted the player and look, nothing more as per vanilla.

This would really enhance stealth, challenge and make AI more believable.

Note bcanNavigateToUseLoc is a made up bool. The pawns would have to check whether they can actually navigate to useLoc (last known location of thing they are seeking) and I don't know how to check for that.

I need an actual coder to such things, this is not my field :cry:
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by Cybernetic pig »

Made a couple of updates. Perhaps I can do this...somehow.

ARGH!
DDL
Traditional Evil Scientist
Traditional Evil Scientist
Posts: 3791
Joined: Mon Oct 17, 2005 10:03 am

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by DDL »

It might be worth pointing out that Pawn navigation code is awful. Really, really fucking awful. And stupid. Really, really fucking stupid.

Getting them to move toward a thing is much much easier than getting them to move toward a place.
As far as I can tell looking through my own comments, GetNextLocation was the biggest dickbag function, with pawns simply going "Ok, so I can't get to X, so instead I'll....fuck it, give up", even when X was like, right in front of them.

This means you end up with situations where they're allegedly supposed to come check where you just were, but DON'T, because that's a place and PLACES R HARD LOL, while conversely if they've actually SEEN you (since you're a thing) they'll merrily navigate right fucking up to your face and start shooting, even if you've run fifty meters away through total pitch darkness.

I ended up basically spawning an invisible pawn in the place they thought they saw you (at the point they lose sight of you), travelling in the direction/speed they thought you were travelling in, and then having them hunt for that. This means I could vary that pawn's properties based on how good a look they got (glimpse meant greater variation in speed/direction etc), and so on, and it means that if you're spotted, but then run off into darkness and duck into an alcove/vent/comedy hiding place, you'll see the bad guys run after you and then carry on running after where they thought you were going.

Horribly hacky, but workable.


Also, all the continueseek: bits are simply placemarkers: the code will run straight past them and ignore them, but you can also tell the code to jump TO that point, so it doesn't execute all the statecode above. So after they go through a door, they just jump to continuefromdoor and carry on, rather than reestablishing tons of seeking parameters.

Also, infinite looping is avoided by seeklevel: this is (roughly) the number of times they'll run around at guess locations. It drops by one each time they go through the motions. Usually starts at 3, I think. So they run over to a spot, pause, look round, pick another spot (picknextdestination(): returns true if it has successfully found and assigned a new destination), run there, look around, pick a third spot, run there, look around, say "ah, guess it was nothing" and return to whatevers.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by Cybernetic pig »

Thanks for that info. You wouldn't happen to still have this seeking code (invisible pawn) would you? It's for SEEKTYPE == Sight right?

Hmm, reading again it seems you done that for seektype guess, post combat. To make them hunt for you a little better.
DDL
Traditional Evil Scientist
Traditional Evil Scientist
Posts: 3791
Joined: Mon Oct 17, 2005 10:03 am

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by DDL »

Basically, yeah. Also, the code is insanely intermingled with various hunt/seek/newtarget/reset/etc functions because
A ) I obviously want them to always focus on a real person rather than a magical invisible person, and
B ) I want to make sure the magical invisible persons evaporate as soon as not needed, lest the map fill up with random invisible pawns, and
C ) I am a fucking terrible coder. Such a hack.

So it's a bit...hard to post. :-/
Hanfling
MJ12
Posts: 406
Joined: Sun Oct 04, 2009 6:54 pm

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by Hanfling »

Most times you can just hack things right in dx. horrible.
I demand my DXE User ID back. Launcher for DeusEx, Rune, Nerf, Unreal & Botpack. HX on Mod DB. Revision on Steam.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by Cybernetic pig »

Well, here's my modifications:

Makes AI do randomized run arounds after a sound event rather than standing there not accepting further events, in addition to cut time in certain actions where they were vulnerable/didn't process further sound events (such as looking around). Sloppy as hell, I'm no coder, but apparently I am. It works, though extensive testing other than my own would be nice.

Feel free to criticise this specific code to help me solidify this (if needed) and you can use it/expand upon it in your own mod if you want.

Code: Select all

TurnToLocation:
	Acceleration = vect(0,0,0);
	PlayTurning();
	if ((SeekType == SEEKTYPE_Guess) && bSeekLocation)
		destLoc = Location + Vector(Rotation+(rot(0,1,0)*(Rand(16384)-8192)))*1000;
	if (bCanTurnHead)
	{
		Sleep(0);  // needed to turn head
		LookAtVector(destLoc, true, false, true);
		TurnTo(Vector(DesiredRotation)*1000+Location);
	}
	else
		TurnTo(destLoc);
	bSeekLocation = false;
	bInterruptSeek = True;  //CyberP: was false

        PlayWaiting();
        if (SeekType == SEEKTYPE_Sight)     //CyberP: if type sight, sleep for 2.0.
        {
            Sleep(2.0);
        }
        else     
	Sleep(1.4); //CyberP: else we don't want much sleeping on the job. :) 

FindAnotherPlaceAgainAgain:               //CyberP: New label for another run around
	SeekLevel--;
	if (PickDestination())
		Goto('GoToLocation');

LookAround:              //CyberP: look around label now takes less time to execute as they are vulnerable to exploitation when doing  
                              //this- they no longer look both left and right, only one or the other. 
	if (bCanTurnHead)
	{
		if (FRand() < 0.5)
		{

			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Left, 1.0, 1.0);
				Sleep(1.0);
			}


		}
		else
		{

			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Right, 1.0, 1.0);
				Sleep(1.0);
			}

		}
		PlayTurnHead(LOOK_Forward, 1.0, 1.0);
		Sleep(0.5);
		StopBlendAnims();
	}
	else
	{
		if (!bSeekLocation)
			Sleep(0.6);  //CyberP: was 1.0

	}

FindAnotherPlace:               //CyberP: New label for another run around
	SeekLevel--;
	if (PickDestination())
		Goto('GoToLocation');


LookAroundAgain:  //CyberP:  Look around again between running
	if (bCanTurnHead)
	{
		if (FRand() < 0.3)
		{

			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Left, 1.0, 1.0);
				Sleep(1.0);
			}


		}
		else
		{

			if (!bSeekLocation)
			{
				PlayTurnHead(LOOK_Right, 1.0, 1.0);
				Sleep(1.0);
			}

		}
		PlayTurnHead(LOOK_Forward, 1.0, 1.0);
		Sleep(0.5);
		StopBlendAnims();
	}
	else
	{
		if (!bSeekLocation)
			Sleep(0.6);  //CyberP: was 1.0

	}


FindAnotherPlaceAgain:               //CyberP: New label for another randomized run around before finishing. Hmm, may need a short sleep                                         
                                            //period after, will look into it.
	SeekLevel--;
	if (PickDestination())
		Goto('GoToLocation');

DoneSeek:
	if (bSeekPostCombat)
		PlayTargetLostSound();
	else
		PlaySearchGiveUpSound();
	bSeekPostCombat = false;
	SeekPawn = None;
	if (Orders != 'Seeking')
		FollowOrders();
	else
		GotoState('Wandering');

ContinueSeek:
ContinueFromDoor:
	FinishAnim();
	Goto('FindAnotherPlace');

}
Last edited by Cybernetic pig on Sat Jul 19, 2014 8:40 pm, edited 3 times in total.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by Cybernetic pig »

here is the in-game result: https://www.youtube.com/watch?v=CtJsP_Q ... e=youtu.be

Annnd I've just noticed I got rid of the FRand() in the sleep time so they are running & looking around in synchronization :/ lol.

So to fix this:

Code: Select all

	PlayWaiting();
        if (SeekType == SEEKTYPE_Sight)     //CyberP: if type sight, sleep for 2.0.
        {
            Sleep(2.0);
        }
        else     
	Sleep(FRand()+0.3); //CyberP: else we don't want much sleeping on the job. :)
^this last line here is modified to randomize when they start to begin running, so they don't all do it synchronized.

Sorted :smile:

Hmm, should probably add a bit of FRand if seektype == sight too, though it doesn't matter as much but it was FRand vanilla and it's more believable so will do:

Code: Select all

	PlayWaiting();
        if (SeekType == SEEKTYPE_Sight)     //CyberP: if type sight, sleep for FRand +1.
        {
            Sleep(FRand()+1.0);
        }
        else     
	Sleep(FRand()+0.3); //CyberP: else we don't want much sleeping on the job. :)
There.

Remember, vanilla they just stood there and you could run up behind them, jump around, run backwards and forwards, and they did nothing, they didn't hear all the ruckus you were making directly behind them and this was a major issue.
Now if someone better at this than I could make AI seek your last known location based on if seektype sight and FRand()...else vanilla behaviour, that'd be great... :cry:
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Deus Ex AI: Documentation of the Labels of State Seeking

Post by Cybernetic pig »

I'm seeking criticism here. I'm releasing v6.1 of my mod soon and it takes DX to yet another level, in addition to featuring a professional level of polish. If there is issue here I'd appreciate knowing.

I wish I still had my old coder. He was ingenious with code. didn't know what I had till it was gone :cry:
Post Reply