emacs-rpgdm/docs/fate-rpg.org
Howard Abrams 5455785b08 Like Athena, this project emerged fully formed from my head
At least, from this historical record as preserved by git, it is.
In reality, this project represents a year of off-and-on development
in another git repository, and has been converted and reformatted
for (potentially) public consumption.

Particularly lacking is the Tables and other charts that make this
useful, but I need to make sure I don't violate any copyright laws, as
many of my tables were copy/pasted from digital books I own.
2022-02-18 15:25:05 -08:00

164 lines
7.1 KiB
Org Mode

#+TITLE: Fate and Fate Accelerated RPG
#+AUTHOR: Howard X. Abrams
#+DATE: 2021-08-25 August
#+TAGS: rpg fate
In my quest for a simple, but generic RPG, I've noticed FATE, and the toolbox for crafting your own stuff.
* Fate Dice
Obviously, step one for me in looking at a new game system is to render their dice, which I want to do slightly differently than returning a random number. This function returns both the random number and its /visual aspect/ of a =+=, =-=, or blank:
#+BEGIN_SRC emacs-lisp :results silent
(defun rpgdm--fate-die ()
"Return a cons of a Fate die and its visual representation."
(seq-random-elt '((-1 . "[-]")
( 0 . "[ ]")
( 1 . "[+]"))))
#+END_SRC
Instead of an even spread (like in many dice ranged from 1-20 or 1-100), we roll FATE dice in groups of four to create a /bell curve/. Sure, the range is -4 to +4, but those edges show up a lot less.
The odds are easy enough to calculate, but I like to see the percentage to roll the actual results, but also to see the change of getting the result or lower/higher, as in the final three columns:
| | *Odds* | Rolling | Rolling this | Rolling this |
| | | Exactly | or Higher | or Lower |
|----+-------+---------+--------------+--------------|
| +4 | 1/81 | 1.2% | 1.2% | 100.0% |
| +3 | 4/81 | 4.9% | 6.2% | 98.8% |
| +2 | 10/81 | 12.3% | 18.5% | 93.8% |
| +1 | 16/81 | 19.8% | 38.3% | 81.5% |
| 0 | 19/81 | 23.5% | 61.7% | 61.7% |
| -1 | 16/81 | 19.8% | 81.5% | 38.3% |
| -2 | 10/81 | 12.3% | 93.8% | 18.5% |
| -3 | 4/81 | 4.9% | 98.8% | 6.2% |
| -4 | 1/81 | 1.2% | 100.0% | 1.2% |
Let's have a function that can roll four dice and give a list of the tuples.
#+BEGIN_SRC emacs-lisp
(defun rpgdm--fate-roll-dice (&optional number)
"Return a list of Fate roll results. Each element of the list
is a cons of its value and its visual representation, see
`rpgdm--fate-die'."
punless number
(setq number 4))
(let (results)
(--dotimes number
(push (rpgdm--fate-die) results))
results))
#+END_SRC
So calling the =rpgdm--fate-role-dice= would return something like:
#+begin_src emacs-lisp :tangle no
'((0 . "[ ]")
(1 . "[+]")
(0 . "[ ]")
(-1 . "[-]"))
#+end_src
My UI will collect the roll and figure out how to sum and display the results as a list where the first element is the total and the second is a string that we could display.
#+BEGIN_SRC emacs-lisp
(defun rpgdm--fate-roll (modifier &optional number)
"Simulate a FATE dice roll and return the results.
Return a list where the first element is the total results,
and the second element is a user-formatted view of the results. "
(unless number (setq number 4))
(let* ((roll (rpgdm--fate-roll-dice number))
(vals (-map 'car roll))
(dice (-map 'cdr roll))
(roll (s-join " " dice))
(sum (apply '+ vals))
(total (+ sum modifier))
(results (propertize (number-to-string total) 'face '(:foreground "green"))))
(list total (format "Rolled: %s :: %s + %d" results roll modifier))))
#+END_SRC
And we make an interactive version:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-fate-roll (modifier)
"Simulate a FATE dice roll and display the results.
The total is the difference of the `+' to `-' plus the MODIFIER.
Note that the NUMBER of dice defaults to 4."
(interactive (list (rpgdm-fate-ladder "effort level")))
(rpgdm-message (second (rpgdm--fate-roll modifier 4))))
#+END_SRC
* Skill Checks
First step is the Ladder, which is a simple table containing the numeric ranking (to use in calculations), a key-mnemonic, and a descriptive /skill/ label. The final column is a descriptive label for /challenge/ level:
#+name: ladder-table
| -2 | t | Terrible | Trivial |
| -1 | p | Poor | Easy |
| 0 | m | Mediocre | Simple |
| 1 | a | Average | Average |
| 2 | f | Fair | |
| 3 | g | Good | Hard |
| 4 | r | Great | Daunting |
| 5 | s | Superb | Extreme |
| 6 | f | Fantastic | Impossible |
| 7 | e | Epic | |
| 8 | l | Legendary | |
And use that table as a global variable:
#+BEGIN_SRC emacs-lisp :var rpgdm-fate-ladder-values=ladder-table
(defvar rpgdm-fate-ladder rpgdm-fate-ladder-values "The FATE RPG ladder of challenge levels.")
#+END_SRC
Now we can use it to prompt for the
#+BEGIN_SRC emacs-lisp
(defun rpgdm-fate-ladder (&optional request-type)
"Prompt for a choice on the FATE ladder, and return the numeric value of that level.
The REQUEST-TYPE is an optional string inserted into the prompt to describe the request."
(interactive)
(unless request-type (setq request-type "challenge level"))
(let* ((choices (mapconcat 'rpgdm--fate-ladder-prompt rpgdm-fate-ladder " "))
(prompt (format "What is the %s?\n%s"
(propertize request-type 'face '(:foreground "yellow"))
choices))
(choice (char-to-string (read-char prompt)))
(entry (--filter (equal (second it) choice) rpgdm-fate-ladder)))
(first (first entry))))
#+END_SRC
This assumes that we can create a prompt from one entry in our table:
#+BEGIN_SRC emacs-lisp
(defun rpgdm--fate-ladder-prompt (entry)
(let* ((entry-number (format "[%d]" (first entry)))
(render-number (propertize entry-number
'face '(:foreground "#888888")))
(keyed-prompt (propertize (second entry) 'face '(:foreground "green"))))
(format "%s) %s %s" keyed-prompt (third entry) render-number))))
#+END_SRC
Let's prompt for both the challenge level as well as the current effort:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-fate-challenge (opposition-level effort-level)
"Return a user message for a FATE dice challenge.
Given a numeric EFFORT-LEVEL as well as the OPPOSITION-LEVEL,
this function rolls the Fate dice and interprets the results."
(interactive (list (rpgdm-fate-ladder "opposition level")
(rpgdm-fate-ladder "effort level")))
(let* ((die-roll (rpgdm--fate-roll effort-level))
(shifts (- (first die-roll) opposition-level))
(results (cond
((< shifts 0) (propertize "Failed" 'face '(:foregound "red")))
((= shifts 0) (propertize "Tie" 'face '(:foreground "yellow")))
((> shifts 3) (propertize "Succeed with Style!" 'face '(:foregound "green")))
(t (propertize "Success" 'face '(:foregound "green"))))))
(rpgdm-message "%s ... %s" results (second die-roll))))
#+END_SRC
#+PROPERTY: header-args:sh :tangle no
#+PROPERTY: header-args:emacs-lisp :tangle ../rpgdm-fate.el
#+PROPERTY: header-args :results none :eval no-export :comments no
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
# Local Variables:
# eval: (add-hook 'after-save-hook #'org-babel-tangle t t)
# End: