Code functional with a good README and Interface
Wrote some reusable instructions as well as polished a minor mode and a hydra for easily using the code. Time to try it out in production.
This commit is contained in:
parent
020d0386b7
commit
558d7eb984
8 changed files with 151 additions and 29 deletions
BIN
README-choose-table.png
Normal file
BIN
README-choose-table.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
README-hydra.png
Normal file
BIN
README-hydra.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
README-results.png
Normal file
BIN
README-results.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
103
README.org
103
README.org
|
|
@ -4,26 +4,107 @@
|
|||
#+DATE: 2021-01-27 January
|
||||
#+TAGS: rpg
|
||||
|
||||
The overlap between Emacs and running a Dungeons and Dragon campaign
|
||||
The overlap between Emacs and running a Dungeons and Dragon campaign is... expected? Jotting notes and plans in an org-mode file has been great, but what if, during a game session, my notes became /more interactive/? I started creating some helper functions, which has now become a minor mode I use as a sort of layer on top of Org.
|
||||
|
||||
The primary interface is =f13= which calls up a /sticky/ Hydra to call my functions, but still allowing full cursor movement (but without changing the code):
|
||||
[[file:README-hydra.png]]
|
||||
* Themes
|
||||
What emerged from some late night hacking is more than just a dice roller, but I may have to explain my rationale.
|
||||
** Yes/No Complications
|
||||
According to [[https://www.hipstersanddragons.com/difficulty-classes-for-ability-checks-5e/][this essay]], the standard DC 15 skill check is actually /too hard/ for most situations,
|
||||
The [[https://www.drivethrurpg.com/product/89534/FU-The-Freeform-Universal-RPG-Classic-rules][FU Rules]] think a attempt with randomness (what D&D calls an ability check) shouldn't be just a yes/no, but could have some /complications/, like "yes, but..." or "no, and...". I want to be able to wrap these ideas into a single interface.
|
||||
The [[https://www.drivethrurpg.com/product/89534/FU-The-Freeform-Universal-RPG-Classic-rules][FU Rules]] believes a attempt with randomness (what D&D calls an ability check) shouldn't be just a yes/no, but could have some /complications/, like "yes, but..." or "no, and...".
|
||||
Those rules used a d6 with six variations:
|
||||
|
||||
1. no, and ... (failure, plus a new complication)
|
||||
2. no
|
||||
3. no, but ... (failure, but a glimmer of something positive)
|
||||
4. yes, but ... (success, but with a complication)
|
||||
5. yes
|
||||
6. yes, and ... (success, plus a bonus)
|
||||
|
||||
I love this idea, and thought that I could extend it to d20 skill check rolls. Perhaps 1 and 6 happened pretty infrequently, followed by 3 and 4, and 2 and 5 happen most often.
|
||||
|
||||
According to [[https://www.hipstersanddragons.com/difficulty-classes-for-ability-checks-5e/][this essay]], the standard DC 15 skill check is actually /too hard/ for most situations, however, I don't trust my bias when choosing a value for a difficulty check. For the life of me, I can't find where I saw this idea, but you could choose a number of d6s for the challenge (1 is easy, 2 is moderate, etc.) and then add 7. That sounds pretty good, so after calling for a check, I can enter it, and Emacs confirm the results.
|
||||
|
||||
I want to be able to wrap both of these ideas into a single interface.
|
||||
** Random Items
|
||||
As a DM, we
|
||||
As a DM, we often have to invent unplanned details to our story...especially names, but even the trinkets in the goblin's pocket. Most DMs make lists of things, and some even pin some of these to their DM Screen, so they can look a player in the eye, and say, "My name is Samuel Gustgiver, I work at the bakery, but my friends just call me Sam."
|
||||
|
||||
I wanted to create a directory full of files containing tabular goodness, and have a function that would read all the files, and then allow me to choose a random item from anything on the list, for instance:
|
||||
|
||||
[[file:README-choose-table.png]]
|
||||
|
||||
And then having the results show up easily:
|
||||
|
||||
[[file:README-results.png]]
|
||||
|
||||
Oh, and when the players ask what the name of that strange NPC was, I made a function to display the last randomly displayed message.
|
||||
|
||||
|
||||
Writing a function to read all the items in a list is pretty trivial, printing out a random name would be nice, but some items on these lists may be more prevalent than others. For instance, what if half the people in Waterdeep belonged to a faction, and I wanted to help my NPC's backstory with a random faction, but wouldn't some factions be more prevalent than others? Same with occupations, as our players would run into more docker workers than workers of magic.
|
||||
|
||||
A file of lists can include a /frequency/, for instance:
|
||||
|
||||
#+begin_example
|
||||
- Xanathar Guild :often:
|
||||
- Church of Talos :scarcely:
|
||||
- The Kraken Society :rarely:
|
||||
- Bregan D’aerthe :seldom:
|
||||
- Bull Elk Tribe :seldom:
|
||||
- Cult of the Dragon :seldom:
|
||||
#+end_example
|
||||
But how much more often is /often/? So, lists are purely randomly distributed, but other frequencies are /pre-calculated/, as in:
|
||||
|
||||
- 4 -- =often=
|
||||
- 3 -- =seldom=, =sometimes=
|
||||
- 2 -- =scarcely=, =scarce=, =hardly ever=
|
||||
- 1 -- =rarely=
|
||||
|
||||
So /often/ is four times likelier than /rarely/. I also have this list:
|
||||
|
||||
- 10 -- =common=
|
||||
- 6 -- =uncommon=
|
||||
- 4 -- =rare=
|
||||
- 2 -- =veryrare=, =very-rare=, =very rare=
|
||||
- 1 -- =legendary=
|
||||
|
||||
Where /common/ is ten times more likelier than /legendary/. Actually, after all the work in getting this working, I'm not sure how often, in an epic fantasy game, where rare should be commonplace for the player.
|
||||
** DM Screen and Roll from my Notes
|
||||
Finally, I wanted to quickly bring up a collection of rules and tables along with my session notes, a bit of a DM Screen for my screen.
|
||||
|
||||
Two things I noticed about org files, is that I could initially hide unnecessary meta information and focus on just the contents of the file's table or list by prepending this blurb:
|
||||
|
||||
#+begin_example
|
||||
# Local Variables:
|
||||
# eval: (narrow-to-region 121 633)
|
||||
# End:
|
||||
#+end_example
|
||||
|
||||
Keep in mind, that this is only good for more /static/ files that don't change, as I have to figure out the range.
|
||||
|
||||
The second thing I realized is that Org's links can call Emacs functions. This allows me to have a bit of random-ness to a table's list, for instance:
|
||||
|
||||
#+begin_example
|
||||
[[elisp:(call-interactively 'rpgdm-skill-check-easy)][Easy DC]]
|
||||
#+end_example
|
||||
|
||||
My initial ideas for listing a bunch of random NPC names and having a link that displayed one of them, got supplanted for the ideas I described above.
|
||||
* Code
|
||||
What do I have here:
|
||||
- [[file:rpgdm.el][rpgdm.el]] :: Primary interface offering:
|
||||
- rpgdm-yes-and-50/50
|
||||
- rpgdm-skill-check given a target and a d20 dice result, returns yes/no, but possibly with complications
|
||||
- rpgdm-skill-check-easy queries a rolled results, and returns a complicated yes/no for an /easy/ skill challenge
|
||||
- rpgdm-skill-check-moderate
|
||||
- rpgdm-skill-check-hard
|
||||
- rpgdm-skill-check-difficult
|
||||
- rpgdm-skill-check-impossible
|
||||
- =rpgdm-mode=, plus a Hydra interface for easily calling the rest of these functions.
|
||||
- =rpgdm-yes-and-50/50=, flip a coin and make a give a result with or without complications or bonuses.
|
||||
- =rpgdm-skill-check=, given a target and a d20 dice result, returns yes/no, but possibly with complications or bonuses, depending on how well the result is.
|
||||
- =rpgdm-skill-check-easy=, queries a rolled results, and returns a complicated yes/no for an /easy/ skill challenge, where the average DC is 10, but it could be anywhere from 8 to 13.
|
||||
- =rpgdm-skill-check-moderate=, same as above, but for moderate challenges where the average DC is 14
|
||||
- =rpgdm-skill-check-hard=, same, but for hard challenges where the average DC is 17 (with a range of 10 to 25, but with a pyramid bell curve, the average is likely)
|
||||
- =rpgdm-skill-check-difficult=, for challenges where the average DC is 20 (range from 11 to 30)
|
||||
- =rpgdm-skill-check-impossible=, the average DC for this is 24 (with a range of 14 to 35)
|
||||
- [[file:rpgdm-dice.el][rpgdm-dice]] :: All the random number generators, plus:
|
||||
- =rpgdm-forward-roll= to move point to the next dice expression
|
||||
- =rpgdm-roll= randomly evaluates dice expression at point, or queries for one
|
||||
- =rpgdm-roll-advantage= / =rpgdm-roll-disadvantage= rolls a d20 with a modifier
|
||||
- [[file:rpgdm-tables.el][rpgdm-tables]] :: For randomly displaying choices from a directory of tables. Call either:
|
||||
- =rpgdm-tables-load= and point to a directory of text files
|
||||
- =rpgdm-tables-choose= and choose from one of the tables dynamically, and a result is displayed.
|
||||
- [[file:rpgdm-screen.el][rpgdm-screen]] :: Still working on this one
|
||||
- =rpgdm-screen= :: to display some tables in buffer windows on the right side of the screen.
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ Unless the point is on a dice roll description, e.g 2d12+3."
|
|||
(match-string-no-properties 0)
|
||||
(read-string "Dice Expression: "))))
|
||||
(let ((roll-results (rpgdm--roll-expression expression)))
|
||||
(message "Rolled: %s" (rpgdm--display-roll roll-results expression))))
|
||||
(rpgdm-message "Rolled: %s" (rpgdm--display-roll roll-results expression))))
|
||||
|
||||
|
||||
;; ----------------------------------------------------------------------
|
||||
|
|
@ -286,7 +286,7 @@ otherwise, prompt for the modifier. Results are displayed."
|
|||
(interactive (list (if (looking-at rpgdm-roll-regexp)
|
||||
(match-string-no-properties 0)
|
||||
(read-number "Advantage roll with modifier: "))))
|
||||
(message "Rolled with Advantage: %s"
|
||||
(rpgdm-message "Rolled with Advantage: %s"
|
||||
(rpgdm--roll-with-choice 'max modifier plus-minus)))
|
||||
|
||||
(defun rpgdm-roll-disadvantage (modifier &optional plus-minus)
|
||||
|
|
@ -297,7 +297,7 @@ otherwise, prompt for the modifier. Results are displayed."
|
|||
(interactive (list (if (looking-at rpgdm-roll-regexp)
|
||||
(match-string-no-properties 0)
|
||||
(read-number "Disadvantage roll with modifier: "))))
|
||||
(message "Rolled with Disadvantage: %s"
|
||||
(rpgdm-message "Rolled with Disadvantage: %s"
|
||||
(rpgdm--roll-with-choice 'min modifier plus-minus)))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,14 +22,6 @@
|
|||
(require 'org-element)
|
||||
(require 's)
|
||||
|
||||
(defvar rpgdm-screen-last-results ""
|
||||
"The results from calls to `rpgdm-screen-' functions are stored here.")
|
||||
|
||||
(defun rpgdm-screen-last-results ()
|
||||
"Display results from the last call to a `rpgdm-screen-' function."
|
||||
(interactive)
|
||||
(message rpgdm-screen-last-results))
|
||||
|
||||
(defun rpgdm-screen--get-list-items ()
|
||||
"Return a list of all the list items in the org document."
|
||||
(org-element-map (org-element-parse-buffer) 'item
|
||||
|
|
@ -44,8 +36,7 @@ The contents of the item is displayed in the mini-buffer."
|
|||
(interactive)
|
||||
(let* ((items (rpgdm-screen--get-list-items))
|
||||
(item (nth (random (length items)) items)))
|
||||
(setq rpgdm-screen-last-results (s-trim item))
|
||||
(message rpgdm-screen-last-results)))
|
||||
(rpgdm-message (s-trim item))))
|
||||
|
||||
(defun rpgdm-screen-choose-sublist ()
|
||||
"Randomly choose an elemeent from the lists in the subtree.
|
||||
|
|
@ -67,12 +58,12 @@ The contents of the item is displayed in the mini-buffer."
|
|||
(dired-hide-details-mode)
|
||||
|
||||
(split-window-right)
|
||||
(find-file "skill-checks.org")
|
||||
(find-file "actions.org")
|
||||
|
||||
(split-window-below)
|
||||
(split-window-below)
|
||||
(other-window 1)
|
||||
(find-file "names.org")
|
||||
(find-file "weather-effects.org")
|
||||
(other-window 1)
|
||||
(find-file "magic-schools.org")
|
||||
(split-window-below)
|
||||
|
|
@ -86,7 +77,7 @@ The contents of the item is displayed in the mini-buffer."
|
|||
(other-window 1)
|
||||
(find-file "gear.org")
|
||||
(other-window 1)
|
||||
(find-file "trinkets.org")
|
||||
(find-file "armor.org")
|
||||
(split-window-below)
|
||||
(other-window 1)
|
||||
(find-file "conditions.org")
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ For instance, `veryrare' and `very-rare' are the same."
|
|||
(defun rpgdm-tables--choose-list (lst)
|
||||
"Randomly choose (equal chance for any) element in LST."
|
||||
(let ((item (seq-random-elt lst)))
|
||||
(message "%s" item)))
|
||||
(rpgdm-message "%s" item)))
|
||||
|
||||
;; However, choosing an element in a hash of tags seems ... challenging. This is
|
||||
;; because I want the tags to somehow add a particular weight to the randomness.
|
||||
|
|
|
|||
52
rpgdm.el
52
rpgdm.el
|
|
@ -18,6 +18,11 @@
|
|||
;;
|
||||
;;; Code:
|
||||
|
||||
(require 'cl)
|
||||
(require 'dash)
|
||||
(require 'hydra)
|
||||
(require 's)
|
||||
|
||||
(defconst rpgdm-base (file-name-directory load-file-name))
|
||||
(load-file (expand-file-name "rpgdm-dice.el" rpgdm-base))
|
||||
(load-file (expand-file-name "rpgdm-screen.el" rpgdm-base))
|
||||
|
|
@ -29,6 +34,51 @@
|
|||
:group 'applications
|
||||
:link '(url-link :tag "Github" "https://gitlab.com/howardabrams/emacs-rpgdm"))
|
||||
|
||||
(define-minor-mode rpgdm-mode
|
||||
"Minor mode for layering role-playing game master functions over your notes."
|
||||
:lighter " D&D"
|
||||
:keymap (let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "<f13>") 'hydra-rpgdm/body)
|
||||
map))
|
||||
|
||||
(defhydra hydra-rpgdm (:color pink :hint nil)
|
||||
"
|
||||
^Dice^ ^Tables^ ^Checks^
|
||||
^^^^^^^----------------------------------------------------------------------------------------
|
||||
_d_: Roll Dice _h_: Dashboard _s_: d20 Skill
|
||||
_f_: Next Dice Expr _t_: Load Tables _e_: Easy check
|
||||
_b_: Previous Expr _c_: Choose from _m_: Moderate
|
||||
_z_: Flip a coin a table _h_: Hard check
|
||||
_a_/_A_: Advantage/Disadvantage _v_: Difficult "
|
||||
("d" rpgdm-roll) ("<f13>" rpgdm-last-results)
|
||||
("f" rpgdm-forward-roll) ("b" rpgdm-forward-roll)
|
||||
("a" rpgdm-roll-advantage) ("A" rpgdm-roll-disadvantage)
|
||||
("z" rpgdm-yes-and-50/50)
|
||||
("s" rpgdm-skill-check) ("i" rpgdm-skill-check-impossible)
|
||||
("e" rpgdm-skill-check-easy) ("m" rpgdm-skill-check-moderate)
|
||||
("h" rpgdm-skill-check-hard) ("v" rpgdm-skill-check-difficult)
|
||||
|
||||
("t" rpgdm-tables-load) ("c" rpgdm-tables-choose)
|
||||
("h" rpgdm-screen)
|
||||
|
||||
("q" nil "quit"))
|
||||
|
||||
|
||||
(defvar rpgdm-last-results ""
|
||||
"The results from calls to `rpgdm-screen-' functions are stored here.")
|
||||
|
||||
(defun rpgdm-message (format-string &rest args)
|
||||
"Replace `messasge' function allowing it to be re-displayed.
|
||||
The FORMAT-STRING is a standard string for the `format' function,
|
||||
and ARGS are substitued values."
|
||||
(setq rpgdm-last-results (apply 'format format-string args))
|
||||
(rpgdm-last-results))
|
||||
|
||||
(defun rpgdm-last-results ()
|
||||
"Display results from the last call to a `rpgdm-screen-' function."
|
||||
(interactive)
|
||||
(message rpgdm-last-results))
|
||||
|
||||
|
||||
(defun rpgdm-yes-and-50/50 ()
|
||||
"Add spice to your 50/50 events (luck) with Yes/No+complications.
|
||||
|
|
@ -152,7 +202,7 @@ The string can return a bit of complications, from `rpgdm--yes-and'."
|
|||
(interactive (list (completing-read "Target Level: "
|
||||
'(Trivial Easy Moderate Hard Difficult Impossible))
|
||||
(read-number "Rolled Results: ")))
|
||||
(message (rpgdm--yes-and target rolled-results)))
|
||||
(rpgdm-message (rpgdm--yes-and target rolled-results)))
|
||||
|
||||
(defun rpgdm-skill-check-easy (rolled-results)
|
||||
"Return an embellished pass/fail from ROLLED-RESULTS for an easy skill check."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue