Flanking

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

Flanking

Post by Cybernetic pig »

Obviously this is directed at Hanfling, as he is the only engineer here: say I wanted to implement NPC flanking, do you have any pointers? The navigation code is scripted to always take the shortest route to the player natively, and does so by looking through the NavigationPointList. What I want to do is under certain conditions to look for a different, longer route, providing the distance is not too far of course.
Hanfling
MJ12
Posts: 406
Joined: Sun Oct 04, 2009 6:54 pm

Re: Flanking

Post by Hanfling »

Though I'm certainly looking forward to overhaul AI code for HX, I have basically not touched AI much apart from fixing a shitload of MP related issues and fixing a few minor bugs as the teleporter related pathnode crashes and shooting through transparent movers and tweak the difficulty setting a bit to not just modify the bullet damage, but I never digged really into the pathfinding stuff.

There is some bSomethingStrafe bool, which enables Pawns to shot when strafing, which is sort of nice gameplay wise, but conflicts a bit with as you need to hold still to aim in Deus Ex. On the other hand, some decent usage of it to lay out some covering fire when changing positions would be nice. However, this isn't really what you want.

I'm currently not at home, and don't have my HX codebase, nor any DX UC source in front of me, I'll take a look at it once I'm back at home in a couple of days.

However, my plans for overhauling AI code for HX is sort of scheduled behind a lot of other stuff, like rendering, rewriting the whole Extension package Window code, etc. as it sort of works and won't cause any crash issues, though performance is horrible on a listen server as the ScripedPawns won't fall into statis in MP (but I recommend running a dedicated server even for two players, so this isn't that big issue...)

The general direction I would like to move with HX is towards having a sort of more Thief I/II like AI, some more sophisticated approaches when dealing with multiple enemies (players), rewriting the event manager, have more decorations emit events (I already added a couple of additional events to some decoration classes in HX like Faucets, etc.). Especially I want to have some this slight delay as in thief before the Pawn jumps into the alerted state, where you can't knock it out easily. It is sort of odd that you cannot coop-prod the two NSF guys at the back of liberty statue, and need to prod the second one twice. :D

Also some system which sort of fast forwards/fakes time passing since player left and revisited a map would be nice.
Last edited by Hanfling on Fri Sep 09, 2016 6:48 pm, edited 1 time in total.
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: Flanking

Post by Cybernetic pig »

Hanfling wrote: There is some bSomethingStrafe bool, which enables Pawns to shot when strafing, which is sort of nice gameplay wise, but conflicts a bit with as you need to hold still to aim in Deus Ex.


Eh? Pawns already shoot when strafing vanilla.
On the other hand, some decent usage of it to lay out some covering fire when changing positions would be nice. However, this isn't really what you want.
Suppressive fire is already in GMDX. AI is vastly improved, I'm just not sure how to override native code always telling pawns to take the shortest route when pursuing the player.
I'll take a look at it once I'm back at home in a couple of days.
Thanks.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Flanking

Post by Cybernetic pig »

Hanf: the native function that is telling pawns to always take the shortest path to the player is FindPathToward().

I'm hoping there's a native alternative already in place. Surely there is given UT had notable bot AI for the time.

Code: Select all

native(517) final function Actor FindPathToward(actor anActor, optional bool bSinglePath, 
												optional bool bClearPaths);
What's the bSinglePath optional bool do? It's not used once by DX code.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Flanking

Post by Cybernetic pig »

Flanking would help towards curing the issue of NPC friendly fire too. In DX enemies kill each other rather a lot, partly because they all try bundling down the same path and partly because of the (in)accuracy system causing NPCs with a clear shot of the player to shoot way off to the side and cap their buddy in the head.
All I intend to do is send a bunch of enemies in a group down the next best path to the player from time to time, providing that next best route is reasonable in terms of distance. Should be simple (well, not really), but it's all natively handled which causes some problems.

Looking at some of the NavigationPointList variables, it appears there's a "paths" array, which is probably the number of possible paths an NPC can take at any given time. Would be better to have Pawns consider at least four of the optimal paths available at any given time, and to check which paths are already in use by nearby pawns.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Flanking

Post by Cybernetic pig »

It's complicated. Every time an NPC moves towards an actor in DX, it does so with the native function FindPathToward() and looks for the shortest, most efficient route. There is no exception, leading to NPCs piling up in doorways and getting stuck on each other, and general predictability in combat. For all GMDX does for the game's AI, this is probably the final hurdle to be able to declare them fixed to a real quality standard.
Hanfling
MJ12
Posts: 406
Joined: Sun Oct 04, 2009 6:54 pm

Re: Flanking

Post by Hanfling »

The first thing I came across that if you set bAdvancedTactics to true you get AlterDestination() events, Botpacks Bots class is implemented as:

Code: Select all

// called when using movetoward with bAdvancedTactics true to temporarily modify destination
event AlterDestination()
{
	local float dir, dist;

	dist = VSize(Destination - Location);
	if ( dist < 120 )
	{
		bAdvancedTactics = false;
		return;
	}
	if ( bNoTact )
		return;

	if ( bTacticalDir )
		Dir = 1;
	else
		Dir = -1;
	Destination = Destination + 1.2 * Dir * dist * Normal((Destination - Location) Cross vect(0,0,1));
}
So this might be a way to influence the behavior at least on a smaller scope.

There is certainly a lot of friendly fire going in 02_NYC_Street, bugs me too, but might not be that hard to fix as one might be able to integrate checks for friendlies in the tracing for beeing able to shot 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: Flanking

Post by Cybernetic pig »

Hanfling wrote:The first thing I came across that if you set bAdvancedTactics to true you get AlterDestination() events, Botpacks Bots class is implemented as:

Code: Select all

// called when using movetoward with bAdvancedTactics true to temporarily modify destination
event AlterDestination()
{
	local float dir, dist;

	dist = VSize(Destination - Location);
	if ( dist < 120 )
	{
		bAdvancedTactics = false;
		return;
	}
	if ( bNoTact )
		return;

	if ( bTacticalDir )
		Dir = 1;
	else
		Dir = -1;
	Destination = Destination + 1.2 * Dir * dist * Normal((Destination - Location) Cross vect(0,0,1));
}
So this might be a way to influence the behavior at least on a smaller scope.
Yes, destination is often changed, like making NPCs find a hide point, and short-term specifically defined movements come into play sometimes like backing up when opening doors, or attempting to run around a deco blocking their path. What is not changed is that NPCs always take the shortest path to the "destination" vector, or any vector\actor they are told to moveToward natively.

That's what needs to change. Because there isn't really an alternative. For example I could make a new class "flank node" and place them in levels and make NPCs sometimes seek them out, but that wouldn't be very good at all. What needs to be done is have NPCs take other potential navigation paths into consideration, those that aren't shortest path. The way I'd do it is only have them consider other potential paths under very specific conditions (FRand(), No more than three NPCs are already flanking, NPC VSize distance to the player is not far etc), so it won't be a significant hit on performance.
There is certainly a lot of friendly fire going in 02_NYC_Street, bugs me too, but might not be that hard to fix as one might be able to integrate checks for friendlies in the tracing for beeing able to shot code.
Already is vanilla, but it's a trace without any defined extent. Even with a little bit of extent added to the trace NPCs will still shoot each other because of the inaccuracy system and NPCs bundling down the same paths.

GMDX's modified AISafeToShoot():

If you're looking to overhaul AI, take a look at GMDX as at least half of the many AI updates are mandatory behavioral fixes.

Code: Select all

// ----------------------------------------------------------------------
// AISafeToShoot()
// ----------------------------------------------------------------------

function bool AISafeToShoot(out Actor hitActor, vector traceEnd, vector traceStart,
							optional vector extent, optional bool bIgnoreLevel)
{
	local Actor            traceActor;
	local Vector           hitLocation;
	local Vector           hitNormal;
	local Pawn             tracePawn;
	local DeusExDecoration traceDecoration;
	local DeusExMover      traceMover;
	local bool             bSafe;
    local float            dista, dista2;
	// Future improvement:
	// Ideally, this should use the ammo type to determine how many shots
	// it will take to destroy an obstruction, and call it unsafe if it takes
	// more than x shots.  Also, if the ammo is explosive, and the
	// obstruction is too close, it should never be safe...

	bSafe    = true;
	hitActor = None;

	foreach TraceActors(Class'Actor', traceActor, hitLocation, hitNormal,
	                    traceEnd, traceStart, extent)
	{
		if (hitActor == None)
			hitActor = traceActor;
		if (traceActor == Level)
		{
		    if (sFire > 0)  //CyberP: related to suppressive fire
		    {
		        dista = Abs(VSize(enemy.Location - Location));
		        dista2 = Abs(VSize(enemy.Location - hitLocation));
		        if (dista < 320 || dista2 > 512)
                    sFire = 0;
                else
                    break;
            }
			if (!bIgnoreLevel)
				bSafe = false;
			break;
		}
		tracePawn = Pawn(traceActor);
		if (tracePawn != None)
		{
			if (tracePawn != self)
			{
				if (GetPawnAllianceType(tracePawn) == ALLIANCE_Friendly && !tracePawn.IsInState('Dying')) //CyberP: dying pawns are ignored. 
//@ Hanf: Alliance check is vanilla. We'd need a new separate trace if we're going to add extent otherwise it'd conflict with all these other things we are tracing for.
					bSafe = false;
				break;
			}
		}
		traceDecoration = DeusExDecoration(traceActor);
		if (traceDecoration != None)
		{
			if (traceDecoration.bExplosive || traceDecoration.bInvincible)
			{
				bSafe = false;
				break;
			}
			else if (weapon != None && traceDecoration.minDamageThreshold > DeusExWeapon(weapon).HitDamage)  //CyberP: get them to shoot through deco.
			{
				bSafe = false;
				break;
			}
		}
		traceMover = DeusExMover(traceActor);
		if (traceMover != None)
		{
			if (!traceMover.bBreakable)
			{
				bSafe = false;
				break;
			}
			else if ((traceMover.minDamageThreshold > 3))  //CyberP: get them to shoot through breakable glass
			{
				bSafe = false;
				break;
			}
			else  // hack
				break;
		}
		//if (Inventory(traceActor) != None) //CyberP: cut this out since inventory is breakable or moves when shot in GMDX
		//{
		//	bSafe = false;
		//	break;
		//}
	}

	return (bSafe);
}
Hanfling
MJ12
Posts: 406
Joined: Sun Oct 04, 2009 6:54 pm

Re: Flanking

Post by Hanfling »

Well I sort of don't really want to start to work on small fixes, but rather start to refactor/rewrite code on a larger scale, before actually digging into making much modifications.

In general I do mind the exhaustive tracing which is performed in AI code, as this is likely one of the biggest performance bottlenecks in the game code. I don't think using always an extent really does reflect the situation. Some sort of cone like trace would be be more appreciate. On the other hand, one could just use fast traces for bsp, from time to time the traces which include actors to check for decorations and such, but otherwise maybe have some (cone) based tracing build around the LevelInfo.PawnList, which should certainly be lower cost. Also it should allow to scale the extend in some cone based form.

However, what I just noticed is that for a quick check what happens inside the check for firing and the firing itsself at a quick glance appear to be different.

FireIfClearShot() calls AICanShoot() before finally executing Weapon.Fire(), which somewhere down the road calls ScripedPawns.AdjustAim(), both utilize ComputeTargetLead() and AISafeToShoot() but in different ways. ComputeTargetLead at least gets a different MaxTime value (5 compared to 20, no idea if that matters in this case), but AdjustAim() has the try again for players head part missing:

Code: Select all

	if (!bIsThrown)
	{
		bSafe = FastTrace(target.Location, projStart);
		if (!bSafe && target.bIsPlayer)  // players only... hack
		{
			projEnd += vect(0,0,1)*target.BaseEyeHeight;
			bSafe = FastTrace(target.Location + vect(0,0,1)*target.BaseEyeHeight, projStart);
		}
		if (!bSafe)
			return false;
	}
etc. and they surely look totally different in any case, nor shouldnt it be required to run the code twice. Maybe adding some PawnFire(), which passes in the results of the modifications done inside FireIfClearShot() to the PawnFire() method, so there is no need to run the exhaustice and missmatching tracing code again.

So yeah, thats why I prefer to start with cleaning up the code in this case instead of just trying to work on ScriptedPawns AI. ^^
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: Flanking

Post by Cybernetic pig »

It's only an extra fastTrace() or two, not too taxing. You could probably just comment that !bIsThrown block out and let AISafeToShoot() do the tracing, rather than rewriting anything.

Edit: thinking about it some more, that extra fastTrace is probably a quick check before calling the AISafeToShoot() function, precisely with performance in mind. See my added demo comments:

Code: Select all

if (!bIsThrown) //CyberP: do a high performance fast trace before continuing... 
	{
		bSafe = FastTrace(target.Location, projStart);
		if (!bSafe && target.bIsPlayer)  // players only... hack
		{
			projEnd += vect(0,0,1)*target.BaseEyeHeight;
			bSafe = FastTrace(target.Location + vect(0,0,1)*target.BaseEyeHeight, projStart);
		}
		if (!bSafe)
			return false;
	}

 //CyberP: If we didn't return, NOW call the more taxing trace function below. The above prevents AISafeToShoot() from being called too often.
	if (dxWeapon.bInstantHit) 
		return (AISafeToShoot(hitActor, projEnd, projStart,, true));
	else
	{
		extent.X = dxWeapon.ProjectileClass.default.CollisionRadius;
		extent.Y = dxWeapon.ProjectileClass.default.CollisionRadius;
		extent.Z = dxWeapon.ProjectileClass.default.CollisionHeight;
		if (bIsThrown && (throwAccuracy > 0))
			return (AISafeToThrow(projEnd, projStart, throwAccuracy, extent));
		else
			return (AISafeToShoot(hitActor, projEnd, projStart, extent*3));
	}
}
So it's probably fine as it is in that regard. FastTrace() is much faster than AISafeToShoot(), especially when AISafeToShoot() is passed trace extent for NPCs with projectile-based weapons.

And if you want to fix DX's AI, you've got to get down and dirty in ScriptedPawn making "small" modifications like GMDX, as that's where most of the flaws are. At this stage the AI in GMDX are nearly as good as modern 3D action game AI in terms of polish, believability and combat/stealth behavior. Just got to figure out this pathing/bottleneck problem.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Flanking

Post by Cybernetic pig »

So I've got this...it's very shit for the time being, just the first pass before I look deeper into it, but it IS resulting in NPCs unpredictably and dynamically going to different locations in combat, rather than always rushing the player's location the same predictable way. I need a more accurate method of finding the appropriate nodes that are also different from the path a pawn would normally take. At the moment it is probably too random.

Code: Select all

State Attacking
{

        function EDestinationType PickDestination()
	{
        
        //vanilla code 
        //vanilla code
        //vanilla code

        //CyberP: flanking
        if (destType == DEST_Failure && weapon.ReloadCount > 1 && bIsHuman)
        {
        if (enemy.IsA('DeusExPlayer') && EnemyLastSeen > 0 && FRand() < 0.6 && !bDefendHome) //Player only for now
		{
		   BroadcastMessage("Ready!");
		   if (VSize(DeusExPlayer(enemy).Location - Location) < 1536 )// && ActorReachable(enemy)) //Don't even bother if we're too far away.
		   {
		       bestPoint = None;
		       foreach ReachablePathnodes(Class'NavigationPoint', navPoint, self, dist) //iterate through reachable pathnodes.
		       {
		          BroadcastMessage("Iterating!");
		          dista2 = VSize(navPoint.Location - DeusExPlayer(enemy).Location);
		          if (dista2 < 1024 && dista2 > 128 && !navPoint.bTaken)
			      {
			         if (FRand() < 0.6)// || (dista2 > 192 && dista2 < 512))
                 {
                     //Trace from the node to the player to ensure the node is in the same room as the player
                     bSafe = FastTrace(DeusExPlayer(enemy).location, navPoint.location+vect(0,0,96)); 
		                if (bSafe)
			            {
			                BroadcastMessage("FLANKING!"); 
                                        //well, not really flanking, just moving to a location that isn't the player but is nearby, which  
                                        //sometimes results in them finding different paths and firing positions they wouldn't have normally. 
			                spawn(class'FleshFragmentNub',,,navPoint.location); //testing purposes to see the location.
				                 bestPoint = navPoint;
						       destType  = DEST_NewLocation;
						         break;
			            }
			         }
			      }
		       }
               if (bestPoint != None) //We've got a location that is relatively close to the player and in the same room as him, so move to it!
               {
                  MoveTarget = FindPathToward(bestPoint);
			      if (MoveTarget != None) 
			      {
					   if (bAvoidHarm) 
						   GetProjectileList(projList, MoveTarget.Location);
					   if (!bAvoidHarm || !IsLocationDangerous(projList, MoveTarget.Location))
					   {
					    	destPoint = MoveTarget;
						    destType  = DEST_NewLocation;
					    }
	              }
               }
            }
        }
        }
        //flanking end

       //vanilla code
       //vanilla code
      //vanilla code
To be able to improve it I need to know more about navigation points, and if there is any native functions/variables that would come in handy.

I need access to all this native stuff and more:

Code: Select all

var int upstreamPaths[16];
var int Paths[16]; //index of reachspecs (used by C++ Navigation code)
var int PrunedPaths[16];
var NavigationPoint VisNoReachPaths[16]; //paths that are visible but not directly reachable
Post Reply