;;; Mudsync --- Live hackable MUD ;;; Copyright © 2016 Christopher Allan Webber ;;; ;;; 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 . (define-module (mudsync game-master) #:use-module (mudsync room) #:use-module (mudsync networking) #:use-module (8sync systems actors) #:use-module (8sync agenda) #:use-module (oop goops) #:use-module (ice-9 match) #:export ( make-default-room-conn-handler)) ;;; The game master! Runs the world. ;;; ================================= (define-class () ;; 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 #: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 "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)) ;; Look up player (define player (hash-ref (gm-client-dir actor) client-id)) ;; debugging (format #t "DEBUG: From ~s: ~s\n" client-id input) (<- actor player 'handle-input #:input input) ;; TODO: Remove this shortly (<- 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) (set! count (+ count 1)) (let* ((guest-name (string-append "Guest-" (number->string count))) (room-id (hash-ref (gm-room-dir gm) default-room)) ;; create and register the player (player (create-actor* gm (@@ (mudsync 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)))))