Breaking the Game: Mods, Hacks, and Cheats

StupidRPG is very, very modular. Most of the core functionality of the engine and the main campaign are implemented in modules. Examples of modules include: combat (fighting things), social (talking to things), containers (things that open and close), darkness (whether things are visible based on available light), etc. There are a few reasons for this structure:

  • The engine is (mostly) reusable for a completely different type of text adventure
  • The game/engine is relatively straightforward to mod
  • I thought it would be a fun challenge

Long-term, I’d like to expand SRPG to include some level of multiplayer. This has been a driving factor in the architecture of the ECS (Entity-Component-System) library. The ‘player’ is not particularly special in the architecture, by design. NPCs possess a similar ability to influence the game world/state, and Player 2 has little technical distinction from an NPC. By extension, within the context of the engine, the player is just another entity imposing its will on the game state. This is useful.

SRPG has no protection against source modification. The game runs entirely in Javascript in the player’s browser. As such, it is very hackable, moddable, and breakable. This applies to both the singleplayer and multiplayer experience.

I’m ok with this.

I spent a bit of time with Cookie Clicker. Hacker/cheater that I am, I eventually discovered the built-in variables and functions for unlocking content, maximizing stats, and generally breaking the game. This is Not A Problem in Cookie Clicker because the game is single player only. If you ‘ruin’ the experience, you’re only ruining it for yourself. I have the same mentality about SRPG, as I see only two valid use cases: playing alone, or playing with friends. In either case, if you want to mess with the engine or content, go nuts.

StupidRPG: Command Interrupts

I’ll talk more about the language parser in SRPG in a future post, but I need to give a little context for this discussion. In the majority of cases, SRPG is a turn-based, player-driven game. The player enters a command, the game engine parses the command, and the game state is updated accordingly. During normal execution, the player’s input is matched to a ‘verb’ (an action handler) along with relevant nouns (objects/entities) and modifiers (cardinal directions, for example). Various command patterns are supported, from the basic GO NORTH (VERB MODIFIER) to the more complex PUT BEER BOTTLE ON PEDESTAL (VERB NOUN MODIFIER NOUN).

This gives a lot of flexibility, but it’s missing support for a couple of use cases: 1) easter eggs; and 2) sass. As it turns out, the command interrupt feature has some less frivolous applications as well.

What is a Command Interrupt?

A command interrupt is a special callback that interrupts the regular flow of command processing. This allows for contextual, off-the-cuff responses from the player, along with aforementioned non-frivolous uses.

Example:


GM: Deep in the gloom, you see the fearsome troglodyte. You’re not sure how to describe it because you don’t remember what a troglodyte is.

Input: yes i do

GM: Oh, my mistake. It looks exactly like you expected a troglodyte to look.


Now let’s break down what’s happening here.

How does a Command Interrupt work?

The game does not have a verb for ‘yes’ or ‘yes i do’. This is a special case, only available for the next action after the GM’s initial text. The code looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Describe troglodyte
queueOutput("{{gm}}<p>Deep in the gloom, you see the fearsome {{tag 'troglodyte' classes='object enemy' command='x troglodyte'}}. You're not sure how to describe it because you don't remember what a troglodyte is.</p>");

NLP.interrupt(
function() { /* Init */ },
function(string){
NLP.interrupt(null);

if(string == "yes i do") {
queueOutput("{{gm}}<p>Oh, my mistake. It looks exactly like you expected a troglodyte to look.</p>");
} else {
// Resume normal input
queueOutput(parse( NLP.parse(string), {} ));
}
}
);

On line 1, the GM response is queued as normal.

The fun stuff happens on line 4, where an interrupt is registered with the NLP (the Natural Language Processor). This tells the NLP that when the player enters their next command, control should first be handed to the interrupt. The interrupt acts autonomously, deciding whether it wants to handle the action or not.

Line 5 is an optional initialization function triggered when the interrupt is queued. Without getting too far into the weeds, I’ll just say that multiple interrupts can be ‘stacked’. When one is completed (unregistered) the next is enabled.

On line 7, the interrupt deregisters itself. If the player doesn’t immediately enter the response we’re looking for, we’re not going to give them another chance. Interrupts are given first priority until they deregister themselves (this is important because it lets us do more interesting things than simple one-off responses to snarky player text).

On line 9, we check for the string we’re expecting. In this case, we’re looking for the player entering “yes i do” in response to the GM’s “you’re not sure how to describe it because you don’t remember what a troglodyte is”. If we matched the string, we queue an equally snarky response from the GM.

If the string is not matched, we re-parse the command. Since we already de-registered the interrupt, the player’s input will be handled normally, and they will have no idea that there was a special response available.

Alternate Uses

Command interrupts were initially intended for use in easter eggs: special responses from the GM based on the player inputting non-standard commands in specific situations. As it turns out, however, there are better uses for the feature. I’ll cover three use cases:

  • Raw text input
  • Menu selections
  • Dialogue trees

Raw Text Input

This is actually the first thing the player encounters when starting the game. The player is prompted for their name, and a command interrupt is used to capture the (arbitrary) text they entered. As usual, the interrupt can decide how to handle the player’s input, and whether to deregister itself or not.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ADD PLAYER NAME TO INTERRUPT QUEUE
NLP.interrupt(
function(){
// Leadup to player name input
queueOutput("{{gm}}<p>You are an adventurer, but not a very good one. You are known as...</p>", 2000);
queueOutput("{{gm}}<p>Hmm.</p>", 1500);
queueOutput("{{gm}}<p>Who are you again?</p>", 1000);
},
function(string){
if(string.length > 0){
player.name = string;
return true;
}

queueOutput("{{gm}}<p>That name seems kind of...short. I'm not going to steal your identity, I promise.</p>");
return false;
}
);

This interrupt is a little more complex than the previous example. It has an initialization function (which prompts the player for their name), and it has a return value rather than manually deregistering itself. This communicates to the NLP whether the interrupt is done. If the interrupt returns false, it will be executed again on the player’s next input. This is useful because we want to make sure the player has entered valid input for their name. For now, we’re just checking for a non-empty string, but we could create more complex validation in the future.

When we’re satisfied the player’s input is valid, the interrupt returns true, telling the NLP to continue (either to the next interrupt, or back to regular command processing). During the SRPG intro segment, several interrupts are queued to capture the player’s name, race, class, gender, and preferences.

The first interrupt in SRPG is a simple text interrupt. The rest of the interrupts during the SRPG intro are menus. They present a list of options, and validate the player’s input against the available options before allowing them to continue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ADD RACE MENU TO INTERRUPT QUEUE
NLP.interrupt(
function(){
// Build menu
var menu = parse("{{menu races}}", {'races':shuffle(ECS.getData('races'))});
queueOutput("{{gm}}<p>If you say so. Well, {{player.name}}, what manner of creature are you?</p>"+menu);
},
function(string){
player.race = string;
disableLastMenu(string);
if(ECS.isValidMenuOption(ECS.getData('races'), string)) {
return true;
}

enableLastMenu();
queueOutput("{{gm}}<p>That wasn't one of the options. Try again, you rebel.</p>");
return false;
}
);

Note the check on line 11, which validates the player’s input. If the input is invalid, the menu is displayed again. A fair bit of work is done on line 5 to build the menu output, which I’ll cover in another post. In general, a menu option includes some text, and a command to execute when the option is clicked.

Dialogue Trees

Dialogue trees function similarly to menus, in that they present a clickable list of options that cannot be ‘escaped’ until certain criteria are established. You can think of a dialogue tree as a set of interlinked menus with more advanced command interrupt logic (to determine which menu options are currently available, and therefore which player inputs are currently valid). The code for handling dialogue trees is a bit too extensive to present here. When a conversation with an NPC is initiated, the Social module handles the process of finding the correct conversation nodes and setting NLP interrupts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function() {
var conversation = this;
var nodes = this.getCurrentNodes();
var menu = [];
var node = this.currentNode;
for(var n in nodes) {
menu.push({'text': nodes[n].prompt, 'command': nodes[n].key, 'subtext': '<small>'+(nodes[n].visited ? '(Visited)' : '')+'</small>'});
}
menu.push({'text':"<i>(EXIT)</i>",'command':'exit','subtext':"I'm done talking."});

NLP.interrupt(
function(){
node.callback();
queueOutput(parse("{{menu options}}", {'options':menu}));
},
function(string){
if(string == 'exit' || conversation.doTopicNode(string)) {
return true;
}

enableLastMenu();
queueOutput("{{gm}}<p>There is no response.</p>");
return false;
}
);
}

This general-purpose block of code populates a menu with dialogue options, then sets the interrupt. Within the interrupt, we check for valid player input (either selecting a dialogue option or exiting the tree) and process the selected node (if any). Lastly, if the input was invalid, we return an appropriate response (later on this will probably become a callback, so different NPCs can give different “no response” responses).

Topic for tomorrow: the game is a mod of itself.

Post Schedule

The overall goal of this journal is not so much to document my dev progress as it is to encourage my dev progress. The basic idea is that if I have to publish a post every day, I’m also forced to actually work on the game at least semi-regularly. Otherwise I’ll run out of things to talk about. We’ll see how that goes.

My planned post schedule looks something like this:

  • Monday: Weekly Update (actual work done on the game in the last week)
  • Tue/Thu/Sat: StupidRPG post (discussion of a component within the game)
  • Wed/Fri/Sun: General post (technical/writing discussion not directly related to SRPG)

Other than the Weekly Update, I’ll shuffle things around as needed.

StupidRPG: The GM

I’ve mentioned the GM a couple times so far, but I haven’t really gone into detail about what, who, or why the GM is in the game. In storytelling terms, the GM is an unreliable narrator. In mechanical terms, the GM is an NPC with meta-abilities. The Stanley Parable provides an excellent example of an unreliable narrator in a video game.

In SRPG, for the most part, the GM narrates the events of the game and provides some level of commentary on the player’s choices. Ostensibly, the GM (as in D&D or other pen-and-paper RPGs) is responsible for the creation and organization of locations, characters, enemies, events, and so on. This particular GM, however, is both unprepared and over-invested, leading (probably) to clashes with the player, other NPCs, the world, and the game mechanics themselves.

It’s unclear at this point whether the player should consider the GM a friend, an enemy, or a force of nature. Something I need to sort out as the storyline continues. I also need to be careful, as breaking the 4th wall is bordering on a trope at this point. It’s integral to the structure of SRPG, so it has to be done right.


GM: The twins lean forward eagerly, excited at the prospect of delivering their latest riddle.

The Twins: We’re not twins.

GM: Oh. I thought…

The Left Twin: You could have asked, you know. Just because two people like to stand in front of a gate and pose riddles in unison doesn’t mean they’re twins.

GM: Sorry?

The Right Twin: You didn’t even ask our names. You’re still calling us twins in your script. ‘Left Twin’ and ‘Right Twin’. Wow.

GM: What should I call you, then?

The Left Twin: My name is Jane

The Right Twin: And I’m Jack

GM: Ok…Jack and Jane, who are not related, lean forward eagerly, excited at the prospect—

Jack: Seriously? We’re brother and sister, we’re just not twins. What are the odds two complete strangers named Jack and Jane would be doing this?

GM: Fine. I don’t actually care. The two J’s have a riddle.

Weekly Update #1

Paying off some technical debt in this update. The scale of the JS source files is getting to a point where I need to restructure as a sanity saver. There’s more cleanup to be done, but I’ve significantly restructured the folders for both JS and CSS files, and created a build script to automate the process.

I also noticed some unexpected engine-breaking bugs when I first loaded up the game again. I need to either not leave work half-finished, or leave notes for myself.

Changelog:

  • Restructured JS to use a preprocessor (WEPP)
  • Restructured CSS to use a preprocessor (SASS)
  • Added a build script to 1) bake the JS files down to: engine.js and game.js; and 2) update the page CSS
  • Added a function to dynamically reload/switch the CSS themes (which means in the future I can add an in-game command to change the theme)
  • Fixed a couple NLP bugs related to half-finished work from a while back

Known Issues:

  • Dialogue node callbacks aren’t working right. Need more testing and refinement of the callbacks and may need some refactoring of the dialogue tree feature in general.

Dungeons & Dragons

The inspiration for StupidRPG comes from two primary sources: Zork (the original) and Dungeons & Dragons (pick your favorite edition). D&D has had an enormous (monstrous?) effect on the world of video games. Some of the top-rated computer RPGs are based directly on specific D&D rulesets (Baldur’s Gate, Neverwinter Nights, Icewind Dale…ok mostly those three series), and Zork is pretty good too.

I’ve been playing D&D for 9 or 10 years, and GM’d my first game a while back. I first popped a Zork disk in a Commodore 64 closer to 20 years ago (I don’t remember when. It’s not important). Now that I’m all grown up, I’d like to blend the two. D&D offers a freeform experience that is difficult to match in a video game. Fortunately, my goal is a bit simpler: making a fun adventure/RPG game, primarily communicated via text, in which the player and an inexperienced GM (I’ll get to that in a bit) attempt to make their way(s) in the world.

The first game I ran (The Burning Wheel) didn’t go well. I had no idea what I was doing, and produced a tightly-scripted campaign that inevitably fell apart almost instantly. “No plan survives contact with the enemy.” Those mistakes and the many more that followed served both as education (toward becoming a better GM) and inspiration (for the GM character in SRPG). So it all turned out alright in the end.

StupidRPG: The Black Box

SRPG is divided into four sections:

  • Prologue: The Black Box
  • Act I
  • Act II
  • Act III

The prologue is the only part that exists right now. It acts as the test bed for the overall project, but over time it’s being refined into a playable introduction to the game. It’s currently called The Black Box, but it could accurately be referred to as a ‘vertical slice‘ just as easily (though it doesn’t sound as cool, unless we’re talking about a swordfighting game). It has (or will have) all the core components of the overall game:

  • Moving around
  • Scenery
  • NPCs
  • Talking to NPCs (dialogue trees!)
  • Murdering NPCs
  • Riddles and puzzles (so the game takes more than 30 minutes)
  • Containers (which can be opened/closed/locked/unlocked)
  • Darkness / light
  • Scripted segments
  • The GM being a bit of a dummy (more on that later)
  • Two years of on-again, off-again development to make a from-scratch text adventure engine that could have been immediately reproduced at any time by an out-of-the-box system known as Inform 7

Engine development aside, the first part of a game is very important. It sets the tone, introduces key gameplay elements, and does a bunch of other stuff that game designers really value a lot. I have 3 goals for the SRPG prologue:

  1. The player is having fun
  2. The player is mildly confused
  3. The player is having fun

Why two funs? Well, you can’t spell confused without funs.

I also have two anti-goals for the SPRG prologue:

  1. The player should not feel like the experience is random nonsense
  2. The player should not not be having fun

The distinction between mild confusion and random nonsense is important. The game has rules, the world within the game has rules. The rules may not be entirely static, but in general the player can expect their actions to have an effect on the world that roughly corresponds to their intent. As in a procedurally generated world, true randomness is not the end goal. Interesting randomness is more appealing.

Tools of the Trade

Tools I use for work and/or SRPG:

Operating Systems

  • Windows 7
  • Ubuntu
  • CentOS
  • Android

Languages (ish)

  • Javascript / jQuery
  • SASS / CSS
  • Ruby
  • PHP
  • SQL (My, Postgre)

Tools, Frameworks, Platforms

  • AWS / OpsWorks / EC2 / RDS / CloudFront
  • Programmer’s Notepad
  • PHP Storm
  • PuTTY
  • GitHub
  • Laravel, Kohana, Codeigniter
  • Middleman
  • Redis
  • Vagrant
  • ConEmu / CMDer

Not for work or SRPG

  • C#
  • Java
  • C++ (learning…)

StupidRPG Dialogue Trees

A game like StupidRPG wouldn’t be complete without extensive NPC interaction. It’s a crucial part of the roleplaying experience the game is inspired by. The world of text adventures and computer RPGs offer many different ways of handling this. I considered several options, each of which has pros and cons. In the end, I tried to take the pieces I liked from multiple styles. This is a non-exhaustive list of the methods I considered.

Dialogue Tree

Popular in computer RPGs and adventure games, dialogue trees are basically directed graphs. Each dialogue option is a ‘node’ that can connect to a semi-arbitrary number of additional nodes. Significant depth is supported, particularly when alternate nodes are locked behind prerequesites (for example: possessing a certain item, meeting a certain NPC, or meeting alignment requirements). This style of NPC interaction has carried into modern games (pretty much any BioWare game, for starters), with a couple tweaks.

Pros:

  • Conversations of varying complexity supported
  • Easy to set up conversational ‘rabbit holes’ which eventually loop back to a root node
  • Easy to ‘gate’ nodes or chains of nodes behind situational requirements

Cons:

  • Rigidly defines what can be talked about and when
  • Player’s dialogue may not match their conceptualization based on the presented (simplified) dialogue text

Glossary / Dictionary

Commonly seen in classic adventure games and text adventures, where the player can ASK SUSAN ABOUT KOBOLDS or drag an inventory item onto a character to learn more about it.

Pros:

  • Very user-driven, somewhat freeform
  • Allows for hidden interactions / easter eggs

Cons:

  • Inevitably motivates the player to attempt every possible combination of item/topic with each NPC
  • Each interaction is isolated from the rest (lack of continuity)

Commands & Parsing

Low-tech as they are, some text adventures allow the player to give directions to NPCs, such as GARY, GO NORTH AND EAT CAKE. It’s not as complicated as it sounds; once you’ve identified the target of the command, the rest is identical to parsing a normal instruction from the player. I haven’t seen an example of more advanced parsing (e.g. TELL GARY HIS FACE IS BAD), but if such a thing exists, kudos.

Pros:

  • Situationally useful feature when dealing with friendly NPCs

Cons:

  • More complex text parsing is really hard

The StupidRPG Way

I settled on a combination of the first two options, with the third a distant goal if I find a use for it. In SRPG, a command like TALK TO THE OGRES generally initiates a dialogue tree (if one is available). Each node has:

  • Text (actually a callback, in case more complex text generation or other triggers are needed)
  • (Optional) Restrictions (e.g. this node is only available if the player previously fought the minotaur)
  • (Optional) A visibility setting (default is visible, some nodes may be hidden for reasons other than access restrictions)
  • (Optional) A node list (nodes available from this point in the conversation)
  • (Optional) A list of keywords (more on that in a second)

The second option comes into play with a list of topics each NPC can discuss. These are actually tree nodes, but the player can shortcut to them via commands like ASK ELLEN ABOUT DIVORCE.

As indicated in a previous post, the SRPG parser allows for a variety of command patterns, including patterns with ‘overflow’ text (i.e. arbitrary text only understood by a particular command or object). One of the neat things about that is the ability to define custom parsing behaviors for specific NPCs or types of NPCs. For example, a town guard might respond poorly to the (non-standard syntax) statement: SAY 'GO LICK A BEAR' TO CAPTAIN ROTH. The sky’s the limit, really.

So I'm Making a Video Game

I like to describe it as “a text adventure with graphics and sound.”
It’s called StupidRPG and it’s a semi-branching, largely fantasy-based game.
Loosely speaking, it’s the text adventure I want to play.
It’s mostly coded in scratch using vanilla Javascript, with a bit of jQuery on the side.

So far I have:

  • An ECS (entity-component-system) engine to store game state and propagate actions
  • A language parser (to turn “PUNCH GOBLIN” into a meaningful action within the game)
  • 1 troglodyte
  • A nice little forest to wander around
  • Magical swords
  • Modules for: combat, lighting, containers, dialogue trees, and a couple other things
  • Just one magical sword, actually
  • A park ranger
  • Music!

The first two items ate up about 90% of the work invested so far, and now that work is starting to pay off in the form of content creation:

1
2
3
4
5
6
7
8
// Race list
ECS.setData('races', [
{'text':'ELF','command':'elf','subtext':'+ KEEN SENSES, + EVASION'},
{'text':'DWARF','command':'dwarf','subtext':'+ DARKVISION'},
{'text':'CEO','command':'ceo','subtext':'+ PERSUASION'},
{'text':'HUMAN','command':'human','subtext':'+1 TO EVERYTHING'},
{'text':'PAN-DIMENSIONAL BEING','command':'pan-dimensional being','subtext':'- MATERIAL NEEDS'}
]);

I’ll be documenting the rest of the game’s development here.