NPC Movement

Not really having an error here. More like a question.

In the book I am currently writing I intend to have a single NPC monster that moves around a facility the PC is inside of. I would prefer the NPC to move semi-randomly (it is not a very intelligent creature) but not so randomly as to be moving through walls the player cannot move through.

The reason this is important is because simply put; the player cannot kill it. They cannot reason with it nor can they trap it (permanently anyways).

Using *rand can set a value for the creature (kind of jury-rigging here) within a specified range (for example there are 6 rooms, if rand = 6 then the creature is in room 6) but is there a way to limit the creature (without going into a massive amount of lines of C-Script) from going from A to C when the player from room A can only move to B?

Movement-wise, I’m looking to put the creature and the player on equal terms in regards to their abilities to maneuver. If possible, I’d also be looking to have it set up so that the player is aware of the creature’s presence in a neighboring room (for example player is room 3 which connects to 2 and 4 and thus either value causes the player character to hear noises; indicating the monster is nearby) so that the player has indicators that they need to hide or go someplace else.

Obviously if the movement is completely random, this is very, very unfair to the player who can only move between a small and very set number of points from any given point. It’s also more unfair because the player has no possible way of predicting the creature’s movements and adapting accordingly.

This particular section of the novel is horror-esque though the player can kill the creature under -very- specific circumstances (having a specific item and a particular skill).

I will also note, this is a SIDE-MISSION in the game and it is made abundantly clear to the PC which skillset they will need to complete this mission. Even if the PC has this skillset they can completely avoid the mission (no stat check, no need to take specific plot avenues to avoid it) by simply clicking the “do not accept this mission” button.

2 Likes

By that, do you mean the creature cannot occupy the same room as the player during the next turn? Hypothetically, this should work:

*rand mc_room 1 6
*rand enemy_room 1 6

*label repeat
*if (enemy_room = mc_room)
  *rand enemy_room 1 6
  *goto repeat

You are in room number ${mc_room}. The monster is in room number ${enemy_room}!

Somewhat.

Basically this:

Room 1 is connected to 2 and 3
Room 2 is connected to 1 and 4
Room 3 is connected to 1 and 4
Room 4 is connected to 1 and 3

If the creature is in room 3, for example, it should only be possible for it to move between there and to room 1, 4 or stay in the same place.

The PC will have complete control over their own movements in this instance. That’s easy enough to do with labels and *goto commands. I’m just trying to make the creature not do something like this:

Creature is in room 4. There is no passage but now it’s in room 2 randomly.

You can’t really shortcut this, if you want code to adhere to some rules, then you need to define those rules in code. The code needs to be told what it can and can’t do.

So you need a way to “model” the the layout of the rooms. One possibility is to use the array syntax to create location “objects” that have next/prev (or first/second) attributes that represent the “paths” between rooms.

Here’s a working example, with some comments… But please don’t just copy and paste it, try to understand it (feel free to ask questions) and adapt it to your specific requirements.

*create implicit_control_flow true
*create location_name_1 "Room 1"
*create location_next_1 2
*create location_prev_1 3

*create location_name_2 "Room 2"
*create location_next_2 4
*create location_prev_2 1

*create location_name_3 "Room 3"
*create location_next_3 4
*create location_prev_3 1

*create location_name_4 "Room 4"
*create location_next_4 1
*create location_prev_4 3

*create player_location 1
*create monster_location 2

*label move
You're in ${location_name[player_location]}
*choice
	# Go to ${location_name[location_next[player_location]]}
		*set player_location location_next[player_location]
		*gosub move_monster
		*goto move
	# Go to ${location_name[location_prev[player_location]]}
		*set player_location location_prev[player_location]
		*gosub move_monster
		*goto move

*label move_monster
*temp monster_move
*comment each room has two adjacent rooms, so we 50:50 which way the monster will go:
*comment we could also add code here to "sniff out" if the player is an adjacent room, but I leave that as exercise for the reader!
*rand monster_move 0 1
*if (monster_move = 0)
	*set monster_location location_next[monster_location]
*else
	*set monster_location location_prev[monster_location]
*if (monster_location = player_location)
	RAWR! YOU GOT EATEN =(
	*line_break
*return
5 Likes

I won’t use code if I don’t understand it so no worries on that front. Is it possible to still use this with temp variables as well?

There’s no difference between temps and creates, except temps are lost on scene change. If all the code remains in one scene, then yes, you can use *temps.

2 Likes

So to make sure I understand correctly it would be like this?

*temp MonMove
*temp PlayerLocation 0
*temp MonLocation 0

(text here describing things)

*choice
     #Slide to the left (Room 1)
       *set PlayerLocation 1
       *gosub MonMoveSub
       *goto Room1
     #Slide to the right
       *set PlayerLocation 2 (Room 2)
       *gosub MonMoveSub
       *goto Room2
     #Criss cross!
       *set PlayerLocation 3 (Room 3)
       *gosub MonMoveSub
       *goto Room3

*label MonMoveSub
*if (MonLocation = 1)
 *rand 0 2
  *if 0
   *set MonLocation = 1 [no movement in this, monster stays put in room 1]
  *if 1
   *set MonLocation = 2 [monster moves to room 2]
  *if 2
   *set MonLocation = 1 [monster moves to room 3]```

Would I then send it to another *gosub to check player location and monster location? If so does: 

*if (MonsterLocation = PlayerLocation)
*goto Munched
*if ((MonsterLocation - PlayerLocation) = 1)) or ((MonsterLocation - PlayerLocation = -1)
insert variable text here?```

Edit: I am apparently horrible at using ` marks.

If the rooms are actually arranged like this, there is a simpler way to implement this.

First, number the rooms in either a clockwise or counterclockwise manner, you can still name the rooms differently, but for the movement this scheme is more convenient.

Now, you can randomly set the monster’s location and create the variable for movements.

*comment set initial monster location
*create monster_location 4
*create monster_movement 0

Then, when updating the position, you randomly generate an integer -1, 0, or 1 corresponding to (in this case) counterclockwise movement, no movement, and clockwise movement.

*comment determining monster movement
*rand monster_movement 1 3
*set monster_movement (monster_movement - 2)

*set monster_location (monster_location + monster_movement)
*if monster_location > 4
    *set monster_location (monster_location modulo 4)

If your actual room layout is more complex, other logic will be needed and more complex systems will be needed.

7 Likes

Thankies thankies. This is a bit easier to follow.

1 Like

Just as a reference for anyone stumbling on this post in the future: @Speedcubing_Gaming’s solution is easier to understand and fits this one scenario well, but @CJW’s solution is more easily expanded to fit different scenarios.

So I’d like to encourage anyone interested in meddling with Systemic Game Desing (as far as ChoiceScript allows it anyway) to learn how to use arrays. :hugs:

6 Likes

My last game Stab the Assassin! has 10 different NPCs wandering around a ship, and all of them have to follow the physical layout of the ship (instead of randomly appearing/disappearing) when moving from one room to another.

The way I did it was to create one file called “router.txt” that lists all possible routes that can be taken from any given room (although the player doesn’t ever see it, all rooms in the game are numbered, for the sake of making the coding easier. But this works equally well if rooms have LOWERCASE names with no spaces, i.e. “dungeon_room”).

Then, a *rand operation determines which room the monster moves to next from the available exits in the current room.

router.txt is split into labels with numbers, such as:

*label 1

*rand diceroll 1 3

*if dieroll = 1
  *set monster_route 13
 
*if dieroll = 2
  *set monster_route 5

*if dieroll = 3
  *set monster_route 11

*return

*label 2

*rand diceroll 1 4

*if diceroll = 1
  *set monster_route 6
 
*if diceroll = 2
  *set monster_route 1

*if diceroll = 3
  *set monster_route 27

*if diceroll = 4
  *set monster_route 5

*return

Each label (such as “1”) in route.txt is the route information for a particular room. Notice how Room #1 has three possible exits while Room #2 has four possible exits.

Then, in your main action scene, you write:

*gosub_scene router {current_room}

Where $current_room is the room the monster is in right now. This subroutine then identifies which room the monster can move to next based on where the monster is right now.

The next line in your action scene is:

*set current_room monster_route

The monster has now moved from its current room to the new room thanks to just two lines of code.

And if you wanted to, you could shorten it to just one line of code by putting the *set current_room monster_route line inside of route.txt.

PS - To see if the monster is in the same room as the player, just add *if player_room = current_room to trigger the text/choices for interacting with the monster.

3 Likes

This topic was automatically closed 24 hours after the last reply. If you want to reopen your WiP, contact the moderators.