How to track multiple NPCs?

Twine Version: 2.3.9 (online)
Story Format: Sugarcube 2.31.1

I am writing an interactive fiction story. Throughout the course of the story some of the NPCs that the character meets can and will die, some by design others by choices made. The player has a ‘rolodex’, in which I track all the notable NPCs the player has met, as well as their relationship status. For example in my StoryInit I have the status key-value-pair and variable set as:

<<set $relations to {
	0: "Not Met",
	1: "Unknown",
	2: "Acquaintance",
	3: "Friend",
	4: "Best Friend",
	5: "Involved",
	8: "Deceased",
	9: "Enemy"
}>>

<<set $rstatusnpc1 to 0>>

As the player progresses, NPC1 (for example) will change up and/or down throughout the story. Once the NPC has been met, I use math.clamp() to ensure any changes don’t go outside the 1-5 range. The rolodex hides entries that are marked ‘0’ and displays the rest of them with the status displayed as the text for the key.

If NPC1 dies in Chapter 2, I would set the variable to 8, and the rolodex would reflect they are deceased. At this point I want that variable locked so that I don’t accidentally clobber myself at some point in a future chapter and accidentally resurrect the NPC.

My current status updating is fairly simple, maybe too much on the simple side, but they can happen pretty often in some sections depending on choices and conversations:

<<set $rstatusnpc1 to Math.clamp($rstatusnpc1 - 1, 1, 5)>>

In this case the status would drop by 1 and stay within range of 1-5. This would prevent me from accidentally setting the status back to 0 or 6+

If the status is set to 8 (NPC is deceased), and I run the set macro example a couple lines above, it sets the variable to 5 as I’ve said that’s the upper limit of the range. The problem is, for a dead NPC this would then set them to ‘Involved’. Now, I don’t want the players involved with dead bodies, as necrophilia is not somewhere I want to go. So I want to make sure not to make that mistake. :slight_smile:

I could toss an ‘if’ around it to filter out the 8 and 9 values before setting, but I’d rather not have to do that each time I need to do a change. In some sections the status can change a few times, for better and/or worse.

I’ve debated trying to write a widget to manage the status changes so it can do the backend processing and prevent me from inadvertently resurrecting an NPC. I’ve incorporate some example/template widgets but need to put some time into actually writing one from scratch. A widget may be the easiest way to properly maintain the state with proper sanity checking built right in, to ensure I don’t break things.

Does this make any sense? Is there a better way to handle it?

Thanks in advance!

This is just my take on it, but I wouldn’t do any error prevention like that. I think you should just be careful to not set the status to something that it’s not. Like why would set a character to involved if they’re dead? How you got into that situation is more a cause of concern than trying to put a bandaid over it, IMO.

If you’re worried about being deceased or an enemy conflicting with their relationship status, maybe consider making them separate variables. Unfortunately, being a best friend and being dead aren’t mutually exclusive.

Thanks for the comments.

The problem I’ve run into, is that in Chapter 1 alone, one of the NPCs has a potential for 10-11 different times the state could change. Not wanting to inadvertently kill, turn an enemy, or set to unmet was why I went with the clamping, so they don’t stray outside the state if they are met. The states could change up, down, or no change, based on how the player plays. And that assumes they even meet the NPC, which I don’t know about until it’s time to check the state. As the writer right now, I don’t know if the reader/player will even meet the NPC let alone what the state will be at any given point in the story. Because of the number of potential options, I can’t say "at this point in Chapter 1, the player will be a status 1 or a 2 with the NPC. They may not have met them, they could be dead or an enemy, or involved with them.

A couple of examples:
set to 1 when you meet the NPC:

  • 1st playthrough of Chapter 1 status changes: up, down, up, up, down, up, enemy, dead, still dead, still dead, still dead.
  • 2nd playthrough of Chapter 1 status changes: up, up, up, down, up, down, up, no change, down, up.
  • 3rd playthrough of Chapter 1: up, down, down, down (clamping keeps it at 1), down, dead, still dead, still dead, still dead, still dead, still dead.
  • 4th playthrough of Chapter 1: never met, all other 10 are never met.
  • This can continue through many iterations.

All of the above are in very different states at the end and at any given point along the way.

This is why I wanted to go with the clamping to ensure it doesn’t inadvertently stray outside the accepted ranges of 1-5, but those only work if the NPC has been met. If they haven’t met, it turns the 0 into a 1, of if they are dead or enemy it pulls them down to involved. I could use 'if’s or 'switch’es but I was trying to see if there was a simpler way than doing that each time I need to change the states.

You might want to take a look at my FlagBit code. That would let you use the first three bits on a single variable to track numbers 0-7, and also have flags for “Deceased” and “Enemy” on that same variable. Then you’d only need to test for the “Deceased” flag, even if the main number changed.

Hope that helps! :grinning:

I did find your site, by complete accident yesterday, while looking for a tooltip macro. Your Hovertip Macro has been fantastic for that. I bookmarked the site, as there are a couple of other things I wanted to go back and take another look at.

I did see the Flagbit code, but a good chunk of it went over my head. But it could be a good fit, as it would let me not only track their immediate status (dead, not met, etc.) but to also track the relationship status. That’s quite interesting, as then the contacts page would allow tracking those that have died, as well as their last relationship status with you. Almost like a wall of loss (if in a relationship) or of victory (if an enemy). Currently if they die, that’s all lost.

A lot of it does go over my head, but I think it’ll be worth the effort to try and figure out, as it’ll help me accomplish something else I was trying to see if I could do.

I’m not really a Twine guy, so there might be other reasons to not do this (or perhaps it’s just too complicated for your story), but you could perhaps have a more basic “health status” (possibly just alive/dead) and a more freeform “relationship status” (starting at 0 for neutral, swinging both positive and negative). That way when changing one you can’t accidentally be changing the other.

Then your named states become thresholds on that status (relationship > 5 is “Acquaintance”, relationship > 20 is “Friend” etc); this lets you keep track of smaller increments to status that don’t yet cross a threshold. Known/unknown can be separate as well or you can bundle that into the “health status” – you probably don’t want to use the “relationship status” for that; doing something nice and then equally nasty to someone doesn’t make them unknown again.

These don’t have to be separate variables, you can (probably) just make the main variable an object that contains these values as subfields. (Or use a FlagBit, if that’s better for game performance for some reason – although at a glance it wasn’t clear if that lets you store negative numbers.)


Or another option – either for this way or your original way – would be to create a function/macro that does the “increment relationship status” logic, and make sure everything uses that instead of doing it directly. That way your “are they alive?” check only happens in one place so you can’t forget to do it.

Routing similar behaviour through one place is a good idea anyway, as that lets you more easily tweak it later, e.g. if you wanted to print something (perhaps only when testing) to show what relationship changes were happening.

If it helps, I added a little tool to make it easier to figure out what number range any particular number of bits represents. (You may have to hit CTRL+F5 to refresh the page if you don’t see it.)

Positive numbers only, but you can work around that by adding N to the number parameter when using setVal(), and subtract that same N when using getVal().

Trying to go over the code for it, and like I said some of it is a bit over my head, so bear with the potentially simple question.

The part I’m having a bit of trouble wrapping my head around is the initial setup. Currently I’ll have 3 variables, one for each NPC, and it will max out around 9 or 10 if all holds togethers.

I see references to the variable “$room[1][5]” in the sample, but I’m not sure what that is in reference to. In my case, I will be using variables (for example): $stateNPC1, $stateNPC2, and $stateNPC2. Are they set up initialized or uninitialized in the StoryInit or does the below automatically handle that?

<<set setup.setFlagName($stateNPC1, "met", 0)>>
<<set setup.setFlagName($stateNPC1, "alive", 1)>>
<<set setup.setFlagName($stateNPC1, "enemy", 2)>>
<<set setup.setFlagName($stateNPC1, "unknown", 3)>>
<<set setup.setFlagName($stateNPC1, "acq", 4)>>
<<set setup.setFlagName($stateNPC1, "friend", 5)>>
<<set setup.setFlagName($stateNPC1, "bff", 6)>>
<<set setup.setFlagName($stateNPC1, "involved", 7)>>

Then I would set up each value as follows, and repeat for 2 and 3?

<<set $stateNPC1 = setup.setFlag($stateNPC1, "met", disabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "alive", enabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "enemy", disabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "unknown", disabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "acq", disabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "friend", disabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "bff", disabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "involved", disabled)>>

Then when an NPC is met, I’d simply do the following?

<<set $stateNPC1 = setup.setFlag($stateNPC1, "met", enabled)>>
<<set $stateNPC1 = setup.setFlag($stateNPC1, "unknown", enabled)>>

From there, I’ll have to play with the logic to facilitate changes in the states and statuses.

  • When a status changes, I’ll need to find what bit is currently true, unset it, and then set the one above or below it within the clamping of bits 3-7.
  • A non-enemy character should not drop below Unknown or raise above Involved.
  • An enemy bit is only set at specific moments, and then the bit set between 3-7 switched to false.

Or did I completely miss the boat here?

The $room[1][5] code is a two dimensional array representing a number of rooms.

Rather than giving each NPC a different variable, you should us an array for them.

You have to initialize the variables yourself in the StoryInit passage.

If you have, for example, 10 NPCs, then you could simply initialize that array like this in the StoryInit passage:

<<set $stateNPC = new Array(10).fill(0)>>

That will create an array with 10 slots, and all of the slots will be filled with zeros. With that, instead of doing $stateNPC1, you’d do $stateNPC[0] (since arrays start at zero) or $stateNPC[_index], where _index is a variable holding the NPC number (0 through 9). Just change the number inside Array(10) if you want a different number of NPCs.

Since they’re initialized to zero, it’s like all flags are set to false on it. Thus you only need to enable the flags you want by setting them to true.

Now for how you’d initialize your flags, I’d recommend using what I suggested earlier, using the first three bits as a value to represent the relation, and use the rest of the bits as flags. So you’d initialize your flags in the StoryInit section like this:

<<set setup.relation = [ "Not Met", "Unknown", "Acquaintance", "Friend", "Best Friend", "Involved" ]>>
<<set setup.setFlagName("enemy", 3)>>
<<set setup.setFlagName("dead", 4)>>

Since the “relation” array won’t ever change, we put that on the SugarCube setup object too.

If you want to initialize the first NPC as a friend, then you could do this:

<<set $stateNPC[0] = setup.setVal($stateNPC[0], 2, setup.relation.indexOf("Friend"))>>

Or, since you don’t have to worry about them having any other flags set yet, you could just do:

<<set $stateNPC[0] = setup.relation.indexOf("Friend")>>

Note that the capitalization of "Friend" has to exactly match the capitalization you use when creating the setup.relation array, otherwise that will set $stateNPC[0] to -1, which will likely break your code.

If you want to initialize the second NPC as an enemy, then you’d do this:

<<set $stateNPC[1] = setup.setFlag($stateNPC[1], "enemy", true)>>

(Note that, instead of enabled or disabled, you’ll need to use something like true or false there.)

Also, keep in mind that you don’t have to create all of the NPCs immediately. In fact, it’s best to only add them as you need them. To add an NPC you’d simply do this:

<<set $stateNPC.push(0)>>

That would add an NPC initialized to 0 at the end of the $stateNPC array. The index of that NPC would be $stateNPC.length - 1.

If you want to add them with the relationship as “Unknown”, then you’d do this instead:

<<set $stateNPC.push(setup.relation.indexOf("Unknown"))>>

At any point later on in your game, you could check the status of any NPC by doing something like this:

NPC #_id status: \
<<if setup.hasFlag($stateNPC[_id], "dead")>>\
	Dead
<<elseif setup.hasFlag($stateNPC[_id], "enemy")>>\
	Enemy
<<else>>\
	<<print setup.relation[setup.getVal($stateNPC[_id], 2)]>>
<</if>>

In this example the _id variable is used to indicate the NPC number, so you’ll need to set that variable beforehand. The above code will first check to see if that NPC is dead, if they’re not dead it checks to see if they’re an enemy, and if they’re not an enemy, then it checks the relationship value stored in bits 0 through 2, and translates that into a name using the setup.relation array we created in the StoryInit passage.

If you want to determine if the PC has met the NPC, then you just need to check to see if setup.getVal($stateNPC[_id], 2) > 0.

If you want to increase an NPC’s relation value, then you’d do this (in this example _index indicates the NPC number):

<<set _newVal = (setup.getVal($stateNPC[_index], 2) + 1).clamp(1, setup.relation.length - 1)>>
<<set $stateNPC[_index] = setup.setVal($stateNPC[_index], 2, _newVal)>>

That would get the current relation value, held in the first three bits (bits 0, 1, and 2), of that NPC, and then try to add one to that value, but limit it to a range from 0 to the length of the setup.relation array - 1. Then it would set that as the new value for those three bits.

If you want to decrease the relationship, then simply change the “+ 1” to “- 1” in the above code.

Hopefully that clears things up. :grinning:

1 Like

It’ll take a bit to wrap my mind around all of that, but it’s probably the best explanation of two dimensional arrays I’ve comes across thus far. Reading what’s written there, I think I’ve got an idea of how it works… but not something I’d have ever figured out on my own…

Thanks!

There was only one line that talked about 2D arrays, and it didn’t even explain them, so I suspect you’re getting concepts mixed up…

A 2D array $x[a][b] is just a 1D array $x[a] that itself contains more arrays, that then finally contain values (or in other words, an array of arrays of values). You could just as easily make a 3D array, which is an array of arrays of arrays of values, or to any depth you like.

Most of that post was talking about a 1D array of FlagBits, though, which is a different thing. A FlagBit isn’t an array, although it is another way to encode multiple pieces of data together.

1 Like

Correct.

If, for example, you wanted to create a 2D array for a 20x10 grid of rooms filled with zeros then you could do this:

<<set $rooms = Array.from(Array(20), function () { return new Array(10).fill(0); })>>

(That’s a shortcut to create a 2D array in JavaScript. See the Array.from() method for details.)

Then you could access the value for any room using $rooms[_x][_y], where the _x and _y variables represent the X (0 to 19) and Y (0 to 9) coordinates on the map, respectively.

Heh… I used to give one of my teachers in college headaches by using 4D and higher arrays, apparently because he couldn’t visualize them. They aren’t particularly efficient memory-wise in most cases, but they tend to be pretty efficient for execution speed (for non-Twine code, anyways).

Correct. Basically, it treats a JavaScript integer value as though it’s a 1D array with 32 Boolean (true/false) values, by using each “bit” (1/0) that makes up the integer individually.

Anyways, I hope that helps clear up any 1D array/2D array confusion. :grinning:

Fair point… but since I didn’t even know about 2D arrays before this thread, he essentially doubled my knowledge on the topic. But even if he was just making it up, I wouldn’t have even known. :smiley:

Thanks again for the extensive details. I’m still tinkering in a demo story to try and make sure I have everything working as I hope it will work.

I understand how the visualizing of the bits works, it’s just getting used to the interface. I’ve got it flipping the bit when someone is met. Using your example, I’m working through the process of setting a newly met NPC as unknown/enemy and whatnot. I’m hoping to start incorporating it into my story starting later today.

I’ve got this working now I think. Had to massage a couple of minor tweaks in, but I’ve got 2 NPCs initializing when they are met, and then changing the status.

It took a bit to figure out how the commands were working, and I generated more errors than should be natural, but I’ve made 4 changes without throwing any red now.

I tried to rename the topic to make it more relevant, such as how to track multiple NPCs, but it won’t seem to let me do that anymore. That said I’ll be marking your answer as correct as it seems to be serving me well, especially for much simpler ‘if’ statements to test if they are in a state to be changed (namely alive).

Fixed.

Glad I could help. :grinning: