While my original goal for creating my [[https://gitlab.com/howardabrams/emacs-rpgdm][rpgdm project]] for Emacs was to help running a role playing game using a laptop instead of a /Dungeon Master Screen/. However, once all my games moved online, having my notes in an org file next to all my friend's faces, was doubly helpful. However, what I used the most from this project was the random tables. Need a name? ~F12 c name~ ... What's the weather like? ~F12 c weather~ (and with completing helpers like [[https://github.com/raxod502/selectrum][selectrum]] and the like, I only needed to type ~we~).
However, the struggles for getting friends to play online proved as challenging as getting them around the table, so I started looking for [[https://www.dicebreaker.com/categories/roleplaying-game/how-to/how-to-play-tabletop-rpgs-by-yourself][solo rpg options]] and discovered [[https://www.ironswornrpg.com/][Ironsworn RPG]]. The bulk of the game is its /moves/ and its /oracles/ (random tables for /everything/). I easily copied sections of the [[https://docs.google.com/document/d/11ypqt6GfLuBhGDJuBGWKlHa-Ru48Tf3G_6zbrYKmXgY/edit#heading=h.xl9vk0d7wwn3][SRD]] into org files, [[file:tables][tables]].
After listening to the author, [[https://twitter.com/ShawnTomkin][Shawn Tomkin]], play the game in his podcast, [[https://ironsworn.podbean.com/][Ask the Oracle]], he used [[https://roll20.net][Roll20]]. The [[https://wiki.roll20.net/Ironsworn][character sheet]] was brilliant, as each move was /described/ along with being able to roll on them. While I love physically rolling dice, perhaps mimicking the approach in org files in Emacs for [[https://www.ironswornrpg.com/products-ironsworn][solo play]] may be ideal.
Neither this, nor the [[https://gitlab.com/howardabrams/emacs-rpgdm][rpgdm project]] are currently in MELPA, so if you wish to follow along at home, you'll need to clone both repos, and add them to your =load-path= variable with =add-to-list=:
Finally, define your character. While I describe the details later, hit ~M-x~ and type: =rpgdm-ironsworn-new-character= and answer the questions. This will create the necessary formatting and you are ready to play with either your key-binding (or ~F12~) to bring up the Hydra of commands:
#+ATTR_HTML: :width 1100px
[[file:images/ui-screenshot.png]]
While the interface may change (and I may not update that screenshot too often), let me explain what you're looking at:
- The ~0~, ~1~ and ~6~ roll dice and display the result. Not very interesting.
- ~d~ rolls the /Ironsworn/ dice, two d10s and a d6 allowing you to add all the modifiers you want. Better.
- ~e~, ~h~, ~i~, ~s~, and ~w~ roll against your character's /edge/, /heart/, /iron/, /shadow/ and /wits/ respectively.
- ~p~ allows you to create a new progress track, or mark progress against it. It has its own submenu:
#+ATTR_HTML: :width 800px
[[file:images/ui-progress.png]]
- ~m~ lets you choose a move, display the details, and lets you roll against it.
#+ATTR_HTML: :width 400px
[[file:images/list-of-moves.png]]
- ~H~, ~S~, ~G~, and ~M~ let you change those stats about your character
- ~z~ let's you ask a yes/no question, choosing how likely it is. Good for both solo play as well as turning it back on your players, "How likely do you think moor is haunted?"
- ~c~ displays a list of random tables to roll against:
#+ATTR_HTML: :width 400px
[[file:images/list-of-tables.png]]
- Ironsworn expects you to roll many of the tables /in tandem/, so the ~O~ shows a list of these, for instance:
#+ATTR_HTML: :width 1200px
[[file:images/ui-oracles.png]]
- ~o~ calls =ace-link= to jump around org links
- ~J~ / ~K~ are convenient for moving around your org file while you display the UI.
- ~y~ yanks (pastes) the last display table or other message into your document.
- ~Y~ yanks the results of the last move (along with the name of the move).
- The Super or Hyper key can be use to view a previous message or roll results.
- And ~q~ (or ~F12~) dismisses the UI.
This may be sufficient, but the rest of this document goes into details about how to use this, as well as the code to make it.
A character sheet, for this project, is just an org mode file where you take notes, and =:PROPERTIES:= drawers contain the current stats for your character. While most of it is /whatever you like it to be/ ... you need to keep a few things in mind.
Set changeable *Character Stats* like =health= and =supply= under any header, keeping in mind that calculations that require a particular value, will start at the point (er, cursor), and works its way up. This means that if you had =spirit= set to =5= at /level 1/, set to =3= at a /level 2/, and is now describing a battle with a Gaunt as a /level 3/ subheader, where you haven't specified... your =spirit= at that point is =3=.
You might say, of course, this makes sense, and it should. However, sibling headers as well as lower-level headers are ignored. In other words, we use the tree structure of the org file, not what came earlier in the buffer file.
Set *Progress Tracks* from Iron vows and bonds to tracking monstrous conflicts, /once/, at a particular headline. That progress is available to that header section and any subsections.
You'll notice that their properties are a bit peculiar, for instance:
#+BEGIN_EXAMPLE
,:PROPERTIES:
,:ironsworn-progress-b5f243e0: "Battle with Monstrosity" 8 16
,:ironsworn-health: 3
,:ironsworn-momentum: 3
,:END:
#+END_EXAMPLE
In this example, along with changing both the /health/ and /momentum/ stats, we define a /progress/. It has a weird ID (that just has to be unique), and properties that describe the progress, the number of ticks to mark per marking, and the number of ticks (4 ticks per box, and 10 boxes per track, means 40 is the max value).
Again, the UI will attempt to update all of these values, so you don't need to concern yourself, but if you are using Emacs, you probably want to know the details.
Details? Did someone say details? Let's talk about the code ... all the code that makes this work.
To begin, we'll need the [[https://gitlab.com/howardabrams/emacs-rpgdm][rpgdm project]] cloned in the =load-path= variable so that we can load it simply by calling:
In *Ironsworn*, all dice rolls follow a pattern where you set the challenge level for a check by rolling /challenge dice/ (two d10s) and compare that against rolling an /action die/ (a single d6 ... adding all modifiers to that six-sided die). You always three possible values:
- A *strong hit* where your action die + modifiers is larger than both d10 challenge dice.
- A *weak hit* is where your action die + modifiers is larger than /only one/ of the d10 challenge dice.
- A *miss* happens when your action die + modifiers is equal to or less than both d10 challenge dice.
I describe the =rpgdm-ironsworn-roll= that implements this below, but I need a helper function.
When we roll, I want one of those three results printed, but in different colors, to easily distinguish. I also want to see the numbers, so we can display those too. Oh, and if the same number shows on both d10s, you should introduce a significant plot twist, so this function, given all the numbers, returns a nicely formatted string:
The basic interface will query for a modifer, roll all three dice, and then display the results using the [[https://gitlab.com/howardabrams/emacs-rpgdm/-/blob/main/rpgdm.el#L76][rpgdm-message]] function:
#+BEGIN_SRC emacs-lisp :results silent
(defun rpgdm-ironsworn-roll (modifier)
"Display a Hit/Miss message based on comparing a d6 action
roll (added to MODIFIER) vs. two d10 challenge dice."
But what might be nice is to have a character sheet that has the default modifiers, and then we wouldn't have to /give/ the modifiers, at least, not the basic ones. You will store these stats and other numbers in your org file, and we'll work [[*Org Interface][the details of that later]].
Assuming we have a new character, let's query the user for all of these stats, and call =rpgdm-ironsworn-store-character-state= for each. This function /attempts/ to only put the bare minimum into the Org File, as I would expect, gentle reader, to have some firm opinions on how this file should be formatted.
We assume you have created an org-file, and the /template/ will just append some basic text at the end of the buffer. Note that if you don't give this function a name, it will randomly choose one.
We need a way to insert three assets, however, I don't want duplicates, or two companions, or ... well, I may come up with more rules, but I codify those rules into a function that returns a list, if it is good, or =nil= otherwise:
And a function that will read the =assets= directory (recursively) and return a list of three filenames randomly chosen.
Once we get the list of filenames, we use a inner helper function, =good-enough-list=, that picks three filenames, and if they are not good enough, will call itself until it picks three good ones. Shouldn't take too many iterations.
Sure, you could open up the appropriate drawer to see a character's stats, but we could do better? An updated org table? A separate buffer? For the moment, I am just going to display it in the mini-buffer whenever I want to see it:
We need an /internal representation/ of a character using a hash table of the attributes and other stats. One key feature is that I want to be able to look up a stat by either symbol or string, e.g. ='edge= or ="edge"= or even =:edge=. For this, I define a /comparator/, er, a Lisp test that uses a function to convert to a common format, a string:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-to-string (a)
"Return a lowercase string from either a string, keyword or symbol."
(downcase
(cond
((keywordp a) (substring (symbol-name a) 1))
((symbolp a) (symbol-name a))
(t a))))
(define-hash-table-test 'str-or-keys
(lambda (a b)
(string-equal (rpgdm-ironsworn-to-string a) (rpgdm-ironsworn-to-string b)))
(lambda (s) (sxhash-equal (rpgdm-ironsworn-to-string s))))
Just to prove it to ourselves, all of the following expressions return the same number (however, only run this in an org file that has a character properly formatted in it):
The [[file:moves][moves]] directory contains one org file for each move. These files contains properties for how a move should behave. To make "a move" involves opening the move description file in a side-buffer, getting details about the move from the file, and then rolling some dice.
Now, let's do the Move interface. We need to load the documentation, and retrieve its =move-stats= property, and then possibly /do something/, like roll:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-make-move (move-file)
"Make an Ironsworn move by loading MOVE-FILE, and optionally querying the
user to make an initial roll based on the properties in the file."
(all-mods (+ largest (string-to-number modifier))))
(rpgdm-ironsworn-roll all-mods)))
#+END_SRC
** Progress
The concept of a /progress/ can be anything from an overland journey to a battle with an Elder Boar. A function to create a new one (asking for a name), another function to mark progress, and a third to remove it.
While the 10 boxes are easy for pen-and-paper games, we really need the number a /ticks/ marking progress amounts:
Adding a progress to a character amounts to an arbitrary name, and the number of ticks, that amount to a /level/. For instance, we want to mark two boxes against a /dangerous/ track, which is =8= ticks. We store this in the character's hash-table, under the key, =progress-tracks=:
(track-val (format "\"%s\" %d %d" name level-value 0)))
(org-set-property track-prop track-val)))
#+END_SRC
Interactively, we can call the =-mark= function multiple times, but we might want to be able to call it multiple times (for instance, in a battle using a sword, we can mark progress /twice/). Regardless, we call =rpgdm-ironsworn--mark-progress-track= to update the character sheet.
If we call the =-amount= function /interactively/, we should get the current results displayed, otherwise, we really just want the number of full boxes marked:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-progress-amount (name)
"Display the progress made against a track, NAME."
(rpgdm-message "[%d] Progress on %s: %d (Ticks: %d)" level name boxes ticks)
boxes)))
#+END_SRC
Rolling against the progress just means we need to request that value instead of rolling the d6, but in this case, we should either take a currently registered progress track, or simply specify a completed amount:
Shawn Tompkin has created some useful oracles (random tables) to consult. I'm breaking my own [[https://gitlab.com/howardabrams/emacs-rpgdm][rpgdm project]] convention, and having this code automatically load those tables.
He designed many of the tables to work together, for instance, you should roll on both the [[file:tables/actions.org][actions]] and [[file:tables/themes.org][themes]] and combine the result to kick-start your ideas.
This function display a single entry of all the character-specific tables, including, a [[file:tables/names-ironlander.org][name]], their [[file:tables/character-role.org][role]] and [[file:tables/character-activity.org][current activity]], a [[file:tables/character-descriptor.org][one-word description]], as well as more hidden aspects, like the character's [[file:tables/character-goal.org][goal]] and [[file:tables/character-disposition.org][disposition]].
The [[file:tables/combat-action.org][combat action]] table isn't often tactical, and I prefer combining the [[file:tables/combat-event-method.org][method]] and [[file:tables/combat-event-target.org][target]] as an /event/. For instance, the following could be results from this method:
- Defy Object or Attack with precision.
- Overwhelm Opening or Intimidate or frighten.
- Withdraw Opening or Use the terrain to gain advantage.
Most tables and charts in the books require rolling a percentile die, so lets create helper functions to call the [[help:rpgdm-roll][rpgdm-roll]] function (which takes a dice string expression, and returns a random number that matches it):
I'd like to repurpose the RPGDM Hydra to be more specific to Ironsworn, so this has both the instructions on how to use it and the key-to-function mapping:
Seems that it would be nice to cache all the player information in an org file. That way, it is editable, but also, we could /tuck the data/ away in property drawers, where a value, like Supply or Health, could /change/ from section to section.
Couple of patterns:
- We store all data as org properties that begin with =:IRONSWORN-xyz=
- A property in a top level will be overwritten by the same property in a lower level header, this allows a /default/ value at the top, and then specifics lower down.
- We set progress, on the other hand, only once in the file, and update it
- Choosing progress to mark will only be available by walking "up" the tree, as we view progress in sibling trees as /completed/.
*** Store Character State
We set all of these values in the current tree using [[help:org-set-property][org-set-property]], as =rpgdm-ironsworn-store-character-state= will function as a wrapper around it:
As I've mentioned before, the code needs to walk "up" an Org Tree looking for properties. The crux is using the /internal/ [[help:org-element--get-node-properties][org-element--get-node-properties]] function, which returns a [[info:elisp#Property Lists][property list]] /iff/ the point is on a header.
So the general idea is:
- Move to the previous header, e.g. [[help:org-up-element][org-up-element]]
- Collect the properties
- Move up to that header's parent
- Collect its properties, etc.
- Stop if the point is at the /top-level/ header
Since we need to know if we are at the top-level, we could have a function, =org-heading-level= that returns =1= if we are at the top-level, and =0= if we aren't at any level:
Enough chit-chat, let's write this function. While we are at it, let's convert the property symbols into short symbols, e.g. =:IRONSWORN-SHADOW= should just be =shadow=, and number values should be numeric:
Where the property, =ironsworn-progress-ID= ends with a unique ID (more on that later). The value is a three-part list of a name, the number of ticks implied by "marking progress", e.g Epic in 1 tick, where /bothersome/ is 12 ticks. The final element is the amount of progress from 0 to 40.
Org can return the value, but breaking it up requires a regular expression:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--progress-values (value)
"Parse a string VALUE returning a list of parts,
Including the initial name, the number of ticks to mark,
and the current progress of the track."
(let ((regxp (rx "\""
(group (one-or-more (not "\"")))
"\"" (one-or-more space)
(group (one-or-more digit))
(one-or-more space)
(group (one-or-more digit)))))
(when (string-match regxp value)
(list (match-string 1 value)
(string-to-number (match-string 2 value))
(string-to-number (match-string 3 value))))))
#+END_SRC
Using the =rpgdm-ironsworn-current-character-state= function to return /all properties/ in the org file, we can filter our all the progress tracks, and then return a list of the parsed values:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-character-progresses ()
"Return a list where each element is progress track list.
Each sublist has three values, a name, the ticks to mark,
Unlike the =rpgdm-ironsworn-store-character-state= function, we want up update the /location/ in the org file where a progress track is defined and stored.
I like the recursive notion of walking higher in the org file using [[help:org-up-element][org-up-element]], however, having a function use both Common Lisp's [[help:cl-loop][loop]] as well as the Clojure-inspired [[help:-let*][-let*]] really shows the fusion that Emacs has become:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--mark-progress-track (str)
"Given a progress track's name, STR, update its progress mark."