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

7.1 KiB

Fate and Fate Accelerated RPG

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:

(defun rpgdm--fate-die ()
  "Return a cons of a Fate die and its visual representation."
  (seq-random-elt '((-1 . "[-]")
                    ( 0 . "[ ]")
                    ( 1 . "[+]"))))

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.

(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))

So calling the rpgdm--fate-role-dice would return something like:

'((0 . "[ ]")
  (1 . "[+]")
  (0 . "[ ]")
  (-1 . "[-]"))

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.

(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))))

And we make an interactive version:

(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))))

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:

-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:

(defvar rpgdm-fate-ladder rpgdm-fate-ladder-values "The FATE RPG ladder of challenge levels.")

Now we can use it to prompt for the

(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))))

This assumes that we can create a prompt from one entry in our table:

(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))))

Let's prompt for both the challenge level as well as the current effort:

(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))))