[SOLVED] RandomSpawner adding spawning conditions

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

[SOLVED] RandomSpawner adding spawning conditions

Postby Virathas » Mon Nov 23, 2020 3:54 am

Hey everyone!

Since ZScript was implemented a lot options appeared. And i believe it is time to "remaster" a few features to work better than they used to be:

In this case i wanted to improve my RandomSpawners to have additional limits in spawning, specificly spawned actor height and radius. While i try to use "similar" size monsters to reduce potential problems, some need to be higher. For instance, as a DoomImp potential replacement a Heretic Knight can spawn. For many monsters i can create an ACS script that simply checks if the actor is too high, and if so, remove and then place the spawner again.

The problems start with "radius" as it is not as simple to verify a monsters is "fitting". And the problems continue with Boss-like monsters (Mancubus, Arachnotron, Baron of Hell, Cyberdemon, Spidermastermind), as the earlier system will break any Boss event checks.

Frankly, I have no clue how to improve the RandomSpawner, although it's code does look like i could add some more "conditions" to spawn.

To picture an example:

Code: Select allExpand view
Actor DoomImpSpawner : RandomSpawner replaces DoomImp
{
   DropItem "DoomImpDropper", 255, 28
   DropItem "KnightDropper", 255, 10 // this one is taller than original imp
}
Actor Cacospawn : RandomSpawner replaces CacoDemon
{
   Dropitem "CacoDemonDropper", 255, 50
   Dropitem "TerranWraith", 255, 5 // This one is larger
}


In a related matter, could the RandomSpawner at spawn time read what classes of players/game settings are set to further refine the setting? For example disabling particular monsters or restricting item spawns(This one would be trmendous).
Last edited by Virathas on Mon Dec 07, 2020 7:13 am, edited 1 time in total.
User avatar
Virathas
 
Joined: 10 Aug 2017

Re: RandomSpawner adding spawning conditions

Postby Virathas » Tue Nov 24, 2020 5:55 am

Ok, i was successfully able to add the monster height restriction, with no ill effects... mostly.

The code of the spawner so far is:
Code: Select allExpand view
class SuperSpawner : RandomSpawner
{
        // Test for if item
   static bool IsItem(DropItem di)
   {
      class<Inventory> pclass = di.Name;
      if (null == pclass)
      {
         return false;
      }

      if (GetDefaultByType(pclass).amount > 0)
      {
         return true;
      }
      else
      {
         return false;
      }
   }
   override void BeginPlay() // Most is unchanged
   {
      DropItem di;   // di will be our drop item list iterator
      DropItem drop; // while drop stays as the reference point.
      int n = 0;
      bool nomonsters = sv_nomonsters || level.nomonsters;

      Super.BeginPlay();
      drop = di = GetDropItems();
      if (di != null)
      {
         while (di != null)
         {
            if (di.Name != 'None')
            {
               if (!nomonsters || !IsMonster(di))
               {
                  int amt = di.Amount;
                  if (amt < 0) amt = 1; // default value is -1, we need a positive value.
                  n += amt; // this is how we can weight the list.
               }
               di = di.Next;
            }
         }
         if (n == 0)
         { // Nothing left to spawn. They must have all been monsters, and monsters are disabled.
            Destroy();
            return;
         }
         // Then we reset the iterator to the start position...
         di = drop;
         // Take a random number...
         n = random[randomspawn](0, n-1);
         // And iterate in the array up to the random number chosen.
         while (n > -1 && di != null)
         {
            if (di.Name != "None" && IsItem(di)) // Debug
            {
               A_Log(TEXTCOLOR_RED .. di.Name .. "is an item" .. Pos);
               class<Inventory> itemek = di.Name;
            }
            if (di.Name != 'None' &&
               (!nomonsters || !IsMonster(di)))
            {
// The following functions if there is vertical room for the actor
               Class<Actor> cls = di.Name;
               readonly<Actor> newmobj= GetDefaultByType(cls);
               double SectorHeight = ceilingz - floorz;
//               A_Log(TEXTCOLOR_RED .. newmobj.RestrictedToPlayerClass .. Pos);
               if (SectorHeight >= newmobj.Height)
               {
               int amt = di.Amount;
               if (amt < 0) amt = 1;
               n -= amt;
               if ((di.Next != null) && (n > -1))
                  di = di.Next;
               else
                  n = -1;
               }
               else
               {
                  A_Log(TEXTCOLOR_RED .. "Actor re-randomized at" .. Pos); // Debug text
                  di = di.Next;
               }
            }
            else
            {
               di = di.Next;
            } // No more edited stuff
         }
         // So now we can spawn the dropped item.
         if (di == null || bouncecount >= MAX_RANDOMSPAWNERS_RECURSION)   // Prevents infinite recursions
         {
            Spawn("Unknown", Pos, NO_REPLACE);      // Show that there's a problem.
            Destroy();
            return;
         }
         else if (random[randomspawn]() <= di.Probability)   // prob 255 = always spawn, prob 0 = almost never spawn.
         {
            // Handle replacement here so as to get the proper speed and flags for missiles
            Class<Actor> cls = di.Name;
            if (cls != null)
            {
            //   A_Log(TEXTCOLOR_RED .. test.Height);
               readonly<Actor> newmobj= GetDefaultByType(cls);
               double SectorHeight = ceilingz - floorz;
               if (SectorHeight < newmobj.Height)
               {
               A_Log(TEXTCOLOR_RED .. "Knight too high at" .. Pos);
               }
               Class<Actor> rep = GetReplacement(cls);
               if (rep != null)
               {
                  cls = rep;
               }
               
            }
            if (cls != null)
            {
               Species = Name(cls);
               readonly<Actor> defmobj = GetDefaultByType(cls);
               Speed = defmobj.Speed;
               bMissile |= defmobj.bMissile;
               bSeekerMissile |= defmobj.bSeekerMissile;
               bSpectral |= defmobj.bSpectral;
            }
            else
            {
               A_Log(TEXTCOLOR_RED .. "Unknown item class ".. di.Name .." to drop from a random spawner\n");
               Species = 'None';
            }
         }
      }
   }
}


Now i am trying to create additional restrictions for item spawning depending on present player classes. For example:
2 players in game, Marine and Hexen's Cleric - so only items for them appear and no items for Corvus.

I still am thinking how to create a list of player classes available, so i can iterate

And additionally, i have no idea how to test for "radius" when spawning, especially considering the fact that enemies can be placed pretty tight and still right.
User avatar
Virathas
 
Joined: 10 Aug 2017

Re: RandomSpawner adding spawning conditions

Postby Player701 » Tue Nov 24, 2020 12:59 pm

Virathas wrote:And additionally, i have no idea how to test for "radius" when spawning, especially considering the fact that enemies can be placed pretty tight and still right.

You can spawn an actor and call TestMobjLocation to verify that it isn't blocked by anything. According to the source code, it checks for both radius and height blockage, so you probably don't need your own code for the latter. If TestMobjLocation returns false, immediately destroy the spawned actor and try again.

UPD:

Virathas wrote:Now i am trying to create additional restrictions for item spawning depending on present player classes. For example:
2 players in game, Marine and Hexen's Cleric - so only items for them appear and no items for Corvus.

If you're using Inventory.RestrictedTo and Inventory.ForbiddenTo, you can spawn an item and call this method:

Code: Select allExpand view
private static bool IsUsefulToAnyPlayer(Inventory item)
{
    for (int i = 0; i < MAXPLAYERS; i++)
    {
        if (!playeringame[i])
        {
            continue;
        }
       
        if (item.CanPickup(players[i].mo))
        {
            return true;
        }
    }
   
    return false;
}

If it returns true, keep the item. Otherwise, destroy the item and try again.
User avatar
Player701
 
 
 
Joined: 13 May 2009
Location: Russia
Discord: Player701#8214
Operating System: Windows 10/8.1/8/201x 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: nVidia with Vulkan support

Re: RandomSpawner adding spawning conditions

Postby Virathas » Wed Nov 25, 2020 4:12 am

The TestMobjLocation works like a charm! While i still need to insert a few failsafes to prevent game crash/lockup it does solve this problem :)

For items(and monsters actually) i never thought of spawning them and running their functions - i tried checking their "default types". Though it did occur to me, that simply using the restricted/forbidden system (That covers majority of cases), will have a few "holes" in the system i.e There are no eligible classes to pick ANY item of the particular spawner. So i did think: Add variables to the actor, a list of player classes akin to the restricted/forbidden ones, that simply say "if only players of this classes are in game first item spawned will be good (since we don't care about an item in this case). As an improvement to that, i was thinking of another variable "FallbackActor" - actor to spawn if "we dont care what to spawn" or some other error occurs.

Thing is, i was unable to add a new variable that i was able to access in DECORATE.
Is it possible to define a new variable/property and then access it via DECORATE? Or do i have to remain fully in ZScript in this case?
User avatar
Virathas
 
Joined: 10 Aug 2017

Re: RandomSpawner adding spawning conditions

Postby Player701 » Wed Nov 25, 2020 9:28 am

Virathas wrote:There are no eligible classes to pick ANY item of the particular spawner.

I get it that such a thing can happen, but I'm not sure if I fully understood the way you were trying to solve it. IMO, this should be done via the spawner itself, not by modifying item classes. Add a property to your random spawner class and use its value to spawn a fallback item if it turns out that no item from the normal list can be picked up by any player. You can define your property as follows:

Code: Select allExpand view
class MyRandomSpawner : RandomSpawner
{
    private class<Inventory> _fallbackItemClass;
   
    property FallbackItemClass: _fallbackItemClass;
   
    // TODO: Code that uses the value of _fallbackItemClass
}


Then, when you subclass your spawner to define lists of actors to spawn, specify the fallback item class when necessary:

Code: Select allExpand view
class ConcreteSpawner1 : MyRandomSpawner
{
    Default
    {
        // TODO: List of items goes here
       
        MyRandomSpawner.FallbackItemClass 'HealthBonus';
    }
}

class ConcreteSpawner1 : MyRandomSpawner
{
    Default
    {
        // TODO: List of items goes here
       
        MyRandomSpawner.FallbackItemClass 'ArmorBonus';
    }
}

Note that in your code, you should use _fallbackItemClass to get the value. If you don't set it, it will be null by default.

Also see: ZScript custom properties
User avatar
Player701
 
 
 
Joined: 13 May 2009
Location: Russia
Discord: Player701#8214
Operating System: Windows 10/8.1/8/201x 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: nVidia with Vulkan support

Re: RandomSpawner adding spawning conditions

Postby Virathas » Thu Nov 26, 2020 4:48 am

That was exactly what i wanted to do, i might have written my thoughts too confusing,

I tried adding either variable OR property to the spawner, but never both - that's why i failed to add them.

Everything now works like i wanted, i still have some "kinks" i need to work out for this, and i'll happily share the "improved", or at least with additional conditions, in this thread.

Thanks a lot for help!
User avatar
Virathas
 
Joined: 10 Aug 2017

Re: RandomSpawner adding spawning conditions

Postby Virathas » Thu Nov 26, 2020 1:07 pm

Apparently i did come across a problem with "monster replacement checking: While the script technically works, with Spawn monster -> Check if it fits, get data -> Destroy it, some data still persists - i did see that "monster count goes up when spawned but is not reduced upon destroying. I do not know if it is the only thing "remaining". Since that was not working, i've tried getting the data by getting default data of an actor: readonly<actor> Monster = GetDefaultByType(class), and then accessing Monster.TestMobjLocation(), and afterwards destroying it. to be safe. However, that created a completely different issue - spawning "phantom" monsters, fully invisible, undetectable, untouchable, but active and deadly nonetheless. Is there a better way to do so?
User avatar
Virathas
 
Joined: 10 Aug 2017

Re: RandomSpawner adding spawning conditions

Postby Player701 » Thu Nov 26, 2020 1:11 pm

I'm pretty sure what you're attempting is considered undefined behavior. To fix the issue with the kill counter, simply call ClearCounters before destroying the actor.
User avatar
Player701
 
 
 
Joined: 13 May 2009
Location: Russia
Discord: Player701#8214
Operating System: Windows 10/8.1/8/201x 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: nVidia with Vulkan support

Re: RandomSpawner adding spawning conditions

Postby Virathas » Tue Dec 01, 2020 1:39 am

And yet another problem surfaced, one i did not expect. While the spawner and all conditions are working perfectly fine, sometimes some items are not "restricted". After some debugging, I have concluded that simply the spawner is initalized way before Players are actually initalized, so no item restrictions apply at that time. While in most cases it is not a problem, since Player spawns are usually set before other items, i cannot "trust" that it will always be so. I thought of 3 different solutions for the problem, although I am not sure if they are even valid.
1. Instead of a spawner replacer, create a "new" actor, that, after 1 tick, will remove itself and create the spawner.
2. Add some sort of delay or wait function in the spawners "BeginPlay" (I have no idea how to do this, if it is even possible)
3. Move all spawning further down the line, from BeginPlay to PostBeginPlay, hoping that all Players will be initialized by then.
User avatar
Virathas
 
Joined: 10 Aug 2017

Re: RandomSpawner adding spawning conditions

Postby Player701 » Tue Dec 01, 2020 1:51 am

Hmm. You might be right. I've looked up the way the engine handles the class filter for mapthings, and it doesn't use players[...].mo for that. Instead, it uses the data directly from PlayerInfo (aka players[...]). You can get the class via players[...].cls, but since Inventory::CanPickup accepts an Actor and not a class, you would have to replicate its functionality, which wouldn't be entirely robust considering that CanPickup is virtual and can be overridden in derived classes. If you don't want to copy-paste code from gzdoom.pk3, I suggest you go with option #3 and see if it solves your problem.
User avatar
Player701
 
 
 
Joined: 13 May 2009
Location: Russia
Discord: Player701#8214
Operating System: Windows 10/8.1/8/201x 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: nVidia with Vulkan support

Re: RandomSpawner adding spawning conditions

Postby Virathas » Tue Dec 01, 2020 2:42 am

I was unable to get data from players[0].cls, as it it still null at the time.
I did override the postbeginplay, and simply put all the random spawner code, and ran "Super.PostBeginPlay()". So far, i see no problems with it, but we'll see.

Also, great thanks for helping me out here, while i have a ton of experience in Decorate, ACS and significant in C++ and Basic, Zscript still was "mysterious" to me, and you helped me understand much of it :)
User avatar
Virathas
 
Joined: 10 Aug 2017

[SOLVED] Re: RandomSpawner adding spawning conditions

Postby Virathas » Mon Dec 07, 2020 7:13 am

So it is done, and works as intended (well, for me, there might be some issues with it, but not affecting anything that it is planned for. I am sharing the code for it here, as I am sure some peopple might find it useful.

Spoiler:
User avatar
Virathas
 
Joined: 10 Aug 2017


Return to Scripting

Who is online

Users browsing this forum: No registered users and 0 guests