0 votes
asked by (120 points)
Is there a way to make an NPC that wanders from passage to passage in Twine? So far, the best I can come up with is to use Sugarcube to randomly determine if the NPC is present, but I would prefer something similar to the "adventurer" in Zork (I believe) where it would say "the adventurer heads east" or whatever and you could follow him if you wanted to. That would be ideal. I just can't see how to make it work with Twine.

2 Answers

0 votes
answered by (59.9k points)
edited by
Twine doesn't have any sort of world model built-in, so things like directions and rooms don't have any specific meaning. To create a system where the player can follow an npc, you'd need to either track their position in the game world non-randomly (i.e. using turns()) or create some sort of data structure to represent a map with directions the player can follow. Neither is particularly difficult, though the latter would require more work.
commented by (2.1k points)
Would JavaScript be able to come up with something like that?
commented by (59.9k points)
edited by
Yeah. There's no particular reason you couldn't do it in TwineScript, though. You'd need a data structure, like nested arrays, for example, that layout rooms or zones tied to passages, potentially with their own properties like items or occupants.

This recipe shows an incredibly simple map implementation: https://twinery.org/cookbook/dungeonmoving/sugarcube/sugarcube_dungeonmoving.html

Something generally like that but that holds objects with references to all the data you need is not particularly difficult to implement, but would require a fair amount of forethought and work.

Unfortunately, anything that requires a fair amount of forethought and work is hard to provide examples for, because there are a number of ways to approach it based on the needs of a given story, meaning there's no one-size-fits-all solution.

Part of this is because some IF / adventure game engines think of rooms as the atomic building block of the game, while Twine things of passages (i.e. the rendered content) as such. The latter is less opinionated and can handle a greater variety of games, while the former essentially forces the author to write in a certain way but has the benefit of making that one specific way easier, like Inform.

So implementing a room system or world model where items or NPCs can be in certain places and move logically to other places is possible but requires you to basically build it yourself.
0 votes
answered by (22.3k points)

Here is an example having two npcs moving randomly through the rooms of an apartment. First we go to StoryInit and assign each room their proper exits and give the npcs their starting location:

<<set $bedroom to {
name: "bedroom",
exits: ["hallway"],
<<set $hallway to {
name: "hallway",
exits: ["bedroom", "bathroom", "livingroom"],
<<set $bathroom to {
name: "bathroom",
exits: ["hallway", "livingroom"],
<<set $livingroom to {
name: "livingroom",
exits: ["bathroom", "hallway"],
<<set $rooms to [$bedroom, $hallway, $bathroom, $livingroom]>>

<<set $john to {
name: "John",
room: "bedroom",
location: $bedroom,
<<set $marie to {
name: "Marie",
room: "bedroom",
location: $bedroom,
<<set $npc to [$john, $marie]>> 

Next we set up a widget in a properly tagged passage that handles the movement of the npcs by randomly choosing one of the exits of their current room before updating their new location. Additionally we let the player know where the npc went should they be in the same room:

<<widget "move">><<nobr>>

<<set $args[0].room to $args[0].location.exits.random()>>

<<if $args[0].location.name == passage()>>
	$args[0].name heads to the $args[0].room.<br>

<<for _i to 0; _i lt $rooms.length; _i++>>
	<<if $rooms[_i].name == $args[0].room>>
		<<set $args[0].location to $rooms[_i]>>


And that's basically it. Whenever we call this function, the specified npc moves to a random passage connected to their current one.

To test out the code we trigger it for both npc in the PassageHeader passage - meaning that whenever a player enters a room, both npcs move - also we add a wait button that just reloads the passage:

<<for _j to 0; _j lt $npc.length; _j++>>

<<move $npc[_j]>>


[[Wait|passage() ]]<br><</nobr>>

In the PassageFooter we put some code letting the player know when there is an npc present in the room:

<<for _i to 0; _i lt $npc.length; _i++>>

<<if $npc[_i].room == passage()>>
	$npc[_i].name is here.<br>

	$npc[_i].name: $npc[_i].room<br>


This method will of course be too work intesive if you have a lot of rooms, like in a procedurally created dungeon labyrinth - in that case you could create a system with coordinates adding and substracting from the x and y coordinates of the npc to move them across the map as long as the new tile in question is one that can be moved upon.

But I think this should at least show the rough principle of one possible way to implement npc movement.