[Solved] Struggling to get NPCs spawned and patrol on a randomly generated map

Curt A. P.
31 Jan 2022, 13:15

For map regeneration I use this script from a previous post: https://textadventures.co.uk/forum/quest/topic/td5yfgkdpu__qojdlp1xmq/solved-the-compass-doesnt-show-diagonal-exits-directions
(With a bug fix since then)

I try to find a way to spawn NPCs at random in the Before first enter script for each room. I have it working with stackable items, but I don't find a good way to spawn a random amount of NPC that don't stack.
In my first try I want to clone and move a random amount of sheep. Then when they are cloned and moved I want to implement the patrolling script, but first things first. For now I want to get them spawned. So far I didn't get any errors, but also no sheep spawning.

Before first enter called by a script attribute:

foreach (obj, GetDirectChildren( z_vault_npc_peaceful)) {
  if (obj.spawn_plains > 0) {
    random_chance = GetRandomInt(1,1000)
    if (random_chance <= obj.spawn_plains) {
      obj.amount_to_spawn = GetRandomInt(obj.spawn_plains_amount_min, obj.spawn_plains_amount_max)
    }
  }
}

This is what I have to get the chance and amount and I struggle to spawn a few sheep.


mrangel
31 Jan 2022, 13:50

I assume that z_valut_npc_peaceful is a room/container containing the prototype sheep?

That all seems valid.

So far I didn't get any errors, but also no sheep spawning.

You didn't show us the code that's supposed to spawn the sheep. So far, you're picking a couple of random numbers. I would have expected it to be something like:

foreach (obj, GetDirectChildren( z_vault_npc_peaceful)) {
  if (obj.spawn_plains > 0) {
    random_chance = GetRandomInt(1,1000)
    if (random_chance <= obj.spawn_plains) {
      obj.amount_to_spawn = GetRandomInt(obj.spawn_plains_amount_min, obj.spawn_plains_amount_max)
      for (n, 1, obj.amount_to_spawn) {
        newclone = CloneObjectAndMove(obj, this)
        // "start patrolling" or path initialisation code would go here if needed
      }
    }
  }
}

Curt A. P.
31 Jan 2022, 14:13

I assume that z_valut_npc_peaceful is a room/container containing the prototype sheep?

That's correct.

You didn't show us the code that's supposed to spawn the sheep. So far, you're picking a couple of random numbers.

Yes, because I couldn't figure out how to to do it in the same script.
So, and I have no clue what the for function is doing. I took a look at it every now and then over the last years but never understood...


mrangel
31 Jan 2022, 15:16

So, and I have no clue what the for function is doing. I took a look at it every now and then over the last years but never understood...

It's like foreach, but instead of going over a list of objects it's just doing numbers.

In this case, if obj.amount_to_spawn is 5, the stuff inside the loop will be run once with n being 1, then 2, 3, 4, and 5. So… it does the contents of the loop 5 times. I assume that's what "amount to spawn" is for.


Curt A. P.
31 Jan 2022, 15:23

In this case, if obj.amount_to_spawn is 5, the stuff inside the loop will be run once with n being 1, then 2, 3, 4, and 5. So… it does the contents of the loop 5 times.

for (n, 1, obj.amount_to_spawn) 

Then, what's n and what's 1 referring to? Just need to understand a bit more...


mrangel
31 Jan 2022, 17:20

n is a variable that contains the number.
The second parameter, 1, is where the count starts.

So for example,

for (number, 4, 9) {
  msg ("The variable is " + number)
}

would give you the output:

The variable is 4
The variable is 5
The variable is 6
The variable is 7
The variable is 8
The variable is 9

In the spawning example, we never use n inside the function because we just want to do the same thing several times.

There's also an optional "step" parameter, which isn't often used. It lets you have the number increase by different amounts. For example, you could make it negative to count backwards:

for (countdown, 5, 1, -1) {
  msg ("Launching in " + countdown)
}

would give you:

Launching in 5
Launching in 4
Launching in 3
Launching in 2
Launching in 1


Curt A. P.
02 Feb 2022, 13:58

Oh man, I had a great time figuring out why the script wasn't working... I had some typos and wondered why I didn't get any error messages. Well, because I have the screen cleared when moving. So, found some more typos thanks to the error messages and it is working.

Still, I can't say I understand the "for" function completely... does the variable matters for anything else? And don't know where the variable gets it number from...
The first parameter is where the count starts, but I don't know what's being counted. I really try to understand this function... Sorry


So, now I have sheep spawning and want them to patrol.

this.patrol => {
  exit = PickOneUnlockedExit (this.parent)
  this.parent = exit.to 

I want to add a convenient way to call the script after a certain amount of time has passed. I am still using the in-game clock via the game.epoch attribute.

this.move_turn_created = false
this.changedmove_turn_created => {
  create turnscript (this.name + "_patrol_turnscript")
}

These are just some snippets how I wanted it to work but I don't find a way to start the cycle.

if (StartsWith(this.parent name, "area_rdm_plains")) {
  this.old_epoch_patrol = game.epoch   
  this.time_staying_here = GetRandomInt(1800,43200)
   SetTurnScript (this.name + "_patrol_turnscript") {
      if (this.time_staying_here >= this.old_epoch_moving + this.time_staying_here) {
        do (this, "patrol")
        }
    EnableTurnScript (this.name + "_turnscript")
    }
}

Or, is a turnscript even a good way to do this?


mrangel
02 Feb 2022, 15:56

I'm not sure what all the variables in this script are for.
If you're using an epoch time system and want multiple NPCs to be patrolling, I suspect it would be simpler to use one changescript to handle all of them.

An example of how I'd handle it:
Script to spawn an NPC:

foreach (obj, GetDirectChildren( z_vault_npc_peaceful)) {
  if (obj.spawn_plains > 0) {
    random_chance = GetRandomInt(1,1000)
    if (random_chance <= obj.spawn_plains) {
      obj.amount_to_spawn = GetRandomInt(obj.spawn_plains_amount_min, obj.spawn_plains_amount_max)
      for (n, 1, obj.amount_to_spawn) {
        newclone = CloneObjectAndMove(obj, this)
        newclone.patrol_after = game.epoch + GetRandomInt(obj.min_patrol_wait, obj.max_patrol_wait)
      }
    }
  }
}

And in game.changedepoch:

foreach (obj, AllObjects()) {
  if (HasInt (obj, "patrol_after")) {
    while (game.epoch > obj.patrol_after) {
      params = QuickParams ("patrol_time", obj.patrol_after)
      obj.patrol_after = obj.patrol_after + GetRandomInt(obj.min_patrol_wait, obj.max_patrol_wait)
      if (HasScript (obj, "patrol")) {
        do (obj, "patrol, params)
      }
      else {
        exit = PickOneUnlockedExit (obj.parent)
        if (not exit = null and HasObject(exit, "to")) {
          obj.parent = exit.to 
        }
      }
    }
  }
}

Giving an object a patrol_after attribute causes it to move after the epoch reaches that number.
This makes a patrolling object move through a random unlocked exit. If you want a particular NPC to have keys for unlocking doors or similar, you can give it a "patrol" script attribute and it will use that instead.

I made this slightly more complex to allow different NPCs to patrol at different speeds using the same script. Just give them numeric min_patrol_wait and max_patrol_wait attributes. I also let the patrol scripts for NPCs have access to a variable named patrol_time which gives the epoch value for the time they moved at. This means that they can override their own patrol_after attribute if you want them to wait longer in particular rooms or something.


Curt A. P.
03 Feb 2022, 09:49

This line is genius. So simple against what I tried to get the same

        newclone.patrol_after = game.epoch + GetRandomInt(obj.min_patrol_wait, obj.max_patrol_wait)

What does while () do?

while
while (expression) { script }
Run a script while the given expression returns true.


        do (obj, "patrol", params) // had a small typo 

The test yesterday with sheep spawning before adding the patrol scripts went well.
The patrol scripts are working fine but the game's running significantly slower as soon as 1-5 sheep are spawned. 1 sheep slow. 4 sheep veeeeeery slow. Where I generated dozens of rooms with many more sheeps in yesterday's test, now I got very long loading times from the start. (When sheeps are spawned). Not sure what to thing of it...


mrangel
03 Feb 2022, 09:58

I used while instead of if because I don't know if you have any occasions where the epoch will increase by a lot (like the player is asleep for 8 hours). It makes the NPCs keep on patrolling until their patrol_after time is in the future again, taking multiple steps if necessary.

The patrol scripts are working fine but the game's running significantly slower as soon as 1-5 sheep are spawned.

I'm not sure why that would happen. Have you got the game somewhere I can take a look at it?


Curt A. P.
17 Feb 2022, 08:19

Sorry for the late answer. I moved into a new apartment and didn't find the time to continue this project.

I'm not sure why that would happen. Have you got the game somewhere I can take a look at it?

I just tested it again and now it seems to be running fine. Normal loading times, so I assume it was just a server sided issue (?)