The first real commit for this project
The project seems to be functional, as well as fully descriptive. Still not sure if any one will be keen to try using it.
This commit is contained in:
parent
babaad7e75
commit
cf416e37f1
8 changed files with 362 additions and 227 deletions
347
README.org
347
README.org
|
@ -11,13 +11,20 @@
|
||||||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||||
|
|
||||||
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. But what I used the most from my project was a 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~).
|
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 games are 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]].
|
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.
|
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.
|
||||||
* Getting Started
|
* Getting Started
|
||||||
Neither this, nor the [[https://gitlab.com/howardabrams/emacs-rpgdm][rpgdm project]] are currently in MELPA, so you'll need to clone both repos, and add them to your =load-path= variable with =add-to-list=, or use something like [[https://github.com/raxod502/straight.el][straight]]:
|
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=:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(add-to-list 'load-path "~/other/emacs-rpgdm")
|
||||||
|
(add-to-list 'load-path "~/other/emacs-ironsworn")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Or better yet, use something like [[https://github.com/raxod502/straight.el][straight]]:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(straight-use-package
|
(straight-use-package
|
||||||
|
@ -27,10 +34,10 @@ Neither this, nor the [[https://gitlab.com/howardabrams/emacs-rpgdm][rpgdm proje
|
||||||
'(el-patch :type git :host gitlab :repo "howardabrams/emacs-ironsworn"))
|
'(el-patch :type git :host gitlab :repo "howardabrams/emacs-ironsworn"))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Next, either turn on the =rpgdm-mode= (minor mode), or simply define a globally accessible shortcut:
|
Next, create an org file, and either turn on the =rpgdm-mode= (minor mode), or simply define a globally accessible shortcut:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(global-set-key (kbd "<f6>") 'hydra-rpgdm/body)
|
(global-set-key (kbd "<f12>") 'hydra-rpgdm/body)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
What I do, is add the following "code" somewhere in my Ironsworn-specific org files:
|
What I do, is add the following "code" somewhere in my Ironsworn-specific org files:
|
||||||
|
@ -41,17 +48,42 @@ What I do, is add the following "code" somewhere in my Ironsworn-specific org fi
|
||||||
# End:
|
# End:
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Finally, define your character. Currently, do this /code/ in your org file:
|
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:
|
||||||
#+BEGIN_EXAMPLE
|
|
||||||
,#+BEGIN_SRC emacs-lisp
|
|
||||||
(rpgdm-ironsworn-character :heart 2 :shadow 2 :wits 3 :iron 1 :edge 1)
|
|
||||||
,#+END_SRC
|
|
||||||
#+END_EXAMPLE
|
|
||||||
|
|
||||||
And evaluate that with ~C-c C-c~, and you are now ready to work the system with 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.
|
||||||
** Character Sheets
|
** Character Sheets
|
||||||
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/ you need to keep a few things in mind.
|
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.
|
||||||
|
|
||||||
|
First, don't narrow the display. Looking up the character's stats, currently doesn't /widen/ the document to find them ... at least, not yet.
|
||||||
*** Character Attributes
|
*** Character Attributes
|
||||||
Specify unchanging *Character Attibutes* like =iron= and =shadow= in a drawer at Level One Headling, for instance:
|
Specify unchanging *Character Attibutes* like =iron= and =shadow= in a drawer at Level One Headling, for instance:
|
||||||
#+BEGIN_EXAMPLE
|
#+BEGIN_EXAMPLE
|
||||||
|
@ -64,8 +96,9 @@ Specify unchanging *Character Attibutes* like =iron= and =shadow= in a drawer at
|
||||||
:ironsworn-wits: 3
|
:ironsworn-wits: 3
|
||||||
:END:
|
:END:
|
||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
|
You'll notice that the call to =rpgdm-ironsworn-new-character= does this.
|
||||||
*** Character Stats
|
*** Character Stats
|
||||||
Set changeable *Character Stats* like =health= and =supply= under any header, keeping in mind that a calculation that requires that 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 in now describing a battle with a Gaunt as a /level 3/ subheader, where you haven't specified, your =spirit= at that point is =3=.
|
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=.
|
||||||
|
|
||||||
#+BEGIN_EXAMPLE
|
#+BEGIN_EXAMPLE
|
||||||
,* The Adventures of Bred
|
,* The Adventures of Bred
|
||||||
|
@ -82,13 +115,28 @@ With the point here, Bred's spirit is 3.
|
||||||
,** Sojourn in Stormlook
|
,** Sojourn in Stormlook
|
||||||
However, at this level two heading, Bred's spirit is 5 (as it would read the level 1 setting).
|
However, at this level two heading, Bred's spirit is 5 (as it would read the level 1 setting).
|
||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
You might say, of course, this makes sense, and it should. However, sibling headers as well as lower-level headers are ignored.
|
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.
|
||||||
*** Progress Tracks
|
*** Progress Tracks
|
||||||
Set *Progress Tracks* from Iron vows and bonds to tracking monstrous conflicts, once, at some headline. That progress is available to that header section and any subsections.
|
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.
|
||||||
|
|
||||||
Since /bonds/ and your character's overarching /epic vow/ should always be available, set these at Level 1.
|
Since /bonds/ and your character's overarching /epic vow/ should always be available, set these at Level 1.
|
||||||
|
|
||||||
|
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.
|
||||||
* Code
|
* Code
|
||||||
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:
|
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:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(require 'rpgdm)
|
(require 'rpgdm)
|
||||||
|
@ -162,7 +210,75 @@ The basic interface will query for a modifer, roll all three dice, and then disp
|
||||||
|
|
||||||
** Character Information
|
** Character Information
|
||||||
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]].
|
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]].
|
||||||
|
*** Creating a Character
|
||||||
|
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.
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun rpgdm-ironsworn-new-character ()
|
||||||
|
"Interactively query the user for a new character's attribute.
|
||||||
|
This function _appends_ this information to the current buffer,
|
||||||
|
which should be using the `org-mode' major mode."
|
||||||
|
(interactive)
|
||||||
|
(let ((name (read-string "What is the new character's name? "))
|
||||||
|
(frmt (seq-random-elt '("* The Adventures of %s"
|
||||||
|
"* The Journeys of %s"
|
||||||
|
"* %s, an Epic Saga"
|
||||||
|
"* The Epic of %s"
|
||||||
|
"* Travels of %s"))))
|
||||||
|
(when (s-blank? name)
|
||||||
|
(setq name (rpgdm-tables-choose "names-ironlander")))
|
||||||
|
(goto-char (point-max))
|
||||||
|
(insert "# Local Variables:
|
||||||
|
# eval: (progn (require 'rpgdm-ironsworn) (rpgdm-mode) (rpgdm-tables-load (concat rpgdm-ironsworn-project \"tables\")))
|
||||||
|
# End:
|
||||||
|
")
|
||||||
|
(insert (format frmt name))
|
||||||
|
|
||||||
|
(dolist (stat '(edge heart iron shadow wits))
|
||||||
|
(rpgdm-ironsworn-store-character-state stat
|
||||||
|
(read-string (format "What is %s's %s stat: " name stat))))
|
||||||
|
|
||||||
|
(dolist (stat '(health spirit supply))
|
||||||
|
(rpgdm-ironsworn-store-character-state stat 5))
|
||||||
|
(rpgdm-ironsworn-store-character-state 'momentum 2)
|
||||||
|
|
||||||
|
(rpgdm-ironsworn-progress-create "Bonds" 1)
|
||||||
|
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1)
|
||||||
|
(message "Alright, the template for %s is complete. Edit away!" name)))
|
||||||
|
#+END_SRC
|
||||||
|
*** Showing a Character Stats
|
||||||
|
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:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun rpgdm-ironsworn--display-stat (stat character)
|
||||||
|
(let* ((value (gethash stat character))
|
||||||
|
(s-val (number-to-string value))
|
||||||
|
(color (cond
|
||||||
|
((< value 1) "red")
|
||||||
|
((< value 3) "orange")
|
||||||
|
((< value 4) "yellow")
|
||||||
|
(t "green"))))
|
||||||
|
(propertize s-val 'face `(:foreground ,color))))
|
||||||
|
|
||||||
|
(defun rpgdm-ironsworn-character-display ()
|
||||||
|
"Easily display the character's stats and other things."
|
||||||
|
(interactive)
|
||||||
|
(let ((character (rpgdm-ironsworn-current-character-state)))
|
||||||
|
(rpgdm-message "Edge: %d Heart: %d Iron: %d Shadow: %d Wits: %d
|
||||||
|
Health: %s Spirit: %s Supply: %s Momentum: %d"
|
||||||
|
(rpgdm-ironsworn-character-stat 'edge character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'heart character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'iron character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'shadow character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'wits character)
|
||||||
|
|
||||||
|
(rpgdm-ironsworn--display-stat 'health character)
|
||||||
|
(rpgdm-ironsworn--display-stat 'spirit character)
|
||||||
|
(rpgdm-ironsworn--display-stat 'supply character)
|
||||||
|
|
||||||
|
(gethash 'momentum character 5))))
|
||||||
|
#+END_SRC
|
||||||
|
*** Retrieving Character Stats
|
||||||
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:
|
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
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
@ -181,41 +297,7 @@ We need an /internal representation/ of a character using a hash table of the at
|
||||||
(lambda (s) (sxhash-equal (rpgdm-ironsworn-to-string s))))
|
(lambda (s) (sxhash-equal (rpgdm-ironsworn-to-string s))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Don't talk to me about efficiency. This will be nice!
|
With this hash-table test in place, we will create
|
||||||
|
|
||||||
Currently, I will have a global variable holding the character's stats. Not ideal, but sufficient for the moment:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defvar rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys)
|
|
||||||
"Stats and attributes for the currently loaded character")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
This helper function can set them all the stats at one time using the Common Lisp function define, where we can specify the keys. Someday, we may want another function that could parse an Org table or something.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :results silent
|
|
||||||
(cl-defun rpgdm-ironsworn-character (&key (edge 1) (heart 1) (iron 1) (shadow 1) (wits 1)
|
|
||||||
(health 5) (spirit 5) (supply 5) (momentum 2))
|
|
||||||
"Store the player character's stats, as well as set up the defaults for the values."
|
|
||||||
(clrhash rpgdm-ironsworn-character)
|
|
||||||
;; (setq rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys))
|
|
||||||
(puthash 'edge edge rpgdm-ironsworn-character)
|
|
||||||
(puthash 'heart heart rpgdm-ironsworn-character)
|
|
||||||
(puthash 'iron iron rpgdm-ironsworn-character)
|
|
||||||
(puthash 'shadow shadow rpgdm-ironsworn-character)
|
|
||||||
(puthash 'wits wits rpgdm-ironsworn-character)
|
|
||||||
|
|
||||||
(puthash 'health health rpgdm-ironsworn-character)
|
|
||||||
(puthash 'spirit spirit rpgdm-ironsworn-character)
|
|
||||||
(puthash 'supply supply rpgdm-ironsworn-character)
|
|
||||||
(puthash 'momentum momentum rpgdm-ironsworn-character))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
This allows us to define our character:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no :results silent
|
|
||||||
(rpgdm-ironsworn-character :heart 2 :shadow 2 :wits 3)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And a help function to retrieve the stats of the character is just a wrapper around =gethash=:
|
And a help function to retrieve the stats of the character is just a wrapper around =gethash=:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
@ -226,7 +308,7 @@ And a help function to retrieve the stats of the character is just a wrapper aro
|
||||||
(gethash stat character 1))
|
(gethash stat character 1))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Just to prove it to ourselves, all of the following expressions return the same number:
|
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):
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no :results silent
|
#+BEGIN_SRC emacs-lisp :tangle no :results silent
|
||||||
(rpgdm-ironsworn-character-stat 'wits)
|
(rpgdm-ironsworn-character-stat 'wits)
|
||||||
|
@ -234,6 +316,7 @@ Just to prove it to ourselves, all of the following expressions return the same
|
||||||
(rpgdm-ironsworn-character-stat "Wits")
|
(rpgdm-ironsworn-character-stat "Wits")
|
||||||
(rpgdm-ironsworn-character-stat :wits)
|
(rpgdm-ironsworn-character-stat :wits)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
*** Adjusting Character Stats
|
||||||
|
|
||||||
We need to modify /some/ of the stored values, like =health= and =supply=:
|
We need to modify /some/ of the stored values, like =health= and =supply=:
|
||||||
|
|
||||||
|
@ -266,37 +349,6 @@ We need to modify /some/ of the stored values, like =health= and =supply=:
|
||||||
(rpgdm-ironsworn-adjust-stat 'momentum momentum-adj 2))
|
(rpgdm-ironsworn-adjust-stat 'momentum momentum-adj 2))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Perhaps we need a special way to display these changing stats? An updated org table? A separate buffer? For the moment, I am just going to display it in the buffer whenever I want to see it:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun rpgdm-ironsworn--display-stat (stat character)
|
|
||||||
(let* ((value (gethash stat character))
|
|
||||||
(s-val (number-to-string value))
|
|
||||||
(color (cond
|
|
||||||
((< value 1) "red")
|
|
||||||
((< value 3) "orange")
|
|
||||||
((< value 4) "yellow")
|
|
||||||
(t "green"))))
|
|
||||||
(propertize s-val 'face `(:foreground ,color))))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-character-display ()
|
|
||||||
"Easily display the character's stats and other things."
|
|
||||||
(interactive)
|
|
||||||
(let ((character (rpgdm-ironsworn-current-character-state)))
|
|
||||||
(rpgdm-message "Edge: %d Heart: %d Iron: %d Shadow: %d Wits: %d
|
|
||||||
Health: %s Spirit: %s Supply: %s Momentum: %d"
|
|
||||||
(rpgdm-ironsworn-character-stat 'edge character)
|
|
||||||
(rpgdm-ironsworn-character-stat 'heart character)
|
|
||||||
(rpgdm-ironsworn-character-stat 'iron character)
|
|
||||||
(rpgdm-ironsworn-character-stat 'shadow character)
|
|
||||||
(rpgdm-ironsworn-character-stat 'wits character)
|
|
||||||
|
|
||||||
(rpgdm-ironsworn--display-stat 'health character)
|
|
||||||
(rpgdm-ironsworn--display-stat 'spirit character)
|
|
||||||
(rpgdm-ironsworn--display-stat 'supply character)
|
|
||||||
|
|
||||||
(gethash 'momentum character 5))))
|
|
||||||
#+END_SRC
|
|
||||||
** Roll against Character Stats
|
** Roll against Character Stats
|
||||||
Which allows us to create character stat-specific rolling functions:
|
Which allows us to create character stat-specific rolling functions:
|
||||||
|
|
||||||
|
@ -387,7 +439,6 @@ Oh, one issue... how do I know where the data files for the moves are?
|
||||||
(concat rpgdm-ironsworn-project "moves")
|
(concat rpgdm-ironsworn-project "moves")
|
||||||
".*\.org$"))))
|
".*\.org$"))))
|
||||||
rpgdm-ironsworn-moves)
|
rpgdm-ironsworn-moves)
|
||||||
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Choosing a move comes from using the =completing-read= along with a /list/ of all moves, like this:
|
Choosing a move comes from using the =completing-read= along with a /list/ of all moves, like this:
|
||||||
|
@ -408,16 +459,12 @@ We'll wrap that in a function to let the user choose a nicely formatted move, bu
|
||||||
Another feature I want, is that after completing a move, to put the results in a register, so that I can paste it into my notes file:
|
Another feature I want, is that after completing a move, to put the results in a register, so that I can paste it into my notes file:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--store-move (results)
|
(defun rpgdm-ironsworn--store-move (title results)
|
||||||
"Store the results in a `m' register. It should also include
|
"Store the results in a `m' register. It should also include
|
||||||
the name of the move, based on the current file."
|
the name of the move, based on the current file."
|
||||||
(set-register ?m
|
(set-register ?m (format "# %s ... %s " title results)))
|
||||||
(format "# %s ... %s "
|
|
||||||
(progn
|
|
||||||
(goto-char (point-min))
|
|
||||||
(cdr (assoc "ITEM" (org-entry-properties))))
|
|
||||||
results)))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
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:
|
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
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
@ -428,12 +475,14 @@ Now, let's do the Move interface. We need to load the documentation, and retriev
|
||||||
|
|
||||||
;; Normally, we'd call `save-window-excursion', however, that buries the file
|
;; Normally, we'd call `save-window-excursion', however, that buries the file
|
||||||
;; we show, and I think we should leave it up for study.
|
;; we show, and I think we should leave it up for study.
|
||||||
(let (props
|
(let (props title
|
||||||
(orig-buf (window-buffer)))
|
(orig-buf (window-buffer)))
|
||||||
(find-file-other-window move-file)
|
(find-file-other-window move-file)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(setq title (cdr (assoc "ITEM" (org-entry-properties))))
|
||||||
(setq props (first (org-property-values "move-stats")))
|
(setq props (first (org-property-values "move-stats")))
|
||||||
(pop-to-buffer orig-buf)
|
(pop-to-buffer orig-buf)
|
||||||
(rpgdm-ironsworn--store-move (rpgdm-ironsworn--make-move props))))
|
(rpgdm-ironsworn--store-move title (rpgdm-ironsworn--make-move props))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
The =rpgdm-ironsworn--make-move= call does something based on the properties stored in the file:
|
The =rpgdm-ironsworn--make-move= call does something based on the properties stored in the file:
|
||||||
|
@ -622,7 +671,11 @@ Let's make sure these function work as we expect:
|
||||||
(should (= (rpgdm-ironsworn-progress-amount track) 1))))
|
(should (= (rpgdm-ironsworn-progress-amount track) 1))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
** Oracles
|
** Oracles
|
||||||
|
Shawn Tompkin has created some useful oracles (random tables) to consult. 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.
|
||||||
|
|
||||||
|
Rolling on one table is simple, but here we have a collection of helper function to roll on multiple tables, and display the result altogether.
|
||||||
*** Action-Theme
|
*** Action-Theme
|
||||||
|
This function displays an entry from both the [[file:tables/actions.org][actions]] and [[file:tables/themes.org][themes]] tables.
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-oracle-action-theme ()
|
(defun rpgdm-ironsworn-oracle-action-theme ()
|
||||||
"Rolls on two tables at one time."
|
"Rolls on two tables at one time."
|
||||||
|
@ -632,8 +685,12 @@ Let's make sure these function work as we expect:
|
||||||
(rpgdm-message "%s / %s" action theme)))
|
(rpgdm-message "%s / %s" action theme)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Character
|
*** Character
|
||||||
|
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]].
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-oracle-npc ()
|
(defun rpgdm-ironsworn-oracle-npc ()
|
||||||
|
"Roll on all the character-related tables and show them together.
|
||||||
|
You'll need to pick and choose what works and discard what doesn't."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((name (rpgdm-tables-choose "names-ironlander"))
|
(let ((name (rpgdm-tables-choose "names-ironlander"))
|
||||||
(goal (rpgdm-tables-choose "character-goal"))
|
(goal (rpgdm-tables-choose "character-goal"))
|
||||||
|
@ -643,9 +700,14 @@ Let's make sure these function work as we expect:
|
||||||
(disposition (rpgdm-tables-choose "character-disposition")))
|
(disposition (rpgdm-tables-choose "character-disposition")))
|
||||||
(rpgdm-message "%s, %s %s (Activity: %s Disposition: %s Goal: %s)"
|
(rpgdm-message "%s, %s %s (Activity: %s Disposition: %s Goal: %s)"
|
||||||
name description role activity disposition goal)))
|
name description role activity disposition goal)))
|
||||||
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Combat Action
|
*** Combat Action
|
||||||
|
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.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-oracle-combat ()
|
(defun rpgdm-ironsworn-oracle-combat ()
|
||||||
(interactive)
|
(interactive)
|
||||||
|
@ -655,6 +717,11 @@ Let's make sure these function work as we expect:
|
||||||
(rpgdm-message "%s %s or %s" method target action)))
|
(rpgdm-message "%s %s or %s" method target action)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Feature
|
*** Feature
|
||||||
|
This function combines the [[file:tables/feature-aspect.org][aspect]] and [[file:tables/feature-focus.org][focus]] of a /feature/, for instance:
|
||||||
|
- Mystical / Message ... could it be an iron pillar with runes?
|
||||||
|
- Unusual / Refuge ... could it be a friendly barge passing by?
|
||||||
|
- Foul / Opening ... could it be a crack in the ground spewing sulfur odors?
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-oracle-feature ()
|
(defun rpgdm-ironsworn-oracle-feature ()
|
||||||
"Rolls on two tables at one time for a Site's feature."
|
"Rolls on two tables at one time for a Site's feature."
|
||||||
|
@ -664,6 +731,12 @@ Let's make sure these function work as we expect:
|
||||||
(rpgdm-message "%s / %s" aspect focus)))
|
(rpgdm-message "%s / %s" aspect focus)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Site Nature
|
*** Site Nature
|
||||||
|
In the Ironsworn Delve expansion, you can randomly choose a /dangerous place/, for instance:
|
||||||
|
- Ravaged Shadowfen :: Mire of Shrouded Silence
|
||||||
|
- Haunted Barrow :: Grave of Radek’s Shadow
|
||||||
|
- Infested Barrow :: Selpulcher of Wasted Bone
|
||||||
|
Notice we also generate a name for the place.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-oracle-site-nature ()
|
(defun rpgdm-ironsworn-oracle-site-nature ()
|
||||||
"Rolls on two tables at one time for a random Site."
|
"Rolls on two tables at one time for a random Site."
|
||||||
|
@ -719,7 +792,8 @@ So, let's generate some random place names:
|
||||||
(rpgdm-ironsworn-oracle-site-name) ; "Sundered Mists of Khulan"
|
(rpgdm-ironsworn-oracle-site-name) ; "Sundered Mists of Khulan"
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Threat
|
*** Threat
|
||||||
Generate a random threat and its motivations.
|
Generate a random threat and its motivations by coding the threat, but using the many [[file:tables/threat-category.org][threats]] available:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar rpgdm-ironsworn-oracle-threats '("Burgeoning Conflict" "Ravaging Horde"
|
(defvar rpgdm-ironsworn-oracle-threats '("Burgeoning Conflict" "Ravaging Horde"
|
||||||
"Cursed Site" "Malignant Plague"
|
"Cursed Site" "Malignant Plague"
|
||||||
|
@ -770,7 +844,7 @@ Ironsworn introduces a simplified /flip-a-coin/ oracle, that might be nice to in
|
||||||
(if yes! "or a twist." ""))))
|
(if yes! "or a twist." ""))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Most tables and charts in the books require rolling a percentile die:
|
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):
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-roll-d6 ()
|
(defun rpgdm-roll-d6 ()
|
||||||
"Roll and display a six-sided die roll."
|
"Roll and display a six-sided die roll."
|
||||||
|
@ -788,7 +862,16 @@ Most tables and charts in the books require rolling a percentile die:
|
||||||
(rpgdm-roll "d100"))
|
(rpgdm-roll "d100"))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Can a Hydra call a hydra?
|
While the move interface puts the details of the move in the ~m~ register, I figured a function on the Hydra would be easier:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun rpgdm-ironsworn-paste-last-move ()
|
||||||
|
"Insert the contents of the `m' register, which should have last move."
|
||||||
|
(interactive)
|
||||||
|
(insert "\n" (get-register ?m)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Can a Hydra call a hydra? Let's more all the special oracle and progress functions to sub-hydras:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defhydra hydra-rpgdm-oracles (:color blue)
|
(defhydra hydra-rpgdm-oracles (:color blue)
|
||||||
|
@ -810,17 +893,17 @@ Can a Hydra call a hydra?
|
||||||
("r" rpgdm-ironsworn-progress-roll "roll"))
|
("r" rpgdm-ironsworn-progress-roll "roll"))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
I'd like to repurpose the RPGDM Hydra to be more specific to Ironsworn:
|
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:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defhydra hydra-rpgdm (:color blue :hint nil)
|
(defhydra hydra-rpgdm (:color blue :hint nil)
|
||||||
"
|
"
|
||||||
^Dice^ 0=d100 1=d10 6=d6 ^Adjust^ ^Oracles/Tables^ ^Moving^ ^Messages^
|
^Dice^ 0=d100 1=d10 6=d6 ^Adjust^ ^Oracles/Tables^ ^Moving/Editing^ ^Messages^
|
||||||
----------------------------------------------------------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
_d_: Roll Dice _p_: Progress _H_: Health _z_: Yes/No Oracle _o_: Links ⌘-h: Show Stats
|
_d_: Roll Dice _p_: Progress _H_: Health _z_: Yes/No Oracle _o_: Links ⌘-h: Show Stats
|
||||||
_e_: Roll Edge _s_: Roll Shadow _S_: Spirit _c_: Show Oracle _J_/_K_: Page up/dn ⌘-l: Last Results
|
_e_: Roll Edge _s_: Roll Shadow _S_: Spirit _c_: Show Oracle _J_/_K_: Page up/dn ⌘-l: Last Results
|
||||||
_h_: Roll Heart _w_: Roll Wits _G_: Supply _O_: Other Oracles _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous
|
_h_: Roll Heart _w_: Roll Wits _G_: Supply _O_: Other Oracles _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous
|
||||||
_i_: Roll Iron _m_: Make Move _M_: Momentum _T_: Load Oracles ⌘-j: ↓ Next "
|
_i_: Roll Iron _m_: Make Move _M_: Momentum _T_: Load Oracles _y_/_Y_: Yank/Move ⌘-j: ↓ Next "
|
||||||
("d" rpgdm-ironsworn-roll) ("D" rpgdm-ironsworn-progress-roll)
|
("d" rpgdm-ironsworn-roll) ("D" rpgdm-ironsworn-progress-roll)
|
||||||
("z" rpgdm-ironsworn-oracle) ("O" rpgdm-oracle)
|
("z" rpgdm-ironsworn-oracle) ("O" rpgdm-oracle)
|
||||||
|
|
||||||
|
@ -840,9 +923,9 @@ I'd like to repurpose the RPGDM Hydra to be more specific to Ironsworn:
|
||||||
("O" hydra-rpgdm-oracles/body)
|
("O" hydra-rpgdm-oracles/body)
|
||||||
("p" hydra-rpgdm-progress/body)
|
("p" hydra-rpgdm-progress/body)
|
||||||
|
|
||||||
("o" ace-link) ("N" org-narrow-to-subtree) ("W" widen)
|
("o" ace-link) ("N" org-narrow-to-subtree) ("W" widen)
|
||||||
("K" scroll-down :color pink) ("J" scroll-up :color pink)
|
("K" scroll-down :color pink) ("J" scroll-up :color pink)
|
||||||
|
("y" rpgdm-paste-last-message) ("Y" rpgdm-ironsworn-paste-last-move)
|
||||||
("s-h" rpgdm-ironsworn-character-display)
|
("s-h" rpgdm-ironsworn-character-display)
|
||||||
("C-m" rpgdm-last-results :color pink)
|
("C-m" rpgdm-last-results :color pink)
|
||||||
("C-n" rpgdm-last-results-next :color pink)
|
("C-n" rpgdm-last-results-next :color pink)
|
||||||
|
@ -862,9 +945,10 @@ Seems that it would be nice to cache all the player information in an org file.
|
||||||
Couple of patterns:
|
Couple of patterns:
|
||||||
- We store all data as org properties that begin with =:IRONSWORN-xyz=
|
- 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.
|
- 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
|
||||||
When we want to update a property in the org file, we need to decide its state.
|
- Choosing progress to mark will only be available by walking "up" the tree, as we view progress in sibling trees as /completed/.
|
||||||
If it is a numeric state, like /health/ or /supply/, then we set the value in the current tree using [[help:org-set-property][org-set-property]].
|
*** 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:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-store-character-state (stat value)
|
(defun rpgdm-ironsworn-store-character-state (stat value)
|
||||||
|
@ -877,7 +961,8 @@ If it is a numeric state, like /health/ or /supply/, then we set the value in th
|
||||||
(org-set-property prop value)))
|
(org-set-property prop value)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Loading state means looking at properties, and we distinguish Ironsworn-specific with a few functions:
|
*** Property Key Conversions and Predicates
|
||||||
|
Loading state means looking at properties, and we distinguish Ironsworn-specific with a few functions:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--property-p (prop)
|
(defun rpgdm-ironsworn--property-p (prop)
|
||||||
|
@ -893,16 +978,17 @@ Loading state means looking at properties, and we distinguish Ironsworn-specific
|
||||||
(string-match (rx bos ":IRONSWORN-PROGRESS-") p)))
|
(string-match (rx bos ":IRONSWORN-PROGRESS-") p)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Since we store both normal and progress props together, we need to distingush between the symbols:
|
*Note:* We may set the property in lowercase, but the symbol always comes back in uppercase.
|
||||||
|
|
||||||
|
Since we store both normal and progress props together, we need to distinguish between the two symbols:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--short-progress-p (prop)
|
(defun rpgdm-ironsworn--short-progress-p (prop)
|
||||||
(let ((p (symbol-name prop)))
|
(let ((p (symbol-name prop)))
|
||||||
(s-starts-with-p "progress-" p)))
|
(s-starts-with-p "progress-" p)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Yes, we may set the property in lowercase, but the symbol always comes back in uppercase.
|
We also need a property-symbol to string conversion. I don't know if the string should be [[help:downcase][downcase-d]] or not, but why not?
|
||||||
|
|
||||||
Speaking of which, let's get a property-symbol to string conversion:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--progress-to-str (prop)
|
(defun rpgdm-ironsworn--progress-to-str (prop)
|
||||||
|
@ -910,15 +996,28 @@ Speaking of which, let's get a property-symbol to string conversion:
|
||||||
(downcase (substring (symbol-name prop) 1)))
|
(downcase (substring (symbol-name prop) 1)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
A test is always explanatory:
|
A test is always explanatory for how this function should behave:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(ert-deftest rpgdm-ironsworn--progress-to-str-test ()
|
(ert-deftest rpgdm-ironsworn--progress-to-str-test ()
|
||||||
(should (equal (rpgdm-ironsworn--progress-to-str :IRONSWORN-PROGRESS-EPIC)
|
(should (equal (rpgdm-ironsworn--progress-to-str :IRONSWORN-PROGRESS-EPIC)
|
||||||
"ironsworn-progress-epic")))
|
"ironsworn-progress-epic")))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
Why do we need it? Well, when we will want this when we /re-set/ the property.
|
Why do we need it? Well, when we will want this when we /re-set/ the property.
|
||||||
*** Walking "Up" an Org Tree looking for Properties
|
*** Locating the Properties
|
||||||
Marking progress on a track amounts to first locating the stored value in the org document, by walking "up" the tree from the current location.
|
:PROPERTIES:
|
||||||
|
:foobar: 5
|
||||||
|
:END:
|
||||||
|
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:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun org-heading-level ()
|
(defun org-heading-level ()
|
||||||
|
@ -926,13 +1025,17 @@ Marking progress on a track amounts to first locating the stored value in the or
|
||||||
(if-let ((level-str (org-element-property :level (org-element-at-point))))
|
(if-let ((level-str (org-element-property :level (org-element-at-point))))
|
||||||
level-str
|
level-str
|
||||||
0))
|
0))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--current-character-state (results)
|
(defun rpgdm-ironsworn--current-character-state (results)
|
||||||
"Recursive helper to insert current header properties in RESULTS.
|
"Recursive helper to insert current header properties in RESULTS.
|
||||||
Calls itself if it is not looking at the top-level header in the file.
|
Calls itself if it is not looking at the top-level header in the
|
||||||
If a property is already in the hash table, RESULTS, it is not overwritten,
|
file. If a property is already in the hash table, RESULTS, it is
|
||||||
thereby having lower-level subtrees take precendence over similar settings
|
not overwritten, thereby having lower-level subtrees take
|
||||||
in higher headers."
|
precendence over similar settings in higher headers."
|
||||||
(defun key-convert (ironsworn-prop)
|
(defun key-convert (ironsworn-prop)
|
||||||
(make-symbol (downcase (substring (symbol-name ironsworn-prop) 11))))
|
(make-symbol (downcase (substring (symbol-name ironsworn-prop) 11))))
|
||||||
|
|
||||||
|
@ -955,9 +1058,9 @@ Marking progress on a track amounts to first locating the stored value in the or
|
||||||
(rpgdm-ironsworn--current-character-state results))))
|
(rpgdm-ironsworn--current-character-state results))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-current-character-state ()
|
(defun rpgdm-ironsworn-current-character-state ()
|
||||||
"Return all set properties based on the position of the cursor in org doc.
|
"Return all set properties based on cursor position in org doc.
|
||||||
Note that values in sibling trees are ignored, and settings in lower levels
|
Note that values in sibling trees are ignored, and settings in
|
||||||
of the tree headings take precedence."
|
lower levels of the tree headings take precedence."
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(let ((results (make-hash-table :test 'str-or-keys)))
|
(let ((results (make-hash-table :test 'str-or-keys)))
|
||||||
(unless (eq 'headline (org-element-type (org-element-at-point)))
|
(unless (eq 'headline (org-element-type (org-element-at-point)))
|
||||||
|
|
|
@ -37,6 +37,66 @@ roll (added to MODIFIER) vs. two d10 challenge dice."
|
||||||
(rpgdm-message (rpgdm-ironsworn--results action-roll modifier
|
(rpgdm-message (rpgdm-ironsworn--results action-roll modifier
|
||||||
one-challenge two-challenge))))
|
one-challenge two-challenge))))
|
||||||
|
|
||||||
|
(defun rpgdm-ironsworn-new-character ()
|
||||||
|
"Interactively query the user for a new character's attribute.
|
||||||
|
This function _appends_ this information to the current buffer,
|
||||||
|
which should be using the `org-mode' major mode."
|
||||||
|
(interactive)
|
||||||
|
(let ((name (read-string "What is the new character's name? "))
|
||||||
|
(frmt (seq-random-elt '("* The Adventures of %s"
|
||||||
|
"* The Journeys of %s"
|
||||||
|
"* %s, an Epic Saga"
|
||||||
|
"* The Epic of %s"
|
||||||
|
"* Travels of %s"))))
|
||||||
|
(when (s-blank? name)
|
||||||
|
(setq name (rpgdm-tables-choose "names-ironlander")))
|
||||||
|
(goto-char (point-max))
|
||||||
|
(insert "# Local Variables:
|
||||||
|
# eval: (progn (require 'rpgdm-ironsworn) (rpgdm-mode) (rpgdm-tables-load (concat rpgdm-ironsworn-project \"tables\")))
|
||||||
|
# End:
|
||||||
|
")
|
||||||
|
(insert (format frmt name))
|
||||||
|
|
||||||
|
(dolist (stat '(edge heart iron shadow wits))
|
||||||
|
(rpgdm-ironsworn-store-character-state stat
|
||||||
|
(read-string (format "What is %s's %s stat: " name stat))))
|
||||||
|
|
||||||
|
(dolist (stat '(health spirit supply))
|
||||||
|
(rpgdm-ironsworn-store-character-state stat 5))
|
||||||
|
(rpgdm-ironsworn-store-character-state 'momentum 2)
|
||||||
|
|
||||||
|
(rpgdm-ironsworn-progress-create "Bonds" 1)
|
||||||
|
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1)
|
||||||
|
(message "Alright, the template for %s is complete. Edit away!" name)))
|
||||||
|
|
||||||
|
(defun rpgdm-ironsworn--display-stat (stat character)
|
||||||
|
(let* ((value (gethash stat character))
|
||||||
|
(s-val (number-to-string value))
|
||||||
|
(color (cond
|
||||||
|
((< value 1) "red")
|
||||||
|
((< value 3) "orange")
|
||||||
|
((< value 4) "yellow")
|
||||||
|
(t "green"))))
|
||||||
|
(propertize s-val 'face `(:foreground ,color))))
|
||||||
|
|
||||||
|
(defun rpgdm-ironsworn-character-display ()
|
||||||
|
"Easily display the character's stats and other things."
|
||||||
|
(interactive)
|
||||||
|
(let ((character (rpgdm-ironsworn-current-character-state)))
|
||||||
|
(rpgdm-message "Edge: %d Heart: %d Iron: %d Shadow: %d Wits: %d
|
||||||
|
Health: %s Spirit: %s Supply: %s Momentum: %d"
|
||||||
|
(rpgdm-ironsworn-character-stat 'edge character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'heart character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'iron character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'shadow character)
|
||||||
|
(rpgdm-ironsworn-character-stat 'wits character)
|
||||||
|
|
||||||
|
(rpgdm-ironsworn--display-stat 'health character)
|
||||||
|
(rpgdm-ironsworn--display-stat 'spirit character)
|
||||||
|
(rpgdm-ironsworn--display-stat 'supply character)
|
||||||
|
|
||||||
|
(gethash 'momentum character 5))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-to-string (a)
|
(defun rpgdm-ironsworn-to-string (a)
|
||||||
"Return a lowercase string from either a string, keyword or symbol."
|
"Return a lowercase string from either a string, keyword or symbol."
|
||||||
(downcase
|
(downcase
|
||||||
|
@ -51,32 +111,16 @@ roll (added to MODIFIER) vs. two d10 challenge dice."
|
||||||
|
|
||||||
(lambda (s) (sxhash-equal (rpgdm-ironsworn-to-string s))))
|
(lambda (s) (sxhash-equal (rpgdm-ironsworn-to-string s))))
|
||||||
|
|
||||||
(defvar rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys)
|
(defun rpgdm-ironsworn-character-stat (stat &optional character)
|
||||||
"Stats and attributes for the currently loaded character")
|
|
||||||
|
|
||||||
(cl-defun rpgdm-ironsworn-character (&key (edge 1) (heart 1) (iron 1) (shadow 1) (wits 1)
|
|
||||||
(health 5) (spirit 5) (supply 5) (momentum 2))
|
|
||||||
"Store the player character's stats, as well as set up the defaults for the values."
|
|
||||||
(clrhash rpgdm-ironsworn-character)
|
|
||||||
;; (setq rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys))
|
|
||||||
(puthash 'edge edge rpgdm-ironsworn-character)
|
|
||||||
(puthash 'heart heart rpgdm-ironsworn-character)
|
|
||||||
(puthash 'iron iron rpgdm-ironsworn-character)
|
|
||||||
(puthash 'shadow shadow rpgdm-ironsworn-character)
|
|
||||||
(puthash 'wits wits rpgdm-ironsworn-character)
|
|
||||||
|
|
||||||
(puthash 'health health rpgdm-ironsworn-character)
|
|
||||||
(puthash 'spirit spirit rpgdm-ironsworn-character)
|
|
||||||
(puthash 'supply supply rpgdm-ironsworn-character)
|
|
||||||
(puthash 'momentum momentum rpgdm-ironsworn-character))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-character-stat (stat)
|
|
||||||
"Return integer value associated with a character's STAT."
|
"Return integer value associated with a character's STAT."
|
||||||
(gethash stat rpgdm-ironsworn-character 1))
|
(when (null character)
|
||||||
|
(setq character (rpgdm-ironsworn-current-character-state)))
|
||||||
|
(gethash stat character 1))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-adjust-stat (stat adj &optional default)
|
(defun rpgdm-ironsworn-adjust-stat (stat adj &optional default)
|
||||||
"Increase or decrease the current character's STAT by ADJ."
|
"Increase or decrease the current character's STAT by ADJ."
|
||||||
(let ((value (+ (gethash stat rpgdm-ironsworn-character default) adj)))
|
(let ((value (+ (gethash stat rpgdm-ironsworn-character default) adj)))
|
||||||
|
;; TODO: Delete this hash bidness
|
||||||
(puthash stat value rpgdm-ironsworn-character)
|
(puthash stat value rpgdm-ironsworn-character)
|
||||||
(rpgdm-ironsworn-store-character-state stat value)))
|
(rpgdm-ironsworn-store-character-state stat value)))
|
||||||
|
|
||||||
|
@ -100,33 +144,6 @@ roll (added to MODIFIER) vs. two d10 challenge dice."
|
||||||
(interactive "nMomentum Adjustment: ")
|
(interactive "nMomentum Adjustment: ")
|
||||||
(rpgdm-ironsworn-adjust-stat 'momentum momentum-adj 2))
|
(rpgdm-ironsworn-adjust-stat 'momentum momentum-adj 2))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--display-stat (stat)
|
|
||||||
(let* ((value (gethash stat rpgdm-ironsworn-character))
|
|
||||||
(s-val (number-to-string value))
|
|
||||||
(color (cond
|
|
||||||
((< value 1) "red")
|
|
||||||
((< value 3) "orange")
|
|
||||||
((< value 4) "yellow")
|
|
||||||
(t "green"))))
|
|
||||||
(propertize s-val 'face `(:foreground ,color))))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-character-display ()
|
|
||||||
"Easily display the character's stats and other things."
|
|
||||||
(interactive)
|
|
||||||
(rpgdm-message "Edge: %d Heart: %d Iron: %d Shadow: %d Wits: %d
|
|
||||||
Health: %s Spirit: %s Supply: %s Momentum: %d"
|
|
||||||
(rpgdm-ironsworn-character-stat 'edge)
|
|
||||||
(rpgdm-ironsworn-character-stat 'heart)
|
|
||||||
(rpgdm-ironsworn-character-stat 'iron)
|
|
||||||
(rpgdm-ironsworn-character-stat 'shadow)
|
|
||||||
(rpgdm-ironsworn-character-stat 'wits)
|
|
||||||
|
|
||||||
(rpgdm-ironsworn--display-stat 'health)
|
|
||||||
(rpgdm-ironsworn--display-stat 'spirit)
|
|
||||||
(rpgdm-ironsworn--display-stat 'supply)
|
|
||||||
|
|
||||||
(gethash 'momentum rpgdm-ironsworn-character 5)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll-stat (stat modifier)
|
(defun rpgdm-ironsworn-roll-stat (stat modifier)
|
||||||
"Roll an action based on a loaded character's STAT with a MODIFIER."
|
"Roll an action based on a loaded character's STAT with a MODIFIER."
|
||||||
(interactive (list (completing-read "Stat Modifier: " '(Edge Heart Iron Shadow Wits))
|
(interactive (list (completing-read "Stat Modifier: " '(Edge Heart Iron Shadow Wits))
|
||||||
|
@ -192,28 +209,26 @@ Health: %s Spirit: %s Supply: %s Momentum: %d"
|
||||||
(tuple (assoc move (rpgdm-ironsworn-moves))))
|
(tuple (assoc move (rpgdm-ironsworn-moves))))
|
||||||
(cadr tuple)))
|
(cadr tuple)))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--store-move (results)
|
(defun rpgdm-ironsworn--store-move (title results)
|
||||||
"Store the results in a `m' register. It should also include
|
"Store the results in a `m' register. It should also include
|
||||||
the name of the move, based on the current file."
|
the name of the move, based on the current file."
|
||||||
(set-register ?m
|
(set-register ?m (format "# %s ... %s " title results)))
|
||||||
(format "# %s ... %s "
|
|
||||||
(progn
|
|
||||||
(goto-char (point-min))
|
|
||||||
(cdr (assoc "ITEM" (org-entry-properties))))
|
|
||||||
results)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-make-move (move-file)
|
(defun rpgdm-ironsworn-make-move (move-file)
|
||||||
"Make an Ironsworn move by loading MOVE-FILE, and optionally querying the
|
"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."
|
user to make an initial roll based on the properties in the file."
|
||||||
(interactive (list (rpgdm-ironsworn-choose-move)))
|
(interactive (list (rpgdm-ironsworn-choose-move)))
|
||||||
|
|
||||||
;; Normally, we'd call `save-window-excursion', however, that buries the file
|
;; Normally, we'd call `save-window-excursion', however, that buries the file
|
||||||
;; we show, and I think we should leave it up for study.
|
;; we show, and I think we should leave it up for study.
|
||||||
(let ((orig-buf (window-buffer)) props)
|
(let (props title
|
||||||
(find-file-other-window move-file)
|
(orig-buf (window-buffer)))
|
||||||
(setq props (first (org-property-values "move-stats")))
|
(find-file-other-window move-file)
|
||||||
(rpgdm-ironsworn--store-move (rpgdm-ironsworn--make-move props))
|
(goto-char (point-min))
|
||||||
(pop-to-buffer orig-buf)))
|
(setq title (cdr (assoc "ITEM" (org-entry-properties))))
|
||||||
|
(setq props (first (org-property-values "move-stats")))
|
||||||
|
(pop-to-buffer orig-buf)
|
||||||
|
(rpgdm-ironsworn--store-move title (rpgdm-ironsworn--make-move props))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--make-move (move-props)
|
(defun rpgdm-ironsworn--make-move (move-props)
|
||||||
"Query user for rolls based on the MOVE-PROPS."
|
"Query user for rolls based on the MOVE-PROPS."
|
||||||
|
@ -221,7 +236,6 @@ user to make an initial roll based on the properties in the file."
|
||||||
(count (seq-length props))
|
(count (seq-length props))
|
||||||
(stats (seq-filter (lambda (s) (string-match (rx (one-or-more alpha)) s)) props))
|
(stats (seq-filter (lambda (s) (string-match (rx (one-or-more alpha)) s)) props))
|
||||||
(first (first props)))
|
(first (first props)))
|
||||||
(message "props: %s count: %d stats: %s first: %s" props count stats first)
|
|
||||||
(cond
|
(cond
|
||||||
((seq-empty-p stats) nil)
|
((seq-empty-p stats) nil)
|
||||||
((equal first "progress") (call-interactively 'rpgdm-ironsworn-progress-roll))
|
((equal first "progress") (call-interactively 'rpgdm-ironsworn-progress-roll))
|
||||||
|
@ -335,6 +349,8 @@ to rolling two d10 challenge dice."
|
||||||
(rpgdm-message "%s / %s" action theme)))
|
(rpgdm-message "%s / %s" action theme)))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-npc ()
|
(defun rpgdm-ironsworn-oracle-npc ()
|
||||||
|
"Roll on all the character-related tables and show them together.
|
||||||
|
You'll need to pick and choose what works and discard what doesn't."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((name (rpgdm-tables-choose "names-ironlander"))
|
(let ((name (rpgdm-tables-choose "names-ironlander"))
|
||||||
(goal (rpgdm-tables-choose "character-goal"))
|
(goal (rpgdm-tables-choose "character-goal"))
|
||||||
|
@ -445,6 +461,10 @@ to rolling two d10 challenge dice."
|
||||||
(interactive)
|
(interactive)
|
||||||
(rpgdm-roll "d100"))
|
(rpgdm-roll "d100"))
|
||||||
|
|
||||||
|
(defun rpgdm-ironsworn-paste-last-move ()
|
||||||
|
"Insert the contents of the `m' register, which should have last move."
|
||||||
|
(insert "\n" (get-register ?m)))
|
||||||
|
|
||||||
(defhydra hydra-rpgdm-oracles (:color blue)
|
(defhydra hydra-rpgdm-oracles (:color blue)
|
||||||
"Oracles"
|
"Oracles"
|
||||||
("a" rpgdm-ironsworn-oracle-action-theme "Action/Theme")
|
("a" rpgdm-ironsworn-oracle-action-theme "Action/Theme")
|
||||||
|
@ -465,12 +485,12 @@ to rolling two d10 challenge dice."
|
||||||
|
|
||||||
(defhydra hydra-rpgdm (:color blue :hint nil)
|
(defhydra hydra-rpgdm (:color blue :hint nil)
|
||||||
"
|
"
|
||||||
^Dice^ 0=d100 1=d10 6=d6 ^Adjust^ ^Oracles/Tables^ ^Moving^ ^Messages^
|
^Dice^ 0=d100 1=d10 6=d6 ^Adjust^ ^Oracles/Tables^ ^Moving/Editing^ ^Messages^
|
||||||
----------------------------------------------------------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
_d_: Roll Dice _p_: Progress _H_: Health _z_: Yes/No Oracle _o_: Links ⌘-h: Show Stats
|
_d_: Roll Dice _p_: Progress _H_: Health _z_: Yes/No Oracle _o_: Links ⌘-h: Show Stats
|
||||||
_e_: Roll Edge _s_: Roll Shadow _S_: Spirit _c_: Show Oracle _J_/_K_: Page up/dn ⌘-l: Last Results
|
_e_: Roll Edge _s_: Roll Shadow _S_: Spirit _c_: Show Oracle _J_/_K_: Page up/dn ⌘-l: Last Results
|
||||||
_h_: Roll Heart _w_: Roll Wits _G_: Supply _O_: Other Oracles _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous
|
_h_: Roll Heart _w_: Roll Wits _G_: Supply _O_: Other Oracles _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous
|
||||||
_i_: Roll Iron _m_: Make Move _M_: Momentum _T_: Load Oracles ⌘-j: ↓ Next "
|
_i_: Roll Iron _m_: Make Move _M_: Momentum _T_: Load Oracles _y_/_Y_: Yank/Move ⌘-j: ↓ Next "
|
||||||
("d" rpgdm-ironsworn-roll) ("D" rpgdm-ironsworn-progress-roll)
|
("d" rpgdm-ironsworn-roll) ("D" rpgdm-ironsworn-progress-roll)
|
||||||
("z" rpgdm-ironsworn-oracle) ("O" rpgdm-oracle)
|
("z" rpgdm-ironsworn-oracle) ("O" rpgdm-oracle)
|
||||||
|
|
||||||
|
@ -490,9 +510,9 @@ to rolling two d10 challenge dice."
|
||||||
("O" hydra-rpgdm-oracles/body)
|
("O" hydra-rpgdm-oracles/body)
|
||||||
("p" hydra-rpgdm-progress/body)
|
("p" hydra-rpgdm-progress/body)
|
||||||
|
|
||||||
("o" ace-link) ("N" org-narrow-to-subtree) ("W" widen)
|
("o" ace-link) ("N" org-narrow-to-subtree) ("W" widen)
|
||||||
("K" scroll-down :color pink) ("J" scroll-up :color pink)
|
("K" scroll-down :color pink) ("J" scroll-up :color pink)
|
||||||
|
("y" rpgdm-paste-last-message) ("Y" rpgdm-ironsworn-paste-last-move)
|
||||||
("s-h" rpgdm-ironsworn-character-display)
|
("s-h" rpgdm-ironsworn-character-display)
|
||||||
("C-m" rpgdm-last-results :color pink)
|
("C-m" rpgdm-last-results :color pink)
|
||||||
("C-n" rpgdm-last-results-next :color pink)
|
("C-n" rpgdm-last-results-next :color pink)
|
||||||
|
@ -527,6 +547,10 @@ Specifically, does it begin with `:IRONSWORN-PROGRESS'"
|
||||||
(let ((p (symbol-name prop)))
|
(let ((p (symbol-name prop)))
|
||||||
(string-match (rx bos ":IRONSWORN-PROGRESS-") p)))
|
(string-match (rx bos ":IRONSWORN-PROGRESS-") p)))
|
||||||
|
|
||||||
|
(defun rpgdm-ironsworn--short-progress-p (prop)
|
||||||
|
(let ((p (symbol-name prop)))
|
||||||
|
(s-starts-with-p "progress-" p)))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--progress-to-str (prop)
|
(defun rpgdm-ironsworn--progress-to-str (prop)
|
||||||
"Convert a progress symbol, PROP to a string."
|
"Convert a progress symbol, PROP to a string."
|
||||||
(downcase (substring (symbol-name prop) 1)))
|
(downcase (substring (symbol-name prop) 1)))
|
||||||
|
@ -539,27 +563,37 @@ Specifically, does it begin with `:IRONSWORN-PROGRESS'"
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--current-character-state (results)
|
(defun rpgdm-ironsworn--current-character-state (results)
|
||||||
"Recursive helper to insert current header properties in RESULTS.
|
"Recursive helper to insert current header properties in RESULTS.
|
||||||
Calls itself if it is not looking at the top-level header in the file.
|
Calls itself if it is not looking at the top-level header in the
|
||||||
If a property is already in the hash table, RESULTS, it is not overwritten,
|
file. If a property is already in the hash table, RESULTS, it is
|
||||||
thereby having lower-level subtrees take precendence over similar settings
|
not overwritten, thereby having lower-level subtrees take
|
||||||
in higher headers."
|
precendence over similar settings in higher headers."
|
||||||
|
(defun key-convert (ironsworn-prop)
|
||||||
|
(make-symbol (downcase (substring (symbol-name ironsworn-prop) 11))))
|
||||||
|
|
||||||
|
(defun value-convert (value)
|
||||||
|
(if (string-match (rx bos (one-or-more digit) eos) value)
|
||||||
|
(string-to-number value)
|
||||||
|
value))
|
||||||
|
|
||||||
(let ((props (org-element--get-node-properties)))
|
(let ((props (org-element--get-node-properties)))
|
||||||
(loop for (k v) on props by (function cddr) do
|
(loop for (k v) on props by (function cddr) do
|
||||||
;; If a key is an ironsworn property, but isn't already in the table...
|
;; If key is ironsworn property, but isn't in the table...
|
||||||
(when (rpgdm-ironsworn--property-p k)
|
(when (rpgdm-ironsworn--property-p k)
|
||||||
(unless (gethash k results)
|
(let ((key (key-convert k))
|
||||||
(puthash k v results))))
|
(val (value-convert v)))
|
||||||
|
(unless (gethash key results)
|
||||||
|
(puthash key val results)))))
|
||||||
|
|
||||||
(unless (= (org-heading-level) 1)
|
(unless (= (org-heading-level) 1)
|
||||||
(org-up-element)
|
(org-up-element)
|
||||||
(rpgdm-ironsworn--current-character-state results))))
|
(rpgdm-ironsworn--current-character-state results))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-current-character-state ()
|
(defun rpgdm-ironsworn-current-character-state ()
|
||||||
"Return all set properties based on the position of the cursor in org doc.
|
"Return all set properties based on cursor position in org doc.
|
||||||
Note that values in sibling trees are ignored, and settings in lower levels
|
Note that values in sibling trees are ignored, and settings in
|
||||||
of the tree headings take precedence."
|
lower levels of the tree headings take precedence."
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(let ((results (make-hash-table)))
|
(let ((results (make-hash-table :test 'str-or-keys)))
|
||||||
(unless (eq 'headline (org-element-type (org-element-at-point)))
|
(unless (eq 'headline (org-element-type (org-element-at-point)))
|
||||||
(org-up-element))
|
(org-up-element))
|
||||||
|
|
||||||
|
@ -588,7 +622,7 @@ and the current progress of the track."
|
||||||
(let ((state (rpgdm-ironsworn-current-character-state)))
|
(let ((state (rpgdm-ironsworn-current-character-state)))
|
||||||
(thread-last state
|
(thread-last state
|
||||||
(hash-table-keys)
|
(hash-table-keys)
|
||||||
(-filter #'rpgdm-ironsworn--progress-p)
|
(-filter #'rpgdm-ironsworn--short-progress-p)
|
||||||
(--map (gethash it state))
|
(--map (gethash it state))
|
||||||
(-map #'rpgdm-ironsworn--progress-values))))
|
(-map #'rpgdm-ironsworn--progress-values))))
|
||||||
|
|
||||||
|
|
44
example.org
44
example.org
|
@ -21,21 +21,15 @@
|
||||||
:ironsworn-progress-bonds: "Bonds" 1 2
|
:ironsworn-progress-bonds: "Bonds" 1 2
|
||||||
:ironsworn-progress-story: "Learn the Magic of the Runes" 1 0
|
:ironsworn-progress-story: "Learn the Magic of the Runes" 1 0
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
My name is Kannan from a small circle called Elkfield near the great Primeval woods. This is my story.
|
My name is Kannan from a small circle called Elkfield near the great Primeval woods. This is my story.
|
||||||
|
|
||||||
*Bonds:*
|
*Bonds:*
|
||||||
- Home Circle of Elkfield
|
- Home Circle of Elkfield
|
||||||
** Background
|
** Background
|
||||||
:PROPERTIES:
|
|
||||||
:ironsworn-supply: 5
|
|
||||||
:END:
|
|
||||||
|
|
||||||
Before my mother died, she told me the hunter who died when I was 10, was not my father. She gave me an iron disk on a leather thong, and made me swear a vow that I would leave Elkfield and journey to Cinderhome to meet my father and face my destiny.
|
Before my mother died, she told me the hunter who died when I was 10, was not my father. She gave me an iron disk on a leather thong, and made me swear a vow that I would leave Elkfield and journey to Cinderhome to meet my father and face my destiny.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :results silent
|
|
||||||
(rpgdm-ironsworn-character :heart 1 :shadow 2 :wits 3 :iron 1 :edge 2)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*Assets*
|
*Assets*
|
||||||
|
|
||||||
- Companion: Raven
|
- Companion: Raven
|
||||||
|
@ -48,28 +42,22 @@ Before my mother died, she told me the hunter who died when I was 10, was not my
|
||||||
When you surround the remains of a recently deceased intelligent creature with lit candles, and summon its spirit, roll +heart. Add +1 if you share a bond. On a strong hit, the spirit appears and you may converse for a few minutes. Make moves as appropriate (add +1). On a weak hit, as above, but the spirit also delivers troubling news unrelated to your purpose. Envision what it tells you (Ask the Oracle if unsure) and Endure Stress (1 stress).
|
When you surround the remains of a recently deceased intelligent creature with lit candles, and summon its spirit, roll +heart. Add +1 if you share a bond. On a strong hit, the spirit appears and you may converse for a few minutes. Make moves as appropriate (add +1). On a weak hit, as above, but the spirit also delivers troubling news unrelated to your purpose. Envision what it tells you (Ask the Oracle if unsure) and Endure Stress (1 stress).
|
||||||
|
|
||||||
*** Bonds
|
*** Bonds
|
||||||
:PROPERTIES:
|
|
||||||
:progress: epic
|
|
||||||
:END:
|
|
||||||
|---+---+---+---+---+---+---+---+---+---|
|
|
||||||
| - | | | | | | | | | |
|
|
||||||
|---+---+---+---+---+---+---+---+---+---|
|
|
||||||
|
|
||||||
People and communities where I share a bond:
|
People and communities where I share a bond:
|
||||||
|
|
||||||
#+NAME: character-bonds
|
- Sibila :: My mother
|
||||||
- Elkfield :: My home circle
|
- Elkfield :: My home circle
|
||||||
** Inciting Vow: Find my Father
|
** Inciting Vow: Find my Father
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ironsworn-progress-vow-inciting: "Find my Father" 8 16
|
:ironsworn-progress-c45edbcc: "Find my Father" 4 8
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
*Milestones:*
|
*Milestones:*
|
||||||
|
|
||||||
- Reaching Cinderhome
|
- Reaching Cinderhome
|
||||||
|
- Finding information from Kori
|
||||||
*** Journey to Cinderhome
|
*** Journey to Cinderhome
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ironsworn-progress-journey-to-cinderhome: "Journey to Cinderhome" troublesome 8 32
|
:ironsworn-progress-7eaac1a2: "Journey to Cinderhome" 12 36
|
||||||
:END:
|
:END:
|
||||||
# Seems like a /troublesome/ journey. Do we want to mark with a table? Generate with a Snippet?
|
# Seems like a /troublesome/ journey. Do we want to mark with a table? Generate with a Snippet?
|
||||||
|
|
||||||
|
@ -110,13 +98,10 @@ Kori told me that Redcrest is a village perched on an island in the Barrier Isla
|
||||||
|
|
||||||
*** COMMENT Journey to the Coast
|
*** COMMENT Journey to the Coast
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ironsworn-progress-journey-to-coast: "Journey to the Coast" 8 16
|
:ironsworn-progress-28fd9563: "Journey to the Coast" 8 24
|
||||||
:ironsworn-supply: 1
|
:ironsworn-supply: 2
|
||||||
:END:
|
:END:
|
||||||
# Create a progress tracker, granted, F12 p n works too:
|
# Create a progress tracker, granted, F12 p n works too:
|
||||||
#+BEGIN_SRC emacs-lisp :results silent
|
|
||||||
(rpgdm-ironsworn-progress-create "Journey to the Coast" "Dangerous")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
# Undertake a Journey ... Weak hit :: 6 (2 + 4) → 6 / 1 - Ruined Moor
|
# Undertake a Journey ... Weak hit :: 6 (2 + 4) → 6 / 1 - Ruined Moor
|
||||||
|
|
||||||
|
@ -140,7 +125,8 @@ I tucked myself in a corner behind some growth, and placed /Curio/ on a branch o
|
||||||
**** Conflict with a Horror
|
**** Conflict with a Horror
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ironsworn-progress-b5f243e0: "Battle with Monstrosity" 8 16
|
:ironsworn-progress-b5f243e0: "Battle with Monstrosity" 8 16
|
||||||
:ironsworn-health: 4
|
:ironsworn-health: 3
|
||||||
|
:ironsworn-momentum: 3
|
||||||
:END:
|
:END:
|
||||||
# Danger: Tiny / Crustecean with Claws, Mandibles and stinger (Scorpion?) ... Move between realities / Poisonous
|
# Danger: Tiny / Crustecean with Claws, Mandibles and stinger (Scorpion?) ... Move between realities / Poisonous
|
||||||
|
|
||||||
|
@ -158,3 +144,15 @@ The strike hits the crustacean sending it flying, but I loose my balance, and fa
|
||||||
|
|
||||||
One of the scorpion-like creatures appears next to me, I try to swat it away with a branch, but miss, as a bugger I didn't see stings me.
|
One of the scorpion-like creatures appears next to me, I try to swat it away with a branch, but miss, as a bugger I didn't see stings me.
|
||||||
The pain is intense, and I realize I have to get out of here.
|
The pain is intense, and I realize I have to get out of here.
|
||||||
|
|
||||||
|
# Secure an Advantage ... Weak hit :: 6 (4 + 2) → 5 / 8
|
||||||
|
|
||||||
|
I grab Curio, tucking him under my arms and leap from the thicket.
|
||||||
|
|
||||||
|
# Clash ... Miss :: 7 (5 + 2) → 7 / 8
|
||||||
|
|
||||||
|
The stinging bugs are appear out of no where, and their barbs hurt. The venom is slowing me down, making it hard to concentrate.
|
||||||
|
|
||||||
|
# Secure an Advantage ... Strong hit :: 4 (2 + 2) → 3 / 2
|
||||||
|
|
||||||
|
I manage to get to the clearing among the shut houses. Do I dare cry for help, or flee back down the path?
|
||||||
|
|
BIN
images/list-of-moves.png
Normal file
BIN
images/list-of-moves.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
images/list-of-tables.png
Normal file
BIN
images/list-of-tables.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
images/ui-oracles.png
Normal file
BIN
images/ui-oracles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
images/ui-progress.png
Normal file
BIN
images/ui-progress.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
images/ui-screenshot.png
Normal file
BIN
images/ui-screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
Loading…
Reference in a new issue