f839931002fe663cabe8c62c2458be623407a4f7
[mudsync.git] / mudsync / game-master.scm
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
19 (define-module (mudsync game-master)
20   #:use-module (mudsync room)
21   #:use-module (mudsync networking)
22   #:use-module (8sync systems actors)
23   #:use-module (8sync agenda)
24   #:use-module (oop goops)
25   #:use-module (ice-9 match)
26   #:export (<game-master>
27             make-default-room-conn-handler))
28
29 ;;; The game master!  Runs the world.
30 ;;; =================================
31
32 (define-class <game-master> (<actor>)
33   ;; Directory of "special" objects.
34   (special-dir #:init-thunk make-hash-table
35                #:getter gm-special-dir)
36
37   ;; Room directory.  Room symbols to locations.
38   (room-dir #:init-thunk make-hash-table
39             #:getter gm-room-dir)
40
41   ;; A mapping of client ids to in-game actors
42   ;; and a reverse ;p
43   (client-dir #:init-thunk make-hash-table
44               #:getter gm-client-dir)
45   (reverse-client-dir #:init-thunk make-hash-table
46                       #:getter gm-reverse-client-dir)
47
48   ;; Network manager
49   (network-manager #:accessor gm-network-manager
50                    #:init-value #f)
51
52   ;; How we get a new connection acclimated to the system
53   (new-conn-handler #:accessor gm-new-conn-handler
54                     #:init-keyword #:new-conn-handler)
55
56   (message-handler
57    #:init-value
58    (make-action-dispatch
59     (init-world (wrap-apply gm-init-world))
60     (client-input (wrap-apply gm-handle-client-input))
61     (lookup-room (wrap-apply gm-lookup-room))
62     (new-client (wrap-apply gm-new-client))
63     (write-home (wrap-apply gm-write-home)))))
64
65
66 ;;; .. begin world init stuff ..
67
68 (define (gm-init-world gm message)
69   ;; Load database
70   ;;  TODO
71
72   ;; Init basic rooms / structure
73   (gm-init-rooms gm (message-ref message 'room-spec))
74
75   ;; Restore database-based actors
76   ;;  TODO
77
78   ;; Set up the network
79   (gm-setup-network gm))
80
81 (define (gm-init-rooms gm rooms-spec)
82   "Initialize the prebuilt rooms"
83   ;; @@: Would it be nicer to just allow passing in
84   ;;     #:exits to the room spec itself?
85   (define (exit-from-spec exit-spec)
86     "Take room exits syntax from the spec, turn it into exits"
87     (match exit-spec
88       ((name to-symbol desc)
89        (make <exit>
90          #:name name
91          #:to-symbol to-symbol
92          #:desc desc))))
93
94   (define rooms
95     (map
96      (match-lambda
97        ((room-symbol room-class
98                      room-args ...
99                      (room-exits ...))
100         ;; initialize the room
101         (let ((room
102                (apply create-actor* gm room-class "room"
103                       #:gm (actor-id gm)
104                       #:exits (map exit-from-spec room-exits)
105                       room-args)))
106           ;; register the room
107           (hash-set! (gm-room-dir gm) room-symbol room)
108           ;; pass it back to the map
109           room)))
110      rooms-spec))
111
112   ;; now wire up all the exits
113   (for-each
114    (lambda (room)
115      (format #t "Wiring up ~s...\n" (address->string room))
116      (<-wait gm room 'wire-exits!))
117    rooms))
118
119
120 (define (gm-setup-network gm)
121   ;; Create a default network manager if none available
122   (set! (gm-network-manager gm)
123         (create-actor* gm <network-manager> "netman"
124                        #:send-input-to (actor-id gm)))
125
126   ;; TODO: Add host and port options
127   (<-wait gm (gm-network-manager gm) 'start-listening))
128
129 (define (gm-setup-database gm)
130   'TODO)
131
132 ;;; .. end world init stuff ...
133
134 (define-mhandler (gm-new-client actor message client)
135   ;; @@: Maybe more indirection than needed for this
136   ((gm-new-conn-handler actor) actor client))
137
138
139 (define (gm-handle-client-input actor message)
140   "Handle input from a client."
141   (define client-id (message-ref message 'client))
142   (define input (message-ref message 'data))
143   ;; Look up player
144   (define player (hash-ref (gm-client-dir actor) client-id))
145
146   ;; debugging
147   (format #t "DEBUG: From ~s: ~s\n" client-id input)
148
149   (<- actor player 'handle-input
150       #:input input)
151
152   ;; TODO: Remove this shortly
153   (<- actor (gm-network-manager actor) 'send-to-client
154       #:client client-id
155       #:data "Thanks, we got it!\n"))
156
157 (define-mhandler (gm-lookup-room actor message symbol)
158   (define room-id
159     (slot-ref (gm-room-dir actor) symbol))
160   (<-reply actor message room-id))
161
162 (define-mhandler (gm-write-home actor message text)
163   (define client-id (hash-ref (gm-reverse-client-dir actor)
164                               (message-from message)))
165   (<- actor (gm-network-manager actor) 'send-to-client
166       #:client client-id
167       #:data text))
168
169
170 ;;; GM utilities
171
172 (define (gm-register-client! gm client-id player)
173   (hash-set! (gm-client-dir gm) client-id player)
174   (hash-set! (gm-reverse-client-dir gm) player client-id))
175
176 (define (gm-unregister-client! gm client-id)
177   "Remove a connection/player combo and ask them to self destruct"
178   (match (hash-remove! (gm-client-dir gm) client-id)  ; Remove from our client dir
179     ((_ . player-id)
180      ;; Remove from reverse table too
181      (hash-remove! (gm-reverse-client-dir gm) client-id)
182      ;; Destroy player 
183      (<- gm player-id 'destroy-self))
184     (#f (throw 'no-client-to-unregister
185                "Can't unregister a client that doesn't exist?"
186                client-id))))
187
188 ;;; An easy default
189
190 (define (make-default-room-conn-handler default-room)
191   "Make a handler for a GM that dumps people in a default room
192 with an anonymous persona"
193   (let ((count 0))
194     (lambda (gm client-id)
195       (set! count (+ count 1))
196       (let* ((guest-name (string-append "Guest-"
197                                         (number->string count)))
198              (room-id
199               (hash-ref (gm-room-dir gm) default-room))
200              ;; create and register the player
201              (player
202               (create-actor* gm (@@ (mudsync player) <player>) "player"
203                              #:username guest-name
204                              #:gm (actor-id gm)
205                              #:client client-id)))
206         ;; Register the player in our database of players -> connections
207         (gm-register-client! gm client-id player)
208         ;; Dump the player into the default room
209         (<-wait gm player 'set-loc! #:id room-id)
210         ;; Initialize the player
211         (<- gm player 'init)))))
212