Standard development disclaimers apply. This is pre-pre-Alpha content, everything is subject to change, features may not be present in the final version, there's a very strong chance none of this will ever be released, etc. etc.
There shouldn't be any plot spoilers in these posts, but there will be occasional discussions related to characters, locations, mechanics, and other aspects of my potential upcoming Shadowrun campaign (tentatively titled "CalFree in Chains"). You may wish to skip them if you'd like to be completely surprised.
Milestone passed! I wrapped up work on the climax, hurrah, which means I've finished a rough first pass at all the scenes in the game, double-hurrah.
What that means and does not mean:
- All planned combat encounters have been implemented. Tuning still remains.
- All unique puzzles and dialogues within missions have been implemented.
- The finale (post-climax wrap-up) still remains.
- The hub (your home base) remains and will be huge.
- Non-unique puzzles and dialogues still remain.
The specific features that I'm working on now are barks and banters. Let's look at them in a bit more detail.
A "bark is a short phrase that a character utters during the course of gameplay, usually in response to some action or event. Examples include "Nice hit!" and "Ouch!" They're a staple of party-based RPGs, and I've been doing them since the very first Antumbra episode.
For the most part, making a campaign is very time-consuming, but not especially hard. I decide what thing to do, and then I carefully do the thing, then I test the thing, then I move on to the next thing, and repeat until the game is done. Barks are one of the only features of my campaign that actually involve some cleverness on my part.
The goal here is to randomly fire off a phrase, unique to the character, when they defeat an enemy NPC. I start by defining all of the available phrases. Shadowrun doesn't have support for arrays, but I'll accomplish something similar by appending integers to well-defined strings, forming a DIY lookup table.
Next, I set up the trigger.
Let's break this down, piece by piece.
- This is set to "Retain", so the same character can bark multiple times on a map. If I wanted to limit to a maximum of one bark, I could un-check this. That's what I do for some other types of barks, like downed ones, because it's annoying to hear characters constantly complain.
- Per the "When" clause, the "Triggering Actor" is the one doing the killing, and the "Triggering Target Actor" is the one being killed.
- Check the tag for the killing actor. Tags are very common and important for a lot of my crew-related triggers like this one. Here, I'm checking to see if Valiri is the one doing the killing. Because I'm checking the tag, I'll find a match even if she's added through the hiring interface.
- I don't want to bark every time, so I flip a virtual N-sided die, and only bark if it comes up with a 1.
- Note that I'm using a "Const" variable here for the number of sides on the die. During testing, I can set this to 1 and it will fire every time Valiri kills someone. When the game is released, I'll change it to something like 4, so she'll bark 25% of the time. This is very important, since I can just update it in one place and not need to modify a ton of existing triggers.
- Next, I roll another 10-sided die to pick the number of the bark. Note that this matches the number of barks I defined in global variables above.
- I use text expansion to substitute the bark text. Note that I'm actually using a nested substitution here: if I roll a 3, then this will become $(story.Bark-LongText_Valiri_3), which in turn will become "You're no match for me!".
- This shows in a speech bubble over the killer. While testing, I noticed that the text would sometimes overflow the speech bubble. My theory is that this is because the game assigns a width to the speech bubble based on the length of the un-expanded string defined in the trigger, and not the substituted string that is eventually inserted. I worked around this by changing the variable name from Bark-Valiri_N to Bark-LongText_Valiri_N, which makes it long enough to show.
I follow a similar process for other types of barks, such as crew reactions when they are downed and dying.
Once I got it working, I adapted it for the other crew members, tested them, and then copy-pasted them to all combat scenes in the game. There's no such thing as a "global trigger" in Shadowrun, but if you set up your triggers so they don't rely on scene-specific elements, then it's trivial to copy them throughout the game and be confident that they will work (though testing is still always a good idea).
Banters are in-scene conversations between crew members. These are also fairly common in party-based RPGs, particularly the BioWare ones that I love so much. I've been doing versions of these as well ever since the Antumbra days, although mine got more sophisticated in Caldecott.
First, create the dialogue.
This particular conversation exclusively occurs between two crewmates, without any intervention from you. Other banters might include dialogue responses from the player character, or a third crew member might jump in. They're designed to be fairly generic, so they make sense regardless of which particular scene it ends up firing on.
Note that each line is spoken by a TAG'd character, set to the crew member's tag. Again, this is important since crew members will usually (not always) be loaded by player spawners through the hiring interface, so we can't directly assign an actor to the line.
Unlike barks, which can repeat multiple times, a player should see each banter no more than once over the course of a game. To ensure this, we create global variables that will track which banters the player has previously seen.
These start as "false", we'll flip each to "true" once they've been seen.
Trigger time! I follow the "tripwire" design of banters, similar to what's used in Dragon Age: Origins. When your player crosses an invisible line in the map, we check for available banters and display one of them if available. That process starts like this:
- Most banters would be inappropriate in the middle of a firefight, so I only trigger them if you're in peaceful freemove mode.
- The player character needs to trigger it. Otherwise it might fire when an NPC (like a patrolling soldier or wandering civilian) crosses the threshold. Not necessarily a problem, but it's simpler to test and verify this way.
- Depending on the map, I might wait until you have collected a certain item or started/finished a certain quest. That helps this feel more dynamic, and less like "Oh, I passed an invisible tripwire."
- Similarly, for this specific banter, the "Wait until triggered again" means that this won't fire until the second time a player crosses this line - again, that helps it feel more natural/random/dynamic.
- Finally, I run a banter trigger. This works somewhat similarly to the bark trigger above. Here, I'm dynamically building the name of the trigger to run, which will vary from BanterA1 to BanterA7.
- Note that there is no "When" clause. This will only be invoked from the tripwire banter trigger above.
- Check to make sure I haven't already shown this banter during the game.
- Check if both of the participants in this banter are present. In Caldecott, I did this by checking if the actor was in the region, but that was a bit tedious since I would need to update the region for every scene. Now, I just check to see whether the actor is alive, which lets me copy-paste this with no edits.
- If this banter is valid to be shown, I flip the boolean so it isn't shown again, and then start the dialogue.
- In this particular case, since there aren't any PC0 lines, I directly start the dialogue between the two crew members, which allows me to have them face each other. In other cases, I would instead start the dialogue with the first speaker in the banter.
- If this banter is NOT valid, then we run the next banter trigger. This will repeat the same steps for the next banter, run the following trigger if it is not valid, and so on and so forth.
- Eventually, either a valid banter is found (in which case the chain stops); or we exhaust all of the banter triggers. Because "Retain this Trigger after firing" is not checked, eventually we'll run Banter A7, which says "Run BanterA1"; but Banter A1 will be inactive and unretained, and so that will break the loop.
As with barks, I set this all up in a test map, ran a ton of tests (selectively loading other crew members and toggling the "Bantered" booleans on and off) until I was satisfied, then copied to all other scenes. There are a lot of triggers involved, but they're fairly simple, and I don't expect that I'll need to update them. (Though I will not in passing that, unlike in Caldecott, I do have separate "sets" of banters this time around separated by acts, which lets me write banters that assume certain events have or have not occurred. Which is to say, there are more than 7 possible banters, it's just that this particular scene only has 7 banters available.)
And that's that! This is all coming together very nicely and smoothly, mostly because my process is very close to what I did on Caldecott and I've already invested the necessary time into conceptualizing and debugging this kind of design.