Moves trigger progress tracks

Also a number of bug fixes and minor cosmetic changes ... of course.
This commit is contained in:
Howard Abrams 2022-04-29 21:30:49 -07:00
parent b5239362d5
commit 9c8b030633
9 changed files with 226 additions and 126 deletions

View file

@ -313,16 +313,16 @@ We assume you have created an org-file, and the /template/ will just append some
(setq name (rpgdm-tables-choose "name/ironlander")))
(let ((frmt (seq-random-elt '("* The Adventures of %s"
"* The Journeys of %s"
"* %s, an Epic Saga"
"* The Epic of %s"
"* Travels of %s"))))
"* The Journeys of %s"
"* %s, an Epic Saga"
"* The Epic of %s"
"* Travels of %s"))))
(goto-char (point-max))
(insert "# Local Variables:
# eval: (progn (require 'rpgdm-ironsworn) (rpgdm-mode))
# End:
")
(insert (format frmt name))))
;; (insert "# Local Variables: # eval: (progn (require 'rpgdm-ironsworn) (rpgdm-mode)) # End: ")
(insert (format frmt name))
(insert "
")))
#+END_SRC
**** Character Assets
We store the assets in a collection of org files in the [[file:assets/][assets]] directory. We'd like the user to choose an asset, so we convert a filename into something nicer to read based on extracting the /description/ from the /filename/, for instance, =:
@ -375,7 +375,7 @@ We can use a function that interactively queries the user for an asset and retur
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--pick-character-asset ()
"Completing read for an Ironsworn asset."
(let ((choice (completing-read "Which asset? " (rpgdm-ironsworn-character-assets))))
(let ((choice (completing-read "Which asset: " (rpgdm-ironsworn-character-assets))))
(thread-first choice
(assoc rpgdm-ironsworn-character-assets 'equal)
(cdr))))
@ -387,12 +387,24 @@ Let the user choose an asset and insert it into the file at the current point. W
(defun rpgdm-ironsworn-insert-character-asset (asset)
"Choose and insert the contents of an ASSET in the current buffer."
(interactive (list (rpgdm-ironsworn--pick-character-asset)))
(when rpgdm-ironsworn-new-character (goto-char (point-max)))
(let ((file (if (consp asset) (cdr asset) asset)))
(insert-file-contents file nil)
(ignore-errors
(insert-file-contents file nil))))
#+END_SRC
(when (called-interactively-p)
(when (y-or-n-p "Insert another asset? ")
(call-interactively 'rpgdm-ironsworn-insert-character-asset)))))
Hrm. Perhaps we just want to /look/ at an asset before inserting it, similar to how we show moves, we could open the asset in another buffer window.
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-show-character-asset (asset)
"Choose and insert the contents of an ASSET in the current buffer."
(interactive (list (rpgdm-ironsworn--pick-character-asset)))
(let ((asset-file (if (consp asset) (cdr asset) asset))
(orig-buf (window-buffer)))
(ignore-errors
(find-file-other-window asset-file)
(goto-char (point-min))
(pop-to-buffer orig-buf))))
#+END_SRC
When you start a character, you choose three assets, but what if we choose them randomly from our asset list? Could be fun, 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:
@ -479,15 +491,23 @@ Now we have a function that inserts the contents of three randomly chosen assets
Whew. We finally can have a function that queries the user about a new character and whether we should insert them randomly or let the user choose.... or do nothing at all.
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-insert-character-assets ()
(ignore-errors
(call-interactively 'rpgdm-ironsworn-insert-character-asset))
(when (y-or-n-p "Insert another asset? ")
(rpgdm-ironsworn-insert-character-assets)))
(defun rpgdm-ironsworn--new-character-assets ()
"Insert the contents of three character assets from the assets directory."
(goto-char (point-max))
(insert "\n** Assets\n")
(if (y-or-n-p "Would you like three random assets? ")
(rpgdm-ironsworn-random-character-assets 3)
(if (y-or-n-p "Would you like to choose your assets? ")
(call-interactively 'rpgdm-ironsworn-insert-character-asset))))
(goto-char (point-max))
(insert "\n** Assets\n")
(if (y-or-n-p "Would you like three random assets? ")
(rpgdm-ironsworn-random-character-assets 3)
(if (y-or-n-p "Would you like to choose your assets? ")
(rpgdm-ironsworn-insert-character-assets))))
#+END_SRC
**** Character Stats
This function will query the user for all of the stats and other properties that we will need to store before we can play the game.
@ -505,13 +525,12 @@ This function will query the user for all of the stats and other properties that
(rpgdm-ironsworn-store-character-state stat 5))
(rpgdm-ironsworn-store-character-state 'momentum 2)
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1)
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow: ") 1)
(rpgdm-ironsworn-progress-create "Bonds" 1)
(rpgdm-ironsworn-progress-mark "Bonds")
(search-forward ":END:")
(end-of-line)
(next-line)
(insert "\n** Bonds\n")
(insert (format " - Your home settlement of %s\n" (rpgdm-tables-choose "settlement/name"))))
(insert (format " - My home settlement of %s\n" (rpgdm-tables-choose "settlement/name"))))
#+END_SRC
**** New Character Interface
Do we choose the stats first, or the assets? Do we ask, or have two separate functions?
@ -521,6 +540,7 @@ Perhaps the clearest approach is to do both, create two process functions, and t
(defun rpgdm-ironsworn--new-character-stats-first (&optional name)
"Insert a new character template for character, NAME.
The character stats are first queried, and then assets inserted."
(goto-char (point-max))
(rpgdm-ironsworn--new-character-stats)
(rpgdm-ironsworn--new-character-assets))
@ -529,9 +549,10 @@ Perhaps the clearest approach is to do both, create two process functions, and t
The assets are inserted first, and then character stats are queried."
;; Saving and restoring point, means the properties should be in the
;; correct, top-level position.
(save-excursion
(rpgdm-ironsworn--new-character-assets))
(rpgdm-ironsworn--new-character-stats))
(let ((p (point)))
(rpgdm-ironsworn--new-character-assets)
(goto-char p)
(rpgdm-ironsworn--new-character-stats)))
(defun rpgdm-ironsworn-new-character (name order)
"Interactively query the user for a new character's attribute.
@ -543,13 +564,19 @@ Perhaps the clearest approach is to do both, create two process functions, and t
(read-string "What is the new character's name? ")
(completing-read "What order should we build this? " '("Statistics first" "Assets first"))))
(setq rpgdm-ironsworn-new-character t)
(rpgdm-ironsworn--new-character-template name)
(if (equal order "Assets first")
(rpgdm-ironsworn--new-character-assets-first)
(rpgdm-ironsworn--new-character-stats-first))
(setq rpgdm-ironsworn-new-character nil)
(message "Alright, the template is complete. Edit away!" name))
#+END_SRC
The asset-insertion and other functions above act slightly differently during the initial character creating process, so we keep track of that with this variable:
#+BEGIN_SRC emacs-lisp
(defvar rpgdm-ironsworn-new-character nil "Are we in the process of creating a new character?")
#+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.
@ -722,7 +749,7 @@ The =rpgdm-ironsworn-adjust-stat= function takes one of the four stats, like =
(:decrease (- curr numb))
(:absolute numb)
(t default))))
(message "Combining curr %d with %d with %s operator" curr numb oper)
;; (message "Combining curr %d with %d with %s operator" curr numb oper)
(rpgdm-ironsworn-store-character-state stat new)))
#+END_SRC
@ -1106,6 +1133,8 @@ Now, let's do the Move interface. We need to load the documentation, and retriev
The =rpgdm-ironsworn--make-move= call does something based on the properties stored in the file:
- If nil, do nothing
- If one or more stats, separated by a =|=, do each stat in series, e.g. =track|heart=
- If single stat that is =track=, call =rpgdm-ironsworn-progress-create=
- If single stat that is =progress=, call =rpgdm-ironsworn-progress-roll=
- If single stat, call =rpgdm-ironsworn-roll-stat= with that stat as a symbol, e.g. ='wits=
- If first entry is =>=, call =rpgdm-ironsworn-roll-stat= with the largest of the listed character's stats
@ -1116,19 +1145,29 @@ Seems like a job for =cond=:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--make-move (move-props)
"Query user for rolls based on the MOVE-PROPS."
(let* ((props (s-split " " (or move-props "")))
(count (seq-length props))
(stats (seq-filter (lambda (s) (string-match (rx (one-or-more alpha)) s)) props))
(first (first props)))
(cond
((seq-empty-p stats) nil)
((equal first "progress") (call-interactively 'rpgdm-ironsworn-progress-roll))
((= count 1) (rpgdm-ironsworn-roll-with first))
((equal first ">") (rpgdm-ironsworn-roll-best stats))
(t (rpgdm-ironsworn-roll-stat
(completing-read "Stat Choice: " stats)
(read-string "Modifier: "))))))
"Query user for rolls based on one or series of MOVE-PROPS."
(if (string-match (rx (group (1+ alpha)) "|" (group (1+ any))) move-props)
(let ((first (match-string 1 move-props))
(rest (match-string 2 move-props)))
(rpgdm-ironsworn--make-single-move first)
(rpgdm-ironsworn--make-move rest))
(rpgdm-ironsworn--make-single-move move-props)))
(defun rpgdm-ironsworn--make-single-move (move-props)
"Query user for rolls based on one MOVE-PROPS."
(let* ((props (s-split " " (or move-props "")))
(count (seq-length props))
(stats (seq-filter (lambda (s) (string-match (rx (one-or-more alpha)) s)) props))
(first (first props)))
(cond
((seq-empty-p stats) nil)
((equal first "track") (call-interactively 'rpgdm-ironsworn-progress-create))
((equal first "progress") (call-interactively 'rpgdm-ironsworn-progress-roll))
((= count 1) (rpgdm-ironsworn-roll-with first))
((equal first ">") (rpgdm-ironsworn-roll-best stats))
(t (rpgdm-ironsworn-roll-stat
(completing-read "Stat Choice: " stats)
(read-string "Modifier: "))))))
(defun rpgdm-ironsworn-roll-with (stat)
"Roll against a character STAT, as a string, and prompt for modifier.
@ -1228,7 +1267,7 @@ Adding a progress to a character amounts to an arbitrary name, and the number of
(title (org-get-heading))
(option '(("At the same level as a sibling?" same-level)
("As a subheading to this?" subheading)
("No new heading. Re-use this." no))))
("No new heading. Re-use this?" no))))
(when (called-interactively-p)
(cl-case (completing-read-value "Create a new heading? " option)
@ -1517,7 +1556,7 @@ With these properties in place, we can now do a much better job with the [[file:
(interactive (list (completing-read "Stat Choice: "
'("wits" "shadow" "edge"))))
(let ((table-name (format "delve/weak-hit/%s" stat)))
(message "Rolling on %s" table-name)
;; (message "Rolling on %s" table-name)
(rpgdm-tables-choose table-name)))
(defun rpgdm-ironsworn-delve-the-depths-weak-edge ()
@ -1546,14 +1585,19 @@ With the theme and domain properties in place, we can now do a much better job w
properties in the current org file, and rolls on the appropriate
chart."
(let* ((theme (rpgdm-ironsworn-character-stat 'site-theme))
(domain (rpgdm-ironsworn-character-stat 'site-domain))
(danger (rpgdm-tables-choose "danger")))
(domain (rpgdm-ironsworn-character-stat 'site-domain))
(danger (rpgdm-tables-choose "danger")))
(cond
((equal danger "Check the theme card.")
(rpgdm-tables-choose (format "danger/theme/%s" theme)))
(rpgdm-tables-choose (format "danger/theme/%s" theme)))
((equal danger "Check the domain card.")
(rpgdm-tables-choose (format "danger/domain/%s" theme)))
(rpgdm-tables-choose (format "danger/domain/%s" domain)))
((s-starts-with? "Roll twice" danger)
(format "%s <AND> %s"
(rpgdm-ironsworn--reveal-a-danger)
(rpgdm-ironsworn--reveal-a-danger)))
(t danger))))
@ -1791,8 +1835,9 @@ Can a Hydra call a hydra? Let's more all the special oracle and progress functio
("A" rpgdm-ironsworn-insert-character-asset "insert new asset")
("a" rpgdm-ironsworn-asset-stat-adjust "adjust asset stat")
("n" rpgdm-ironsworn-asset-stat-create "new asset stat")
("s" rpgdm-ironsworn-asset-stat-show "show")
("r" rpgdm-ironsworn-asset-stat-roll "roll asset as modifier"))
("s" rpgdm-ironsworn-asset-stat-show "show asset stat")
("r" rpgdm-ironsworn-asset-stat-roll "roll asset as modifier")
("v" rpgdm-ironsworn-show-character-asset "view asset"))
#+END_SRC
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. A pattern I would like to follow is that a uppercase letters indicate altering something.
@ -1811,7 +1856,7 @@ I would also like pairings where lowercase ~p~ rolls against Supply, but a ~P~ c
But we roll some of these more than others, especially since "moves" does most of the work.
#+begin_src emacs-lisp
(defhydra hydra-rpgdm (:color blue :hint nil)
(defhydra hydra-rpgdm (:color pink :hint nil)
"
^Dice^ 0=d100 1=d10 6=d6 ^Roll/Adjust^ ^Oracles/Tables^ ^Moving/Editing^ ^Messages^
------------------------------------------------------------------------------------------------------------------------------
@ -1839,9 +1884,9 @@ But we roll some of these more than others, especially since "moves" does most o
("O" rpgdm-tables-load) ("c" rpgdm-tables-choose) ("C" rpgdm-tables-choose :color pink)
("d" hydra-rpgdm-delve/body)
("p" hydra-rpgdm-progress/body)
("a" hydra-rpgdm-assets/body)
("d" hydra-rpgdm-delve/body :color blue)
("p" hydra-rpgdm-progress/body :color blue)
("a" hydra-rpgdm-assets/body :color blue)
("o" ace-link) ("N" org-narrow-to-subtree) ("W" widen)
("K" scroll-down :color pink) ("J" scroll-up :color pink)
@ -1857,6 +1902,7 @@ But we roll some of these more than others, especially since "moves" does most o
("0" rpgdm-roll-d100 :color pink)
("1" rpgdm-roll-d10 :color pink)
("6" rpgdm-roll-d6 :color pink)
("RET" evil-open-below :color blue)
("q" nil "quit") ("<f6>" nil))
#+END_SRC
** Org Interface
@ -1868,10 +1914,13 @@ Couple of patterns:
- 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:
I may make a distinction between different types of character stats, as some, like the “Iron” attribute is a stat a character always has, and doesnt change. Others, like “health” changes, but never really goes away. Still others, my be temporary, and only needing to be accessible for /part/ of an document and would go away when we create a new heading at the same level.
To make a temporary stat, we set its values in the current document tree using [[help:org-set-property][org-set-property]], and the function =rpgdm-ironsworn-store-character-temp-state= its function.
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-store-character-state (stat value)
(defun rpgdm-ironsworn-store-character-temp-state (stat value)
"Store the VALUE of a character's STAT in the current org tree property.
Note that STAT should be a symbol, like `supply' and VALUE should be a
number, but doesn't have to be."
@ -1879,11 +1928,14 @@ We set all of these values in the current tree using [[help:org-set-property][or
(when (numberp value)
(setq value (number-to-string value)))
(org-set-property prop value)))
#+END_SRC
Oh, but what if we want to store a value for a /default stat/? This needs to go to the top-level header first:
But since most character stats do not simply /go away/ but are constantly in flux, and these need to be stored at the top-most level of the org file, so the function, =rpgdm-ironsworn-store-character-state=, walks up the heading tree until it reaches the top.
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-store-default-character-state (stat value)
(defun rpgdm-ironsworn-store-character-state (stat value)
"Store the VALUE of a character's STAT in the top-level org tree property.
Note that STAT should be a symbol, like `supply' and VALUE should be a
number, but doesn't have to be."
@ -1891,7 +1943,14 @@ Oh, but what if we want to store a value for a /default stat/? This needs to go
(org-up-heading)
(while (> (org-heading-level) 1)
(org-up-heading))
(rpgdm-ironsworn-store-character-state stat value)))
(rpgdm-ironsworn-store-character-temp-state stat value)))
#+END_SRC
Attributes (stats that never change) are actually stored just like any other stat (at least, at the moment), so this “default” function name is an alias for the normal process of storing state:
#+BEGIN_SRC emacs-lisp
(defalias 'rpgdm-ironsworn-store-default-character-state
'rpgdm-ironsworn-store-character-state)
#+END_SRC
*** Property Key Conversions and Predicates
Loading state means looking at properties, and we distinguish Ironsworn-specific with a few functions:
@ -1964,7 +2023,7 @@ Since [[help:org-up-element][org-up-element]]s behavior has changed, and [[he
(defun org-up-heading ()
"Move the point to next parent heading, unless already at the top-level."
(if (= 0 (org-heading-level))
(outline-up-heading 0)
(org-previous-visible-heading 1)
(outline-up-heading 1)))
#+END_SRC
@ -1991,7 +2050,6 @@ Enough chit-chat, let's write this function. While we are at it, let's convert t
(when (rpgdm-ironsworn--property-p k)
(let ((key (key-convert k))
(val (value-convert v)))
(message "Found %s : %s" key val)
(unless (gethash key results)
(puthash key val results)))))
@ -2014,7 +2072,6 @@ Enough chit-chat, let's write this function. While we are at it, let's convert t
(second)
(plist-get :raw-value))
results)
(message "Hash: %s" results)
(rpgdm-ironsworn--current-character-state results)
results)))
#+END_SRC
@ -2043,8 +2100,9 @@ Org can return the value, but breaking it up requires a regular expression:
(list (match-string 1 value)
(string-to-number (match-string 2 value))
(string-to-number (match-string 3 value))))))
#+END_SRC
#+END_SRC
(rx (syntax character-quote))
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

View file

@ -0,0 +1,31 @@
** Begin a Journey
When you travel across hazardous or unfamiliar lands, first set the rank of your journey.
- Troublesome journey :: 3 progress per waypoint.
- Dangerous journey :: 2 progress per waypoint.
- Formidable journey :: 1 progress per waypoint.
- Extreme journey :: 2 ticks per waypoint.
- Epic journey :: 1 tick per waypoint.
Then, for each segment of your journey, make the [[file:undertake-a-journey.org][Undertake a Journey]] move.
*** Details
:PROPERTIES:
:move-stats: track
:VISIBILITY: folded
:END:
This is Ironsworns travel move. When you set off toward a destination, make this move.
First, give your journey a rank. Decide how far—and how hazardous—it is based on the established fiction. If youre unsure, [[file:../fate/ask-the-oracle.org][Ask the Oracle]]. Most of your journeys should be troublesome or dangerous. Formidable or extreme journeys might require weeks within your narrative, with appropriate stops, side quests, and adventures along the way. An epic journey is one of months, or even years. It is the journey of a lifetime.
If the journey is mundane—a relatively short distance through safe territory, —dont make this move. Just narrate the trip and jump to what happens or what you do when you arrive.
**** Along for the Ride?
If you are part of a caravan or party of NPCs, and arent an active participant in the planning or execution of the journey, you wont make this move or track progress. The journey will be resolved in the fiction. You can [[file:../fate/ask-the-oracle.org][Ask the Oracle]] to determine what happens en route or when you arrive.
**** Allies and Journeys
If you are traveling with allies, one of you makes the *Undertake a Journey* roll for each segment, and you share a progress track. The responsibility for leading the journey can switch from segment to segment as you like.
Your fellow travelers can assist by making the [[file:../relationship/aid-your-ally.org][Aid Your Ally]] move. Perhaps they are scouting ahead or sustaining you with a lively song. They can also Resupply to represent foraging or hunting for supplies en route. Everyone should offer narrative color for what they do and see on the journey, even if they are not making moves.
Only the character making the move takes the momentum bonus on a *strong hit*. But, because your supply track is shared, each of you mark -1 supply when the acting character makes that choice on a *strong hit* or when they suffer a *weak hit*.

View file

@ -1,14 +1,6 @@
** Undertake a Journey
When you travel across hazardous or unfamiliar lands, first set the rank of your journey.
- Troublesome journey :: 3 progress per waypoint.
- Dangerous journey :: 2 progress per waypoint.
- Formidable journey :: 1 progress per waypoint.
- Extreme journey :: 2 ticks per waypoint.
- Epic journey :: 1 tick per waypoint.
Then, for each segment of your journey, roll +wits. If you are setting off from a community with which you share a bond, add +1 to your initial roll.
For each segment of your journey, roll +wits. If you are just setting off from a community with which you share a bond (see the [[file:begin-a-journey.org][Begin a Journey]] move), add +1 to your initial roll.
On a *strong hit*, you reach a waypoint. If the waypoint is unknown to you, envision it or roll the [[elisp:(rpgdm-tables-choose "locations")][locations]] or [[elisp:(rpgdm-tables-choose "location-descriptors")][descriptors]] tables (or [[elisp:(kill-new (format "- %s %s" (rpgdm-tables-choose "location-descriptors") (rpgdm-tables-choose "locations")))][both]]), or even the site-specific [[elisp:(rpgdm-tables-choose "site-name-detail")][detail]] or [[elisp:(rpgdm-tables-choose "site-name-description")][description]]s, to help describe the waypoint. Then, choose one:

View file

@ -25,7 +25,7 @@ On a *miss*, combat begins with you at a disadvantage. [[file:../fate/pay-the-pr
*** Details
:PROPERTIES:
:move-stats: heart shadow wits
:move-stats: track|heart shadow wits
:VISIBILITY: folded
:END:

View file

@ -14,6 +14,7 @@ Then, [[file:delve-the-depths.org][Delve the Depths]] to explore this place.
*** Details
:PROPERTIES:
:move-stats: track
:VISIBILITY: folded
:END:

View file

@ -12,7 +12,7 @@ On a *miss*, you face a significant obstacle before you can begin your quest. En
*** Details
:PROPERTIES:
:move-stats: heart
:move-stats: track|heart
:VISIBILITY: folded
:END:

View file

@ -29,7 +29,7 @@ On a *miss*, also suffer -1 momentum. If you are at 0 health, you must mark woun
*** Details
:PROPERTIES:
:move-stats: > heart iron
:move-stats: > health iron
:VISIBILITY: folded
:END:

View file

@ -10,8 +10,8 @@
;;
;;; Commentary:
;;
;; This file is conspicuously absent from commentary or even
;; comments. This is because this file is created from tangling
;; This file is conspicuously absent of commentary or even
;; comments. This is because we create this file by tangling
;; the README.org file in this directory.
;;
;;; Code:
@ -96,16 +96,16 @@ a random name is generated for the purposes of the template."
(setq name (rpgdm-tables-choose "name/ironlander")))
(let ((frmt (seq-random-elt '("* The Adventures of %s"
"* The Journeys of %s"
"* %s, an Epic Saga"
"* The Epic of %s"
"* Travels of %s"))))
"* The Journeys of %s"
"* %s, an Epic Saga"
"* The Epic of %s"
"* Travels of %s"))))
(goto-char (point-max))
(insert "# Local Variables:
# eval: (progn (require 'rpgdm-ironsworn) (rpgdm-mode))
# End:
")
(insert (format frmt name))))
;; (insert "# Local Variables: # eval: (progn (require 'rpgdm-ironsworn) (rpgdm-mode)) # End: ")
(insert (format frmt name))
(insert "
")))
(defun rpgdm-ironsworn--character-asset-label (filename)
"Given a FILENAME of an Ironsworn asset, return an Asset label."
@ -142,7 +142,7 @@ the `assets' directory, otherwise, we return a cached version."
(defun rpgdm-ironsworn--pick-character-asset ()
"Completing read for an Ironsworn asset."
(let ((choice (completing-read "Which asset? " (rpgdm-ironsworn-character-assets))))
(let ((choice (completing-read "Which asset: " (rpgdm-ironsworn-character-assets))))
(thread-first choice
(assoc rpgdm-ironsworn-character-assets 'equal)
(cdr))))
@ -150,6 +150,7 @@ the `assets' directory, otherwise, we return a cached version."
(defun rpgdm-ironsworn-insert-character-asset (asset)
"Choose and insert the contents of an ASSET in the current buffer."
(interactive (list (rpgdm-ironsworn--pick-character-asset)))
(when rpgdm-ironsworn-new-character (goto-char (point-max)))
(let ((file (if (consp asset) (cdr asset) asset)))
(insert-file-contents file nil)
@ -221,17 +222,16 @@ Note: The stats are added as properties using the
(rpgdm-ironsworn-store-character-state stat 5))
(rpgdm-ironsworn-store-character-state 'momentum 2)
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1)
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow: ") 1)
(rpgdm-ironsworn-progress-create "Bonds" 1)
(rpgdm-ironsworn-progress-mark "Bonds")
(search-forward ":END:")
(end-of-line)
(insert "\n** Bonds\n")
(insert (format " - Your home settlement of %s\n" (rpgdm-tables-choose "settlement/name"))))
(defun rpgdm-ironsworn--new-character-stats-first (&optional name)
"Insert a new character template for character, NAME.
The character stats are first queried, and then assets inserted."
(goto-char (point-max))
(rpgdm-ironsworn--new-character-stats)
(rpgdm-ironsworn--new-character-assets))
@ -240,9 +240,10 @@ The character stats are first queried, and then assets inserted."
The assets are inserted first, and then character stats are queried."
;; Saving and restoring point, means the properties should be in the
;; correct, top-level position.
(save-excursion
(rpgdm-ironsworn--new-character-assets))
(rpgdm-ironsworn--new-character-stats))
(let ((p (point)))
(rpgdm-ironsworn--new-character-assets)
(goto-char p)
(rpgdm-ironsworn--new-character-stats)))
(defun rpgdm-ironsworn-new-character (name order)
"Interactively query the user for a new character's attribute.
@ -254,12 +255,16 @@ which should be using the `org-mode' major mode."
(read-string "What is the new character's name? ")
(completing-read "What order should we build this? " '("Statistics first" "Assets first"))))
(setq rpgdm-ironsworn-new-character t)
(rpgdm-ironsworn--new-character-template name)
(if (equal order "Assets first")
(rpgdm-ironsworn--new-character-assets-first)
(rpgdm-ironsworn--new-character-stats-first))
(setq rpgdm-ironsworn-new-character nil)
(message "Alright, the template is complete. Edit away!" name))
(defvar rpgdm-ironsworn-new-character nil "Are we in the process of creating a new character?")
(defun rpgdm-ironsworn--display-stat (stat character)
"Colorized the STAT from a CHARACTER hash containing it.
See `rpgdm-ironsworn-character-display'."
@ -350,7 +355,7 @@ If the STAT isn't found, returns DEFAULT."
(:decrease (- curr numb))
(:absolute numb)
(t default))))
(message "Combining curr %d with %d with %s operator" curr numb oper)
;; (message "Combining curr %d with %d with %s operator" curr numb oper)
(rpgdm-ironsworn-store-character-state stat new)))
(defun rpgdm-ironsworn-adjust-health ()
@ -574,19 +579,29 @@ on the properties in the file."
(rpgdm-ironsworn--store-move title (rpgdm-ironsworn--make-move props))))
(defun rpgdm-ironsworn--make-move (move-props)
"Query user for rolls based on the MOVE-PROPS."
(let* ((props (s-split " " (or move-props "")))
(count (seq-length props))
(stats (seq-filter (lambda (s) (string-match (rx (one-or-more alpha)) s)) props))
(first (first props)))
(cond
((seq-empty-p stats) nil)
((equal first "progress") (call-interactively 'rpgdm-ironsworn-progress-roll))
((= count 1) (rpgdm-ironsworn-roll-with first))
((equal first ">") (rpgdm-ironsworn-roll-best stats))
(t (rpgdm-ironsworn-roll-stat
(completing-read "Stat Choice: " stats)
(read-string "Modifier: "))))))
"Query user for rolls based on one or series of MOVE-PROPS."
(if (string-match (rx (group (1+ alpha)) "|" (group (1+ any))) move-props)
(let ((first (match-string 1 move-props))
(rest (match-string 2 move-props)))
(rpgdm-ironsworn--make-single-move first)
(rpgdm-ironsworn--make-move rest))
(rpgdm-ironsworn--make-single-move move-props)))
(defun rpgdm-ironsworn--make-single-move (move-props)
"Query user for rolls based on one MOVE-PROPS."
(let* ((props (s-split " " (or move-props "")))
(count (seq-length props))
(stats (seq-filter (lambda (s) (string-match (rx (one-or-more alpha)) s)) props))
(first (first props)))
(cond
((seq-empty-p stats) nil)
((equal first "track") (call-interactively 'rpgdm-ironsworn-progress-create))
((equal first "progress") (call-interactively 'rpgdm-ironsworn-progress-roll))
((= count 1) (rpgdm-ironsworn-roll-with first))
((equal first ">") (rpgdm-ironsworn-roll-best stats))
(t (rpgdm-ironsworn-roll-stat
(completing-read "Stat Choice: " stats)
(read-string "Modifier: "))))))
(defun rpgdm-ironsworn-roll-with (stat)
"Roll against a character STAT, as a string, and prompt for modifier.
@ -655,7 +670,7 @@ NAME should be a short title, not a description."
(title (org-get-heading))
(option '(("At the same level as a sibling?" same-level)
("As a subheading to this?" subheading)
("No new heading. Re-use this." no))))
("No new heading. Re-use this?" no))))
(when (called-interactively-p)
(cl-case (completing-read-value "Create a new heading? " option)
@ -846,7 +861,7 @@ The STAT should be the symbol, 'wits, 'shadow, or 'edge."
(interactive (list (completing-read "Stat Choice: "
'("wits" "shadow" "edge"))))
(let ((table-name (format "delve/weak-hit/%s" stat)))
(message "Rolling on %s" table-name)
;; (message "Rolling on %s" table-name)
(rpgdm-tables-choose table-name)))
(defun rpgdm-ironsworn-delve-the-depths-weak-edge ()
@ -871,14 +886,19 @@ or domain tables, this reads the `site-theme' and `site-domain'
properties in the current org file, and rolls on the appropriate
chart."
(let* ((theme (rpgdm-ironsworn-character-stat 'site-theme))
(domain (rpgdm-ironsworn-character-stat 'site-domain))
(danger (rpgdm-tables-choose "danger")))
(domain (rpgdm-ironsworn-character-stat 'site-domain))
(danger (rpgdm-tables-choose "danger")))
(cond
((equal danger "Check the theme card.")
(rpgdm-tables-choose (format "danger/theme/%s" theme)))
((equal danger "Check the domain card.")
(rpgdm-tables-choose (format "danger/domain/%s" theme)))
(rpgdm-tables-choose (format "danger/domain/%s" domain)))
((s-starts-with? "Roll twice" danger)
(format "%s <AND> %s"
(rpgdm-ironsworn--reveal-a-danger)
(rpgdm-ironsworn--reveal-a-danger)))
(t danger))))
@ -1156,7 +1176,7 @@ Return 0 if not at a heading, or above first headline."
(defun org-up-heading ()
"Move the point to next parent heading, unless already at the top-level."
(if (= 0 (org-heading-level))
(outline-up-heading 0)
(org-previous-visible-heading 1)
(outline-up-heading 1)))
(defun rpgdm-ironsworn--current-character-state (results)
@ -1179,7 +1199,6 @@ precendence over similar settings in higher headers."
(when (rpgdm-ironsworn--property-p k)
(let ((key (key-convert k))
(val (value-convert v)))
(message "Found %s : %s" key val)
(unless (gethash key results)
(puthash key val results)))))
@ -1202,7 +1221,6 @@ lower levels of the tree headings take precedence."
(second)
(plist-get :raw-value))
results)
(message "Hash: %s" results)
(rpgdm-ironsworn--current-character-state results)
results)))

View file

@ -5,15 +5,15 @@
#+TAGS: rpg table ironsworn
Roll on Table: d100
| 1-30 | Check the theme card. |
| 31-45 | Check the domain card. |
| 46-57 | You encounter a hostile denizen. |
| 58-68 | You face an environmental or architectural hazard. |
| 69-76 | A discovery undermines or complicates your quest. |
| 77-79 | You confront a harrowing situation or sensation. |
| 80-82 | You face the consequences of an earlier choice or approach. |
| 83-85 | Your way is blocked or trapped. |
| 86-88 | A resource is diminished, broken, or lost. |
| 89-91 | You face a perplexing mystery or tough choice. |
| 92-94 | You lose your way or are delayed. |
| 95-00 | Roll twice more on this table. Both results occur. |
| 1-30 | Check the theme card. |
| 31-45 | Check the domain card. |
| 46-57 | You encounter a hostile denizen. |
| 58-68 | You face an environmental or architectural hazard. |
| 69-76 | A discovery undermines or complicates your quest. |
| 77-79 | You confront a harrowing situation or sensation. |
| 80-82 | You face the consequences of an earlier choice or approach. |
| 83-85 | Your way is blocked or trapped. |
| 86-88 | A resource is diminished, broken, or lost. |
| 89-91 | You face a perplexing mystery or tough choice. |
| 92-94 | You lose your way or are delayed. |
| 95-100 | Roll twice more on this table. Both results occur. |