Limit assets not appropriate for new characters
Shouldn't be a Revenant without a little tragedy first. Also, describe how progress tracks are nested.
This commit is contained in:
parent
9c8b030633
commit
1cb1e45efa
7 changed files with 245 additions and 125 deletions
192
README.org
192
README.org
|
@ -58,6 +58,13 @@ What I do, is add the following "code" somewhere in my Ironsworn-specific org fi
|
||||||
# End:
|
# End:
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
Finally, many of the displayed org files contain embedded hyperlinks that actually call functions (defined below). I find it helpful to ignore the constant prompts for selecting this links by setting the [[help:org-link-elisp-skip-confirm-regexp][org-link-elisp-skip-confirm-regexp]] variable, as in:
|
||||||
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
|
(setq org-link-elisp-skip-confirm-regexp (rx string-start (optional "(") "rpgdm-"
|
||||||
|
(or "tables-" "ironsworn-")
|
||||||
|
(one-or-more any)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
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:
|
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
|
#+ATTR_HTML: :width 1100px
|
||||||
|
@ -96,6 +103,72 @@ While the interface may change (and I may not update that screenshot too often),
|
||||||
- And ~q~ (or ~F6~) dismisses the UI.
|
- And ~q~ (or ~F6~) 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.
|
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.
|
||||||
|
** File Organization
|
||||||
|
I really didn’t want to dictate how Ironsworn notes should be organized, but I wanted the /progress tracks/ to be relevant. Sure, one can mark progress on one or more tracks, but I didn’t want see tracks that were old. I toyed with the idea of deleting, or just closing, old tracks, but I realized that progress tracks are really just arcs in the stories plot. Some of long, like a character /inciting vow/ and others are short, like a battle with a Cave Lion, but they are all nested in a hierarchical tree.
|
||||||
|
|
||||||
|
Let me explain with an illustration:
|
||||||
|
#+BEGIN_SRC dot :file images/progress-tracks-tree.png :exports file :results file
|
||||||
|
digraph G {
|
||||||
|
bgcolor="transparent";
|
||||||
|
node [fillcolor="white" style="filled" fontname="Arial"];
|
||||||
|
|
||||||
|
Background [label="Background / Theme"]
|
||||||
|
Arc1 [label="Story Arc"]
|
||||||
|
Arc2 [label="Story Arc"]
|
||||||
|
Arc3 [label="Story Arc"]
|
||||||
|
Scene1 [label="Scene"]
|
||||||
|
Scene2 [label="Montage"]
|
||||||
|
Scene3 [label="Montage"]
|
||||||
|
Scene4 [label="Scene"]
|
||||||
|
Scene5 [label="Scene"]
|
||||||
|
Scene6 [label="Montage"]
|
||||||
|
Event1 [label="Event"]
|
||||||
|
Event2 [label="Challenge"]
|
||||||
|
Event3 [label="Event"]
|
||||||
|
Event4 [label="Challenge"]
|
||||||
|
Event5 [label="Event"]
|
||||||
|
Event6 [label="Challenge"]
|
||||||
|
Event7 [label="Event"]
|
||||||
|
Event8 [label="Challenge"]
|
||||||
|
Event9 [label="Event"]
|
||||||
|
Event10 [label="Challenge"]
|
||||||
|
Event11 [label="Event"]
|
||||||
|
Event12 [label="Challenge"]
|
||||||
|
|
||||||
|
Background -> Arc1;
|
||||||
|
Background -> Arc2;
|
||||||
|
Background -> Arc3;
|
||||||
|
|
||||||
|
Arc1 -> Scene1;
|
||||||
|
Arc1 -> Scene2;
|
||||||
|
Arc2 -> Scene3;
|
||||||
|
Arc2 -> Scene4;
|
||||||
|
Arc3 -> Scene5;
|
||||||
|
Arc3 -> Scene6;
|
||||||
|
|
||||||
|
Scene1 -> Event1;
|
||||||
|
Scene1 -> Event2;
|
||||||
|
Scene2 -> Event3;
|
||||||
|
Scene3 -> Event4;
|
||||||
|
Scene3 -> Event5;
|
||||||
|
Scene3 -> Event6;
|
||||||
|
Scene4 -> Event7;
|
||||||
|
Scene5 -> Event8;
|
||||||
|
Scene5 -> Event9;
|
||||||
|
Scene6 -> Event10;
|
||||||
|
Scene6 -> Event11;
|
||||||
|
Scene6 -> Event12;
|
||||||
|
}
|
||||||
|
#+END_SRC
|
||||||
|
[[file:images/progress-tracks-tree.png]]
|
||||||
|
|
||||||
|
A character or party has a theme or premise that drives the entire story, like Destroying the One Ring of Power or, teenagers solving mysteries with a talking dog. In Ironsworn, this is referred to as an /Epic Vow/. This epic story is broken into long-running arcs or character goals … perhaps they could be packaged into three books to make a Trilogy, or even seven books for each year in a school. These are the [[file:moves/quest/swear-an-iron-vow.org][various vows]], like the initial /Inciting Vow/.
|
||||||
|
|
||||||
|
These, in turn, are further divided into scenes like Journeying to Bree, Escaping Moria, or even each Saturday morning episode. In Ironsworn, these are moves like [[file:moves/adventure/undertake-a-journey.org][Undertake a Journey]] or [[file:moves/delve/delve-the-depths.org][Delve the Depths]]. And these segments can be punctuated by conflict and combat. These tracks are not concern with the length of a progress, as a battle with an Elder Beast will conclude before arriving at Grandma’s House, even those the Elder Beast was /formidable/ and the journey was merely /troublesome/.
|
||||||
|
|
||||||
|
Notice that this structure works well with the outline of a typical document, and by choosing this structure and tying a progress track to a header, completed tracks would disappear on their own, and I didn’t have to manage old tracks. As you call the progress functions, these prompt with a question:
|
||||||
|
[[file:images/progress-placement-prompt.png]]
|
||||||
|
|
||||||
** 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 it to be/ ... 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.
|
||||||
|
|
||||||
|
@ -207,7 +280,7 @@ Oh, and if the same number shows on both d10s, you should introduce a significan
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--results (action modifier one-challenge two-challenge
|
(defun rpgdm-ironsworn--results (action modifier one-challenge two-challenge
|
||||||
&optional momentum)
|
&optional momentum)
|
||||||
"Return formatted string for an Ironsworn dice roll results.
|
"Return formatted string for an Ironsworn dice roll results.
|
||||||
The ACTION is the d6 which is added to the MODIFIER (which can
|
The ACTION is the d6 which is added to the MODIFIER (which can
|
||||||
have character attribute values as well as any bonuses. The sum
|
have character attribute values as well as any bonuses. The sum
|
||||||
|
@ -219,45 +292,45 @@ Oh, and if the same number shows on both d10s, you should introduce a significan
|
||||||
(setq momentum 0))
|
(setq momentum 0))
|
||||||
|
|
||||||
(cl-flet ((strong-p (value dice1 dice2) (and (> value dice1) (> value dice2)))
|
(cl-flet ((strong-p (value dice1 dice2) (and (> value dice1) (> value dice2)))
|
||||||
(weak-p (value dice1 dice2) (or (> value dice1) (> value dice2)))
|
(weak-p (value dice1 dice2) (or (> value dice1) (> value dice2)))
|
||||||
(miss-p (value dice1 dice2) (and (<= value dice1) (<= value dice2)))
|
(miss-p (value dice1 dice2) (and (<= value dice1) (<= value dice2)))
|
||||||
(faded (str) (propertize str 'face '(:foreground "#888")))
|
(faded (str) (propertize str 'face '(:foreground "#888")))
|
||||||
(noted (str) (propertize str 'face '(:foreground "light blue")))
|
(noted (str) (propertize str 'face '(:foreground "light blue")))
|
||||||
(strong (str) (propertize str 'face '(:foreground "green")))
|
(strong (str) (propertize str 'face '(:foreground "green")))
|
||||||
(weak (str) (propertize str 'face '(:foreground "yellow")))
|
(weak (str) (propertize str 'face '(:foreground "yellow")))
|
||||||
(interest (str) (propertize str 'face '(:foreground "orange")))
|
(interest (str) (propertize str 'face '(:foreground "orange")))
|
||||||
(miss (str) (propertize str 'face '(:foreground "red"))))
|
(miss (str) (propertize str 'face '(:foreground "red"))))
|
||||||
|
|
||||||
(let* ((action-results (+ action modifier))
|
(let* ((action-results (+ action modifier))
|
||||||
(str-results (cond
|
(str-results (cond
|
||||||
((strong-p action-results one-challenge two-challenge)
|
((strong-p action-results one-challenge two-challenge)
|
||||||
(strong "Strong hit"))
|
(strong "Strong hit"))
|
||||||
((weak-p action-results one-challenge two-challenge)
|
((weak-p action-results one-challenge two-challenge)
|
||||||
(weak "Weak hit"))
|
(weak "Weak hit"))
|
||||||
(t (miss "Miss"))))
|
(t (miss "Miss"))))
|
||||||
(burn-msg (if (> momentum action-results)
|
(burn-msg (if (> momentum action-results)
|
||||||
(cond
|
(cond
|
||||||
((and (strong-p momentum one-challenge two-challenge)
|
((and (strong-p momentum one-challenge two-challenge)
|
||||||
(not (strong-p action-results one-challenge two-challenge)))
|
(not (strong-p action-results one-challenge two-challenge)))
|
||||||
(concat " -- Burn momentum for a " (strong "Strong hit")))
|
(concat " -- Burn momentum for a " (strong "Strong hit")))
|
||||||
((and (weak-p momentum one-challenge two-challenge)
|
((and (weak-p momentum one-challenge two-challenge)
|
||||||
(miss-p action-results one-challenge two-challenge))
|
(miss-p action-results one-challenge two-challenge))
|
||||||
(concat " -- Burn momentum for a " (weak "Weak hit")))
|
(concat " -- Burn momentum for a " (weak "Weak hit")))
|
||||||
(t ""))
|
(t ""))
|
||||||
""))
|
""))
|
||||||
(matched-msg (if (= one-challenge two-challenge)
|
(matched-msg (if (= one-challenge two-challenge)
|
||||||
(concat " ← " (interest "Create a Twist"))
|
(concat " ← " (interest "Create a Twist"))
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
(format "%s %s %d %s%d %s %d%s %s %d %s %d%s%s"
|
(format "%s %s %d %s%d %s %d%s %s %d %s %d%s%s"
|
||||||
str-results (faded "::")
|
str-results (faded "::")
|
||||||
(+ action modifier) (faded "(")
|
(+ action modifier) (faded "(")
|
||||||
action (faded "+")
|
action (faded "+")
|
||||||
modifier (faded ")")
|
modifier (faded ")")
|
||||||
(noted "→")
|
(noted "→")
|
||||||
one-challenge (faded "/")
|
one-challenge (faded "/")
|
||||||
two-challenge
|
two-challenge
|
||||||
matched-msg burn-msg))))
|
matched-msg burn-msg))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
So the following messages, given various /rolls/ should cover those possibilities with text properties:
|
So the following messages, given various /rolls/ should cover those possibilities with text properties:
|
||||||
|
@ -325,7 +398,7 @@ We assume you have created an org-file, and the /template/ will just append some
|
||||||
")))
|
")))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
**** Character Assets
|
**** 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, =:
|
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:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--character-asset-label (filename)
|
(defun rpgdm-ironsworn--character-asset-label (filename)
|
||||||
|
@ -410,19 +483,30 @@ Hrm. Perhaps we just want to /look/ at an asset before inserting it, similar to
|
||||||
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:
|
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:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn--good-character-assets (asset-files)
|
(defun rpgdm-ironsworn--good-character-assets (assets)
|
||||||
"Return ASSET-FILES if all given are _good enough_.
|
"Return ASSET-FILES if all given are _good enough_.
|
||||||
That is, all are unique, only one companion, etc."
|
That is, all are unique, only one companion, etc."
|
||||||
(cl-flet ((companion-p (entry)
|
(cl-flet ((only-files (entry) (if (consp entry) (cdr entry) entry))
|
||||||
(when (consp entry)
|
(is-companion? (file) (string-match (rx "companions") file))
|
||||||
(setq entry (cdr entry)))
|
(not-at-first? (file) (when (or (s-ends-with? "revenant.org" file)
|
||||||
(string-match (rx "companions") entry)))
|
(s-ends-with? "weaponmaster.org" file)
|
||||||
(when (and
|
(s-ends-with? "masked.org" file)
|
||||||
(equal asset-files (seq-uniq asset-files))
|
(s-ends-with? "battle-scarred.org" file)
|
||||||
(<= (seq-length
|
(s-ends-with? "ritualist.org" file)
|
||||||
(seq-filter #'companion-p asset-files))
|
(s-ends-with? "shadow-kin.org" file)
|
||||||
1))
|
(s-ends-with? "oathbreaker.org" file))
|
||||||
asset-files)))
|
t)))
|
||||||
|
(let* ((asset-files (-map #'only-files assets))
|
||||||
|
(num-of-companions (seq-count #'is-companion? asset-files)))
|
||||||
|
(when (and
|
||||||
|
;; Are all the assets in the list unique?
|
||||||
|
(equal asset-files (seq-uniq asset-files))
|
||||||
|
;; Does the list only include first-time-only?
|
||||||
|
(-none? #'not-at-first? asset-files)
|
||||||
|
;; Does the list include, at most, one companion?
|
||||||
|
(<= num-of-companions 1))
|
||||||
|
assets))))
|
||||||
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
And I can write a little unit test to verify my test cases:
|
And I can write a little unit test to verify my test cases:
|
||||||
|
@ -430,6 +514,10 @@ And I can write a little unit test to verify my test cases:
|
||||||
#+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
|
#+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
|
||||||
(ert-deftest rpgdm-ironsworn--good-character-assets-test ()
|
(ert-deftest rpgdm-ironsworn--good-character-assets-test ()
|
||||||
(should (rpgdm-ironsworn--good-character-assets '("foo" "bar" "baz")))
|
(should (rpgdm-ironsworn--good-character-assets '("foo" "bar" "baz")))
|
||||||
|
(should (rpgdm-ironsworn--good-character-assets '(("Companions :: Dog" . "assets/companions/dog.org")
|
||||||
|
("Paths :: Good Guy" . "assets/paths/good-guy.org")
|
||||||
|
("Ritual :: Booboo" . "assets/ritual/booboo.org"))))
|
||||||
|
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "paths/shadow-kin.org")))
|
||||||
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "foo")))
|
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "foo")))
|
||||||
(should-not (rpgdm-ironsworn--good-character-assets '("assets/companions/dog.org"
|
(should-not (rpgdm-ironsworn--good-character-assets '("assets/companions/dog.org"
|
||||||
"assets/paths/good-guy.org"
|
"assets/paths/good-guy.org"
|
||||||
|
@ -736,7 +824,7 @@ Best if we wrote some unit tests to both explain and verify this function. This
|
||||||
|
|
||||||
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.
|
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
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-adjust-stat (stat &optional default)
|
(defun rpgdm-ironsworn-adjust-stat (stat &optional default)
|
||||||
"Increase or decrease the current character's STAT by ADJ.
|
"Increase or decrease the current character's STAT by ADJ.
|
||||||
If the STAT isn't found, returns DEFAULT."
|
If the STAT isn't found, returns DEFAULT."
|
||||||
|
@ -791,7 +879,7 @@ The previous functions allows us to create character stat-specific rolling funct
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
And we could have a function for each:
|
And we could have a function for each:
|
||||||
#+BEGIN_SRC emacs-lisp :results silent
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun rpgdm-ironsworn-roll-edge (modifier)
|
(defun rpgdm-ironsworn-roll-edge (modifier)
|
||||||
"Roll an action based on a loaded character's Edge stat with a MODIFIER."
|
"Roll an action based on a loaded character's Edge stat with a MODIFIER."
|
||||||
(interactive (list (read-string "Edge + Modifier: ")))
|
(interactive (list (read-string "Edge + Modifier: ")))
|
||||||
|
|
BIN
images/progress-placement-prompt.png
Normal file
BIN
images/progress-placement-prompt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
images/progress-tracks-tree.png
Normal file
BIN
images/progress-tracks-tree.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
|
@ -4,7 +4,7 @@ When you seek to resolve questions, discover details in the world, determine how
|
||||||
|
|
||||||
- Draw a conclusion :: Decide the answer based on the most interesting and obvious result.
|
- Draw a conclusion :: Decide the answer based on the most interesting and obvious result.
|
||||||
- Ask a yes/no question :: Decide the odds of a ‘yes’, and roll on the table below to check the answer.
|
- Ask a yes/no question :: Decide the odds of a ‘yes’, and roll on the table below to check the answer.
|
||||||
- Pick two :: Envision two options. Rate one as ‘likely’, and [[elisp:(rpgdm-ironsworn-oracle)][roll on the table]] below to see if it is true. If not, it is the other.
|
- Pick two :: Envision two options. Rate one as ‘likely’, and [[elisp:rpgdm-ironsworn-oracle][roll on the table]] below to see if it is true. If not, it is the other.
|
||||||
- Spark an idea :: Brainstorm or use a random prompt.
|
- Spark an idea :: Brainstorm or use a random prompt.
|
||||||
|
|
||||||
| Odds | The answer is ‘yes’ if you roll... |
|
| Odds | The answer is ‘yes’ if you roll... |
|
||||||
|
@ -68,6 +68,3 @@ When you’re unsure what a match might mean, you can roll on another oracle tab
|
||||||
In guided play, your GM is the oracle. You won’t make this move unless you are talking things out and need a random result or a bit of inspiration. Your GM can use this move (or ask you to make it) to help guide the story.
|
In guided play, your GM is the oracle. You won’t make this move unless you are talking things out and need a random result or a bit of inspiration. Your GM can use this move (or ask you to make it) to help guide the story.
|
||||||
|
|
||||||
#+STARTUP: showall
|
#+STARTUP: showall
|
||||||
# Local Variables:
|
|
||||||
# eval: (flycheck-mode -1)
|
|
||||||
# End:
|
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
When you *suffer the outcome of a move*, choose one.
|
When you *suffer the outcome of a move*, choose one.
|
||||||
|
|
||||||
- Make the most obvious negative outcome happen.
|
- Make the most obvious negative outcome happen.
|
||||||
- Envision two negative outcomes. Rate one as ‘likely’, and [[file:ask-the-oracle.org][Ask the Oracle]] using the yes/no table. On a ‘yes’, make that outcome happen. Otherwise, make it the other.
|
- Envision two negative outcomes. Rate one as ‘likely’, and [[file:ask-the-oracle.org][Ask the Oracle]] using the yes/no table.
|
||||||
|
On a ‘yes’, make that outcome happen.
|
||||||
|
Otherwise, make it the other.
|
||||||
- [[elisp:(rpgdm-tables-choose "pay-the-price")][Roll on the following table]]. If you have difficulty interpreting the result to fit the current situation, roll again.
|
- [[elisp:(rpgdm-tables-choose "pay-the-price")][Roll on the following table]]. If you have difficulty interpreting the result to fit the current situation, roll again.
|
||||||
|
|
||||||
| Roll | Result |
|
| Roll | Result |
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
|
|
||||||
(ert-deftest rpgdm-ironsworn--good-character-assets-test ()
|
(ert-deftest rpgdm-ironsworn--good-character-assets-test ()
|
||||||
(should (rpgdm-ironsworn--good-character-assets '("foo" "bar" "baz")))
|
(should (rpgdm-ironsworn--good-character-assets '("foo" "bar" "baz")))
|
||||||
|
(should (rpgdm-ironsworn--good-character-assets '(("Companions :: Dog" . "assets/companions/dog.org")
|
||||||
|
("Paths :: Good Guy" . "assets/paths/good-guy.org")
|
||||||
|
("Ritual :: Booboo" . "assets/ritual/booboo.org"))))
|
||||||
|
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "paths/shadow-kin.org")))
|
||||||
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "foo")))
|
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "foo")))
|
||||||
(should-not (rpgdm-ironsworn--good-character-assets '("assets/companions/dog.org"
|
(should-not (rpgdm-ironsworn--good-character-assets '("assets/companions/dog.org"
|
||||||
"assets/paths/good-guy.org"
|
"assets/paths/good-guy.org"
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
"The root directory to the rpgdm-ironsworn project.")
|
"The root directory to the rpgdm-ironsworn project.")
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--results (action modifier one-challenge two-challenge
|
(defun rpgdm-ironsworn--results (action modifier one-challenge two-challenge
|
||||||
&optional momentum)
|
&optional momentum)
|
||||||
"Return formatted string for an Ironsworn dice roll results.
|
"Return formatted string for an Ironsworn dice roll results.
|
||||||
The ACTION is the d6 which is added to the MODIFIER (which can
|
The ACTION is the d6 which is added to the MODIFIER (which can
|
||||||
have character attribute values as well as any bonuses. The sum
|
have character attribute values as well as any bonuses. The sum
|
||||||
|
@ -34,45 +34,45 @@ use could burn that in order to improve the roll."
|
||||||
(setq momentum 0))
|
(setq momentum 0))
|
||||||
|
|
||||||
(cl-flet ((strong-p (value dice1 dice2) (and (> value dice1) (> value dice2)))
|
(cl-flet ((strong-p (value dice1 dice2) (and (> value dice1) (> value dice2)))
|
||||||
(weak-p (value dice1 dice2) (or (> value dice1) (> value dice2)))
|
(weak-p (value dice1 dice2) (or (> value dice1) (> value dice2)))
|
||||||
(miss-p (value dice1 dice2) (and (<= value dice1) (<= value dice2)))
|
(miss-p (value dice1 dice2) (and (<= value dice1) (<= value dice2)))
|
||||||
(faded (str) (propertize str 'face '(:foreground "#888")))
|
(faded (str) (propertize str 'face '(:foreground "#888")))
|
||||||
(noted (str) (propertize str 'face '(:foreground "light blue")))
|
(noted (str) (propertize str 'face '(:foreground "light blue")))
|
||||||
(strong (str) (propertize str 'face '(:foreground "green")))
|
(strong (str) (propertize str 'face '(:foreground "green")))
|
||||||
(weak (str) (propertize str 'face '(:foreground "yellow")))
|
(weak (str) (propertize str 'face '(:foreground "yellow")))
|
||||||
(interest (str) (propertize str 'face '(:foreground "orange")))
|
(interest (str) (propertize str 'face '(:foreground "orange")))
|
||||||
(miss (str) (propertize str 'face '(:foreground "red"))))
|
(miss (str) (propertize str 'face '(:foreground "red"))))
|
||||||
|
|
||||||
(let* ((action-results (+ action modifier))
|
(let* ((action-results (+ action modifier))
|
||||||
(str-results (cond
|
(str-results (cond
|
||||||
((strong-p action-results one-challenge two-challenge)
|
((strong-p action-results one-challenge two-challenge)
|
||||||
(strong "Strong hit"))
|
(strong "Strong hit"))
|
||||||
((weak-p action-results one-challenge two-challenge)
|
((weak-p action-results one-challenge two-challenge)
|
||||||
(weak "Weak hit"))
|
(weak "Weak hit"))
|
||||||
(t (miss "Miss"))))
|
(t (miss "Miss"))))
|
||||||
(burn-msg (if (> momentum action-results)
|
(burn-msg (if (> momentum action-results)
|
||||||
(cond
|
(cond
|
||||||
((and (strong-p momentum one-challenge two-challenge)
|
((and (strong-p momentum one-challenge two-challenge)
|
||||||
(not (strong-p action-results one-challenge two-challenge)))
|
(not (strong-p action-results one-challenge two-challenge)))
|
||||||
(concat " -- Burn momentum for a " (strong "Strong hit")))
|
(concat " -- Burn momentum for a " (strong "Strong hit")))
|
||||||
((and (weak-p momentum one-challenge two-challenge)
|
((and (weak-p momentum one-challenge two-challenge)
|
||||||
(miss-p action-results one-challenge two-challenge))
|
(miss-p action-results one-challenge two-challenge))
|
||||||
(concat " -- Burn momentum for a " (weak "Weak hit")))
|
(concat " -- Burn momentum for a " (weak "Weak hit")))
|
||||||
(t ""))
|
(t ""))
|
||||||
""))
|
""))
|
||||||
(matched-msg (if (= one-challenge two-challenge)
|
(matched-msg (if (= one-challenge two-challenge)
|
||||||
(concat " ← " (interest "Create a Twist"))
|
(concat " ← " (interest "Create a Twist"))
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
(format "%s %s %d %s%d %s %d%s %s %d %s %d%s%s"
|
(format "%s %s %d %s%d %s %d%s %s %d %s %d%s%s"
|
||||||
str-results (faded "::")
|
str-results (faded "::")
|
||||||
(+ action modifier) (faded "(")
|
(+ action modifier) (faded "(")
|
||||||
action (faded "+")
|
action (faded "+")
|
||||||
modifier (faded ")")
|
modifier (faded ")")
|
||||||
(noted "→")
|
(noted "→")
|
||||||
one-challenge (faded "/")
|
one-challenge (faded "/")
|
||||||
two-challenge
|
two-challenge
|
||||||
matched-msg burn-msg))))
|
matched-msg burn-msg))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll (modifier &optional momentum)
|
(defun rpgdm-ironsworn-roll (modifier &optional momentum)
|
||||||
"Display a Hit/Miss message based on an Ironsworn roll.
|
"Display a Hit/Miss message based on an Ironsworn roll.
|
||||||
|
@ -152,25 +152,42 @@ the `assets' directory, otherwise, we return a cached version."
|
||||||
(interactive (list (rpgdm-ironsworn--pick-character-asset)))
|
(interactive (list (rpgdm-ironsworn--pick-character-asset)))
|
||||||
(when rpgdm-ironsworn-new-character (goto-char (point-max)))
|
(when rpgdm-ironsworn-new-character (goto-char (point-max)))
|
||||||
(let ((file (if (consp asset) (cdr asset) asset)))
|
(let ((file (if (consp asset) (cdr asset) asset)))
|
||||||
(insert-file-contents file nil)
|
(ignore-errors
|
||||||
|
(insert-file-contents file nil))))
|
||||||
|
|
||||||
(when (called-interactively-p)
|
(defun rpgdm-ironsworn-show-character-asset (asset)
|
||||||
(when (y-or-n-p "Insert another asset? ")
|
"Choose and insert the contents of an ASSET in the current buffer."
|
||||||
(call-interactively 'rpgdm-ironsworn-insert-character-asset)))))
|
(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))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--good-character-assets (asset-files)
|
(defun rpgdm-ironsworn--good-character-assets (assets)
|
||||||
"Return ASSET-FILES if all given are _good enough_.
|
"Return ASSET-FILES if all given are _good enough_.
|
||||||
That is, all are unique, only one companion, etc."
|
That is, all are unique, only one companion, etc."
|
||||||
(cl-flet ((companion-p (entry)
|
(cl-flet ((only-files (entry) (if (consp entry) (cdr entry) entry))
|
||||||
(when (consp entry)
|
(is-companion? (file) (string-match (rx "companions") file))
|
||||||
(setq entry (cdr entry)))
|
(not-at-first? (file) (when (or (s-ends-with? "revenant.org" file)
|
||||||
(string-match (rx "companions") entry)))
|
(s-ends-with? "weaponmaster.org" file)
|
||||||
(when (and
|
(s-ends-with? "masked.org" file)
|
||||||
(equal asset-files (seq-uniq asset-files))
|
(s-ends-with? "battle-scarred.org" file)
|
||||||
(<= (seq-length
|
(s-ends-with? "ritualist.org" file)
|
||||||
(seq-filter #'companion-p asset-files))
|
(s-ends-with? "shadow-kin.org" file)
|
||||||
1))
|
(s-ends-with? "oathbreaker.org" file))
|
||||||
asset-files)))
|
t)))
|
||||||
|
(let* ((asset-files (-map #'only-files assets))
|
||||||
|
(num-of-companions (seq-count #'is-companion? asset-files)))
|
||||||
|
(when (and
|
||||||
|
;; Are all the assets in the list unique?
|
||||||
|
(equal asset-files (seq-uniq asset-files))
|
||||||
|
;; Does the list only include first-time-only?
|
||||||
|
(-none? #'not-at-first? asset-files)
|
||||||
|
;; Does the list include, at most, one companion?
|
||||||
|
(<= num-of-companions 1))
|
||||||
|
assets))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--some-character-assets (asset-filenames &optional number)
|
(defun rpgdm-ironsworn--some-character-assets (asset-filenames &optional number)
|
||||||
"Return a list of NUMBER elements from ASSET-FILENAMES... randomly.
|
"Return a list of NUMBER elements from ASSET-FILENAMES... randomly.
|
||||||
|
@ -200,14 +217,20 @@ The chosen assets are _good_ in that they won't have duplicates, etc."
|
||||||
(dolist (file (rpgdm-ironsworn--random-character-assets number-of-assets))
|
(dolist (file (rpgdm-ironsworn--random-character-assets number-of-assets))
|
||||||
(rpgdm-ironsworn-insert-character-asset file)))
|
(rpgdm-ironsworn-insert-character-asset file)))
|
||||||
|
|
||||||
|
(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 ()
|
(defun rpgdm-ironsworn--new-character-assets ()
|
||||||
"Insert the contents of three character assets from the assets directory."
|
"Insert the contents of three character assets from the assets directory."
|
||||||
(goto-char (point-max))
|
(goto-char (point-max))
|
||||||
(insert "\n** Assets\n")
|
(insert "\n** Assets\n")
|
||||||
(if (y-or-n-p "Would you like three random assets? ")
|
(if (y-or-n-p "Would you like three random assets? ")
|
||||||
(rpgdm-ironsworn-random-character-assets 3)
|
(rpgdm-ironsworn-random-character-assets 3)
|
||||||
(if (y-or-n-p "Would you like to choose your assets? ")
|
(if (y-or-n-p "Would you like to choose your assets? ")
|
||||||
(call-interactively 'rpgdm-ironsworn-insert-character-asset))))
|
(rpgdm-ironsworn-insert-character-assets))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--new-character-stats ()
|
(defun rpgdm-ironsworn--new-character-stats ()
|
||||||
"Insert character stats after querying user for them.
|
"Insert character stats after querying user for them.
|
||||||
|
@ -225,8 +248,9 @@ Note: The stats are added as properties using the
|
||||||
(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-create "Bonds" 1)
|
||||||
(rpgdm-ironsworn-progress-mark "Bonds")
|
(rpgdm-ironsworn-progress-mark "Bonds")
|
||||||
|
(next-line)
|
||||||
(insert "\n** Bonds\n")
|
(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"))))
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--new-character-stats-first (&optional name)
|
(defun rpgdm-ironsworn--new-character-stats-first (&optional name)
|
||||||
"Insert a new character template for character, NAME.
|
"Insert a new character template for character, NAME.
|
||||||
|
@ -1075,10 +1099,11 @@ You'll need to pick and choose what works and discard what doesn't."
|
||||||
("A" rpgdm-ironsworn-insert-character-asset "insert new asset")
|
("A" rpgdm-ironsworn-insert-character-asset "insert new asset")
|
||||||
("a" rpgdm-ironsworn-asset-stat-adjust "adjust asset stat")
|
("a" rpgdm-ironsworn-asset-stat-adjust "adjust asset stat")
|
||||||
("n" rpgdm-ironsworn-asset-stat-create "new asset stat")
|
("n" rpgdm-ironsworn-asset-stat-create "new asset stat")
|
||||||
("s" rpgdm-ironsworn-asset-stat-show "show")
|
("s" rpgdm-ironsworn-asset-stat-show "show asset stat")
|
||||||
("r" rpgdm-ironsworn-asset-stat-roll "roll asset as modifier"))
|
("r" rpgdm-ironsworn-asset-stat-roll "roll asset as modifier")
|
||||||
|
("v" rpgdm-ironsworn-show-character-asset "view asset"))
|
||||||
|
|
||||||
(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^
|
^Dice^ 0=d100 1=d10 6=d6 ^Roll/Adjust^ ^Oracles/Tables^ ^Moving/Editing^ ^Messages^
|
||||||
------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -1106,9 +1131,9 @@ You'll need to pick and choose what works and discard what doesn't."
|
||||||
|
|
||||||
("O" rpgdm-tables-load) ("c" rpgdm-tables-choose) ("C" rpgdm-tables-choose :color pink)
|
("O" rpgdm-tables-load) ("c" rpgdm-tables-choose) ("C" rpgdm-tables-choose :color pink)
|
||||||
|
|
||||||
("d" hydra-rpgdm-delve/body)
|
("d" hydra-rpgdm-delve/body :color blue)
|
||||||
("p" hydra-rpgdm-progress/body)
|
("p" hydra-rpgdm-progress/body :color blue)
|
||||||
("a" hydra-rpgdm-assets/body)
|
("a" hydra-rpgdm-assets/body :color blue)
|
||||||
|
|
||||||
("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)
|
||||||
|
@ -1124,9 +1149,10 @@ You'll need to pick and choose what works and discard what doesn't."
|
||||||
("0" rpgdm-roll-d100 :color pink)
|
("0" rpgdm-roll-d100 :color pink)
|
||||||
("1" rpgdm-roll-d10 :color pink)
|
("1" rpgdm-roll-d10 :color pink)
|
||||||
("6" rpgdm-roll-d6 :color pink)
|
("6" rpgdm-roll-d6 :color pink)
|
||||||
|
("RET" evil-open-below :color blue)
|
||||||
("q" nil "quit") ("<f6>" nil))
|
("q" nil "quit") ("<f6>" nil))
|
||||||
|
|
||||||
(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.
|
"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
|
Note that STAT should be a symbol, like `supply' and VALUE should be a
|
||||||
number, but doesn't have to be."
|
number, but doesn't have to be."
|
||||||
|
@ -1135,7 +1161,7 @@ number, but doesn't have to be."
|
||||||
(setq value (number-to-string value)))
|
(setq value (number-to-string value)))
|
||||||
(org-set-property prop value)))
|
(org-set-property prop value)))
|
||||||
|
|
||||||
(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.
|
"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
|
Note that STAT should be a symbol, like `supply' and VALUE should be a
|
||||||
number, but doesn't have to be."
|
number, but doesn't have to be."
|
||||||
|
@ -1143,7 +1169,10 @@ number, but doesn't have to be."
|
||||||
(org-up-heading)
|
(org-up-heading)
|
||||||
(while (> (org-heading-level) 1)
|
(while (> (org-heading-level) 1)
|
||||||
(org-up-heading))
|
(org-up-heading))
|
||||||
(rpgdm-ironsworn-store-character-state stat value)))
|
(rpgdm-ironsworn-store-character-temp-state stat value)))
|
||||||
|
|
||||||
|
(defalias 'rpgdm-ironsworn-store-default-character-state
|
||||||
|
'rpgdm-ironsworn-store-character-state)
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--property-p (prop)
|
(defun rpgdm-ironsworn--property-p (prop)
|
||||||
"Given a symbol PROP, return non-nil if it is an ironsworn keyword.
|
"Given a symbol PROP, return non-nil if it is an ironsworn keyword.
|
||||||
|
|
Loading…
Reference in a new issue