Random Start [Updated 11/26/21]

Post your example zscripts/ACS scripts/etc here.

Random Start [Updated 11/26/21]

Postby Hey Doomer » Tue Nov 09, 2021 4:05 am

The RandomPlayerStarts keyword will randomize existing spawn points (I have not tried this), but is it possible to have a truly random starting point in a particular level? I did this using ACS with arbitrary limitations:

  • To avoid too many instructions that terminate the script (a runaway), a random search is conducted in a decreasing radius to 64 units
  • The PlayerCheck dummy actor must be within line of sight using CheckSight
  • The new location has a sector height > 64
  • The floor texture isn't blank and the terrain is SOLID (probably not necessary)

DECORATE:
Code: Select allExpand view
Actor PlayerCheck
{
   +Invisible
}


ACS:
Code: Select allExpand view
#library "randomstart"
#include "zcommon.acs"

script "randomstart" ENTER
{
    int tid = 21000;
    int pid = tid;
    Thing_ChangeTID(0, pid);

    int x = GetActorX(0) >> 16;
    int y = GetActorY(0) >> 16;

    int rad = GetUserCVar(0, "rand_radius");
    bool debug = GetUserCVar(0, "rand_debug");

    if (debug)
    {
        Log(f:GetActorFloorZ(pid), s:", ", f:GetActorCeilingZ(pid), s:", ", s:GetActorFloorTexture(pid));
    }
    while (1==1)
    {
        if (debug)
        {
            Log(s:"Checking radius: ", d:rad);
        }
        int new_x = x + Random(0, rad) - Random(0, rad);
        int new_y = y + Random(0, rad) - Random(0, rad);
        int ceilingZ = GetSectorCeilingZ(0, new_x, new_y) >> 16;
        int floorZ = GetSectorFloorZ(0, new_x, new_y) >> 16;
        int sectorHeight = ceilingZ - floorZ;

        if (sectorHeight > 64)
        {
            tid++;
            Spawn("PlayerCheck", new_x << 16, new_y << 16, floorZ << 16, tid);

            if (debug)
            {
                Log(f:GetActorFloorZ(tid), s:", ", f:GetActorCeilingZ(tid), s:", ", s:GetActorFloorTexture(tid));
            }

            if (CheckSight(tid, pid, 0)==true && GetActorFloorTexture(tid)!="" && GetActorFloorTerrain(tid)=="SOLID")
            {
                if (debug)
                {
                    Log(s:"x: ", d:new_x, s:" y: ", d:new_y, s:" sector height: ", d:sectorHeight, s:" floor: ", s:GetActorFloorTexture(tid));
                    Log(s:"moving...");
                }
                SetActorPosition(pid, new_x << 16, new_y << 16, floorZ << 16, false);
                break;
            }
            rad -= 2;
            if (rad < 64)
            {
                break;
            }
        }
    }
}


I've included cvars rand_radius and rand_debug. If the starting area is very small it the script terminates. Otherwise the player is moved to a random point within line of sight before the level appears. I've tested this with a number of random maps, and I haven't seen instances of moving the player into another actor. I suppose that's possible. I also have kept the angle the same, although that can be randomized as well. Generally levels are designed so the player enters a level facing something, and it seems better to keep that as is.

Anyway still playing with this. I'm not sure, for example, if it would crash with multiple hops around the room to (perhaps) start down a corridor. If the script runs too long the player may appear at the mapped spawn point, breaking the effect.

Update
I've added rand_hops as a loop variable to allow the random start to hop around the spawn area. Seems to work.
You do not have the required permissions to view the files attached to this post.
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: Random Start [Updated 11/26/21]

Postby Hey Doomer » Fri Nov 26, 2021 4:04 pm

Interesting problem: How do I know an actor is on the map?

Initially, I wondered if the ceiling, floor, terrain, and other qualities would make a difference. Then I wondered about CheckSight. These do not appear to be reliable. I am trying tagging each sector and using ThingCountSector. This should work if the counted tid is unique and all sectors are tagged the same. Initial test plays look promising.

Zscript:
Code: Select allExpand view
class rs_EventHandler : EventHandler
{
   bool isRestart;
   override void WorldLoaded(WorldEvent e)
   {
      isRestart = e.IsSaveGame;
   }
   override void PlayerSpawned(PlayerEvent e)
   {
      PlayerInfo player = players[e.PlayerNumber];
      if (isRestart)
      {
         player.mo.ACS_NamedExecute("randomstart", 0, 0, 0, 0);
      }
   }
}

class rand_PostProcessor : LevelPostProcessor
{
   // add sector tags to determine if something is on the map
   protected void Apply(Name checksum, String mapname)
   {
      int tag = Cvar.FindCVar("rand_tag").GetInt();
      for (int i = 0; i < Level.Sectors.Size(); i++)
      {
         AddSectorTag(i, tag);
      }
   }
}


I've added a check for IsSaveGame, so the level may continue to be played at a somewhat randomized position from the last save. That's the idea. Not convinced yet that this works.

ACS:
Code: Select allExpand view
script "randomstart" (void)
{
    int tid = 21000;
    int pid = tid;
    Thing_ChangeTID(0, pid);

    int rad = GetUserCVar(0, "rand_radius");
    bool debug = GetUserCVar(0, "rand_debug");
    int hops = GetUserCVar(0, "rand_hops");
    int rand_tag = GetUserCVar(0, "rand_tag");

    while (hops > 0)
    {
        int x = GetActorX(pid) >> 16;
        int y = GetActorY(pid) >> 16;

        if (debug)
        {
            Log(s:"Hop: ", d:hops, s:", ", f:GetActorFloorZ(pid), s:", ", f:GetActorCeilingZ(pid), s:", ", s:GetActorFloorTexture(pid));
        }
        while (1==1)
        {
            if (debug)
            {
                Log(s:"Checking radius: ", d:rad);
            }
            // scouting new location
            int new_x = x + Random(0, rad) - Random(0, rad);
            int new_y = y + Random(0, rad) - Random(0, rad);
            int ceilingZ = GetSectorCeilingZ(0, new_x, new_y) >> 16;
            int floorZ = GetSectorFloorZ(0, new_x, new_y) >> 16;
            int sectorHeight = ceilingZ - floorZ;

            if (sectorHeight > 64)
            {
                tid++;
                Spawn("PlayerCheck", new_x << 16, new_y << 16, floorZ << 16, tid);
                if (CheckSight(pid, tid, 0))
                {
                    int n = ThingCountSector(T_NONE, tid, rand_tag);
                    Thing_Remove(tid);
                    if (n > 0)
                    {
                        if (debug)
                        {
                            Log(s:"x: ", d:new_x, s:" y: ", d:new_y, s:" sector height: ", d:sectorHeight, s:" floor: ", s:GetActorFloorTexture(tid));
                            Log(s:"moving...");
                        }
                        SetActorPosition(pid, new_x << 16, new_y << 16, floorZ << 16, false);
                        break;
                    }
                }
                rad -= 2;
                if (rad < 64)
                {
                    break;
                }
            }
        }
        hops--;
    }
}


This is simpler, mostly because I'm no longer looking at textures, which appear to be irrelevant. Before adding the thing count, I noticed that in a number of cases CheckSight fails or, more precisely, one can still be "seen" if off map. I'm still relying on that and searching for the PlayerCheck actor in all sectors.

Dunno... is there an easier way?
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