I'm alive! I'm still working on Primetime Detective. Making games on the side with a full-time job and a one-year-old is /hard/.
Nothing has visibly changed about the game since I first posted about it, but lots has changed behind the scenes. Most of this time has been focused on the parser, which takes the raw .txt files I write and transfers them into the game.
These text files are 'storylets' - small pieces of narrative that prompt the player to make a decision - in a similar mould to Fallen London or other interactive fiction games. They also confer structural information (what other storylets they link to) and can change contents based on the current game state.
Creating this system has all been ad hoc and designed to solve the game I'm making, so it's only after the fact that I've been reading about different formats for interactive stories. The closest to what I've made is
what Emily Short calls a salience-based narrative: each storylet has a set of parameters that include or exclude it from a list of potential stories according to whether the game state makes those parameters true; the player makes decisions in storylet that change the game state; the game chooses the next storylet according to which parameters are now true.
There's more structure to this as indicated by each story being an episode of murder mystery television: there needs to be an intro scene that sets up a crime; there need to be scenes of interrogation and investigation; there needs to be a twist; and there needs to be a conclusion, in which you accuse someone and succeed or fail. But within there's a lot of flexibility I define within the storylets.
The greater challenge in all this has been coming up with markup language for these storylet text files that doesn't become too cumbersome or intrusive when writing. I'd say I've failed at this - it works for me, but would be a nightmare for anyone else to use. The issue is that there are so many wildcards: a suspect responding in an interrogation needs to say lines of dialogue which take into account i) their current mood ii) whether they're lying or not iii) the tone in which the player asked the question (grumpily, happily, casually, etc.) iv) the procedurally generated facts of the case, including the name of the victim, any evidence, location of the crime, etc. v) their relationship with other suspects... It's a lot to account for.
But it all works now, more or less. I'm currently in the process of writing a set of simple, sample scripts, that would allow episodes to be played through from beginning to end. It won't reflect the full variety available, because with just a couple of episodes players won't be able to radically change the game state in the way they will eventually. These types of branching narratives get better when you have a density of storylets large enough that the player's available options, and the resulting consequences, becomes thrilling. That's a ways off, but a playable build draws nearer.
I can now write something like this in a text file:
*It was you!
{amIGuilty(true): What possible reason would I have for doing that? ~motiveList() %accuse%}{amIGuilty(false): You're a fool. %accuse% >wrong=all< }
And when the player says "It was you!" in the game, it'll look up this text file, and then if the player is right, have the suspect say, "What possible reason would I have for doing that?", then draw a list of possible responses for the player from the 'discovered motives' list, and register this whole thing as a scene of accusation. If they're wrong, it does the "You're a fool." response, then skips to a new part of the script called "wrong" and plays out everything present in that scene. This is the tip of the iceberg.
Thanks for reading! I have no idea if this is of interest, but I need need need to get into the habit of talking about the game again. To end, here's a partial list of commands and variables I can place in text files and the parser can interpret.
//this denotes a comment
*name - names a section, like an anchor you can search for
//%name%,%askedabout%,%asked%,%opinionOf%
//pronouns: (the game'll support other pronouns than just he/she, at least for player characters)
%heShe:victim%
%himHer:suspect%
%hisHer:guilty%
//names
%victimFull% - full name
%victimF% - first
%victimS% - second
//evidence
%evidence% - mention an existing piece of evidence at random
%evidenceNew% - mention a not-yet discovered piece of evidence, and reveal it
//%crimeScene% - the crimescene, duh
//{asked(>2):}, amIGuilty(true), amIGuilty(false), chance(50), high(patience), low(patience), high(rel), low(rel), mid(rel)
//{whatDoIKnow(questionPartial):} - I think this reveals something random that I know about the person I’m asking about
//goalTarget(questionPartial), goalTarget(!questionPartial), goalTarget(me) - if the suspect’s goalTarget is being asked about, or not, or is themselves
//~patience(-10), relVal(-10),
//~questionAdd(What’s a dog?) - adds a question to the person being interrogated
//~questionAll(What’s a dog?) - adds a question to every suspect
//~questionNamed(What’s a dog?) - adds a question to the suspect named as part of another question
//~questionGoalTarget(What’s a dog?) - adds a question to a suspects goalTarget
//~motiveList() - lists the motives you’ve discovered about that suspect as questions.
//~evidenceFind(all) - find all evidence
//~evidenceFind(1) - find the specified number of evidence
// ~answer(name) - grab a random answer from the ‘name’ suspect/character and start an interrogation with them
// ~answer(scene) - 'scene' is literal, not a placeholder. Grabs answers from the currently open file.
// %accuse% - jump to the fourth act.
// %motiveNew% - reveal and name an as-yet undiscovered motive.
//>grab< from *grab a random part of the script and >grab=all< to grab everything from *grab
//>grab=file=filename< - grab everything from section 'grab' in file filename
//>grab=filerand=filename< - grab something random from section 'grab' in file filename
//>grab=desc< - grab a random description line from file filename
//if the scenechange doesn't specify (eg. >grab<) then it'll look at the script for line types. If THOSE don't specify, I think it'll default to dialogue?
?
//~traitAdd(traitName)
//{trait(traitName): If you have this trait, this message will appear.}
//#name - add ‘name’ to a list, so it can be looked up later and used as a checked flag
//!name - look up the list of checked flags to see if ‘name’ exists, proceed if true