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

Post your example zscripts/ACS scripts/etc here.

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

Postby Hey Doomer » Sun Dec 12, 2021 6:00 am

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:
You do not have the required permissions to view the files attached to this post.
Last edited by Hey Doomer on Sat Dec 18, 2021 12:01 pm, edited 4 times in total.
User avatar
Hey Doomer
 
Joined: 25 Sep 2021
Operating System: Windows 11
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

Re: Flankers (Experimental)

Postby Enjay » Sun Dec 12, 2021 6:15 am

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.
User avatar
Enjay
Everyone is a moon, and has a dark side which he never shows to anybody. Twain
 
 
 
Joined: 15 Jul 2003
Location: Scotland

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

Postby Hey Doomer » Tue Dec 14, 2021 12:25 pm

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 allExpand view
   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.
You do not have the required permissions to view the files attached to this post.
Last edited by Hey Doomer on Tue Dec 14, 2021 12:40 pm, edited 1 time in total.
User avatar
Hey Doomer
 
Joined: 25 Sep 2021
Operating System: Windows 11
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

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

Postby Enjay » Tue Dec 14, 2021 12:33 pm

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 allExpand view
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
User avatar
Enjay
Everyone is a moon, and has a dark side which he never shows to anybody. Twain
 
 
 
Joined: 15 Jul 2003
Location: Scotland

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

Postby Hey Doomer » Tue Dec 14, 2021 7:00 pm

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 allExpand view
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
User avatar
Hey Doomer
 
Joined: 25 Sep 2021
Operating System: Windows 11
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

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

Postby openroadracer » Wed Dec 15, 2021 3:20 pm

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...
User avatar
openroadracer
 
Joined: 23 Sep 2019
Operating System: Windows Vista/7/2008 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

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

Postby Hey Doomer » Thu Dec 16, 2021 3:58 am

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-)
User avatar
Hey Doomer
 
Joined: 25 Sep 2021
Operating System: Windows 11
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

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

Postby openroadracer » Thu Dec 16, 2021 5:17 am

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?
User avatar
openroadracer
 
Joined: 23 Sep 2019
Operating System: Windows Vista/7/2008 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

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

Postby Hey Doomer » Thu Dec 16, 2021 9:23 am

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 allExpand view
               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.
User avatar
Hey Doomer
 
Joined: 25 Sep 2021
Operating System: Windows 11
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

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

Postby Hey Doomer » Sat Dec 18, 2021 12:25 pm

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 allExpand view
               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...
You do not have the required permissions to view the files attached to this post.
User avatar
Hey Doomer
 
Joined: 25 Sep 2021
Operating System: Windows 11
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support

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

Postby Hey Doomer » Sun Dec 19, 2021 10:09 am

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.
You do not have the required permissions to view the files attached to this post.
User avatar
Hey Doomer
 
Joined: 25 Sep 2021
Operating System: Windows 11
OS Test Version: No (Using Stable Public Version)
Graphics Processor: ATI/AMD with Vulkan Support


Return to Script Library

Who is online

Users browsing this forum: No registered users and 1 guest