Page 1 of 1

Flankers (BETA) [Update 12/18/21]

Posted: Sun Dec 12, 2021 6:00 am
by Hey Doomer
Enhancing monster AI in Doom is interesting, trying to answer questions such as How can they find the player? and How can they alert others? These attempt to get monsters "unstuck" in complex map geometry (a mapper and not AI issue?). Anyhoo this got me thinking about attacking flanks of an opponent as an alternative to a head-on approach. The tactic is a random number of critters are "flankers," and while most of the meat charges head on flankers sneak around to either side of the player and whack him unawares. Basic stuff, right? This is my attempt at that.
Spoiler:
The idea is simple. In the WorldLoaded event ID_Point markers are placed at the center of each sector. I have kept this at the Sector property for now. Similar to other AI mods, these are monster waypoints. For the present all these are marked by a flat sprite and all monsters are flankers. The flank() function uses a ThinkerIterator to find all monsters within a certain distance of the player in a See state. Then another thinker loops through the ID_Points to find the first one farther away than the player and on either side of the player. If a point is found the monster attempts to move in that direction. For debugging purposes I apply a different RenderStyle when that happens.

I've just started looking at monster AI, and there are issues around map geometry, infighting, placement of monsters, etc. A player is seldom still, another significant variable. All the same, my early testing seems promising. VectorAngle() can select ID_Points in the direction of the player (a positive or negative angle), and I think AngleTo can find player flanks (I assume this depends on the player's current facing angle). I'm still playing with this, but the idea is to implement a general attack strategy that isn't head on.

Work in progress...

Update 12/14/21
Spoiler:

Re: Flankers (Experimental)

Posted: Sun Dec 12, 2021 6:15 am
by Enjay
This sounds very interesting. As someone who likes realistic-ish military style mods/scenarios, having some of the enemies behave with a bit more intelligence (apparent intelligence anyway) would be good. Some way of including/excluding particular enemy types from the AI improvement would be nice. Like, soldiers might use sensible tactics to try and out flank the player but guard dogs are more likely to just run the target. Both types of enemies could easily appear in a single mod.

Re: Flankers (ALPHA) [Update 12/14/21]

Posted: Tue Dec 14, 2021 12:25 pm
by Hey Doomer
I've got this to a point where monster behavior is more or less what I'd like to see, although it's far from perfect. Here's the logic:
  • A thinker searches for monsters that are flankers in a See state within a certain radius of the player
  • If a monster has already targeted a sector ID_Point, it keeps going unless it's flanking the player
  • If a monster isn't targeting a sector ID_Point, it looks for one between itself and the player, preferring those that flank the player
Here's the meat:

Code: Select all

	void flank(float distance)
	{
		ThinkerIterator MonsterFinder;
		ThinkerIterator ID_PointFinder;
		MonsterFinder = ThinkerIterator.Create("Actor");
		ID_PointFinder = ThinkerIterator.Create("ID_Point");

		Actor mo;
		int x, y, z;
		vector2 v2;
		vector3 v3;
		ID_Point idpoint;

		MonsterFinder.Reinit();
		while (mo = Actor(MonsterFinder.Next()))
		{
			if (mo.bIsMonster && mo.args[MOFLAG] == 1)
			{
				if (!mo.InStateSequence(mo.CurState, mo.ResolveState("See")))
				{
					mo.args[FLSTATUS] = 0;
					mo.A_SetRenderStyle(1.0, STYLE_Normal);
				}
				else
				{
					if (mo.args[FLSTATUS] > 0) // still on target
					{
						mo.A_FaceMovementDirection(0, 0, 0, FMDF_INTERPOLATE);

						if (mo.CheckIfTargetInLOS())
						{
							double fAngle = mo.AbsAngle(mo.AngleTo(player.mo),mo.angle);
							if (fAngle < 45 || (fAngle > 135 && fAngle < 225) || fAngle > 315)
							{
								mo.args[FLSTATUS] = 0;
							}
						}
						else
						{
							mo.args[FLSTATUS]--;
						}
						if (mo.args[FLSTATUS] == 0)
						{
							mo.bNOTARGETSWITCH = false;
							mo.target = null;
							mo.A_Face(player.mo);
						}
					}
					else // find new target
					{
						float pldist = mo.Distance2D(player.mo);
						if (pldist < distance)
						{
							float nearest = distance;

							ID_Point ptr = null;
							ID_PointFinder.Reinit();
							while (idpoint = ID_Point(ID_PointFinder.Next()))
							{
								// z height that is reachable
								if ((idpoint.floorz > mo.floorz && (idpoint.floorz - mo.floorz) >= mo.maxstepheight)
								|| (idpoint.floorz < mo.floorz && (mo.floorz - idpoint.floorz) <= mo.maxdropoffheight)
								|| (idpoint.floorz == mo.floorz) && mo.isVisible(idpoint, true))
								{
									// always move closer to player
									float iddist = player.mo.Distance2D(idpoint);
									float modist = mo.Distance2D(idpoint);

									if (modist < nearest && iddist < pldist)
									{
										ptr = idpoint;
										nearest = pldist;

										double fAngle = player.mo.AbsAngle(player.mo.AngleTo(idpoint),player.mo.angle);
										if (fAngle < 45 || (fAngle > 135 && fAngle < 225) || fAngle > 315)
										{
											break; // always flank
										}
									}
								}
							}
							if (ptr) // a sector point was found to move toward
							{
								mo.target = ptr;
								mo.bNOTARGETSWITCH = true;
								mo.args[FLSTATUS] = 5; // stay on this target x5
								mo.A_SetRenderStyle(1.0, STYLE_Shadow);

								int steps = 5;
								while (steps--)
								{
									x = mo.pos.x + (mo.pos.x == ptr.pos.x ? 0 : mo.pos.x > ptr.pos.x ? Random(-1,-2) : Random(1,2));
									y = mo.pos.y + (mo.pos.y == ptr.pos.y ? 0 : mo.pos.y > ptr.pos.y ? Random(-1,-2) : Random(1,2));

									v2 = (x, y);
									if (ptr.floorz <= mo.floorz)
									{
										v3 = (x, y, mo.floorz); // drop off
									}
									else
									{
										v3 = (x, y, ptr.GetZAt(x, y, 0, GZF_ABSOLUTEPOS)); // climb
									}

									if (mo.CheckPosition(v2))
									{
										mo.A_StartSound("breath", CHAN_AUTO, CHANF_OVERLAP, 0.5, ATTN_STATIC);
										mo.SetOrigin(v3, true);
										mo.A_FaceMovementDirection(0, 0, 0, FMDF_INTERPOLATE);
									}
								}
							}
						}
					}
				}
			}
		}
	}
For testing purposes a few things are happening. The distance is defined at 2048. Flanking is 90 degrees on either side of the player. Flankers spawn at a rate of 50% and include all monsters. Once a flanker in a See state targets an ID_Point I change its render style to shadow. Interrupting the Chase movement in the See state means the monster can still attack, but it jitters the sprite. I chose this for now instead of defining a new state, since I'd prefer this be universal. Generally this seems to work, although I've seen monsters stuck in walls here and there.

What I have seen that is good is monsters that will flank to one side while I'm attacked head on. If I'm hiding behind a beam and peek out to see a flanker, he will immediately run off to one side or the other.

I also added a breathy sound as an audio cue for the flankers. Perhaps, that helps.

Re: Flankers (Experimental) [Update 12/14/21]

Posted: Tue Dec 14, 2021 12:33 pm
by Enjay
It took me a while to figure out what was going on until I re-read the description. It seems to be doing much as you described (including the health warnings about getting stuck etc).

I noticed a couple of warnings on startup:

Code: Select all

Script warning, "flankers.pk3:flankers_events.zs" line 142:
Truncation of floating point value
Script warning, "flankers.pk3:flankers_events.zs" line 143:
Truncation of floating point value

Re: Flankers (Experimental) [Update 12/14/21]

Posted: Tue Dec 14, 2021 7:00 pm
by Hey Doomer
Enjay wrote:It took me a while to figure out what was going on until I re-read the description. It seems to be doing much as you described (including the health warnings about getting stuck etc).

I noticed a couple of warnings on startup:

Code: Select all

Script warning, "flankers.pk3:flankers_events.zs" line 142:
Truncation of floating point value
Script warning, "flankers.pk3:flankers_events.zs" line 143:
Truncation of floating point value
Thanks - I appreciate the feedback

Re: Flankers (ALPHA) [Update 12/14/21]

Posted: Wed Dec 15, 2021 3:20 pm
by openroadracer
I just have to ask: How does this mod interact with this other AI Enhancer mod, if at all?

I wonder how having enemies that can intelligently chase and hunt the player would interact with having enemies that intelligently flank the player would work...

Re: Flankers (ALPHA) [Update 12/14/21]

Posted: Thu Dec 16, 2021 3:58 am
by Hey Doomer
openroadracer wrote:I just have to ask: How does this mod interact with this other AI Enhancer mod, if at all?

I wonder how having enemies that can intelligently chase and hunt the player would interact with having enemies that intelligently flank the player would work...
I think these will be compatible, because I'm taking a different approach and modifying different behavior. Testing will tell. 8-)

Re: Flankers (ALPHA) [Update 12/14/21]

Posted: Thu Dec 16, 2021 5:17 am
by openroadracer
Hey Doomer wrote:
openroadracer wrote:I just have to ask: How does this mod interact with this other AI Enhancer mod, if at all?

I wonder how having enemies that can intelligently chase and hunt the player would interact with having enemies that intelligently flank the player would work...
I think these will be compatible, because I'm taking a different approach and modifying different behavior. Testing will tell. 8-)
Guess I'll just have to download this and let you know how it goes, then. Any suggestions on relative load order?

Re: Flankers (ALPHA) [Update 12/14/21]

Posted: Thu Dec 16, 2021 9:23 am
by Hey Doomer
openroadracer wrote:
Hey Doomer wrote:
openroadracer wrote:I just have to ask: How does this mod interact with this other AI Enhancer mod, if at all?

I wonder how having enemies that can intelligently chase and hunt the player would interact with having enemies that intelligently flank the player would work...
I think these will be compatible, because I'm taking a different approach and modifying different behavior. Testing will tell. 8-)
Guess I'll just have to download this and let you know how it goes, then. Any suggestions on relative load order?
I don't think that matters.

At this stage I'd recommend waiting, but of course you are free to try it. I have refactored code to eliminate all movement jittering and make smarter decisions. The new movement code is pretty seamless, and I've realized it's not so much flanking as also getting behind the player. I'm also making little changes that should greatly improve this.

Example:

Code: Select all

					if (mo.CurState == mo.ResolveState("See"))
					{
						ID_Point ptr = findtarget(mo, player.mo, distance);
						if (ptr)
						{
							flankon(mo, ptr);
							seekflank(mo, ptr);
						}
					}
Rather than look if the monster is anywhere the in "See" state, I capture the start of the state as I understand the CurState return. It has taken some experimentation to understand some of this, and I think the end results will be interesting. Should have an update by tomorrow! :D

It should be compatible with any of the other AI mods.

Re: Flankers (BETA) [Update 12/18/21]

Posted: Sat Dec 18, 2021 12:25 pm
by Hey Doomer
This is a major update of the movement and detection code after playing around quite a lot.

Screenshots:
Spoiler:
Summary of changes:
  • Decided to keep CenterSpot after testing various centroid calculations, most of which aren't any better with an irregular polygon
  • Made the movement code less random and now factored around getting closer to the target
  • Cleaned up target point selection, although I'm still having trouble up and down stairs. Not sure Distance3D is accurate.
  • Completely rewrote flank test to use CheckLOF by casting rays to see if the monster can be seen between angles
  • Added menu settings
  • Added a debug mode that shows the sector points as well as a flanking camera view
The first screenshot shows a monster in "flanking mode" flanking to the player's right. The second shows the automatic view of a monster that has flanked and is facing the player.

This works and should be compatible with any of the other AI mods, I think, although I haven't tested that.

One thing I have noticed (just a reality of what a "flank" is) is that a flank range narrows dramatically as a monster approaches. A monster can appear to be almost in front of the player with a 45 degree flank range and technically be flanking. I've tried a wider range of 65 degrees, which is now the default option, and that seems to work OK. This funnel view can be confusing until you see it from the monster's camera, I think.

The movement should also be somewhat smoother than earlier builds. I accomplished this by greatly simplifying the code that turns on the flanking. The only time a new target is sought is when the "See" state begins:

Code: Select all

					else if (mo.CurState == mo.ResolveState("See"))
					{
						ID_Point ptr = findtarget(mo, player.mo);
						if (ptr)
						{
							flankon(mo, ptr);
						}
					}


Update 12-19-21: revamping detection again with monster cam. Seems to be working. Close to release...

Re: Flankers (BETA) [Update 12/19/21]

Posted: Sun Dec 19, 2021 10:09 am
by Hey Doomer
After spending more time on this, I have made the following changes:
  • Since a monster is "targeting" an ID_Point, using A_Chase(). This bypasses movement code although still having trouble with some stairs.
  • Swapped out ray tracing flank detection with an angle comparison (much cleaner):
    Spoiler:
  • Simplified the flanking action.
    Spoiler:
  • Choose flanking ID_Points over anything else.
  • Added menu options for different center finding techniques (there are now three).
The refactored action is about as simple as I can make it. Flankon is called only once - when an ID_Point is acquired, and Flankoff only twice - when a monster is flanked to the player OR out of range. Much simpler I think.

Monsters now rapidly flank and immediately attack once flanked. If this is tried with E1M1 on the easiest level facing a single Zombieman in debug mode, you can turn this way or that and see how you are flanked by the monster's camera (assuming you are fast enough). The previous movement code caused monsters to get stuck in walls here and there even with a CheckMove(); I assume A_Chase() avoids that.

Irregular polygon center algorithm choices in Options menu could make a difference depending on map design:
  • CenterSpot - a GZDoom sector property (blockmap center)
  • Centroid - a simple average of all vectors
  • CenterOfMass - average of center of all triangles defined by the sector
These all yield slightly different layouts. For now I've done this to ensure target consistency, assuming a player is usually moving or focused on whatever is in view. While this seems to work I'm not convinced it's optimal. It varies depending on the map, but I hope in a large battle it will be effective.

This all seems to work, and monsters flank with considerable speed. It seems (I'm still testing) that once a flankers spots the player it runs around the player faster than a Pinky Demon after chugging a gallon of Red Bull. Turning "Flanker Chance" option to 1:1 (all monsters) makes this confusing and somewhat impossible to beat. I have, however, tested the detection method enough to believe that's working and this is ready for another beta release.

Oh, and a few builds ago (in case I did not mention it) the "flankers" are chosen from non-floating, missile-state monsters. This excludes Cacodemons, for example.

This is getting close to where I'd like to see it and certainly makes flankers hard to catch and kill. Also makes it easier for them to kill you.

Again I don't see this conflicting with any other AI mod.

Re: Flankers (BETA) [Update 12/18/21]

Posted: Mon Jan 31, 2022 3:11 am
by inkoalawetrust
Any chance that you could make this and the Ambushers script also work when two NPCs are fighting each other (e.g a friendly NPC fighting hostile NPCs). Instead of only working with NPCs fighting the player(s) specifically ? Would also be useful if these two scripts could be toggled on a per-monster basis instead of universally applying on all NPCs.

I've also noticed a weird bug with the Ambushers script specifically, when I started it up on E1M1 a few times. It seems to cause some monster to move around while only running the first frame of their See state. While also making their sight sound every time they move.

Re: Flankers (BETA) [Update 12/18/21]

Posted: Mon Jan 31, 2022 11:37 am
by Hey Doomer
inkoalawetrust wrote:Any chance that you could make this and the Ambushers script also work when two NPCs are fighting each other (e.g a friendly NPC fighting hostile NPCs). Instead of only working with NPCs fighting the player(s) specifically ? Would also be useful if these two scripts could be toggled on a per-monster basis instead of universally applying on all NPCs.

I've also noticed a weird bug with the Ambushers script specifically, when I started it up on E1M1 a few times. It seems to cause some monster to move around while only running the first frame of their See state. While also making their sight sound every time they move.
These work by targeting the player, but they could target any hostile I suppose.

I haven't noticed this Ambushers bug - is anything else running when that happens?