egme.el 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. ;;Copyright (C) 2021 Category <category@[no_spam]quintendo.uk>
  2. ;;
  3. ;;This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2.
  4. ;;
  5. ;;This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  6. ;;
  7. ;;You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  8. (defgroup egme nil
  9. "Variables for customizing the Emacs Games Master Emulator."
  10. :group 'games)
  11. (defcustom egme-print-line-start "~~|Games Master|~~~~~~~~~"
  12. "String printed before games master ouput in non-org buffers."
  13. :type 'string
  14. :group 'egme)
  15. (defcustom egme-print-line-end "~~~~~~~~~~~~~~~~~~~~~~~~~"
  16. "String printed after games master output in non-org buffers."
  17. :type 'string
  18. :group 'egme)
  19. (defcustom egme-action-list '("Abandon"
  20. "Abuse"
  21. "Adversity"
  22. "Agree"
  23. "Ambush"
  24. "Antagonise"
  25. "Arrive"
  26. "Assist"
  27. "Attach"
  28. "Befriend"
  29. "Bestow"
  30. "Betray"
  31. "Block"
  32. "Break"
  33. "Celebrate"
  34. "Change"
  35. "Communicate"
  36. "Control"
  37. "Create"
  38. "Cruelty"
  39. "Deceive"
  40. "Delay"
  41. "Desert"
  42. "Develop"
  43. "Dispute"
  44. "Disrupt"
  45. "Divide"
  46. "Dominate"
  47. "Expose"
  48. "Failure"
  49. "Fight"
  50. "Gratify"
  51. "Guide"
  52. "Harm"
  53. "Heal"
  54. "Immitate"
  55. "Imprison"
  56. "Inform"
  57. "Inquire"
  58. "Inspect"
  59. "Intolerance"
  60. "Judge"
  61. "Kill"
  62. "Malice"
  63. "Mistrust"
  64. "Move"
  65. "Neglect"
  66. "Open"
  67. "Oppose"
  68. "Oppress"
  69. "Passion"
  70. "Persecute"
  71. "Praise"
  72. "Procrastinate"
  73. "Propose"
  74. "Punish"
  75. "Pursue"
  76. "Release"
  77. "Return"
  78. "Ruin"
  79. "Separate"
  80. "Spy"
  81. "Starting"
  82. "Stop"
  83. "Take"
  84. "Transform"
  85. "Travel"
  86. "Trick"
  87. "Trust"
  88. "Violate"
  89. "Waste"
  90. "Work")
  91. "List of 'Action' variables used in the random event generator."
  92. :type '(repeat string)
  93. :group 'egme)
  94. (defcustom egme-subject-list '("A path"
  95. "A project"
  96. "Adversities"
  97. "Advice"
  98. "Allies"
  99. "Ambush"
  100. "Animals"
  101. "Art"
  102. "Attention"
  103. "Balance"
  104. "Bureaucracy"
  105. "Business"
  106. "Competition"
  107. "Danger"
  108. "Death"
  109. "Dispute"
  110. "Disruption"
  111. "Dreams"
  112. "Elements"
  113. "Emotions"
  114. "Energy"
  115. "Environment"
  116. "Expectations"
  117. "Extravagance"
  118. "Failure"
  119. "Fame"
  120. "Fears"
  121. "Friendship"
  122. "Goals"
  123. "Home"
  124. "Hope"
  125. "Illness"
  126. "Information"
  127. "Inside"
  128. "Intrigues"
  129. "Jealousy"
  130. "Joy"
  131. "Leadership"
  132. "Lies"
  133. "Masses"
  134. "Messages"
  135. "Military"
  136. "Nature"
  137. "New ideas"
  138. "Opposition"
  139. "Outside"
  140. "Pain"
  141. "Peace"
  142. "Plans"
  143. "Pleasures"
  144. "Portals"
  145. "Possessions"
  146. "Power"
  147. "Prison"
  148. "Randomness"
  149. "Reality"
  150. "Riches"
  151. "Rumor"
  152. "Stalemate"
  153. "Status quo"
  154. "Success"
  155. "Suffering"
  156. "Tactics"
  157. "Technology"
  158. "Travel"
  159. "Trials"
  160. "Vehicle"
  161. "Victory"
  162. "Weapons"
  163. "Weather"
  164. "Wishes"
  165. "Wounds")
  166. "List of 'Subject' variables used in the random event generator."
  167. :type '(repeat string)
  168. :group 'egme)
  169. (setq egme-dice-history (list))
  170. ;; Standard probability list for ido-completing-read
  171. (setq egme-probability-list (list
  172. "0 Even odds"
  173. "-1 Unlikely"
  174. "-2 Very Unlikely"
  175. "-3 Extremely Unlikely"
  176. "-4 Near Impossible"
  177. "+4 Near Certain"
  178. "+3 Extremely Likely"
  179. "+2 Very Likely"
  180. "+1 Likely"))
  181. (setq egme-random-counter 0)
  182. (setq egme-random-event-list (list
  183. "Remote event"
  184. "NPC action"
  185. "New NPC appears"
  186. "Move towards thread"
  187. "Move away from thread"
  188. "PC positive"
  189. "PC negative"
  190. "NPC positive"
  191. "NPC negative"
  192. "Ambiguous event"))
  193. (setq egme-npc-list (list))
  194. (setq egme-thread-list (list))
  195. (defun egme-random-list-item (list-to-pick-from)
  196. "This function takes a list as an argument, and returns a random element from within that list.
  197. Will return nil if provided list is nil."
  198. (cond
  199. (list-to-pick-from (nth (random (length list-to-pick-from)) list-to-pick-from))
  200. (t nil)))
  201. (defun egme-get-dice ()
  202. "Get the required dice-roll from user input on the mini-buffer. Dice rolls to be expected in the usual [number]D[dice-type][modifier] format used by RPGs, for example '2D6' for 2 six-sided dice, or '3d8+2' for 3 eight-sided dice, with 2 added to the result. If the format is given without number (for example 'd100'), then it is assume to be a single dice being rolled.
  203. If no input is given, then it will return the last dice rolled. A full history of rolls is stored in 'egme-dice-history', accessible via the arrow keys when asked for input.
  204. Returns the dice-type, which is also stored in the variable egme-current-dice - returns nil if input can't be parsed into a dice roll."
  205. (setq egme-current-dice (read-string (format "Enter dice roll (default %s): " (car egme-dice-history))
  206. nil
  207. 'egme-dice-history
  208. (car egme-dice-history)))
  209. ;; Add a leading "1" in case user inputs without type (i.e just "D100")
  210. (when (string-match "^[dD]" egme-current-dice)
  211. (setq egme-current-dice (concat "1" egme-current-dice)))
  212. ;; Look for string in dice-roll format, return if found
  213. (when (string-match "[1-9][0-9]?[dD][1-9][0-9]*\\([+-][0-9]+\\)?" egme-current-dice)
  214. (setq egme-current-dice (match-string 0 egme-current-dice))))
  215. (defun egme-calculate-dice (&optional dice-roll)
  216. "Calculates the current dice roll. If called alone, roll the variable egme-current-dice. If argument DICE-ROLL is provided, roll that - it must be in RPG dice notation ('1d20', '3d10+8', '2d6-4', etc). Return the result of the dice roll, and store in the variable egme-roll-result.
  217. Current roll is broken down into the following variable for calculating:-
  218. +egme-current-dice-quantity
  219. +egme-current-dice-type
  220. +egme-current-dice-modifier
  221. This function loops for the quantity of dice, summing up random numbers for the appropriate type, then applying the modifier. In the case of a multiple D6 type (ie D66/D666/D6666...) then instead of summing the results it treats each D6 roll as a different digit in the final result."
  222. ;; Reset last roll result
  223. (setq egme-roll-result 0)
  224. (setq egme-multi-6-temp nil)
  225. ;; Set egme-current-dice if an option was passed with the function call
  226. (when dice-roll
  227. (setq egme-current-dice dice-roll))
  228. ;; Get quantity of dice rolled
  229. (string-match "^[1-9]+" egme-current-dice)
  230. (setq egme-current-dice-quantity (string-to-number (match-string 0 egme-current-dice)))
  231. ;; Get current dice type
  232. (string-match "[dD][1-9][0-9]*" egme-current-dice)
  233. (setq egme-current-dice-type (string-to-number (string-trim-left (match-string 0 egme-current-dice) "[dD]")))
  234. ;; Get modifier (if present, else set to 0)
  235. (if (string-match "[+-][0-9]+$" egme-current-dice)
  236. (setq egme-current-dice-modifier (string-to-number (match-string 0 egme-current-dice)))
  237. (setq egme-current-dice-modifier 0))
  238. ;; Check if dice type is a D66/D666/D6666 etc
  239. (if (string-match "^66+$" (number-to-string egme-current-dice-type))
  240. ;; If a multi-6 dice, roll each D6 and combine as string, then repeat for each quantity of rolls
  241. (dotimes (n egme-current-dice-quantity)
  242. (dotimes (n (length (number-to-string egme-current-dice-type)))
  243. (setq egme-multi-6-temp (concat egme-multi-6-temp (number-to-string (+ 1 (random 6))))))
  244. (setq egme-roll-result (string-to-number egme-multi-6-temp)))
  245. ;; Else calculate dice as usual
  246. (dotimes (n egme-current-dice-quantity)
  247. (setq egme-roll-result (+ egme-roll-result (+ 1 (random egme-current-dice-type))))))
  248. ;; Add the modifier to the result, for the final roll
  249. (setq egme-roll-result (+ egme-roll-result egme-current-dice-modifier)))
  250. (defun egme-print-output (print-string)
  251. "This function takes a string in as an argument, and prints it's output into the current buffer, between lines highlighting it as games-master output.
  252. For normal text files, the visual braces are stored as the following strings:-
  253. egme-print-line-start
  254. egme-print-line-end
  255. If the current buffer is an org-mode document, the output is placed inside a quote block so it can retain the bonuses of export fomatting."
  256. ;; Move point to "safe" position
  257. (end-of-line)
  258. ;; Add additional newline if current line contains is non-blank
  259. (when (string-match "[:ascii:]" (thing-at-point 'line t))
  260. (newline))
  261. (newline)
  262. ;;; Output the start line
  263. ;; Check if current buffer is an org-mode file, and output accordingly
  264. (if (equal (with-current-buffer (current-buffer) major-mode) 'org-mode)
  265. ;; If an org-file, output into a GameMaster block
  266. (insert "#+BEGIN_GameMaster")
  267. ;; Else output the opening brace
  268. (insert egme-print-line-start))
  269. (newline)
  270. ;; Output text generated by egme functions
  271. (insert print-string)
  272. (newline)
  273. ;;;; Output the end line
  274. ;; Check if current buffer is an org-mode file
  275. (if (equal (with-current-buffer (current-buffer) major-mode) 'org-mode)
  276. ;; If an org-file, close the GameMaster block
  277. (insert "#+END_GameMaster")
  278. ;; Else output the closing brace brace
  279. (insert egme-print-line-end))
  280. (newline 2)
  281. t)
  282. (defun egme-random-event ()
  283. "A function for genereating unexpected events.
  284. When an oracle question is asked, this function is called. It keeps a counter in the variable egme-random-counter, which is incremented easch time this is called. Then a single 1d20 is rolled - if the result is lower than the current egme-random-counter value, then a random event is generated. A focus, action and subject are randomly selected from the lists (egme-random-event-list, egme-action-list, and egme-subject-list respectively). If a random event was generated, the counter is reset to 0.
  285. If the chosen event concerns an NPC (ignoring the New NPC event), it will display a random NPC from the current list (if available). Likewise, if the event concerns a thread, it will pick a random one from the list.
  286. This function then returns the random event text, for the calling function to pass on to for user output."
  287. ;; Increment random counter
  288. (setq egme-random-counter (1+ egme-random-counter))
  289. ;; Clear random event output text
  290. (setq egme-random-event-output nil)
  291. ;; Compare the random counter to a d20 roll
  292. (if (< (egme-calculate-dice "1d20") egme-random-counter)
  293. ;; Below batch of steps to take if
  294. (progn
  295. ;; Announce the event
  296. (setq egme-random-event-output "\n------------\nRandom Event!")
  297. ;; Pick random event from the random event focus list
  298. (setq egme-random-event-output (concat egme-random-event-output (format "\n Focus: %s" (egme-random-list-item egme-random-event-list))))
  299. ;; Check if it's an NPC event
  300. (if (string-match-p "NPC" egme-random-event-output)
  301. ;; Make sure it is /not/ a "New NPC" event
  302. (if (not (string-match-p "New NPC" egme-random-event-output))
  303. ;; Only change output if NPC list is non-nil
  304. (if (egme-parse-npc-list)
  305. (setq egme-random-event-output (concat egme-random-event-output (format "\n NPC: %s" (egme-random-list-item (egme-parse-npc-list))))))))
  306. ;; Check if it's a Thread event, add a random Thread from the list - just checks if "thread" is in the current print output variable
  307. (if (string-match-p "thread" egme-random-event-output)
  308. ;; Only change output if Thread list is non-nil
  309. (if (egme-parse-thread-list)
  310. (setq egme-random-event-output (concat egme-random-event-output (format "\n Thread: %s" (egme-random-list-item (egme-parse-thread-list)))))))
  311. ;; Add event details
  312. (setq egme-random-event-output (concat egme-random-event-output (format "\n Detail: %s" (egme-random-list-item egme-action-list))(format " / %s" (egme-random-list-item egme-subject-list))))
  313. ;; Reset the random counter
  314. (setq egme-random-counter 0)
  315. ;; Return text output
  316. egme-random-event-output)
  317. ;; Return nil if no event found
  318. nil))
  319. (defun egme-open-org-drawer ()
  320. "This function will open an org-mode drawer on the current line, if it is currently closed.
  321. Open state is determined by checking if current line is a drawer, and if the text at the end of the line is visible. If it is invisible, open the drawer with org-cycle."
  322. (interactive)
  323. (if (and (org-at-drawer-p) (invisible-p (point-at-eol)))
  324. (org-cycle)
  325. (message "No closed drawer to open.")))
  326. (defun egme-close-org-drawer ()
  327. "This function will close an org-mode drawer on the current line, if it is currently open.
  328. Open state is determined by checking if current line is a drawer, and if the text at the end of the line is visible. If it is not invisible, close the drawer with org-cycle."
  329. (interactive)
  330. (if (and (org-at-drawer-p) (not (invisible-p (point-at-eol))))
  331. (org-cycle)
  332. (message "No open drawer to close")))
  333. (defun egme-roll-dice ()
  334. "This function is for a user to generate the results from a dice roll, and output them into the current buffer.
  335. egme-get-dice is called to get user input, egme-calculate dice is used to generate the result, and egme-print-output is used to place this into the current buffer, creating new lines below the point.
  336. This function is interactively callable via M-x, and a prime input option for key-binding."
  337. ; Let user call via M-x
  338. (interactive)
  339. ; Get dice size from user
  340. (egme-get-dice)
  341. ; Check dice input was correct
  342. (if egme-current-dice
  343. ; If valid then calculate result
  344. (egme-calculate-dice)
  345. ; Else drop an error message and exit
  346. (user-error "Could not parse dice roll"))
  347. ; Print results
  348. (egme-print-output (concat (format "Rolled: %s" egme-current-dice) (format "\nResult: %s" egme-roll-result)))
  349. egme-roll-result)
  350. (defun egme-y-n-oracle ()
  351. "The basic oracle function. This will provide Yes/No answers to questions posed to the games master, and outputs the results in the current buffer in the standard games master format.
  352. The user will be asked to input a question - if the end of the current line is parsed as a question, then that will be set as the initial user input. If a quesiton is provided, it will be printed along with the results.
  353. Next, the user will be asked for the likelihood of this result. These options are stored in the list egme-probability-list, and selected via ido-completing-read. Each option is a modifier between -4 and +4, along with a basic description of the probability. This basic description will be printed along with the results.
  354. The answer is generated by rolling 1D10 and applying the chosen modifier, any result of a 6+ will be a 'Yes', anything else a 'No'. A D6 is also rolled, to see if it is an extreme answer - on a 1 it is a minor result (', but...'), and on a 2 it is a major result (', and...').
  355. The function egme-random-event is also called to see if anything unexpected occurs - any change will be added to the variable egme-oracle-output before it gets passed on for user output."
  356. (interactive)
  357. ; Reset some variables
  358. (setq egme-oracle-ouput nil)
  359. (setq egme-oracle-answer nil)
  360. (setq egme-current-question nil)
  361. ; Check if the current line contains a question (ends in a question mark, and gets everything from the last ellipses to the end of the line)
  362. (setq egme-current-line (thing-at-point 'line t))
  363. (if (string-match "\\.?[0-9A-Za-z ,:;']+\\? *$" egme-current-line)
  364. ; If that current line is a question, strip any leading ellipses or spaces, then set as pre-filled input when asking for the current question
  365. (setq egme-current-question (read-string "What is the question? " (replace-regexp-in-string " *$" "" (replace-regexp-in-string "^\\.* *" "" (match-string 0 egme-current-line)))))
  366. ; Else just ask user for question
  367. (setq egme-current-question (read-string "What is the question?: ")))
  368. ; Get probability from the user
  369. (setq egme-current-probability-choice (ido-completing-read "Probability modifier: " egme-probability-list))
  370. (string-match "[+\-]?[0-9]" egme-current-probability-choice)
  371. (setq egme-current-probability-modifier (match-string 0 egme-current-probability-choice))
  372. ; Roll dice, apply modifier
  373. (setq egme-oracle-answer-roll (+ (egme-calculate-dice "1d10") (string-to-number egme-current-probability-modifier)))
  374. (setq egme-oracle-answer-modifier (egme-calculate-dice "1d6"))
  375. ; Convert dice rolls into result text - check if modified oracle roll is 6+ ('Yes')
  376. (if (>= egme-oracle-answer-roll 6)
  377. ; If greater, then answer yes
  378. (setq egme-oracle-answer "Yes")
  379. ; Else answer no
  380. (setq egme-oracle-answer "No"))
  381. ; Apply answer modifier (if applicable)
  382. ; Add 'but' if rolled 1, add 'and' if rolled 2
  383. (cond ((eq egme-oracle-answer-modifier 1) (setq egme-oracle-answer (concat (format "%s" egme-oracle-answer) ", but...")))
  384. ((eq egme-oracle-answer-modifier 2) (setq egme-oracle-answer (concat (format "%s" egme-oracle-answer) ", and..."))))
  385. ;; Prepare output for printing
  386. ; Check if a question was input...
  387. (if (> (length egme-current-question) 0)
  388. ; ..then add quesiton to the output with results
  389. (setq egme-oracle-output (format " Question: %s\n" egme-current-question))
  390. (setq egme-oracle-output ""))
  391. ; Get probability text
  392. (string-match "[A-Za-z][A-Za-z ]*" egme-current-probability-choice)
  393. (setq egme-probability-text (match-string 0 egme-current-probability-choice))
  394. ; Add probability and results to output
  395. (setq egme-oracle-output (concat egme-oracle-output (format "Probability: %s\n------------" egme-probability-text) (format "\n Answer: %s" egme-oracle-answer)))
  396. ; Check for Random events, add any text to output
  397. (setq egme-oracle-output (concat egme-oracle-output (egme-random-event)))
  398. ; Send output string to display to user
  399. (egme-print-output egme-oracle-output))
  400. (defun egme-dashboard ()
  401. "This function will create a temporary buffer to display current details of the game state.
  402. At present this is the NPC list & Thread list, formatted 1 item per line.
  403. This function always returns nil."
  404. (interactive)
  405. ;; Update all lists from curent file
  406. (egme-parse-npc-list)
  407. (egme-parse-thread-list)
  408. ;; Remember old window split thresholds, and change current to 1 too force a horizontal split
  409. (setq egme-old-threshold split-width-threshold)
  410. (setq split-width-threshold 1)
  411. ;; Save location in current buffer
  412. (save-excursion
  413. ;; Create temporary read-only buffer and move to it for output
  414. (with-output-to-temp-buffer "GameMaster"
  415. (set-buffer "GameMaster")
  416. ;; Print header
  417. (org-mode)
  418. (insert "*eGME- Emacs GameMaster Emulator*\n---\n\n\n")
  419. ;; Check if NPC list is empty
  420. (if (not egme-npc-list)
  421. ;; Output when no list found
  422. (insert "No NPCs at present")
  423. ;; Output when NPCs found
  424. (progn
  425. (insert "NPCs\n---\n")
  426. ;; Loop through NPC list
  427. (while egme-npc-list
  428. ;; Pop the list, using each item as output followed by newline
  429. (insert (pop egme-npc-list))
  430. (newline))))
  431. (newline)
  432. (newline)
  433. ;; Check if Thread list is empty
  434. (if (not egme-thread-list)
  435. ;; Output when no list found
  436. (insert "No Threads at present")
  437. ;; Output when Threads found
  438. (progn
  439. (insert "Threads\n---\n")
  440. ;; Loop through Thread list
  441. (while egme-thread-list
  442. ;; Pop the list, using each item as output followed by newline
  443. (insert (pop egme-thread-list))
  444. (newline)))))
  445. ;; Switch to the new window, temporarily alow horizontal changes, and shrink it to fit the contents
  446. (other-window 1)
  447. (set-variable 'fit-window-to-buffer-horizontally 1)
  448. (fit-window-to-buffer)
  449. ;; Make it a bit bigger because it was shrinking too much...
  450. (enlarge-window-horizontally 7)
  451. (other-window 1))
  452. ;; Return to original split settings
  453. (setq split-width-threshold egme-old-threshold)
  454. nil)
  455. (defun egme-update-display-buffer ()
  456. "Simple function to reopen the game-state display if it is visible.
  457. This is to be called at the end of anything that changes displayed information."
  458. (if (get-buffer-window "GameMaster")
  459. (egme-dashboard)))
  460. (defun egme-add-npc (&optional npc-name)
  461. "This function adds an NPC to the current file.
  462. NPCS are stored at the end of the file, under an :NPCS: drawer. It will search backwards from the end of the file for the drawer, and create it if not found. npc-name is then inserted on at the beginning of the drawer."
  463. (interactive)
  464. ;; Ask for NPC name if nothing is passed to the function
  465. (if npc-name
  466. t
  467. (setq npc-name (read-string "New NPC name? ")))
  468. ;; save-excursion so cursor returns to users current position
  469. (save-excursion
  470. (progn
  471. (end-of-buffer)
  472. ;; Search backwards for ":NPCS:"
  473. (if (search-backward ":NPCS:" nil t)
  474. ;; The drawer has been found, check if npc-name already exists - add if missing, throw an error if it already exists
  475. (if (member npc-name (egme-parse-npc-list))
  476. (user-error "NPC is already in the list")
  477. (progn
  478. (egme-open-org-drawer)
  479. (end-of-line)
  480. (newline)
  481. (insert npc-name)))
  482. ;; The :NPCS: drawer doesn't exist, create it and add the new npc-name
  483. (insert (concat "\n:NPCS:\n" npc-name "\n:END:\n")))
  484. ;; Fold the Drawer closed
  485. (search-backward ":NPCS:" nil t)
  486. (egme-close-org-drawer)))
  487. ;; Refresh dispay buffer if open
  488. (egme-update-display-buffer)
  489. ;; Return the added npc-name
  490. npc-name)
  491. (defun egme-parse-npc-list ()
  492. "This function gets locates the NPC list in the given file, and store all the names in the list egme-npc-list
  493. If the :NPCS: drawer cannot be found, then an error message will be created, and the function returns nil. Otherwise, the generated list will be returned (in addtion to being added to egme-npc-list variable)."
  494. (setq egme-npc-list nil)
  495. (save-excursion
  496. (progn
  497. (end-of-buffer)
  498. ;; Find NPC drawer
  499. (if (search-backward ":NPCS:" nil t)
  500. ;; Drawer found, turn it into a list
  501. (progn
  502. ;; Open drawer before parsing
  503. (egme-open-org-drawer)
  504. (next-line)
  505. ;; Loop until end of drawer found
  506. (while (not (string-match "^:END:" (thing-at-point 'line t)))
  507. (progn
  508. ;; Add current element, minus final character (trailing newline), then move to next
  509. (push (substring (thing-at-point 'line t) 0 -1) egme-npc-list)
  510. (next-line)))
  511. ;; Close the drawer again
  512. (search-backward ":NPCS:" nil t)
  513. (egme-close-org-drawer))
  514. ;; No NPC drawer found
  515. (message "No NPC list in current file"))))
  516. ; Return list contents (or nil if nothing is found)
  517. egme-npc-list)
  518. (defun egme-delete-npc ()
  519. "This function deletes an NPC from the active list.
  520. The NPC list is parsed, and all are offered as options with ido-completing-read. This is then found within the NPC list drawer, and the chosen option is deleted. This function then re-parses and returns the updated list."
  521. (interactive)
  522. ;; Check NPC list has been created
  523. (if (egme-parse-npc-list)
  524. ;; Parse latest NPC list, and get user input for which to delete
  525. (setq deleting-npc (ido-completing-read "NPC to delete? " (egme-parse-npc-list)))
  526. ;; Throw an error if nothing found
  527. (user-error "No NPCs in current file"))
  528. (save-excursion
  529. (progn
  530. ;; Go to end of buffer, then look backwards for the NPC list and open it
  531. (end-of-buffer)
  532. (search-backward ":NPCS:" nil t)
  533. (egme-open-org-drawer)
  534. ;; Search forwards for the selected deletion
  535. (search-forward deleting-npc nil t)
  536. (beginning-of-line)
  537. ;; Delete line and remove the newline to avoid a blank entry
  538. (kill-line)
  539. (kill-line)
  540. ;; Close the NPC drawer
  541. (search-backward ":NPCS:" nil t)
  542. (egme-close-org-drawer)))
  543. ;; Refresh dispay buffer if open
  544. (egme-update-display-buffer)
  545. ;; Return updated list
  546. (egme-parse-npc-list))
  547. (defun egme-add-thread (&optional new-thread)
  548. "This function adds a Thread to the current file.
  549. Threads are stored at the end of the file, under an :THREADS: drawer. It will search backwards from the end of the file for the drawer, and create it if not found. new-thread is then inserted on at the beginning of the drawer."
  550. (interactive)
  551. ;; Ask for Thread if nothing is passed to the function
  552. (if new-thread
  553. t
  554. (setq new-thread (read-string "New Thread description? ")))
  555. ;; save-excursion so cursor returns to users current position
  556. (save-excursion
  557. (progn
  558. (end-of-buffer)
  559. ;; Search backwards for ":THREADS:"
  560. (if (search-backward ":THREADS:" nil t)
  561. ;; The drawer has been found, check if new-thread already exists - add if missing, throw an error if it already exists
  562. (if (member new-thread (egme-parse-thread-list))
  563. (user-error "Thread is already in the list")
  564. (progn
  565. (egme-open-org-drawer)
  566. (end-of-line)
  567. (newline)
  568. (insert new-thread)))
  569. ;; The :THREADS: drawer doesn't exist, create it and add the new-thread
  570. (insert (concat "\n:THREADS:\n" new-thread "\n:END:\n")))
  571. ;; Fold the Drawer closed
  572. (search-backward ":THREADS:" nil t)
  573. (egme-close-org-drawer)))
  574. ;; Refresh dispay buffer if open
  575. (egme-update-display-buffer)
  576. ;; Return the added new-thread
  577. new-thread)
  578. (defun egme-parse-thread-list ()
  579. "This function gets locates the Thread list in the given file, and store all the items in the list egme-thread-list
  580. If the :THREADS: drawer cannot be found, then an error message will be created, and the function returns nil. Otherwise, the generated list will be returned (in addtion to being added to egme-thread-list variable)."
  581. (setq egme-thread-list nil)
  582. (save-excursion
  583. (progn
  584. (end-of-buffer)
  585. ;; Find Thread drawer
  586. (if (search-backward ":THREADS:" nil t)
  587. ;; Drawer found, turn it into a list
  588. (progn
  589. ;; Open drawer before parsing
  590. (egme-open-org-drawer)
  591. (next-line)
  592. ;; Loop until end of drawer found
  593. (while (not (string-match "^:END:" (thing-at-point 'line t)))
  594. (progn
  595. ;; Add current element, minus final character (trailing newline), then move to next
  596. (push (substring (thing-at-point 'line t) 0 -1) egme-thread-list)
  597. (next-line)))
  598. ;; Close the drawer again
  599. (search-backward ":THREADS:" nil t)
  600. (egme-close-org-drawer))
  601. ;; No THREADS drawer found
  602. (message "No Thread list in current file"))))
  603. ; Return list contents (or nil if nothing is found)
  604. egme-thread-list)
  605. (defun egme-delete-thread ()
  606. "This function deletes a Thread from the active list.
  607. The Thread list is parsed, and all are offered as options with ido-completing-read. This is then found within the Thread list drawer, and the chosen option is deleted. This function then re-parses and returns the updated list."
  608. (interactive)
  609. ;; Check Thread list has been created
  610. (if (egme-parse-thread-list)
  611. ;; Parse latest Thread list, and get user input for which to delete
  612. (setq deleting-thread (ido-completing-read "Thread to delete? " (egme-parse-thread-list)))
  613. ;; Throw an error if nothing found
  614. (user-error "No Threads in current file"))
  615. (save-excursion
  616. (progn
  617. ;; Go to end of buffer, then look backwards for the Thread list and open it
  618. (end-of-buffer)
  619. (search-backward ":THREADS:" nil t)
  620. (egme-open-org-drawer)
  621. ;; Search forwards for the selected deletion
  622. (search-forward deleting-thread nil t)
  623. (beginning-of-line)
  624. ;; Delete line and remove the newline to avoid a blank entry
  625. (kill-line)
  626. (kill-line)
  627. ;; Close the Thread drawer
  628. (search-backward ":THREADS:" nil t)
  629. (egme-close-org-drawer)))
  630. ;; Refresh dispay buffer if open
  631. (egme-update-display-buffer)
  632. ;; Return updated list
  633. (egme-parse-thread-list))
  634. (define-prefix-command 'egme-map)
  635. (define-key mode-specific-map (kbd "C-g") 'egme-map)
  636. (define-key egme-map (kbd "r") 'egme-roll-dice)
  637. (define-key egme-map (kbd "q") 'egme-y-n-oracle)
  638. (define-key egme-map (kbd "n") 'egme-add-npc)
  639. (define-key egme-map (kbd "N") 'egme-delete-npc)
  640. (define-key egme-map (kbd "t") 'egme-add-thread)
  641. (define-key egme-map (kbd "T") 'egme-delete-thread)
  642. (define-key egme-map (kbd "d") 'egme-dashboard)