game-master.scm 8.6 KB

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