Compare commits
10 commits
e570e9ccd0
...
213391a54e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
213391a54e | ||
|
|
cf24af20b6 | ||
|
|
25c282c094 | ||
|
|
5c5f17e7a6 | ||
|
|
14f6524eec | ||
|
|
1a99ec6539 | ||
|
|
b0c3601e4c | ||
|
|
769fcc27d4 | ||
|
|
71777f5792 | ||
|
|
8b89bf665b |
12 changed files with 135 additions and 433 deletions
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
The overlap between Emacs and running a Dungeons & Dragons 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. Let's blame this insanity on the pandemic, but it has been fun.
|
The overlap between Emacs and running a Dungeons & Dragons 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. Let's blame this insanity on the pandemic, but it has been fun.
|
||||||
|
|
||||||
The primary interface is =f12= which calls up a /sticky/ Hydra to call my functions, but still allowing full cursor movement (mostly):
|
The primary interface is =f6= which calls up a /sticky/ Hydra to call my functions, but still allowing full cursor movement (mostly):
|
||||||
|
|
||||||
#+attr_html: :width 800px
|
#+attr_html: :width 800px
|
||||||
[[file:images/screenshot-of-hydra.png]]
|
[[file:images/screenshot-of-hydra.png]]
|
||||||
|
|
@ -99,6 +99,7 @@ The second thing I realized is that Org's links can call Emacs functions. This a
|
||||||
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.
|
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
|
* Code
|
||||||
What do I have here:
|
What do I have here:
|
||||||
|
- [[file:rpgdm-core.el][rpgdm-core]] :: Package that provides common functionality; namely in messaging and the =rpgdm-last-results= ring.
|
||||||
- [[file:rpgdm.el][rpgdm]] :: Primary interface offering:
|
- [[file:rpgdm.el][rpgdm]] :: Primary interface offering:
|
||||||
- =rpgdm-mode=, plus a Hydra interface for easily calling the rest of these functions.
|
- =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-yes-and-50/50=, flip a coin and make a give a result with or without complications or bonuses.
|
||||||
|
|
@ -127,4 +128,4 @@ I'm also intrigued with rulesets that are unique, for instance:
|
||||||
|
|
||||||
- [[file:docs/fate-rpg.org][FATE]] :: Easy character creation and a nice bell-curve dice roll, but it requires [[https://fudgerpg.com/products/fudge-dice.html][special Fudge dice]], that are easy enough to recreate in Emacs. See [[https://fate-srd.com/][fate-srd.com]] for details about this game.
|
- [[file:docs/fate-rpg.org][FATE]] :: Easy character creation and a nice bell-curve dice roll, but it requires [[https://fudgerpg.com/products/fudge-dice.html][special Fudge dice]], that are easy enough to recreate in Emacs. See [[https://fate-srd.com/][fate-srd.com]] for details about this game.
|
||||||
- [[file:docs/mythic-rpg.org][Mythic RPG]] :: A nice RPG for solo play as it has a GM-less option that I wanted to capture, see [[https://www.wordmillgames.com/mythic-rpg.html][Wordmill Games]] for details.
|
- [[file:docs/mythic-rpg.org][Mythic RPG]] :: A nice RPG for solo play as it has a GM-less option that I wanted to capture, see [[https://www.wordmillgames.com/mythic-rpg.html][Wordmill Games]] for details.
|
||||||
- [[file:docs/ironsworn-rpg.org][Ironsworn]] :: Another good solo RPG, I wanted to capture its quick check resolution. See [[https://www.ironswornrpg.com/][ironswornrpg.com]] for the free rules.
|
- [[https://gitlab.com/howardabrams/emacs-ironsworn][Ironsworn]] :: Another good solo RPG, I wanted to capture its quick check resolution. See [[https://www.ironswornrpg.com/][ironswornrpg.com]] for the free rules.
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ For instance, Xanathar's Guide to Everything, a Dungeons and Dragons supplement
|
||||||
To represent these types of tables, we create a special type, called a =dice-table=. Where the first "slot" is the dice expression (or the number of sides of a dice to roll), and an associative list of result values and the choice.
|
To represent these types of tables, we create a special type, called a =dice-table=. Where the first "slot" is the dice expression (or the number of sides of a dice to roll), and an associative list of result values and the choice.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :results silent
|
#+BEGIN_SRC emacs-lisp :results silent
|
||||||
(defstruct dice-table dice rows)
|
(cl-defstruct dice-table dice rows)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
How is this used to render the example table above?
|
How is this used to render the example table above?
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ decrement the ROLL value."
|
||||||
;; (message "Comparing %d <= %d for %s" roll num-elems tag)
|
;; (message "Comparing %d <= %d for %s" roll num-elems tag)
|
||||||
(if (<= roll num-elems)
|
(if (<= roll num-elems)
|
||||||
(return tag)
|
(return tag)
|
||||||
(decf roll num-elems))))
|
(cl-decf roll num-elems))))
|
||||||
|
|
||||||
(ert-deftest rpgdm-tables--find-tag-test ()
|
(ert-deftest rpgdm-tables--find-tag-test ()
|
||||||
(let ((weighted-tags
|
(let ((weighted-tags
|
||||||
|
|
|
||||||
80
rpgdm-core.el
Normal file
80
rpgdm-core.el
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
;;; rpgdm-core --- Core functionality for rpgdm -*- lexical-binding: t -*-
|
||||||
|
;;
|
||||||
|
;; Copyright (C) 2021 Howard X. Abrams
|
||||||
|
;;
|
||||||
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||||
|
;; Jeremy Friesen <jeremy@jeremyfriesen.com>
|
||||||
|
;; Maintainer: Howard X. Abrams
|
||||||
|
;; Created: December 10, 2023
|
||||||
|
;;
|
||||||
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; There are functions shared across different `rpgdm' packages. These are
|
||||||
|
;; considered "core" functionality.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
(defvar rpgdm-base
|
||||||
|
(seq-find (lambda (elt) (string-match "rpgdm" elt)) load-path (getenv "HOME"))
|
||||||
|
"Default directory to look for supporting data, like tables and charts.")
|
||||||
|
|
||||||
|
(defvar rpgdm-last-results (make-ring 10)
|
||||||
|
"The results from calls to `rpgdm-screen-' functions are stored here.")
|
||||||
|
|
||||||
|
(defvar rpgdm-last-results-ptr 0
|
||||||
|
"Keeps track of where we are in the message display ring.
|
||||||
|
Each call to `rpgdm-last-results' resets this to 0.")
|
||||||
|
|
||||||
|
(defun rpgdm-message (format-string &rest args)
|
||||||
|
"Replace `message' function allowing it to be re-displayed.
|
||||||
|
The FORMAT-STRING is a standard string for the `format' function,
|
||||||
|
and ARGS are substitued values."
|
||||||
|
(let ((message (apply 'format format-string args)))
|
||||||
|
(ring-insert rpgdm-last-results message)
|
||||||
|
(kill-new message)
|
||||||
|
(rpgdm-last-results)))
|
||||||
|
|
||||||
|
(defun rpgdm-last-results ()
|
||||||
|
"Display results from the last call to a `rpgdm-message' function."
|
||||||
|
(interactive)
|
||||||
|
(setq rpgdm-last-results-ptr 0)
|
||||||
|
(message (ring-ref rpgdm-last-results rpgdm-last-results-ptr)))
|
||||||
|
|
||||||
|
(defun rpgdm-last-results-previous ()
|
||||||
|
"Display results from an earlier call to `rpgdm-message'."
|
||||||
|
(interactive)
|
||||||
|
(cl-incf rpgdm-last-results-ptr)
|
||||||
|
(when (>= rpgdm-last-results-ptr (ring-length rpgdm-last-results))
|
||||||
|
(setq rpgdm-last-results-ptr 0))
|
||||||
|
(message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr)))
|
||||||
|
|
||||||
|
(defun rpgdm-last-results-next ()
|
||||||
|
"Display results from an later call to `rpgdm-message'.
|
||||||
|
Meant to be used with `rpgdm-last-results-previous'."
|
||||||
|
(interactive)
|
||||||
|
(when (> rpgdm-last-results-ptr 0)
|
||||||
|
(cl-decf rpgdm-last-results-ptr))
|
||||||
|
(message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr)))
|
||||||
|
|
||||||
|
(defun rpgdm-paste-last-message ()
|
||||||
|
"Yank, e.g. paste, the last displayed message."
|
||||||
|
(interactive)
|
||||||
|
(insert (rpgdm-last-results)))
|
||||||
|
|
||||||
|
(ert-deftest rpgdm-last-results-test ()
|
||||||
|
(progn
|
||||||
|
(setq rpgdm-last-results (make-ring 10))
|
||||||
|
(rpgdm-message "First in, so this is the oldest")
|
||||||
|
(rpgdm-message "Something or other")
|
||||||
|
(rpgdm-message "Almost the newest")
|
||||||
|
(rpgdm-message "Newest"))
|
||||||
|
|
||||||
|
(should (equal "Newest" (rpgdm-last-results)))
|
||||||
|
(should (equal "1> Almost the newest" (rpgdm-last-results-previous)))
|
||||||
|
(should (equal "2> Something other" (rpgdm-last-results-previous)))
|
||||||
|
(should (equal "1> Almost the newest" (rpgdm-last-results-next)))
|
||||||
|
(should (equal "0> Almost the newest" (rpgdm-last-results-next)))
|
||||||
|
(should (equal "0> Almost the newest" (rpgdm-last-results-next))))
|
||||||
|
|
||||||
|
(provide 'rpgdm-core)
|
||||||
|
;;; rpgdm-core.el ends here
|
||||||
|
|
@ -81,7 +81,7 @@ This really tests the `rpgdm--test-rolls' function."
|
||||||
(8 1 8)
|
(8 1 8)
|
||||||
(20 1 20)
|
(20 1 20)
|
||||||
(100 1 100)))
|
(100 1 100)))
|
||||||
(destructuring-bind (die lowest highest) test-data
|
(cl-destructuring-bind (die lowest highest) test-data
|
||||||
(rpgdm--test-rolls #'rpgdm--roll-die (list die) lowest highest))))
|
(rpgdm--test-rolls #'rpgdm--roll-die (list die) lowest highest))))
|
||||||
|
|
||||||
;; ----------------------------------------------------------------------
|
;; ----------------------------------------------------------------------
|
||||||
|
|
@ -155,7 +155,7 @@ average value of AVG, if given."
|
||||||
((3 6 4) 7 22)
|
((3 6 4) 7 22)
|
||||||
((4 6 4 "-") 0 20))))
|
((4 6 4 "-") 0 20))))
|
||||||
(dolist (test-seq test-data)
|
(dolist (test-seq test-data)
|
||||||
(destructuring-bind (dice-args lowest highest) test-seq
|
(cl-destructuring-bind (dice-args lowest highest) test-seq
|
||||||
(rpgdm--test-roll-series 'rpgdm--roll dice-args lowest highest)))))
|
(rpgdm--test-roll-series 'rpgdm--roll dice-args lowest highest)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -240,7 +240,7 @@ the following:
|
||||||
("2d12" 2 24)
|
("2d12" 2 24)
|
||||||
("3d6+2" 5 20))))
|
("3d6+2" 5 20))))
|
||||||
(dolist (test-data test-cases)
|
(dolist (test-data test-cases)
|
||||||
(destructuring-bind (dice-expression lowest highest) test-data
|
(cl-destructuring-bind (dice-expression lowest highest) test-data
|
||||||
(rpgdm--test-roll-series 'rpgdm--roll-expression (list dice-expression) lowest highest)))))
|
(rpgdm--test-roll-series 'rpgdm--roll-expression (list dice-expression) lowest highest)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,313 +0,0 @@
|
||||||
(require 'rpgdm)
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--results (action modifier one-challenge two-challenge)
|
|
||||||
(let* ((action-results (+ action modifier))
|
|
||||||
(str-results (cond
|
|
||||||
((and (> action-results one-challenge) (> action-results two-challenge))
|
|
||||||
(propertize "Strong hit" 'face '(:foreground "green")))
|
|
||||||
((or (> action-results one-challenge) (> action-results two-challenge))
|
|
||||||
(propertize "Weak hit" 'face '(:foreground "yellow")))
|
|
||||||
(t (propertize "Miss" 'face '(:foreground "red")))))
|
|
||||||
(matched-msg (if (= one-challenge two-challenge)
|
|
||||||
(propertize " ← Create a Twist" 'face '(:foreground "orange"))
|
|
||||||
"")))
|
|
||||||
(format "%s %s %d %s %d %s %d %s %d %s" str-results
|
|
||||||
(propertize "::" 'face '(:foreground "#888"))
|
|
||||||
action
|
|
||||||
(propertize "+" 'face '(:foreground "#888"))
|
|
||||||
modifier
|
|
||||||
(propertize "→" 'face '(:foreground "#888"))
|
|
||||||
one-challenge
|
|
||||||
(propertize "/" 'face '(:foreground "#888"))
|
|
||||||
two-challenge matched-msg)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll (modifier)
|
|
||||||
"Display a Hit/Miss message based on comparing a d6 action
|
|
||||||
roll (added to MODIFIER) vs. two d10 challenge dice."
|
|
||||||
(interactive "nModifier: ")
|
|
||||||
(let ((one-challenge (rpgdm--roll-die 10))
|
|
||||||
(two-challenge (rpgdm--roll-die 10))
|
|
||||||
(action-roll (rpgdm--roll-die 6)))
|
|
||||||
(rpgdm-message (rpgdm-ironsworn--results action-roll modifier
|
|
||||||
one-challenge two-challenge))))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-progress-roll (progress-value)
|
|
||||||
"Display a Hit/Miss message based on comparing the PROGRESS-VALUE
|
|
||||||
to rolling two d10 challenge dice."
|
|
||||||
(interactive "nCurrent Progress Value: ")
|
|
||||||
(let ((one-challenge (rpgdm--roll-die 10))
|
|
||||||
(two-challenge (rpgdm--roll-die 10)))
|
|
||||||
(rpgdm-message (rpgdm-ironsworn--results progress-value 0
|
|
||||||
one-challenge two-challenge))))
|
|
||||||
|
|
||||||
(define-hash-table-test 'str-or-keys
|
|
||||||
(lambda (a b)
|
|
||||||
(string-equal
|
|
||||||
(downcase
|
|
||||||
(if (symbolp a) (symbol-name a) a))
|
|
||||||
(downcase
|
|
||||||
(if (symbolp b) (symbol-name b) b))))
|
|
||||||
|
|
||||||
(lambda (s) (sxhash-equal (downcase
|
|
||||||
(if (symbolp s) (symbol-name s) s)))))
|
|
||||||
|
|
||||||
(defvar rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys)
|
|
||||||
"Stats and attributes for the currently loaded character")
|
|
||||||
|
|
||||||
(cl-defun rpgdm-ironsworn-character (&key edge heart iron shadow wits
|
|
||||||
(health 5) (spirit 5) (supply 5)
|
|
||||||
(momentum 2))
|
|
||||||
"Store the player character's stats, as well as set up the defaults for the others."
|
|
||||||
(clrhash rpgdm-ironsworn-character)
|
|
||||||
;; (setq rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys))
|
|
||||||
(puthash 'edge edge rpgdm-ironsworn-character)
|
|
||||||
(puthash 'heart heart rpgdm-ironsworn-character)
|
|
||||||
(puthash 'iron iron rpgdm-ironsworn-character)
|
|
||||||
(puthash 'shadow shadow rpgdm-ironsworn-character)
|
|
||||||
(puthash 'wits wits rpgdm-ironsworn-character)
|
|
||||||
|
|
||||||
(puthash 'health health rpgdm-ironsworn-character)
|
|
||||||
(puthash 'spirit spirit rpgdm-ironsworn-character)
|
|
||||||
(puthash 'supply supply rpgdm-ironsworn-character)
|
|
||||||
(puthash 'momentum momentum rpgdm-ironsworn-character))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-adjust-health (health-adj)
|
|
||||||
"Increase or decrease the current character's health by HEALTH-ADJ."
|
|
||||||
(interactive "nHealth Adjustment: ")
|
|
||||||
(puthash 'health
|
|
||||||
(+ (gethash 'health rpgdm-ironsworn-character 5) health-adj)
|
|
||||||
rpgdm-ironsworn-character))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-adjust-spirit (spirit-adj)
|
|
||||||
"Increase or decrease the current character's spirit by SPIRIT-ADJ."
|
|
||||||
(interactive "nSpirit Adjustment: ")
|
|
||||||
(puthash 'spirit
|
|
||||||
(+ (gethash 'spirit rpgdm-ironsworn-character 5) spirit-adj)
|
|
||||||
rpgdm-ironsworn-character))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-adjust-supply (supply-adj)
|
|
||||||
"Increase or decrease the current character's supply by SUPPLY-ADJ."
|
|
||||||
(interactive "nSupply Adjustment: ")
|
|
||||||
(puthash 'supply
|
|
||||||
(+ (gethash 'supply rpgdm-ironsworn-character 5) supply-adj)
|
|
||||||
rpgdm-ironsworn-character))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-adjust-momentum (momentum-adj)
|
|
||||||
"Increase or decrease the current character's momentum by MOMENTUM-ADJ."
|
|
||||||
(interactive "nMomentum Adjustment: ")
|
|
||||||
(puthash 'momentum
|
|
||||||
(+ (gethash 'momentum rpgdm-ironsworn-character 5) momentum-adj)
|
|
||||||
rpgdm-ironsworn-character))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn--display-stat (stat)
|
|
||||||
(let* ((value (gethash stat rpgdm-ironsworn-character 5))
|
|
||||||
(s-val (number-to-string value))
|
|
||||||
(color (cond
|
|
||||||
((< value 1) "red")
|
|
||||||
((< value 3) "orange")
|
|
||||||
((< value 4) "yellow")
|
|
||||||
(t "green"))))
|
|
||||||
(propertize s-val 'face `(:foreground ,color))))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-character-display ()
|
|
||||||
"Easily display the character's stats and other things."
|
|
||||||
(interactive)
|
|
||||||
(rpgdm-message "Edge: %d Heart: %d Iron: %d Shadow: %d Wits: %d
|
|
||||||
Health: %s Spirit: %s Supply: %s Momentum: %d"
|
|
||||||
(gethash 'edge rpgdm-ironsworn-character 0)
|
|
||||||
(gethash 'heart rpgdm-ironsworn-character 0)
|
|
||||||
(gethash 'iron rpgdm-ironsworn-character 0)
|
|
||||||
(gethash 'shadow rpgdm-ironsworn-character 0)
|
|
||||||
(gethash 'wits rpgdm-ironsworn-character 0)
|
|
||||||
|
|
||||||
(rpgdm-ironsworn--display-stat 'health)
|
|
||||||
(rpgdm-ironsworn--display-stat 'spirit)
|
|
||||||
(rpgdm-ironsworn--display-stat 'supply)
|
|
||||||
|
|
||||||
(gethash 'momentum rpgdm-ironsworn-character 5)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll-stat (stat modifier)
|
|
||||||
"Roll an action based on a loaded character's STAT with a MODIFIER."
|
|
||||||
(interactive (list (completing-read "Stat Modifier: " '(Edge Heart Iron Shadow Wits))
|
|
||||||
(read-string "Other Modifier: ")))
|
|
||||||
(let ((all-mods (+ (gethash stat rpgdm-ironsworn-character)
|
|
||||||
(string-to-number modifier))))
|
|
||||||
(rpgdm-ironsworn-roll all-mods)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll-edge (modifier)
|
|
||||||
"Roll an action based on a loaded character's Edge stat with a MODIFIER."
|
|
||||||
(interactive (list (read-string "Edge + Modifier: ")))
|
|
||||||
(rpgdm-ironsworn-roll-stat 'edge modifier))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll-heart (modifier)
|
|
||||||
"Roll an action based on a loaded character's Heart stat with a MODIFIER."
|
|
||||||
(interactive (list (read-string "Heart + Modifier: ")))
|
|
||||||
(rpgdm-ironsworn-roll-stat 'heart modifier))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll-iron (modifier)
|
|
||||||
"Roll an action based on a loaded character's Iron stat with a MODIFIER."
|
|
||||||
(interactive (list (read-string "Iron + Modifier: ")))
|
|
||||||
(rpgdm-ironsworn-roll-stat 'iron modifier))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll-shadow (modifier)
|
|
||||||
"Roll an action based on a loaded character's Shadow stat with a MODIFIER."
|
|
||||||
(interactive (list (read-string "Shadow + Modifier: ")))
|
|
||||||
(rpgdm-ironsworn-roll-stat 'shadow modifier))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-roll-wits (modifier)
|
|
||||||
"Roll an action based on a loaded character's Wits stat with a MODIFIER."
|
|
||||||
(interactive (list (read-string "Wits + Modifier: ")))
|
|
||||||
(rpgdm-ironsworn-roll-stat 'wits modifier))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-action-theme ()
|
|
||||||
"Rolls on two tables at one time."
|
|
||||||
(interactive)
|
|
||||||
(let ((action (rpgdm-tables-choose "actions"))
|
|
||||||
(theme (rpgdm-tables-choose "themes")))
|
|
||||||
(rpgdm-message "%s / %s" action theme)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-npc ()
|
|
||||||
(interactive)
|
|
||||||
(let ((name (rpgdm-tables-choose "names-ironlander"))
|
|
||||||
(goal (rpgdm-tables-choose "character-goal"))
|
|
||||||
(role (rpgdm-tables-choose "character-role"))
|
|
||||||
(activity (rpgdm-tables-choose "character-activity"))
|
|
||||||
(description (rpgdm-tables-choose "character-descriptor"))
|
|
||||||
(disposition (rpgdm-tables-choose "character-disposition")))
|
|
||||||
(rpgdm-message "%s, %s %s (Activity: %s Disposition: %s Goal: %s)"
|
|
||||||
name description role activity disposition goal)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-combat ()
|
|
||||||
(interactive)
|
|
||||||
(let ((action (rpgdm-tables-choose "combat-action"))
|
|
||||||
(method (rpgdm-tables-choose "combat-event-method"))
|
|
||||||
(target (rpgdm-tables-choose "combat-event-target")))
|
|
||||||
(rpgdm-message "%s %s or %s" method target action)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-feature ()
|
|
||||||
"Rolls on two tables at one time for a Site's feature."
|
|
||||||
(interactive)
|
|
||||||
(let ((aspect (rpgdm-tables-choose "feature-aspect"))
|
|
||||||
(focus (rpgdm-tables-choose "feature-focus")))
|
|
||||||
(rpgdm-message "%s / %s" aspect focus)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-site-nature ()
|
|
||||||
"Rolls on two tables at one time for a random Site."
|
|
||||||
(interactive)
|
|
||||||
(let* ((theme (rpgdm-tables-choose "site-theme"))
|
|
||||||
(domain (rpgdm-tables-choose "site-domain"))
|
|
||||||
(place (downcase domain))
|
|
||||||
(name (rpgdm-ironsworn-oracle-site-name place)))
|
|
||||||
(rpgdm-message "%s %s :: %s" theme domain name)))
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-site-name (&optional place-type)
|
|
||||||
"Rolling on multiple tables to return a random site name."
|
|
||||||
(interactive (list (completing-read "Place type: "
|
|
||||||
'(barrow cavern icereach mine pass ruin
|
|
||||||
sea-cave shadowfen stronghold
|
|
||||||
tanglewood underkeep))))
|
|
||||||
(unless place-type
|
|
||||||
(setq place-type "unknown"))
|
|
||||||
(let ((description (rpgdm-tables-choose "site-name-description"))
|
|
||||||
(detail (rpgdm-tables-choose "site-name-detail"))
|
|
||||||
(namesake (rpgdm-tables-choose "site-name-namesake"))
|
|
||||||
(place (rpgdm-tables-choose (format "site-name-place-%s" place-type)))
|
|
||||||
(roll (rpgdm--roll-die 100)))
|
|
||||||
(rpgdm-message
|
|
||||||
(cond
|
|
||||||
((<= roll 25) (format "%s %s" description place))
|
|
||||||
((<= roll 50) (format "%s of %s" place detail))
|
|
||||||
((<= roll 70) (format "%s of %s %s" place description detail))
|
|
||||||
((<= roll 80) (format "%s of %s's %s" place namesake detail))
|
|
||||||
((<= roll 85) (format "%s's %s" namesake place))
|
|
||||||
((<= roll 95) (format "%s %s of %s" description place namesake))
|
|
||||||
(t (format "%s of %s" place namesake))))))
|
|
||||||
|
|
||||||
(defvar rpgdm-ironsworn-oracle-threats '("Burgeoning Conflict" "Ravaging Horde"
|
|
||||||
"Cursed Site" "Malignant Plague"
|
|
||||||
"Scheming Leader" "Zealous Cult"
|
|
||||||
"Environmental Calamity" "Power-Hungry Mystic"
|
|
||||||
"Rampaging Creature")
|
|
||||||
"A list of threats that correspond to tables")
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle-threat-goal (&optional category)
|
|
||||||
"Given a CATEGORY, display a threat goal."
|
|
||||||
(interactive (list (completing-read "Threat: " rpgdm-ironsworn-oracle-threats)))
|
|
||||||
(unless category
|
|
||||||
(setq category (seq-random-elt rpgdm-ironsworn-oracle-threats)))
|
|
||||||
(let ((table-name (format "threat-%s" (downcase (string-replace " " "-" category)))))
|
|
||||||
(rpgdm-message "%s: %s" category (rpgdm-tables-choose table-name))))
|
|
||||||
|
|
||||||
(rpgdm-ironsworn-oracle-threat-goal)
|
|
||||||
|
|
||||||
(defun rpgdm-ironsworn-oracle ()
|
|
||||||
"Given a LIKLIHOOD as a single character, return weighted coin flip."
|
|
||||||
(interactive)
|
|
||||||
(let* ((prompt "What are the odds?
|
|
||||||
c) Almost Certain l) Likely h) 50/50 u) Unlikely n) Small Chance ")
|
|
||||||
(odds (read-char prompt))
|
|
||||||
(roll (rpgdm--roll-die 100))
|
|
||||||
(yes! (when (or (and (= roll 11) (eq odds ?c))
|
|
||||||
(and (= roll 26) (eq odds ?l))
|
|
||||||
(and (= roll 51) (eq odds ?h))
|
|
||||||
(and (= roll 76) (eq odds ?u))
|
|
||||||
(and (= roll 91) (eq odds ?n)))
|
|
||||||
t))
|
|
||||||
(yes (when (or (and (> roll 11) (eq odds ?c))
|
|
||||||
(and (> roll 26) (eq odds ?l))
|
|
||||||
(and (> roll 51) (eq odds ?h))
|
|
||||||
(and (> roll 76) (eq odds ?u))
|
|
||||||
(and (> roll 91) (eq odds ?n)))
|
|
||||||
t)))
|
|
||||||
(rpgdm-message "%s %s %s"
|
|
||||||
(if yes! "Extreme" "")
|
|
||||||
(if yes "Yes" "No")
|
|
||||||
(if yes! "or a twist." ""))))
|
|
||||||
|
|
||||||
(defhydra hydra-rpgdm (:color blue :hint nil)
|
|
||||||
"
|
|
||||||
^Dice^ ^Adjust^ ^Oracles/Tables^ ^Moving^ ^Messages^
|
|
||||||
----------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
_d_: Roll Dice _D_: Progress Dice _H_: Health _z_: Yes/No Oracle _a_: Action/Theme _n_: NPC _o_: Links ⌘-‿: Show Stats
|
|
||||||
_e_: Roll Edge _s_: Roll Shadow _S_: Spirit _c_: Show Oracle _c_: Combat Action _f_: Feature _J_/_K_: Page up/dn ⌘-l: Last Results
|
|
||||||
_h_: Roll Heart _w_: Roll Wits _G_: Supply _O_: Other Oracle _p_: Place Name _P_: Place _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous
|
|
||||||
_i_: Roll Iron _x_: Roll Stat _M_: Momentum _T_: Load Oracles _t_: Threat Goal ⌘-j: ↓ Next "
|
|
||||||
("d" rpgdm-ironsworn-roll) ("D" rpgdm-ironsworn-progress-roll)
|
|
||||||
("z" rpgdm-ironsworn-oracle) ("O" rpgdm-oracle)
|
|
||||||
|
|
||||||
("a" rpgdm-ironsworn-oracle-action-theme)
|
|
||||||
("n" rpgdm-ironsworn-oracle-npc)
|
|
||||||
("c" rpgdm-ironsworn-oracle-combat)
|
|
||||||
("f" rpgdm-ironsworn-oracle-feature)
|
|
||||||
("P" rpgdm-ironsworn-oracle-site-nature)
|
|
||||||
("p" rpgdm-ironsworn-oracle-site-name)
|
|
||||||
("t" rpgdm-ironsworn-oracle-threat-goal)
|
|
||||||
|
|
||||||
("e" rpgdm-ironsworn-roll-edge)
|
|
||||||
("h" rpgdm-ironsworn-roll-heart)
|
|
||||||
("i" rpgdm-ironsworn-roll-iron)
|
|
||||||
("s" rpgdm-ironsworn-roll-shadow)
|
|
||||||
("w" rpgdm-ironsworn-roll-wits)
|
|
||||||
("x" rpgdm-ironsworn-roll-stat :color pink)
|
|
||||||
|
|
||||||
("H" rpgdm-ironsworn-adjust-health :color pink)
|
|
||||||
("S" rpgdm-ironsworn-adjust-spirit :color pink)
|
|
||||||
("G" rpgdm-ironsworn-adjust-supply :color pink)
|
|
||||||
("M" rpgdm-ironsworn-adjust-momentum :color pink)
|
|
||||||
|
|
||||||
("T" rpgdm-tables-load) ("c" rpgdm-tables-choose) ("C" rpgdm-tables-choose :color pink)
|
|
||||||
("o" ace-link) ("N" org-narrow-to-subtree) ("W" widen)
|
|
||||||
("K" scroll-down :color pink) ("J" scroll-up :color pink)
|
|
||||||
|
|
||||||
("s-SPC" rpgdm-ironsworn-character-display)
|
|
||||||
("C-m" rpgdm-last-results :color pink)
|
|
||||||
("C-n" rpgdm-last-results-next :color pink)
|
|
||||||
("C-p" rpgdm-last-results-previous :color pink)
|
|
||||||
("s-l" rpgdm-last-results :color pink)
|
|
||||||
("s-j" rpgdm-last-results-next :color pink)
|
|
||||||
("s-k" rpgdm-last-results-previous :color pink)
|
|
||||||
|
|
||||||
("q" nil "quit") ("<f12>" nil))
|
|
||||||
|
|
||||||
(provide 'rpgdm-ironsworn)
|
|
||||||
;;; rpgdm-ironsworn.el ends here
|
|
||||||
|
|
@ -28,9 +28,9 @@
|
||||||
;;;
|
;;;
|
||||||
;;; CCode:
|
;;; CCode:
|
||||||
|
|
||||||
(defvar rpgdm-base ".")
|
(require 'rpgdm-core (expand-file-name "rpgdm-core.el" rpgdm-base) t)
|
||||||
(require 'rpgdm-dice (expand-file-name "rpgdm-dice.el" rpgdm-base) t)
|
(require 'rpgdm-dice (expand-file-name "rpgdm-dice.el" rpgdm-base) t)
|
||||||
(require 'rpgdm-dice (expand-file-name "rpgdm-tables.el" rpgdm-base) t)
|
(require 'rpgdm-tables (expand-file-name "rpgdm-tables.el" rpgdm-base) t)
|
||||||
|
|
||||||
(defun rpgdm-npc-gender-name ()
|
(defun rpgdm-npc-gender-name ()
|
||||||
"Return nil or non-nil for male or female names."
|
"Return nil or non-nil for male or female names."
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@
|
||||||
(require 'org)
|
(require 'org)
|
||||||
(require 'org-element)
|
(require 'org-element)
|
||||||
(require 's)
|
(require 's)
|
||||||
|
(require 'rpgdm-core (expand-file-name "rpgdm-core.el" rpgdm-base) t)
|
||||||
(defvar rpgdm-base ".")
|
|
||||||
|
|
||||||
(defvar rpgdm-screen-directory
|
(defvar rpgdm-screen-directory
|
||||||
(expand-file-name "dnd-5e" rpgdm-base)
|
(expand-file-name "dnd-5e" rpgdm-base)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(defstruct dice-table dice rows)
|
(cl-defstruct dice-table dice rows)
|
||||||
|
|
||||||
(defun rpgdm-tables--choose-dice-table (table)
|
(defun rpgdm-tables--choose-dice-table (table)
|
||||||
"Choose a string from a random dice table."
|
"Choose a string from a random dice table."
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ decrement the ROLL value."
|
||||||
;; (message "Comparing %d <= %d for %s" roll num-elems tag)
|
;; (message "Comparing %d <= %d for %s" roll num-elems tag)
|
||||||
(if (<= roll num-elems)
|
(if (<= roll num-elems)
|
||||||
(return tag)
|
(return tag)
|
||||||
(decf roll num-elems))))
|
(cl-decf roll num-elems))))
|
||||||
|
|
||||||
(ert-deftest rpgdm-tables--find-tag-test ()
|
(ert-deftest rpgdm-tables--find-tag-test ()
|
||||||
(let ((weighted-tags
|
(let ((weighted-tags
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
;; Copyright (C) 2021 Howard X. Abrams
|
;; Copyright (C) 2021 Howard X. Abrams
|
||||||
;;
|
;;
|
||||||
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||||
;; Maintainer: Howard X. Abrams
|
;; Maintainer: Howard X. Abrams <howard.abrams@workday.com>
|
||||||
;; Created: January 8, 2021
|
;; Created: January 8, 2021
|
||||||
;;
|
;;
|
||||||
;; This file is not part of GNU Emacs.
|
;; This file is not part of GNU Emacs.
|
||||||
|
|
@ -25,11 +25,10 @@
|
||||||
;;
|
;;
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'ert)
|
(require 'rpgdm-core (expand-file-name "rpgdm-core.el" rpgdm-base) t)
|
||||||
|
(require 'rpgdm-dice (expand-file-name "rpgdm-dice.el" rpgdm-base) t)
|
||||||
(require 'rpgdm-dice)
|
(require 'rpgdm-tables-freq (expand-file-name "rpgdm-tables-freq.el" rpgdm-base) t)
|
||||||
(require 'rpgdm-tables-freq)
|
(require 'rpgdm-tables-dice (expand-file-name "rpgdm-tables-dice.el" rpgdm-base) t)
|
||||||
(require 'rpgdm-tables-dice)
|
|
||||||
|
|
||||||
|
|
||||||
(defvar rpgdm-tables-directory
|
(defvar rpgdm-tables-directory
|
||||||
|
|
@ -37,7 +36,10 @@
|
||||||
"Directory path containing the tables to load and create functions.")
|
"Directory path containing the tables to load and create functions.")
|
||||||
|
|
||||||
(defvar rpgdm-tables (make-hash-table :test 'equal)
|
(defvar rpgdm-tables (make-hash-table :test 'equal)
|
||||||
"Collection of tables and lists for the Dungeon Master.")
|
"Collection of tables and lists for the Dungeon Master.
|
||||||
|
When a table directory is first loaded, the _values_ are the
|
||||||
|
filenames. After a call to `rpgdm-tables-choose', the value
|
||||||
|
is replaced by the data (in the form of a data structure).")
|
||||||
|
|
||||||
(defun rpgdm-tables-clear ()
|
(defun rpgdm-tables-clear ()
|
||||||
"Clear previously loaded tables."
|
"Clear previously loaded tables."
|
||||||
|
|
@ -88,7 +90,6 @@ Store it by NAME in the `rpgdm-tables' hash table."
|
||||||
(message "Read: %s" name))
|
(message "Read: %s" name))
|
||||||
(puthash name contents rpgdm-tables)))
|
(puthash name contents rpgdm-tables)))
|
||||||
|
|
||||||
|
|
||||||
(defun rpgdm-tables-choose (table-name)
|
(defun rpgdm-tables-choose (table-name)
|
||||||
"Return random item from a table of a given TABLE-NAME string.
|
"Return random item from a table of a given TABLE-NAME string.
|
||||||
|
|
||||||
|
|
@ -102,16 +103,25 @@ dice table (see `rpgdm-tables--choose-dice-table')."
|
||||||
(when-let ((table (gethash table-name rpgdm-tables)))
|
(when-let ((table (gethash table-name rpgdm-tables)))
|
||||||
(when (stringp table)
|
(when (stringp table)
|
||||||
(setq table (rpgdm-tables-load-file table table-name)))
|
(setq table (rpgdm-tables-load-file table table-name)))
|
||||||
(let* ((result (cond ((dice-table-p table) (rpgdm-tables--choose-dice-table table))
|
(let* ((initial (cond ((dice-table-p table) (rpgdm-tables--choose-dice-table table))
|
||||||
((hash-table-p table) (rpgdm-tables--choose-freq-table table))
|
((hash-table-p table) (rpgdm-tables--choose-freq-table table))
|
||||||
|
((functionp table) (call-interactively table))
|
||||||
((listp table) (rpgdm-tables--choose-list table))
|
((listp table) (rpgdm-tables--choose-list table))
|
||||||
(t "Error: Could choose anything from %s (internal bug?)" table-name)))
|
(t "Error: Could not choose anything from %s (internal bug?)" table-name)))
|
||||||
;; Replace any dice expression in the message with an roll:
|
|
||||||
|
;; Function to return dice expression as a sum (and string):
|
||||||
(dice-sum (lambda (dice-exp) (number-to-string (rpgdm-roll-sum dice-exp))))
|
(dice-sum (lambda (dice-exp) (number-to-string (rpgdm-roll-sum dice-exp))))
|
||||||
(no-dice-nums (replace-regexp-in-string rpgdm-roll-regexp dice-sum result))
|
|
||||||
(no-alt-words (rpgdm-tables--choose-string-list no-dice-nums)))
|
(results (thread-last initial
|
||||||
(kill-new no-alt-words)
|
;; Replace dice expression in the message with an roll:
|
||||||
(rpgdm-message "%s" no-alt-words))))
|
(replace-regexp-in-string rpgdm-roll-regexp dice-sum )
|
||||||
|
;; Replace [[table-name]] with results from table:
|
||||||
|
(rpgdm-tables--choose-string-from-table)
|
||||||
|
;; Replace [one/two/three] with one of those words:
|
||||||
|
(rpgdm-tables--choose-string-list))))
|
||||||
|
|
||||||
|
(kill-new results)
|
||||||
|
(rpgdm-message "%s" results))))
|
||||||
|
|
||||||
(defun rpgdm-tables--choose-list (lst)
|
(defun rpgdm-tables--choose-list (lst)
|
||||||
"Randomly choose (equal chance for any) element in LST."
|
"Randomly choose (equal chance for any) element in LST."
|
||||||
|
|
@ -123,30 +133,17 @@ dice table (see `rpgdm-tables--choose-dice-table')."
|
||||||
For instance, the string: 'You found a [chair/box/statue]'
|
For instance, the string: 'You found a [chair/box/statue]'
|
||||||
would be converted randomly to something like: 'You found a box.'"
|
would be converted randomly to something like: 'You found a box.'"
|
||||||
(let ((regexp (rx "[" (+? any) "/" (+? any) "]"))
|
(let ((regexp (rx "[" (+? any) "/" (+? any) "]"))
|
||||||
(subbed (lambda (str) (--> str
|
(subbed (lambda (str) (--> str
|
||||||
(substring it 1 -1)
|
(substring it 1 -1)
|
||||||
(s-split (rx "/") it)
|
(s-split (rx (*? space) "/" (*? space)) it)
|
||||||
(seq-random-elt it)
|
(seq-random-elt it)))))
|
||||||
(s-trim it)))))
|
|
||||||
(replace-regexp-in-string regexp subbed str)))
|
(replace-regexp-in-string regexp subbed str)))
|
||||||
|
|
||||||
(ert-deftest rpgdm-tables--choose-string-list ()
|
(defun rpgdm-tables--choose-string-from-table (str)
|
||||||
(let ((empty-string "")
|
"Replace <<table-name>> sequence in STR with call to `rpgdm-tables-choose'."
|
||||||
(no-op-string "This is just a phrase.")
|
(let ((regexp (rx "<<" (+? any) ">>"))
|
||||||
(two-choices "We can have [this/that]")
|
(subbed (lambda (s) (rpgdm-tables-choose (substring s 2 -2)))))
|
||||||
(two-choices1 "We can have this")
|
(replace-regexp-in-string regexp subbed str nil nil 0)))
|
||||||
(two-choices2 "We can have that")
|
|
||||||
(tri-choices "We can have [this / that / the other].")
|
|
||||||
(tri-choices1 "We can have this.")
|
|
||||||
(tri-choices2 "We can have that.")
|
|
||||||
(tri-choices3 "We can have the other."))
|
|
||||||
|
|
||||||
(should (equal (rpgdm-tables--choose-string-list empty-string) empty-string))
|
|
||||||
(should (equal (rpgdm-tables--choose-string-list no-op-string) no-op-string))
|
|
||||||
(let ((chosen (rpgdm-tables--choose-string-list two-choices)))
|
|
||||||
(should (or (equal chosen two-choices1) (equal chosen two-choices2))))
|
|
||||||
(let ((chosen (rpgdm-tables--choose-string-list tri-choices)))
|
|
||||||
(should (or (equal chosen tri-choices1) (equal chosen tri-choices2) (equal chosen tri-choices3))))))
|
|
||||||
|
|
||||||
;; I originally thought that I could have a single regular expression that
|
;; I originally thought that I could have a single regular expression that
|
||||||
;; matched all possible tables, but that is a bit too complicated. The following
|
;; matched all possible tables, but that is a bit too complicated. The following
|
||||||
|
|
|
||||||
78
rpgdm.el
78
rpgdm.el
|
|
@ -25,20 +25,16 @@
|
||||||
|
|
||||||
(require 'ert)
|
(require 'ert)
|
||||||
|
|
||||||
|
(require 'rpgdm-core)
|
||||||
(require 'rpgdm-dice)
|
(require 'rpgdm-dice)
|
||||||
(require 'rpgdm-screen)
|
(require 'rpgdm-screen)
|
||||||
(require 'rpgdm-tables)
|
(require 'rpgdm-tables)
|
||||||
|
|
||||||
|
|
||||||
(defvar rpgdm-base
|
|
||||||
(seq-find (lambda (elt) (string-match "rpgdm" elt)) load-path (getenv "HOME"))
|
|
||||||
"Default directory to look for supporting data, like tables and charts.")
|
|
||||||
|
|
||||||
(define-minor-mode rpgdm-mode
|
(define-minor-mode rpgdm-mode
|
||||||
"Minor mode for layering role-playing game master functions over your notes."
|
"Minor mode for layering role-playing game master functions over your notes."
|
||||||
:lighter " D&D"
|
:lighter " D&D"
|
||||||
:keymap (let ((map (make-sparse-keymap)))
|
:keymap (let ((map (make-sparse-keymap)))
|
||||||
(define-key map (kbd "<f12>") 'hydra-rpgdm/body)
|
(define-key map (kbd "<f6>") 'hydra-rpgdm/body)
|
||||||
map))
|
map))
|
||||||
|
|
||||||
(defhydra hydra-rpgdm (:color pink :hint nil)
|
(defhydra hydra-rpgdm (:color pink :hint nil)
|
||||||
|
|
@ -73,64 +69,6 @@
|
||||||
|
|
||||||
("q" nil "quit") ("<f12>" nil))
|
("q" nil "quit") ("<f12>" nil))
|
||||||
|
|
||||||
|
|
||||||
(defvar rpgdm-last-results (make-ring 10)
|
|
||||||
"The results from calls to `rpgdm-screen-' functions are stored here.")
|
|
||||||
|
|
||||||
(defvar rpgdm-last-results-ptr 0
|
|
||||||
"Keeps track of where we are in the message display ring.
|
|
||||||
Each call to `rpgdm-last-results' resets this to 0.")
|
|
||||||
|
|
||||||
(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."
|
|
||||||
(let ((message (apply 'format format-string args)))
|
|
||||||
(ring-insert rpgdm-last-results message)
|
|
||||||
(rpgdm-last-results)))
|
|
||||||
|
|
||||||
(defun rpgdm-last-results ()
|
|
||||||
"Display results from the last call to a `rpgdm-message' function."
|
|
||||||
(interactive)
|
|
||||||
(setq rpgdm-last-results-ptr 0)
|
|
||||||
(message (ring-ref rpgdm-last-results rpgdm-last-results-ptr)))
|
|
||||||
|
|
||||||
(defun rpgdm-last-results-previous ()
|
|
||||||
"Display results from an earlier call to `rpgdm-message'."
|
|
||||||
(interactive)
|
|
||||||
(incf rpgdm-last-results-ptr)
|
|
||||||
(when (>= rpgdm-last-results-ptr (ring-length rpgdm-last-results))
|
|
||||||
(setq rpgdm-last-results-ptr 0))
|
|
||||||
(message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr)))
|
|
||||||
|
|
||||||
(defun rpgdm-last-results-next ()
|
|
||||||
"Display results from an later call to `rpgdm-message'.
|
|
||||||
Meant to be used with `rpgdm-last-results-previous'."
|
|
||||||
(interactive)
|
|
||||||
(when (> rpgdm-last-results-ptr 0)
|
|
||||||
(decf rpgdm-last-results-ptr))
|
|
||||||
(message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr)))
|
|
||||||
|
|
||||||
(defun rpgdm-paste-last-message ()
|
|
||||||
"Yank, e.g. paste, the last displayed message."
|
|
||||||
(interactive)
|
|
||||||
(insert (rpgdm-last-results)))
|
|
||||||
|
|
||||||
(ert-deftest rpgdm-last-results-test ()
|
|
||||||
(progn
|
|
||||||
(setq rpgdm-last-results (make-ring 10))
|
|
||||||
(rpgdm-message "First in, so this is the oldest")
|
|
||||||
(rpgdm-message "Something or other")
|
|
||||||
(rpgdm-message "Almost the newest")
|
|
||||||
(rpgdm-message "Newest"))
|
|
||||||
|
|
||||||
(should (equal "Newest" (rpgdm-last-results)))
|
|
||||||
(should (equal "1> Almost the newest" (rpgdm-last-results-previous)))
|
|
||||||
(should (equal "2> Something other" (rpgdm-last-results-previous)))
|
|
||||||
(should (equal "1> Almost the newest" (rpgdm-last-results-next)))
|
|
||||||
(should (equal "0> Almost the newest" (rpgdm-last-results-next)))
|
|
||||||
(should (equal "0> Almost the newest" (rpgdm-last-results-next))))
|
|
||||||
|
|
||||||
(defvar rpgdm-oracle-mod 0 "Cummulative skew to create more tension.")
|
(defvar rpgdm-oracle-mod 0 "Cummulative skew to create more tension.")
|
||||||
|
|
||||||
(defun rpgdm-oracle ()
|
(defun rpgdm-oracle ()
|
||||||
|
|
@ -195,12 +133,12 @@ The formula is based on the NUMBER-OF-DICE. According to the
|
||||||
Players Handbook in Dungeons and Dragons, we have this table
|
Players Handbook in Dungeons and Dragons, we have this table
|
||||||
to determine difficulty skill check levels:
|
to determine difficulty skill check levels:
|
||||||
|
|
||||||
- Very easy 5
|
- Very easy 5
|
||||||
- Easy 10
|
- Easy 10
|
||||||
- Medium 15
|
- Medium 15
|
||||||
- Hard 20
|
- Hard 20
|
||||||
- Very hard 25
|
- Very hard 25
|
||||||
- Nearly impossible 30
|
- Nearly impossible 30
|
||||||
|
|
||||||
But I read somewhere that you could roll some 6 sided die to help
|
But I read somewhere that you could roll some 6 sided die to help
|
||||||
add a bit of randomness to the leve setting. Essentially, roll
|
add a bit of randomness to the leve setting. Essentially, roll
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue