game-master.scm 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. ;;; Mudsync --- Live hackable MUD
  2. ;;; Copyright © 2016 Christopher Allan 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 game-master)
  19. #:use-module (mudsync networking)
  20. #:use-module (8sync systems actors)
  21. #:use-module (8sync agenda)
  22. #:use-module (oop goops)
  23. #:use-module (ice-9 match)
  24. #:use-module (srfi srfi-26)
  25. #:export (<game-master>
  26. make-default-room-conn-handler))
  27. ;;; The game master! Runs the world.
  28. ;;; =================================
  29. (define-class <game-master> (<actor>)
  30. ;; Directory of "special" objects.
  31. (special-dir #:init-thunk make-hash-table
  32. #:getter gm-special-dir)
  33. ;; A mapping of client ids to in-game actors
  34. ;; and a reverse ;p
  35. (client-dir #:init-thunk make-hash-table
  36. #:getter gm-client-dir)
  37. (reverse-client-dir #:init-thunk make-hash-table
  38. #:getter gm-reverse-client-dir)
  39. ;; Network manager
  40. (network-manager #:getter gm-network-manager
  41. #:init-value #f)
  42. ;; How we get a new connection acclimated to the system
  43. (new-conn-handler #:getter gm-new-conn-handler
  44. #:init-keyword #:new-conn-handler)
  45. (message-handler
  46. #:init-value
  47. (make-action-dispatch
  48. (init-world (wrap-apply gm-init-world))
  49. (client-input (wrap-apply gm-handle-client-input))
  50. (lookup-special (wrap-apply gm-lookup-special))
  51. (new-client (wrap-apply gm-new-client))
  52. (write-home (wrap-apply gm-write-home))
  53. (client-closed (wrap-apply gm-client-closed))
  54. (inject-special! (wrap-apply gm-inject-special!)))))
  55. ;;; .. begin world init stuff ..
  56. (define (gm-init-world gm message)
  57. ;; Load database
  58. ;; TODO
  59. ;; Init basic rooms / structure
  60. (gm-init-game-spec gm (message-ref message 'game-spec))
  61. ;; Restore database-based actors
  62. ;; TODO
  63. ;; Set up the network
  64. (gm-setup-network gm))
  65. ;; @@: If you change this code, update gm-inject-special! if appropriate.
  66. (define (gm-init-game-spec gm game-spec)
  67. "Initialize the prebuilt special objects"
  68. (define set-locs '())
  69. (define specials
  70. (map
  71. (match-lambda
  72. ((symbol class loc args ...)
  73. ;; initialize the special object
  74. (let ((special-obj
  75. (apply create-actor* gm class
  76. ;; set cookie to be the object's symbol
  77. (symbol->string symbol)
  78. #:gm (actor-id gm)
  79. args)))
  80. ;; register the object
  81. (hash-set! (gm-special-dir gm) symbol special-obj)
  82. ;; Give ourselves an instruction to set the location
  83. (set! set-locs (cons (cons special-obj loc) set-locs))
  84. ;; pass it back to the map
  85. special-obj)))
  86. game-spec))
  87. ;; Set all initial locations
  88. (for-each
  89. (match-lambda
  90. ((special-obj . loc)
  91. (if loc
  92. (<-wait gm special-obj 'set-loc!
  93. #:loc (hash-ref (gm-special-dir gm) loc)))))
  94. set-locs)
  95. ;; now init all the objects
  96. (for-each
  97. (lambda (special-obj)
  98. (format #t "Initializing ~s...\n" (address->string special-obj))
  99. (<-wait gm special-obj 'init))
  100. specials))
  101. (define (gm-setup-network gm)
  102. ;; Create a default network manager if none available
  103. (slot-set! gm 'network-manager
  104. (create-actor* gm <network-manager> "netman"
  105. #:send-input-to (actor-id gm)))
  106. ;; TODO: Add host and port options
  107. (<-wait gm (gm-network-manager gm) 'start-listening))
  108. (define (gm-setup-database gm)
  109. 'TODO)
  110. ;;; .. end world init stuff ...
  111. (define-mhandler (gm-new-client actor message client)
  112. ;; @@: Maybe more indirection than needed for this
  113. ((gm-new-conn-handler actor) actor client))
  114. (define (gm-handle-client-input actor message)
  115. "Handle input from a client."
  116. (define client-id (message-ref message 'client))
  117. (define input (message-ref message 'data))
  118. ;; Look up player
  119. (define player (hash-ref (gm-client-dir actor) client-id))
  120. ;; debugging
  121. (format #t "DEBUG: From ~s: ~s\n" client-id input)
  122. (<- actor player 'handle-input
  123. #:input input))
  124. (define-mhandler (gm-lookup-special actor message symbol)
  125. (<-reply actor message
  126. #:val (hash-ref (slot-ref actor 'special-dir) symbol)))
  127. (define-mhandler (gm-write-home actor message text)
  128. (define client-id (hash-ref (gm-reverse-client-dir actor)
  129. (message-from message)))
  130. (<- actor (gm-network-manager actor) 'send-to-client
  131. #:client client-id
  132. #:data text))
  133. (define-mhandler (gm-client-closed gm message client)
  134. ;; Do we have this client registered to an actor? Get the id if so.
  135. (define actor-id (hash-ref (gm-client-dir gm) client))
  136. ;; Have the actor appropriately disappear / be removed from its
  137. ;; room, if we have one.
  138. ;; (In some games, if the user never connected)
  139. (when actor-id
  140. (<-wait gm actor-id 'disconnect-self-destruct)
  141. ;; Unregister from the client directories.
  142. (gm-unregister-client! gm client)))
  143. (define-mhandler (gm-inject-special! gm message
  144. special-symbol gameobj-spec)
  145. "Inject, possiibly replacing the original, special symbol
  146. using the gameobj-spec."
  147. (define existing-obj
  148. (hash-ref (slot-ref gm 'special-dir) special-symbol))
  149. ;; There's a lot of overlap here with gm-init-game-spec.
  150. ;; We could try to union them? It seemed hard last time I looked,
  151. ;; because things need to run in a different order.
  152. (match gameobj-spec
  153. (((? (cut eq? <> special-symbol) symbol) class loc args ...)
  154. ;; initialize the special object
  155. (let ((special-obj
  156. (apply create-actor* gm class
  157. ;; set cookie to be the object's symbol
  158. (symbol->string symbol)
  159. #:gm (actor-id gm)
  160. args)))
  161. ;; Set the location
  162. (<-wait gm special-obj 'set-loc!
  163. #:loc (hash-ref (gm-special-dir gm) loc))
  164. ;; Initialize the object, and depending on if an object
  165. ;; already exists with this info, ask it to coordinate
  166. ;; replacing with the existing object.
  167. (if existing-obj
  168. (<-wait gm special-obj 'init #:replace existing-obj)
  169. (<-wait gm special-obj 'init))
  170. ;; Register the object
  171. (hash-set! (gm-special-dir gm) symbol special-obj)
  172. ;; Destroy the original, if it exists.
  173. (if existing-obj
  174. (<- gm existing-obj 'self-destruct #:why 'replaced))))))
  175. ;;; GM utilities
  176. (define (gm-register-client! gm client-id player)
  177. (hash-set! (gm-client-dir gm) client-id player)
  178. (hash-set! (gm-reverse-client-dir gm) player client-id))
  179. (define* (gm-unregister-client! gm client-id #:optional destroy-player)
  180. "Remove a connection/player combo and ask them to self destruct"
  181. (match (hash-remove! (gm-client-dir gm) client-id) ; Remove from our client dir
  182. ((_ . player-id)
  183. ;; Remove from reverse table too
  184. (hash-remove! (gm-reverse-client-dir gm) client-id)
  185. ;; Destroy player
  186. (if destroy-player
  187. (<- gm player-id 'self-destruct)))
  188. (#f (throw 'no-client-to-unregister
  189. "Can't unregister a client that doesn't exist?"
  190. client-id))))
  191. ;;; An easy default
  192. (define (make-default-room-conn-handler default-room)
  193. "Make a handler for a GM that dumps people in a default room
  194. with an anonymous persona"
  195. (let ((count 0))
  196. (lambda (gm client-id)
  197. (set! count (+ count 1))
  198. (let* ((guest-name (string-append "Guest-"
  199. (number->string count)))
  200. (room-id
  201. (hash-ref (gm-special-dir gm) default-room))
  202. ;; create and register the player
  203. (player
  204. (create-actor* gm (@@ (mudsync player) <player>) "player"
  205. #:name guest-name
  206. #:gm (actor-id gm)
  207. #:client client-id)))
  208. ;; Register the player in our database of players -> connections
  209. (gm-register-client! gm client-id player)
  210. ;; Dump the player into the default room
  211. (<-wait gm player 'set-loc! #:loc room-id)
  212. ;; Initialize the player
  213. (<-wait gm player 'init)
  214. (<- gm room-id 'tell-room
  215. #:text (format #f "You see ~a materialize out of thin air!\n"
  216. guest-name)
  217. #:exclude player)))))