{"id":20863343,"url":"https://github.com/howardabrams/emacs-rpgtk","last_synced_at":"2025-12-25T14:36:51.702Z","repository":{"id":246593429,"uuid":"821222686","full_name":"howardabrams/emacs-rpgtk","owner":"howardabrams","description":"The Solo RPG Toolkit for Emacs","archived":false,"fork":false,"pushed_at":"2024-06-28T21:53:24.000Z","size":344,"stargazers_count":6,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-19T08:15:27.394Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Emacs Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/howardabrams.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-28T04:43:33.000Z","updated_at":"2024-08-28T20:09:56.000Z","dependencies_parsed_at":"2024-06-28T22:49:44.988Z","dependency_job_id":"a216aa5a-f2fc-4fac-be81-042393fa6ff8","html_url":"https://github.com/howardabrams/emacs-rpgtk","commit_stats":null,"previous_names":["howardabrams/emacs-rpgtk"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Femacs-rpgtk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Femacs-rpgtk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Femacs-rpgtk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Femacs-rpgtk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/howardabrams","download_url":"https://codeload.github.com/howardabrams/emacs-rpgtk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243236537,"owners_count":20258855,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-18T05:28:36.983Z","updated_at":"2025-12-25T14:36:51.696Z","avatar_url":"https://github.com/howardabrams.png","language":"Emacs Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+title:  Emacs RPG Toolkit\n#+author: Howard Abrams\n#+email:  howard@sting\n#+date:   2023-10-01 October\n#+tags:   emacs rpg solottrpg\n#+startup: inlineimages\n\nThis project supplies a collection of functions in Emacs Lisp to allow\ndevelopers to craft RPG-inspired programs, with a focus on Solo RPGs\nplayed in Org-formatted files.\n\nSure, if you are a non-Lisper Emacs user, you may get some benefit,\nbut this project is a /toolkit/ for making (or at least, emulating) some\ntypical TTRPGs (with or without the table of friends).\n\nNote that we store different /features/ in different files, described in\neach of the following sections. Your project would =require= the\nindividual files you need.\n* Dice Rollers\nThe basis of most games are random numbers, and RPGs have depended on\ndice. The [[file:rpgtk-dice.el][rpgtk-dice]] file contains functions for rolling dice, and\ndisplaying the results.\n\nThe function, =rpgtk-roll=, is the primary user interface, as it rolls a\ndice based on a /dice expression/, e.g. =3d8+2=, either at the point\nlocation or from a prompt from the user.\n\n[[file:images/screenshot-rpgtk-roll.png]]\n\nNotice in the example, the code displays the expression used on the right\nside, and all the dice and calculations are also displayed, as if you\ndid the math.\n\nOther interesting functions are =rpgtk-roll-again= (to re-roll previous\nrolls), and =rpgtk-forward-roll= that moves the point to the next dice\nexpression in the current buffer.\n\nGame often /craft/ the game’s specialty dice mechanic. For instance, in\nplaying /Year Zero/-based games, like Vaesen, you roll a pool of\nsix-sided dice, choose the highest, and see if you rolled a six.\n\nA dice mechanic involves three steps:\n  1. Rolling one or more dice, using =rpgtk-dice-roll=\n  2. Modifying the results through a series of steps using =rpgtk-dice-roll-mod=\n  3. Displaying the results using =rpgtk-dice-format-roll=\n\nFor instance:\n#+begin_src emacs-lisp\n  (defun year-zero-roll (dice-pool)\n    \"Roll a DICE-POOL number of six sided dice.\n    Displays the highest value. Shows 6s in green.\"\n    (interactive \"nDice Pool Size: \")\n    (let* ((roll (rpgtk-dice-roll num-dice 6))\n           (high (rpgtk-dice-roll-mod roll :max)))\n      (rpgtk-message (rpgtk-dice-format-roll high \"YZ\" 6))))\n#+end_src\n\nTo display:\n[[file:images/screenshot-rpgtk-roll-yz.png]]\n\nGames based on /Blades in the Dark/ have a similar mechanic for rolling\na pool of six-side dice, choosing the highest value, but instead of a\nsingle six being success, a dice of =4= or =5= is a partial success. We\ncould make a complete function to simulate this dice mechanic:\n\n#+begin_src emacs-lisp\n  (defun blades-in-the-dark-roll (dice-pool)\n    \"Display formatted dice expression for Blades in the Dark.\n  Where NUM-DICE are the number six-sided dice to roll.\"\n    (interactive \"nNumber of Dice: \")\n    (thread-first dice-pool\n                  (rpgtk-dice-roll 6)        ; Roll a pool of d6s\n                  (rpgtk-dice-roll-mod :max) ; Select the highest\n                  (rpgtk-dice-format-roll\n                   (format \"%dd\" dice-pool)  ; Create a dice expression (opt)\n                   6                         ; Set the success level\n                   4)                        ; Set partial success level\n                  (rpgtk-message)))\n#+end_src\n\nIf the highest roll was a 6, the function shows it in a green color\n(actually based on the [[file:rpgtk-dice.el::(defface rpgtk-successful-roll][rpgtk-successful-roll]] face), and for a\n/partial success/, we use the [[file:rpgtk-dice.el::(defface rpgtk-middlin-roll][rpgtk-middlin-roll]] face:\n\n[[file:images/screenshot-rpgtk-roll-bitd1.png]]\n\nOtherwise, we show that value with a [[file:rpgtk-dice.el::(defface rpgtk-failed-roll][rpgtk-failed-roll]] face:\n\n[[file:images/screenshot-rpgtk-roll-bitd2.png]]\n\nAs a final example, in the 5th Edition of /Dungeons and Dragons/, a\nplayer can roll /with advantage/ by rolling two twenty-sided dice,\nchoosing the higher of the two, and then adding their =modifier= bonus.\nThe =rpgtk-dice-roll-mod= function call would look something like:\n\n#+begin_src emacs-lisp\n  (rpgtk-dice-roll-mod (rpgtk-dice-roll 2 20)\n                       :max  ; Choose larger roll\n                       :add (or modifier 0)\n                       :sum)\n#+end_src\n\nSee the function documentation for =rpgtk-dice-roll-mod= for a list of\nmodifications.\n* Deck Draws\nThe [[file:rpgtk-deck.el][rpgtk-deck]] contains functions for drawing cards from a deck.\n\n*Note:* A deck is simply a list of strings, but normally built from a small list of numbers (called /orders/) and /suits/. These may or may not be followed by a list of /trumps/, e.g. The Jokers in a standard deck of playing cards.\n\nDrawing a card and putting it back randomly into the deck is simulated with the function: =rpgtk-deck-random= (which, if the order and suits are not given, defaults to a standard deck of playing cards). Instead of this function, you might as well make a table (see below).\n\nDrawing one or more cards from a deck (and having a discard pile, so those cards are not redrawn), first, make a /shuffled/ deck with =rpgtk-deck-shuffle= (or =rpgtk-deck-shuffle-standard=), and draw using =rpgtk-deck-draw= (or =rpgtk-deck-deck-draw-cards= for more than one).\n\nFor instance, an RPG that tells you to use a standard deck of playing cards, but only use Ace through 6. The RPG uses the /suits/ for different aspects of a character’s life, and the order (number) be the intensity of the situation:\n\n#+begin_src emacs-lisp :results silent\n  (rpgtk-deck-shuffle\n    ;; Instead of numbers, we use some T-shirt sizes:\n    '(\"XS\" \"S\" \"M\" \"L\" \"XL\" \"XXL\")\n    ;; We use Hearts Diamonds Clubs and Spades for:\n    '(\" Relationship\" \" at Work\" \" Project\" \" in School\"))\n    ;; Notice the spaces above.\n#+end_src\n\nThis could results in drawing three cards at a complicated point in the character’s life:\n#+begin_example\nCards: ⸢XL at Work⸥ ⸢S Relationship⸥ ⸢XL in School⸥\n#+end_example\n\nHrm. Perhaps asking out the crush caused our character to get fired from working at the diner as well as a failing grade in Chemistry. /shrug/\n* Random Tables\nFrom Random Encounters, to Treasure Tables, to answering, “What’s the\nweather like?” RPGs have relied on randomly choosing entries from\ntables. This toolkit offers a function, =rpgtk-tables-load=, that parses\na directory of text files (recursively). Then call =rpgtk-tables-choose=\nand select a random table, e.g.\n\n#+attr_html: :width 600px\n[[file:images/screenshot-rpgtk-choose-1.png]]\n\nUsing fuzzy enhancers to =completing-read=, allows you to trim down the\noptions:\n\n#+attr_html: :width 600px\n[[file:images/screenshot-rpgtk-choose-2.png]]\n\nAnd using something like [[https://github.com/oantolin/orderless][Orderless]], limits the choices even more:\n\n#+attr_html: :width 600px\n[[file:images/screenshot-rpgtk-choose-3.png]]\n\nUntil you have what you want:\n\n#+attr_html: :width 600px\n[[file:images/screenshot-rpgtk-choose-4.png]]\n\nThe random entry from the table is both displayed and copied to the\nclipboard, er, kill-ring.\n\nThe table parsing function accepts three /types/ of formats for these\ntext files:\n  - lists\n  - frequency tables\n  - dice tables\n** List Tables\nMost text files for these files contain a list of items. The file\ncould contain items where each line is one entry, e.g.\n#+begin_example\nGrughuc Coinhelm\nLobatin Flaskhide\nKoghurum Longgut\nEmgus Barbedpike\nBelbek Bronzehand\nLasris Blazingblade\nEmthrun Stronghammer\nThurthrum Norsk\nGwynmura Rejuhkak\nJintin Glowdust\nGergrom Frosthorn\nNysdille Heavybeard\n#+end_example\n\nUnlike published RPG material that relies on dice combinations, the\nbeauty of these random selection tables is you can have any number of\nitems. For instance, a list may have seven items, and you wouldn’t\nhave to add either an item or an entry that says, “Roll twice on this\ntable”.\n\nThe entries in the files can begin with /list characters/, i.e. ~-~, ~+~,\n~*~, and ~|~. This allows the file to mimic an org-mode list or table. The\ncode ignores lines beginning with ~#~ as comments, which allows a table\nwriter to specify meta information, e.g.\n#+begin_example\n#+name: Elf Names\n#+property: source-url=https://www.fantasynamegenerators.com/elf-names.php\n\n  - Rydel Helegwyn\n  - Merellien Reywynn\n  - Ivasaar Theric\n  - Naertho Inanorin\n  - Folen Zumnorin\n  - Inchel Craroris\n  - Simimar Yesdove\n  - Cyran Qimaer\n  - Naeryndam Thelynn\n  - Eriladar Carsatra\n#+end_example\n\nEach entry can specify a random numerical value, e.g. for a Random\nEncounter Table,\n#+begin_example\n  - A group of 1d4+2 goblins gambling at dice\n  - A bugbear dangles 2d20+10' above the characters, ready to drop.\n  - A hobgoblin carries 1d4 bags of loot.\n  …\n#+end_example\nWhich could return:\n#+begin_example\nA bugbear dangles 31' above the characters, ready to drop.\n#+end_example\n\nEntries can also specify textual choices, e.g.\n#+begin_example\n  - A group of 1d4+2 [stealthy/drunk/sleeping/angry] goblins\n#+end_example\nWhich could return:\n#+begin_example\nA group of 5 sleeping goblins\n#+end_example\n\nText that matches a pattern between double angle brackets are replaced by a recursive call to another table. For instance, you could have a table of monsters in =monsters.txt=:\n#+begin_example\n  - ogres\n  - goblins\n  - trolls\n  - orcs\n  ...\n#+end_example\nAnd another table, =monster-activity.txt= that has stuff like:\n#+begin_example\n  - sleeping\n  - playing [dice/cards/stones]\n  - drinking\n  - arguing\n  ...\n#+end_example\nAnd now, in your =random-dungeon-encounters.txt= table, you can have:\n#+begin_example\n  ...\n  - mannacles attached to the wall\n  - 2d4+1 \u003c\u003cmonsters\u003e\u003e \u003c\u003cmonster-activity\u003e\u003e\n  ...\n#+end_example\nAnd now, you might get a response, like:\n#+begin_example\n  3 goblins playing cards\n#+end_example\nThis feature can also be used instead of rolling on multiple tables. For instance, you could have an =npc= table that has a single entry, like:\n#+begin_example\n  - \u003c\u003cnpc/name\u003e\u003e, who appears to be a \u003c\u003cnpc/occupation\u003e\u003e is \u003c\u003cnpc/activity\u003e\u003e ...\n#+end_example\n** Dice Tables\nA /dice table/ is a table that is easy to manipulate with dice, and is\npretty typical in published supplements. The general idea is to roll\none or more specific dice, and compare the number in the first column.\n\nFor instance, /Xanathar's Guide to Everything/, a Dungeons \u0026\nDragons supplement from Wizards of the Coast, allows you to\nchoose a random alignment for a character with the following table:\n\n| 3d6    | Alignment                                   |\n|--------|---------------------------------------------|\n| 3      | Chaotic evil (50%) or chaotic neutral (50%) |\n| 4--5   | Lawful evil                                 |\n| 6--8   | Neutral evil                                |\n| 9--12  | Neutral                                     |\n| 13--15 | Neutral good                                |\n| 16--17 | Lawful good (50%) or lawful neutral (50%)   |\n| 18     | Chaotic good (50%) or chaotic neutral (50%) |\n\nThis would be render as a table with a range in the first column,\nand equally weighted choices in the rest of the columns. For instance:\n\n#+begin_example\nRoll on Table: 3d6\n\n|      3 | Chaotic evil | chaotic neutral |\n|   4--5 | Lawful evil  |                 |\n|   6--8 | Neutral evil |                 |\n|  9--12 | Neutral      |                 |\n| 13--15 | Neutral good |                 |\n| 16--17 | Lawful good  | lawful neutral  |\n|     18 | Chaotic good | chaotic neutral |\n#+end_example\n\nNotice that we need to have a /dice expression/ to explain how to arrive\nat a number for selecting a row. To do this, add the text,\n=Roll on Table= and a standard /dice expression/.\n\nThese types of tables are good when rendering published material, but\nare obnoxious to create.\n** Frequency Tables\n\nWhile the a table could be simple lists to choose a random element,\nsome lists could return /some elements/ more often than /other elements/.\nWhile that sounds great in a sentence, this code in this section\ndescribes this concept of /frequency tables/. For instance, here is a\nFaction Encounter table:\n\n#+begin_example\n\n | Church of Talos :: Worshipers of the god of storms/destruction    | scarcely   |\n | City Watch :: Members of the Waterdeep constabulary               | often      |\n | Cult of the Dragon :: Cultists who venerate evil dragons          | seldom     |\n | Emerald Enclave :: Alliance of druids/rangers to defend the wilds | seldom     |\n | Enclave of Red Magic :: Thayan mages who smuggle slaves           | sometimes  |\n | Force Grey :: League of heroes sworn to protect Waterdeep         | often      |\n | Halaster’s Heirs :: Dark arcanists trained at a hidden academy    | rarely     |\n | The Kraken Society :: Shadowy group of thieves and mages          | rarely     |\n …\n#+end_example\n\nWhile Waterdeep could have over 50 factions running around, we\nwould assume players would run into the City Watch more often than\nthe delusional members of the /Kraken Society/.\n\nUnlike a normal list, these text files have two columns, where the\nfirst is the item and the second determines the frequency, which can\neither be from this group:\n\n   - =rarely=\n   - =seldom= or =sometimes=, which is twice as likely as =rarely=\n   - =scarcely= , =scarce= or =hardly ever=, which is three-times more\n     likely than =rarely=\n   - =often=, which is four-times more likely than =rarely=\n\nOr this group:\n     - =legendary=\n     - =veryrare=, =very-rare=, or =very rare=, which is /twice/ =legendary=\n     - =rare=, which is /four-times/ the occurrence of =legendary=\n     - =uncommon=, is /seven-times/ the occurrence of =legendary=\n     - =common=, is /twelve-times/ more likely to be selected\n\nAs you can tell, the current implementation, while useful, is quite\nawful for a /toolkit/, and we need to change the code to allow a\ntable-writer to specify the frequency levels and grouping, and not\nrely on English semantics.\n* Result Messages\nUnlike regular Emacs call to =message=, strings sent to =rpgtk-message=\nare available to be /re-seen/.\n\n  - =rpgtk-last-results= : shows the last results\n  - =rpgtk-last-results-previous= : shows earlier results, called\n    multiple times after calling =rpgtk-last-results=.\n  - =rpgtk-last-results-next= : shows a later result, and\n    called after a call to =rpgtk-last-results-previous=.\n\nWhile you can bind each of these functions to keys, it might be easier\nto bind a key to =rpgtk-last-message= to show the latest message shown\n(for instance, a dice roll or an entry from a table), and assuming a\nuser has installed Hydra, allows the user to iterate over previous\nmessages.\n\nFor instance, after walking through a forest, in some acclimate\nweather, our hero befriends a dwarf, rolling a pretty good score.\n“What did you say her name was again?” they say … no problem, you call\n=rpgtk-last-message= to see:\n\n[[file:images/screenshot-rpgtk-last-message-1.png]]\n\nPressing ~j~ (since you’ve installed Evil, otherwise, it would be ~p~),\nyou see the name of the dwarf:\n\n[[file:images/screenshot-rpgtk-last-message-2.png]]\n\nPress it again to remind you of the random weather your table reported:\n\n[[file:images/screenshot-rpgtk-last-message-3.png]]\n\n*Note:* The user may insert the last message seen using any of these\ncommands, with a standard call to =yank=.\n\nFor RPG designers, you should call =rpgtk-message2=, as it takes a\nmessage shown to the user, as well as a second string for the user to\npaste. For instance, when viewing a dice roll, the message is verbose,\nbut when yanking the roll into the buffer, the total is all that is in\nthe kill-ring.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhowardabrams%2Femacs-rpgtk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhowardabrams%2Femacs-rpgtk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhowardabrams%2Femacs-rpgtk/lists"}