Split mudsync.scm out into multiple files
[mudsync.git] / mudsync / game-master.scm
diff --git a/mudsync/game-master.scm b/mudsync/game-master.scm
new file mode 100644 (file)
index 0000000..bb8756a
--- /dev/null
@@ -0,0 +1,204 @@
+;;; Mudsync --- Live hackable MUD
+;;; Copyright © 2016 Christopher Allan Webber <cwebber@dustycloud.org>
+;;;
+;;; This file is part of Mudsync.
+;;;
+;;; Mudsync 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; either version 3 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; Mudsync 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.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Mudsync.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (mudsync game-master)
+  #:use-module (mudsync room)
+  #:use-module (mudsync player)
+  #:use-module (mudsync networking)
+  #:use-module (8sync systems actors)
+  #:use-module (8sync agenda)
+  #:use-module (oop goops)
+  #:use-module (ice-9 match)
+  #:export (<game-master>
+            make-default-room-conn-handler))
+
+;;; The game master!  Runs the world.
+;;; =================================
+
+(define-class <game-master> (<actor>)
+  ;; Directory of "special" objects.
+  (special-dir #:init-thunk make-hash-table
+               #:getter gm-special-dir)
+
+  ;; Room directory.  Room symbols to locations.
+  (room-dir #:init-thunk make-hash-table
+            #:getter gm-room-dir)
+
+  ;; A mapping of client ids to in-game actors
+  ;; and a reverse ;p
+  (client-dir #:init-thunk make-hash-table
+              #:getter gm-client-dir)
+  (reverse-client-dir #:init-thunk make-hash-table
+                      #:getter gm-reverse-client-dir)
+
+  ;; Network manager
+  (network-manager #:accessor gm-network-manager
+                   #:init-value #f)
+
+  ;; How we get a new connection acclimated to the system
+  (new-conn-handler #:accessor gm-new-conn-handler
+                    #:init-keyword #:new-conn-handler)
+
+  (message-handler
+   #:init-value
+   (make-action-dispatch
+    (init-world (wrap-apply gm-init-world))
+    (client-input (wrap-apply gm-handle-client-input))
+    (lookup-room (wrap-apply gm-lookup-room))
+    (new-client (wrap-apply gm-new-client))
+    (write-home (wrap-apply gm-write-home)))))
+
+
+;;; .. begin world init stuff ..
+
+(define (gm-init-world gm message)
+  ;; Load database
+  ;;  TODO
+
+  ;; Init basic rooms / structure
+  (gm-init-rooms gm (message-ref message 'room-spec))
+
+  ;; Restore database-based actors
+  ;;  TODO
+
+  ;; Set up the network
+  (gm-setup-network gm))
+
+(define (gm-init-rooms gm rooms-spec)
+  "Initialize the prebuilt rooms"
+  ;; @@: Would it be nicer to just allow passing in
+  ;;     #:exits to the room spec itself?
+  (define (exit-from-spec exit-spec)
+    "Take room exits syntax from the spec, turn it into exits"
+    (match exit-spec
+      ((name to-symbol desc)
+       (make <exit>
+         #:name name
+         #:to-symbol to-symbol
+         #:desc desc))))
+
+  (define rooms
+    (map
+     (match-lambda
+       ((room-symbol room-class
+                     room-args ...
+                     (room-exits ...))
+        ;; initialize the room
+        (let ((room
+               (apply create-actor* gm room-class "room"
+                      #:gm (actor-id gm)
+                      #:exits (map exit-from-spec room-exits)
+                      room-args)))
+          ;; register the room
+          (hash-set! (gm-room-dir gm) room-symbol room)
+          ;; pass it back to the map
+          room)))
+     rooms-spec))
+
+  ;; now wire up all the exits
+  (for-each
+   (lambda (room)
+     (format #t "Wiring up ~s...\n" (address->string room))
+     (<-wait gm room 'wire-exits!))
+   rooms))
+
+
+(define (gm-setup-network gm)
+  ;; Create a default network manager if none available
+  (set! (gm-network-manager gm)
+        (create-actor* gm <network-manager> "netman"
+                       #:send-input-to (actor-id gm)))
+
+  ;; TODO: Add host and port options
+  (<-wait gm (gm-network-manager gm) 'start-listening))
+
+(define (gm-setup-database gm)
+  'TODO)
+
+;;; .. end world init stuff ...
+
+(define-mhandler (gm-new-client actor message client)
+  ;; @@: Maybe more indirection than needed for this
+  ((gm-new-conn-handler actor) actor client))
+
+
+(define (gm-handle-client-input actor message)
+  "Handle input from a client."
+  (define client-id (message-ref message 'client))
+  (define input (message-ref message 'data))
+  (format #t "From ~s: ~s\n" client-id input)
+  (<- actor (gm-network-manager actor) 'send-to-client
+      #:client client-id
+      #:data "Thanks, we got it!\n"))
+
+(define-mhandler (gm-lookup-room actor message symbol)
+  (define room-id
+    (slot-ref (gm-room-dir actor) symbol))
+  (<-reply actor message room-id))
+
+(define-mhandler (gm-write-home actor message text)
+  (define client-id (hash-ref (gm-reverse-client-dir actor)
+                              (message-from message)))
+  (<- actor (gm-network-manager actor) 'send-to-client
+      #:client client-id
+      #:data text))
+
+
+;;; GM utilities
+
+(define (gm-register-client! gm client-id player)
+  (hash-set! (gm-client-dir gm) client-id player)
+  (hash-set! (gm-reverse-client-dir gm) player client-id))
+
+(define (gm-unregister-client! gm client-id)
+  "Remove a connection/player combo and ask them to self destruct"
+  (match (hash-remove! (gm-client-dir gm) client-id)  ; Remove from our client dir
+    ((_ . player-id)
+     ;; Remove from reverse table too
+     (hash-remove! (gm-reverse-client-dir gm) client-id)
+     ;; Destroy player 
+     (<- gm player-id 'destroy-self))
+    (#f (throw 'no-client-to-unregister
+               "Can't unregister a client that doesn't exist?"
+               client-id))))
+
+;;; An easy default
+
+(define (make-default-room-conn-handler default-room)
+  "Make a handler for a GM that dumps people in a default room
+with an anonymous persona"
+  (let ((count 0))
+    (lambda (gm client-id)
+      (define guest-name (string-append "Guest-"
+                                        (number->string count)))
+      (define room-id
+        (hash-ref (gm-room-dir gm) default-room))
+      ;; create and register the player
+      (define player
+        (create-actor* gm <player> "player"
+                       #:username guest-name
+                       #:gm (actor-id gm)
+                       #:client client-id))
+
+      ;; Register the player in our database of players -> connections
+      (gm-register-client! gm client-id player)
+      ;; Dump the player into the default room
+      (<-wait gm player 'set-loc! #:id room-id)
+      ;; Initialize the player
+      (<- gm player 'init))))
+