diff --git a/README.org b/README.org index 9d7dacb..1a26971 100644 --- a/README.org +++ b/README.org @@ -48,7 +48,7 @@ What I do, is add the following "code" somewhere in my Ironsworn-specific org fi # End: #+END_SRC -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 ~F6~) to bring up the Hydra of commands: +Finally, define your character. While I describe the details later, first, load an org-mode file and hit ~M-x~ to 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 ~F6~) to bring up the Hydra of commands: #+ATTR_HTML: :width 1100px [[file:images/ui-screenshot.png]] @@ -56,16 +56,21 @@ Finally, define your character. While I describe the details later, hit ~M-x~ an 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. + - ~D~ rolls the /Ironsworn/ dice, two d10s and a d6 allowing you to add all the modifiers you want. Better. + - ~e~, ~r~, ~i~, ~h~, and ~w~ roll against your character's /edge/, /heart/, /iron/, /shadow/ and /wits/ respectively. + - ~l~, ~s~, and ~p~ rolls against your current /health/, /spirit/ and /supply/ respectively. + - ~L~, ~S~, and ~P~ allows you to adjust your /health/, /spirit/ and /supply/ respectively. + - ~M~ let you change your momentum (rolls that benefit from momentum will show it) - ~p~ allows you to create a new progress track, or mark progress against it. It has its own submenu: #+ATTR_HTML: :width 1100px [[file:images/ui-progress.png]] - ~m~ lets you choose a move, display the details, and lets you roll against it. #+ATTR_HTML: :width 1100px [[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?" + - ~d~ lets you choose a Delve-specific actions, chosen after you’ve performed the move. + #+ATTR_ORG: :width 1627px + [[file:images/ui-delve.png]] + - ~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 the moor is haunted?" - ~c~ displays a list of random tables to roll against: #+ATTR_HTML: :width 1100px [[file:images/list-of-tables.png]] @@ -606,37 +611,127 @@ Just to prove it to ourselves, all of the following expressions return the same #+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,= but the value I want to enter could be: -#+BEGIN_SRC emacs-lisp :results silent - (defun rpgdm-ironsworn-adjust-stat (stat adj &optional default) - "Increase or decrease the current character's STAT by ADJ. - If the STAT isn't found, returns DEFAULT." - (let* ((curr (rpgdm-ironsworn-character-stat stat)) - (new (+ curr adj))) - (rpgdm-ironsworn-store-character-state stat new))) + - A modifier to /increase/ the value incrementally, like +1 + - A modifier to /decrease/ the value incrementally, like -2 + - A set value (like =5= when Sojourning). + - A reset to some default value, like going back to =2= for momentum. - (defun rpgdm-ironsworn-adjust-health (health-adj) - "Increase or decrease the current character's health by HEALTH-ADJ." - (interactive "nHealth Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'health health-adj 5)) +This function will be used for =interactive= to return a tuple of both the /operation/ as well as the number: - (defun rpgdm-ironsworn-adjust-spirit (spirit-adj) - "Increase or decrease the current character's spirit by SPIRIT-ADJ." - (interactive "nSpirit Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'spirit spirit-adj 5)) +#+BEGIN_SRC emacs-lisp + (defun rpgdm-ironsworn--read-stat (label) + "A `read-string', but for the changeable value associated with LABEL. + A `+1' means increasing the stat by 1, while a `-1' decreased that stat. + A `=3' sets the stat to the number (in this case, `3'). - (defun rpgdm-ironsworn-adjust-supply (supply-adj) - "Increase or decrease the current character's supply by SUPPLY-ADJ." - (interactive "nSupply Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'supply supply-adj 5)) + Hitting return (entering a blank value) increments the 'momentum stat, + but decrements any other stats by `1'. Any other value means to take + the default for that stat." + (let ((value (read-string (format "Adjustment to %s (+/-/= for absolute value): " label))) + (rxnum (rx (group (optional (or "+" "-" "="))) (* space) (group (+ digit)) (* space)))) - (defun rpgdm-ironsworn-adjust-momentum (momentum-adj) - "Increase or decrease the current character's momentum by MOMENTUM-ADJ." - (interactive "nMomentum Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'momentum momentum-adj 2)) + (if (string-match rxnum value) + (let ((sign (match-string 1 value)) + (numb (string-to-number (match-string 2 value)))) + (cond + ((equal sign "-") `(:decrease ,numb)) + ((equal sign "+") `(:increase ,numb)) + ((equal sign "=") `(:absolute ,numb)) + (t (if (eq label `momentum) `(:increase ,numb) `(:decrease ,numb))))) + + (if (string-blank-p value) + (if (eq label 'momentum) '(:increase 1) '(:decrease 1)) + '(:reset 0))))) #+END_SRC +Best if we wrote some unit tests to both explain and verify this function. This test uses the [[help:cl-letf][letf]], which allows us to override the [[help:read-string][read-string]] function for my tests. Why yes, this is clever way of doing what other languages would need a /mock/ object. + +#+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el + (ert-deftest rpgdm-ironsworn--read-stat-test () + ;; Numbers with a minus sign always should indicate a decrease to the current value: + (cl-letf (((symbol-function 'read-string) (lambda (s) "-2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:decrease 2)))) + + ;; Numbers with a minus sign always should indicate a increase to the current value: + (cl-letf (((symbol-function 'read-string) (lambda (s) "+2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:increase 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:increase 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:increase 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:increase 2)))) + + ;; Numbers with a minus sign always should indicate a new setting: + (cl-letf (((symbol-function 'read-string) (lambda (s) "=2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:absolute 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:absolute 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:absolute 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:absolute 2)))) + + ;; Just a number should change based on the type so stat, most go down, momentum goes up: + (cl-letf (((symbol-function 'read-string) (lambda (s) "2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:increase 2)))) + + ;; No numeric value, most stats go down by one, but momentum goes up by one: + (cl-letf (((symbol-function 'read-string) (lambda (s) ""))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:decrease 1))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:decrease 1))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:decrease 1))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:increase 1)))) + + ;; Anything else should return a :reset, as it will take the default value: + (cl-letf (((symbol-function 'read-string) (lambda (s) "go back"))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:reset 0))))) +#+END_SRC + +The =rpgdm-ironsworn-adjust-stat= function takes one of the four stats, like =’health= or =’momentum=, as well as its =default= or /starting/ value, collects the /current value/ (the =curr= variable), and then creates a new value based on the /operator/ determined by the input from =rpgdm-ironsworn--read-stat=. It sets the new stat by calling =rpgdm-ironsworn-store-character-state= defined below. + +#+BEGIN_SRC emacs-lisp :results silent + (defun rpgdm-ironsworn-adjust-stat (stat &optional default) + "Increase or decrease the current character's STAT by ADJ. + If the STAT isn't found, returns DEFAULT." + (let* ((tuple (rpgdm-ironsworn--read-stat stat)) + (curr (rpgdm-ironsworn-character-stat stat)) + (oper (first tuple)) + (numb (second tuple)) + (new (cl-case oper + (:increase (+ curr numb)) + (:decrease (- curr numb)) + (:absolute numb) + (t default)))) + (message "Combining curr %d with %d with %s operator" curr numb oper) + (rpgdm-ironsworn-store-character-state stat new))) +#+END_SRC + +A few sugar functions for adding to our interface for each of the four stats: + +#+BEGIN_SRC emacs-lisp + (defun rpgdm-ironsworn-adjust-health () + "Increase or decrease the current character's health interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'health 5)) + + (defun rpgdm-ironsworn-adjust-spirit () + "Increase or decrease the current character's spirit interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'spirit 5)) + + (defun rpgdm-ironsworn-adjust-supply () + "Increase or decrease the current character's supply interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'supply 5)) + + (defun rpgdm-ironsworn-adjust-momentum () + "Increase or decrease the current character's momentum interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'momentum 2)) +#+END_SRC ** Roll against Character Stats Which allows us to create character stat-specific rolling functions: @@ -1193,7 +1288,7 @@ Let’s put this function too in our =rpgdm-tables= hash table, so I can easily To begin delving into a site, you choose a /theme/ and a /domain/, and then . Let’a have a function that allows us to choose both (and store) so that we can refer to them again. #+BEGIN_SRC emacs-lisp - (defun rpgdm-ironsworn-discover-a-site (theme domain) + (defun rpgdm-ironsworn-discover-a-site (theme domain &optional name level) "Store a Delve Site information in the org file. The THEME and DOMAIN need to match org files in the `tables' directory, and the choices themselves come from these files. @@ -1207,12 +1302,16 @@ To begin delving into a site, you choose a /theme/ and a /domain/, and then . Le (completing-read "What is the site theme? " rpgdm-ironsworn-site-themes) (completing-read "What is the domain? " rpgdm-ironsworn-site-domains))) - (rpgdm-ironsworn-progress-create - (read-string "Site Name: " - (rpgdm-ironsworn-oracle-site-name domain)) - (completing-read-value "Progress Level: " - rpgdm-ironsworn-progress-levels)) - (next-line 2) + (when (called-interactively-p) + (setq name (read-string "Site Name: " (rpgdm-ironsworn-oracle-site-name domain))) + (setq level (completing-read-value "Progress Level: " rpgdm-ironsworn-progress-levels))) + + (org-insert-heading-respect-content) + (org-shiftmetaright) + (insert name) + (rpgdm-ironsworn-progress-create name level) + (ignore-errors + (next-line 2)) (rpgdm-ironsworn-store-character-state 'site-theme (downcase theme)) (rpgdm-ironsworn-store-character-state 'site-domain (downcase domain))) #+END_SRC @@ -1515,7 +1614,7 @@ But we roll some of these more than others, especially since "moves" does most o (defhydra hydra-rpgdm (:color blue :hint nil) " ^Dice^ 0=d100 1=d10 6=d6 ^Roll/Adjust^ ^Oracles/Tables^ ^Moving/Editing^ ^Messages^ - ---------------------------------------------------------------------------------------------------------------------------------------------------- + ------------------------------------------------------------------------------------------------------------------------------ _D_: Roll Dice _p_: Progress _l_/_L_: Health _z_/_Z_: Yes/No Oracle _o_: Links ⌘-h: Show Stats _e_: Roll Edge _h_: Roll Shadow _t_/_T_: Spirit _c_/_C_: Show Oracle _J_/_K_: Page up/dn ⌘-l: Last Results _r_: Roll Heart _w_: Roll Wits _s_/_S_: Supply _O_: Load Oracles _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous diff --git a/images/ui-delve.png b/images/ui-delve.png new file mode 100644 index 0000000..7a0d223 Binary files /dev/null and b/images/ui-delve.png differ diff --git a/images/ui-screenshot.png b/images/ui-screenshot.png index 9eba061..45f3194 100644 Binary files a/images/ui-screenshot.png and b/images/ui-screenshot.png differ diff --git a/rpgdm-ironsworn-tests.el b/rpgdm-ironsworn-tests.el index 8e44a4a..611b56a 100644 --- a/rpgdm-ironsworn-tests.el +++ b/rpgdm-ironsworn-tests.el @@ -27,6 +27,46 @@ (should (= 4 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6) 4)))) (should (= 3 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6)))))) +(ert-deftest rpgdm-ironsworn--read-stat-test () + ;; Numbers with a minus sign always should indicate a decrease to the current value: + (cl-letf (((symbol-function 'read-string) (lambda (s) "-2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:decrease 2)))) + + ;; Numbers with a minus sign always should indicate a increase to the current value: + (cl-letf (((symbol-function 'read-string) (lambda (s) "+2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:increase 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:increase 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:increase 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:increase 2)))) + + ;; Numbers with a minus sign always should indicate a new setting: + (cl-letf (((symbol-function 'read-string) (lambda (s) "=2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:absolute 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:absolute 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:absolute 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:absolute 2)))) + + ;; Just a number should change based on the type so stat, most go down, momentum goes up: + (cl-letf (((symbol-function 'read-string) (lambda (s) "2"))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:decrease 2))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:increase 2)))) + + ;; No numeric value, most stats go down by one, but momentum goes up by one: + (cl-letf (((symbol-function 'read-string) (lambda (s) ""))) + (should (equal (rpgdm-ironsworn--read-stat 'health) '(:decrease 1))) + (should (equal (rpgdm-ironsworn--read-stat 'spirit) '(:decrease 1))) + (should (equal (rpgdm-ironsworn--read-stat 'supply) '(:decrease 1))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:increase 1)))) + + ;; Anything else should return a :reset, as it will take the default value: + (cl-letf (((symbol-function 'read-string) (lambda (s) "go back"))) + (should (equal (rpgdm-ironsworn--read-stat 'momentum) '(:reset 0))))) + (ert-deftest rpgdm-ironsworn--move-tuple-test () (let ((file "moves/fate/ask-the-oracle.org") (full "~/other/over/here/moves/fate/ask-the-oracle.org")) diff --git a/rpgdm-ironsworn.el b/rpgdm-ironsworn.el index c664f9a..2a57533 100644 --- a/rpgdm-ironsworn.el +++ b/rpgdm-ironsworn.el @@ -313,32 +313,64 @@ the `rpgdm-ironsworn-current-character-state' function." (setq character (rpgdm-ironsworn-current-character-state))) (gethash stat character 1)) -(defun rpgdm-ironsworn-adjust-stat (stat adj &optional default) +(defun rpgdm-ironsworn--read-stat (label) + "A `read-string', but for the changeable value associated with LABEL. +A `+1' means increasing the stat by 1, while a `-1' decreased that stat. +A `=3' sets the stat to the number (in this case, `3'). + +Hitting return (entering a blank value) increments the 'momentum stat, +but decrements any other stats by `1'. Any other value means to take +the default for that stat." + (let ((value (read-string (format "Adjustment to %s (+/-/= for absolute value): " label))) + (rxnum (rx (group (optional (or "+" "-" "="))) (* space) (group (+ digit)) (* space)))) + + (if (string-match rxnum value) + (let ((sign (match-string 1 value)) + (numb (string-to-number (match-string 2 value)))) + (cond + ((equal sign "-") `(:decrease ,numb)) + ((equal sign "+") `(:increase ,numb)) + ((equal sign "=") `(:absolute ,numb)) + (t (if (eq label `momentum) `(:increase ,numb) `(:decrease ,numb))))) + + (if (string-blank-p value) + (if (eq label 'momentum) '(:increase 1) '(:decrease 1)) + '(:reset 0))))) + +(defun rpgdm-ironsworn-adjust-stat (stat &optional default) "Increase or decrease the current character's STAT by ADJ. -If the STAT isn't found, returns DEFAULT." - (let* ((curr (rpgdm-ironsworn-character-stat stat)) - (new (+ curr adj))) + If the STAT isn't found, returns DEFAULT." + (let* ((tuple (rpgdm-ironsworn--read-stat stat)) + (curr (rpgdm-ironsworn-character-stat stat)) + (oper (first tuple)) + (numb (second tuple)) + (new (cl-case oper + (:increase (+ curr numb)) + (:decrease (- curr numb)) + (:absolute numb) + (t default)))) + (message "Combining curr %d with %d with %s operator" curr numb oper) (rpgdm-ironsworn-store-character-state stat new))) -(defun rpgdm-ironsworn-adjust-health (health-adj) - "Increase or decrease the current character's health by HEALTH-ADJ." - (interactive "nHealth Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'health health-adj 5)) +(defun rpgdm-ironsworn-adjust-health () + "Increase or decrease the current character's health interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'health 5)) -(defun rpgdm-ironsworn-adjust-spirit (spirit-adj) - "Increase or decrease the current character's spirit by SPIRIT-ADJ." - (interactive "nSpirit Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'spirit spirit-adj 5)) +(defun rpgdm-ironsworn-adjust-spirit () + "Increase or decrease the current character's spirit interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'spirit 5)) -(defun rpgdm-ironsworn-adjust-supply (supply-adj) - "Increase or decrease the current character's supply by SUPPLY-ADJ." - (interactive "nSupply Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'supply supply-adj 5)) +(defun rpgdm-ironsworn-adjust-supply () + "Increase or decrease the current character's supply interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'supply 5)) -(defun rpgdm-ironsworn-adjust-momentum (momentum-adj) - "Increase or decrease the current character's momentum by MOMENTUM-ADJ." - (interactive "nMomentum Adjustment: ") - (rpgdm-ironsworn-adjust-stat 'momentum momentum-adj 2)) +(defun rpgdm-ironsworn-adjust-momentum () + "Increase or decrease the current character's momentum interactively." + (interactive) + (rpgdm-ironsworn-adjust-stat 'momentum 2)) (defun rpgdm-ironsworn-roll-stat (stat modifier) "Roll an action based on a loaded character's STAT with a MODIFIER." @@ -701,7 +733,7 @@ The nature is a combination of theme and domain." (puthash "site" 'rpgdm-ironsworn-oracle-site-nature rpgdm-tables) -(defun rpgdm-ironsworn-discover-a-site (theme domain) +(defun rpgdm-ironsworn-discover-a-site (theme domain &optional name level) "Store a Delve Site information in the org file. The THEME and DOMAIN need to match org files in the `tables' directory, and the choices themselves come from these files. @@ -715,12 +747,16 @@ and progress level, and stores all this information in the org file." (completing-read "What is the site theme? " rpgdm-ironsworn-site-themes) (completing-read "What is the domain? " rpgdm-ironsworn-site-domains))) - (rpgdm-ironsworn-progress-create - (read-string "Site Name: " - (rpgdm-ironsworn-oracle-site-name domain)) - (completing-read-value "Progress Level: " - rpgdm-ironsworn-progress-levels)) - (next-line 2) + (when (called-interactively-p) + (setq name (read-string "Site Name: " (rpgdm-ironsworn-oracle-site-name domain))) + (setq level (completing-read-value "Progress Level: " rpgdm-ironsworn-progress-levels))) + + (org-insert-heading-respect-content) + (org-shiftmetaright) + (insert name) + (rpgdm-ironsworn-progress-create name level) + (ignore-errors + (next-line 2)) (rpgdm-ironsworn-store-character-state 'site-theme (downcase theme)) (rpgdm-ironsworn-store-character-state 'site-domain (downcase domain))) @@ -937,7 +973,7 @@ You'll need to pick and choose what works and discard what doesn't." (defhydra hydra-rpgdm (:color blue :hint nil) " ^Dice^ 0=d100 1=d10 6=d6 ^Roll/Adjust^ ^Oracles/Tables^ ^Moving/Editing^ ^Messages^ - ---------------------------------------------------------------------------------------------------------------------------------------------------- + ------------------------------------------------------------------------------------------------------------------------------ _D_: Roll Dice _p_: Progress _l_/_L_: Health _z_/_Z_: Yes/No Oracle _o_: Links ⌘-h: Show Stats _e_: Roll Edge _h_: Roll Shadow _t_/_T_: Spirit _c_/_C_: Show Oracle _J_/_K_: Page up/dn ⌘-l: Last Results _r_: Roll Heart _w_: Roll Wits _s_/_S_: Supply _O_: Load Oracles _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous