;;; rpgdm.el --- Support utilities for the RPG Game Master -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2021 Howard X. Abrams ;; ;; Author: Howard X. Abrams ;; Maintainer: Howard X. Abrams ;; Created: January 4, 2021 ;; ;; This file is not part of GNU Emacs. ;; ;;; Commentary: ;; ;; This package includes all the help and support I can think for the Game ;; Master running a role-playing game. ;; ;; This include a minor mode, `rpgdm' that adds a few keybindings useful ;; with either org-mode or markdown-formatted files. ;; ;;; Code: (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)) (load-file (expand-file-name "rpgdm-tables.el" rpgdm-base)) (defgroup rpgdm nil "Customization for the Dungeon Master support package." :prefix "rpgdm-" :group 'applications :link '(url-link :tag "Github" "https://gitlab.com/howardabrams/emacs-rpgdm")) (defun rpgdm-yes-and-50/50 () "Add spice to your 50/50 events (luck) with Yes/No+complications. The Freeform Universal RPG has the idea that you could succeed or fail, but have extra complications or extra bonuses. This returns one of six answers with equal frequency: - No, and ... in other words, no luck, plus a minor complication. - No. ... Nope, no luck at this time. - No, but ... in other words, no luck, but something else good. - Yes, but ... you got what you wanted, but with a complication. - Yes. ... Yup, luck is on your side. - Yes, and ... Yes, plus you get a little something-something. https://www.drivethrurpg.com/product/89534/FU-The-Freeform-Universal-RPG-Classic-rules" (interactive) (let (rolled (rpgdm--roll-die 6)) (cond ((= rolled 1) "No, and... (fails badly that you add a complication)") ((= rolled 2) "No.") ((= rolled 3) "No, but... (fails, but add a little bonus or consolation prize)") ((= rolled 4) "Yes, but... (succeeds, but add a complication or caveat)") ((= rolled 5) "Yes.") (t "Yes, and... (succeeds, plus add a litle extra something-something)")))) ;; ---------------------------------------------------------------------- ;; SKILL CHECKS ;; ---------------------------------------------------------------------- ;; I would like to have a function that (defun rpgdm--skill-level-dice (number-of-dice) "Return a random skill challenge level. The formula is based on the NUMBER-OF-DICE. According to the Players Handbook in Dungeons and Dragons, we have this table to determine difficulty skill check levels: - Very easy 5 - Easy 10 - Medium 15 - Hard 20 - Very hard 25 - Nearly impossible 30 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 the 6d and add 7. Easy -- Die: 1 Lowest: 8 Highest: 13 Average: 10 Medium -- Die: 2 Lowest: 9 Highest: 19 Average: 14 Hard -- Die: 3 Lowest: 10 Highest: 25 Average: 17 Very hard -- Die: 4 Lowest: 11 Highest: 30 Average: 20 Nearly Impossible -- Die: 5 Lowest: 14 Highest: 35 Average: 24" (rpgdm--sum (rpgdm--roll number-of-dice 6 7))) ;; Let's verify my assumptions: ;; (dolist (die '(0 1 2 3 4 5)) ;; (let* ((rolls (repeatedly 'rpgdm--skill-level-dice (list die) 1000)) ;; (highest (apply 'max rolls)) ;; (lowest (apply 'min rolls)) ;; (average (/ (-sum rolls) 1000))) ;; (message ;; (format "Die: %d Lowest: %d Highest: %d Average: %d\n" die lowest highest average)))) (defun rpgdm--skill-level (target) "Return a skill challenge level by Interpreting TARGET. This parameter can be a symbol or string for 'easy', 'hard', etc. Or it can be an actual number." (when (symbolp target) (setq target (symbol-name target))) (cond ((string-prefix-p target "trivial" t) 5) ((string-prefix-p target "easy" t) (rpgdm--skill-level-dice 1)) ((string-prefix-p target "moderate" t) (rpgdm--skill-level-dice 2)) ((string-prefix-p target "medium" t) (rpgdm--skill-level-dice 2)) ((string-prefix-p target "hard" t) (rpgdm--skill-level-dice 3)) ((string-prefix-p target "difficult" t) (rpgdm--skill-level-dice 4)) ((string-prefix-p target "very hard" t) (rpgdm--skill-level-dice 4)) ((string-prefix-p target "impossible" t) (rpgdm--skill-level-dice 5)) ((numberp target) target) (t (max (string-to-number target) 12)))) (defun rpgdm--yes-and (target rolled-results) "Instead of returning a pass/fail, return 'Yes, but' strings. The Freeform Universal RPG has the idea that you could succeed or fail, but have extra complications or extra bonuses based on how high/low you pass/fail. I have expanded the idea with a d20, so given a TARGET number, like '12', and the ROLLED-RESULTS from the player, this returns a string based on a table. https://www.drivethrurpg.com/product/89534/FU-The-Freeform-Universal-RPG-Classic-rules" (cond ((< rolled-results (- target 7)) "No, and... !!") ((< rolled-results (- target 3)) "No.") ((< rolled-results target) "No, but...") ((< rolled-results (+ target 3)) "Yes, but...") ((< rolled-results (+ target 7)) "Yes.") (t "Yes, and... !!"))) (ert-deftest rpgdm--yes-and-test () (should (equal (rpgdm--yes-and 10 1) "No, and...")) (should (equal (rpgdm--yes-and 10 2) "No, and...")) (should (equal (rpgdm--yes-and 10 3) "No.")) (should (equal (rpgdm--yes-and 10 4) "No.")) (should (equal (rpgdm--yes-and 10 5) "No.")) (should (equal (rpgdm--yes-and 10 6) "No.")) (should (equal (rpgdm--yes-and 10 7) "No, but...")) (should (equal (rpgdm--yes-and 10 8) "No, but...")) (should (equal (rpgdm--yes-and 10 9) "No, but...")) (should (equal (rpgdm--yes-and 10 10) "Yes, but...")) (should (equal (rpgdm--yes-and 10 11) "Yes, but...")) (should (equal (rpgdm--yes-and 10 12) "Yes, but...")) (should (equal (rpgdm--yes-and 10 13) "Yes.")) (should (equal (rpgdm--yes-and 10 14) "Yes.")) (should (equal (rpgdm--yes-and 10 15) "Yes.")) (should (equal (rpgdm--yes-and 10 16) "Yes.")) (should (equal (rpgdm--yes-and 10 17) "Yes, and..."))) (defun rpgdm-skill-check (target rolled-results) "Given a TARGET skill check, and ROLLED-RESULTS, return pass/fail. 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))) (defun rpgdm-skill-check-easy (rolled-results) "Return an embellished pass/fail from ROLLED-RESULTS for an easy skill check." (interactive "nRolled Results: ") (rpgdm-skill-check (rpgdm--skill-level 'easy) rolled-results)) (defun rpgdm-skill-check-moderate (rolled-results) "Return an embellished pass/fail from ROLLED-RESULTS for a moderately-difficult skill check." (interactive "nRolled Results: ") (rpgdm-skill-check (rpgdm--skill-level 'medium) rolled-results)) (defun rpgdm-skill-check-hard (rolled-results) "Return an embellished pass/fail from ROLLED-RESULTS for a hard skill check." (interactive "nRolled Results: ") (rpgdm-skill-check (rpgdm--skill-level 'hard) rolled-results)) (defun rpgdm-skill-check-difficult (rolled-results) "Return an embellished pass/fail from ROLLED-RESULTS for a difficult skill check." (interactive "nRolled Results: ") (rpgdm-skill-check (rpgdm--skill-level 'difficult) rolled-results)) (defun rpgdm-skill-check-impossible (rolled-results) "Return an embellished pass/fail from ROLLED-RESULTS for an almost impossible skill check." (interactive "nRolled Results: ") (rpgdm-skill-check (rpgdm--skill-level 'impossible) rolled-results)) (provide 'rpgdm) ;;; rpgdm.el ends here