player.scm 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. ;;; Mudsync --- Live hackable MUD
  2. ;;; Copyright © 2016 Christine Lemmer-Webber <cwebber@dustycloud.org>
  3. ;;;
  4. ;;; This file is part of Mudsync.
  5. ;;;
  6. ;;; Mudsync is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or
  9. ;;; (at your option) any later version.
  10. ;;;
  11. ;;; Mudsync is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. ;;; General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with Mudsync. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (mudsync player)
  19. #:use-module (mudsync command)
  20. #:use-module (mudsync gameobj)
  21. #:use-module (mudsync game-master)
  22. #:use-module (mudsync parser)
  23. #:use-module (8sync actors)
  24. #:use-module (8sync agenda)
  25. #:use-module (8sync rmeta-slot)
  26. #:use-module (ice-9 control)
  27. #:use-module (ice-9 format)
  28. #:use-module (ice-9 match)
  29. #:use-module (oop goops)
  30. #:use-module (srfi srfi-1)
  31. #:use-module (srfi srfi-9)
  32. #:export (<player>))
  33. ;;; Players
  34. ;;; =======
  35. (define-class <player> (<gameobj>)
  36. (username #:init-keyword #:username
  37. #:getter player-username)
  38. (self-commands #:allocation #:each-subclass
  39. #:init-thunk
  40. (build-commands
  41. (("inventory" "inv" "i") ((empty-command cmd-inventory)))
  42. ("help" ((empty-command cmd-help)))))
  43. (actions #:allocation #:each-subclass
  44. #:init-thunk
  45. (build-actions
  46. (init player-init)
  47. (handle-input player-handle-input)
  48. (tell player-tell)
  49. (disconnect-self-destruct player-disconnect-self-destruct)
  50. (cmd-inventory player-cmd-inventory)
  51. (cmd-help player-cmd-help))))
  52. ;;; player message handlers
  53. (define (player-init player message)
  54. ;; Look around the room we're in
  55. (<- (gameobj-loc player) 'look-room))
  56. (define* (player-handle-input player message #:key input)
  57. (define split-input (split-verb-and-rest input))
  58. (define input-verb (car split-input))
  59. (define input-rest (cdr split-input))
  60. (define command-candidates
  61. (player-gather-command-handlers player input-verb))
  62. (define winner
  63. (find-command-winner command-candidates input-rest))
  64. (match winner
  65. ((cmd-action winner-id message-args)
  66. (apply <- winner-id cmd-action message-args))
  67. (#f
  68. (<- (gameobj-gm player) 'write-home
  69. #:text "Sorry, I didn't understand that? (type \"help\" for common commands)\n"))))
  70. (define* (player-tell player message #:key text)
  71. (<- (gameobj-gm player) 'write-home
  72. #:text text))
  73. (define (player-disconnect-self-destruct player message)
  74. "Action routine for being told to disconnect and self destruct."
  75. (define loc (gameobj-loc player))
  76. (when loc
  77. (<- loc 'tell-room
  78. #:exclude (actor-id player)
  79. #:text (format #f "~a disappears in a puff of entropy!\n"
  80. (slot-ref player 'name))))
  81. (gameobj-self-destruct player))
  82. (define (player-cmd-inventory player message)
  83. "Display the inventory for the player"
  84. (define inv-names
  85. (map
  86. (lambda (inv-item)
  87. (mbody-val (<-wait inv-item 'get-name)))
  88. (gameobj-occupants player)))
  89. (define text-to-show
  90. (if (eq? inv-names '())
  91. "You aren't carrying anything.\n"
  92. `((p "You are carrying:")
  93. (ul ,(map (lambda (item-name)
  94. `(li ,item-name))
  95. inv-names)))))
  96. (<- (actor-id player) 'tell #:text text-to-show))
  97. (define (player-cmd-help player message)
  98. (<- (actor-id player) 'tell
  99. #:text '((strong "** Mudsync Help **")(br)
  100. (p "You're playing Mudsync, a multiplayer text-adventure. "
  101. "Type different commands to interact with your surroundings "
  102. "and other players.")
  103. (p "Some common commands:"
  104. (ul (li (strong "say <message>") " -- "
  105. "Chat with other players in the same room. "
  106. "(Also aliased to the " (b "\"") " character.)")
  107. (li (strong "look") " -- "
  108. "Look around the room you're in.")
  109. (li (strong "look [at] <object>") " -- "
  110. "Examine a particular object.")
  111. (li (strong "go <exit>") " -- "
  112. "Move to another room in <exit> direction.")))
  113. (p "Different objects can be interacted with in different ways. "
  114. "For example, if there's a bell in the same room as you, "
  115. "you might try typing " (em "ring bell")
  116. " and see what happens."))))
  117. ;;; Command handling
  118. ;;; ================
  119. ;; @@: Hard to know whether this should be in player.scm or here...
  120. ;; @@: This could be more efficient as a stream...!?
  121. (define (player-gather-command-handlers player verb)
  122. (define player-loc
  123. (let ((result (gameobj-loc player)))
  124. (if result
  125. result
  126. (throw 'player-has-no-location
  127. "Player ~a has no location! How'd that happen?\n"
  128. #:player-id (actor-id player)))))
  129. ;; Ask the room for its commands
  130. (define room-dom-commands
  131. ;; TODO: Map room id and sort
  132. (mbody-receive (_ #:key commands)
  133. (<-wait player-loc 'get-container-dom-commands
  134. #:verb verb)
  135. commands))
  136. (define room-sub-commands
  137. ;; TODO: Map room id and sort
  138. (mbody-receive (_ #:key commands)
  139. (<-wait player-loc 'get-container-sub-commands
  140. #:verb verb)
  141. commands))
  142. ;; All the co-occupants of the room (not including ourself)
  143. (define co-occupants
  144. (remove
  145. (lambda (x) (equal? x (actor-id player)))
  146. (mbody-val (<-wait player-loc 'get-occupants))))
  147. ;; @@: There's a race condition here if someone leaves the room
  148. ;; during this, heh...
  149. ;; I'm not sure it can be solved, but "lag" on the race can be
  150. ;; reduced maybe?
  151. ;; Get all the co-occupants' commands
  152. (define co-occupant-commands
  153. (fold
  154. (lambda (co-occupant prev)
  155. (mbody-receive (_ #:key commands goes-by)
  156. (<-wait co-occupant 'get-commands
  157. #:verb verb)
  158. (append
  159. (map (lambda (command)
  160. (list command goes-by co-occupant))
  161. commands)
  162. prev)))
  163. '()
  164. co-occupants))
  165. ;; Append our own command handlers
  166. (define our-commands
  167. (class-rmeta-ref (class-of player) 'self-commands verb
  168. #:dflt '()))
  169. ;; Append our inventory's relevant command handlers
  170. (define inv-items
  171. (gameobj-occupants player))
  172. (define inv-item-commands
  173. (fold
  174. (lambda (inv-item prev)
  175. (mbody-receive (_ #:key commands goes-by)
  176. (<-wait inv-item 'get-contained-commands
  177. #:verb verb)
  178. (append
  179. (map (lambda (command)
  180. (list command goes-by inv-item))
  181. commands)
  182. prev)))
  183. '()
  184. inv-items))
  185. ;; Now return a big ol sorted list of ((actor-id . command))
  186. (append
  187. (sort-commands-append-actor room-dom-commands
  188. player-loc '()) ; room doesn't go by anything
  189. (sort-commands-multi-actors co-occupant-commands)
  190. (sort-commands-append-actor our-commands
  191. (actor-id player) '()) ; nor does player
  192. (sort-commands-multi-actors inv-item-commands)
  193. (sort-commands-append-actor room-sub-commands
  194. player-loc '())))
  195. (define (sort-commands-append-actor commands actor-id goes-by)
  196. (sort-commands-multi-actors
  197. (map (lambda (command) (list command goes-by actor-id)) commands)))
  198. (define (sort-commands-multi-actors actors-and-commands)
  199. (sort
  200. actors-and-commands
  201. (lambda (x y)
  202. (> (command-priority (car x))
  203. (command-priority (car y))))))
  204. (define (find-command-winner sorted-candidates line)
  205. "Find a command winner from a sorted list of candidates"
  206. ;; A cache of results from matchers we've already seen
  207. ;; TODO: fill in this cache. This is a *critical* optimization!
  208. (define matcher-cache '())
  209. (call/ec
  210. (lambda (return)
  211. (for-each
  212. (match-lambda
  213. ((command actor-goes-by actor-id)
  214. (let* ((matcher (command-matcher command))
  215. (matched (matcher line)))
  216. (if (and matched
  217. ;; Great, it matched, but does it also pass
  218. ;; should-handle?
  219. (apply (command-should-handle command)
  220. actor-goes-by
  221. matched)) ; matched is kwargs if truthy
  222. (return (list (command-action command)
  223. actor-id matched))
  224. #f))))
  225. sorted-candidates)
  226. #f)))