more tweaks
[mudsync.git] / mudsync / room.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 room)
20   #:use-module (mudsync command)
21   #:use-module (mudsync gameobj)
22   #:use-module (8sync systems actors)
23   #:use-module (8sync agenda)
24   #:use-module (oop goops)
25   #:use-module (srfi srfi-1)
26   #:use-module (ice-9 control)
27   #:export (<room>
28             room-actions
29             room-actions*
30
31             <exit>))
32
33 \f
34 ;;; Exits
35 ;;; =====
36
37 (define-class <exit> ()
38   ;; Used for wiring
39   (to-symbol #:init-keyword #:to-symbol)
40   ;; The actual address we use
41   (to-address #:init-keyword #:address)
42   ;; Name of the room (@@: Should this be names?)
43   (name #:getter exit-name
44         #:init-keyword #:name)
45   (desc #:init-keyword #:desc
46         #:init-value #f)
47
48   ;; *Note*: These two methods have an extra layer of indirection, but
49   ;;   it's for a good reason.
50   (visible-check #:init-value (const #t)
51                  #:init-keyword #:visible-check)
52   ;; By default all exits can be traversed
53   (traverse-check #:init-value (const #t)
54                   #:init-keyword #:traverse-check))
55
56 (define* (exit-can-traverse? exit actor
57                              #:optional (target-actor (actor-id actor)))
58   ((slot-ref exit 'traverse-check) exit actor target-actor))
59
60 (define* (exit-is-visible? exit actor
61                            #:optional (target-actor (actor-id actor)))
62   ((slot-ref exit 'traverse-check) exit actor target-actor))
63
64
65 \f
66 ;;; Rooms
67 ;;; =====
68
69 (define %room-contain-commands
70   (list
71    (loose-direct-command "look" 'cmd-look-at)
72    (empty-command "look" 'cmd-look-room)
73    (empty-command "go" 'cmd-go-where)
74    (loose-direct-command "go" 'cmd-go)
75    ;; (greedy-command "say" 'cmd-say)
76    ))
77
78 (define room-actions
79   (build-actions
80    ;; desc == description
81    (init (wrap-apply room-init))
82    (wire-exits! (wrap-apply room-wire-exits!))
83    (cmd-go (wrap-apply room-cmd-go))
84    (cmd-go-where (wrap-apply room-cmd-go-where))
85    (look-room (wrap-apply room-look-room))
86    ;; in this case the command is the same version as the normal
87    ;; look-room version
88    (cmd-look-room (wrap-apply room-look-room))
89    (cmd-look-at (wrap-apply room-look-at))))
90
91 (define room-actions*
92   (append room-actions gameobj-actions))
93
94 (define room-action-dispatch
95   (simple-dispatcher room-actions*))
96
97 ;; TODO: Subclass from container?
98 (define-class <room> (<gameobj>)
99   ;; A list of <exit>
100   (exits #:init-value '()
101          #:init-keyword #:exits
102          #:getter room-exits)
103
104   (container-commands
105    #:init-value (wrap %room-contain-commands))
106
107   (message-handler
108    #:allocation #:each-subclass
109    ;; @@: Can remove this indirection once things settle
110    #:init-value (wrap-apply room-action-dispatch)))
111
112 (define (room-init room message)
113   (room-wire-exits! room))
114
115 (define (room-wire-exits! room)
116   "Actually hook up the rooms' exit addresses to the rooms they
117 claim to point to."
118   (for-each
119    (lambda (exit)
120      (define new-exit
121        (message-ref
122         (<-wait room (gameobj-gm room) 'lookup-special
123                 #:symbol (slot-ref exit 'to-symbol))
124         'room-id))
125
126      (slot-set! exit 'to-address new-exit))
127
128    (room-exits room)))
129
130 (define-mhandler (room-cmd-go room message direct-obj)
131   (define exit
132     (find
133      (lambda (exit)
134        (equal? (exit-name exit) direct-obj))
135      (room-exits room)))
136   (cond
137    (exit
138     ;; Set the player's new location
139     (<-wait room (message-from message) 'set-loc!
140             #:loc (slot-ref exit 'to-address))
141     ;; Have the new room update the player to the new location
142     (<- room (slot-ref exit 'to-address) 'look-room
143         #:to-id (message-from message)))
144    (else
145     (<- room (message-from message) 'tell
146         #:text "You don't see any way to go there.\n"))))
147
148 (define-mhandler (room-cmd-go-where room message)
149   (<- room (message-from message) 'tell
150       #:text "Go where?\n"))
151
152 ;;; look commands
153
154 (define (list-words-as-string words)
155   "A little utility for listing a bunch of words in an English-style list"
156   ;; TODO: This could be made faster by traversing the O(n)
157   ;;   list once, not twice
158   (let ((word-length (length words)))
159     (cond 
160      ((eqv? word-length 0) "")
161      ((eqv? word-length 1) (car words))
162      (else
163       ;; TODO: and this is NOT efficient
164       (string-append
165        (string-join
166         (drop-right words 1)
167         ", ")
168        " and "
169        (last words))))))
170
171 (define (room-player-looks-around room player-id)
172   "Handle looking around the room"
173   ;; Get the room text
174   (define room-text
175     (format #f "**~a**\n~a\n"
176             (slot-ref room 'name)
177             (slot-ref room 'desc)))
178
179   ;; Get a list of other things the player would see in the room
180   (define occupant-names-all
181     (map
182      (lambda (occupant)
183        (message-ref
184         (<-wait room occupant 'visible-name
185                 #:whos-looking player-id)
186         'text))
187      (remove
188       (lambda (x) (equal? x player-id))
189       (hash-map->list (lambda (x _) x)
190                       (slot-ref room 'occupants)))))
191
192   ;; Strip out the #f responses (these aren't listed because they lack a name
193   ;; or they aren't "obviously visible" to the player)
194   (define occupant-names-filtered
195     (filter identity occupant-names-all))
196
197   (define occupant-names-string
198     (if (eq? occupant-names-filtered '())
199         #f
200         (format #f "You see here: ~a.\n"
201                 (list-words-as-string occupant-names-filtered))))
202
203   (define final-text
204     (if occupant-names-string
205         (string-append room-text occupant-names-string)
206         room-text))
207   
208   (<- room player-id 'tell
209       #:text final-text))
210
211
212 (define-mhandler (room-look-room room message)
213   "Command: Player asks to look around the room"
214   (room-player-looks-around
215    room
216    ;; Either send it to the #:to-id of the message, or to the
217    ;; sender of the message
218    (message-ref message 'to-id
219                 (message-from message))))
220
221 (define (room-find-thing-called room called-this)
222   "Find something called CALLED-THIS in the room, if any."
223   (call/ec
224    (lambda (return)
225      (for-each
226       (lambda (occupant)
227         (define goes-by
228           (message-ref (<-wait room occupant 'goes-by)
229                        'goes-by #f))
230         (display "here!\n")
231         (if (member called-this goes-by)
232             (return occupant)))
233       (hash-map->list (lambda (key val) key)
234                       (slot-ref room 'occupants)))
235      #f)))
236
237 (define %formless-desc
238   "You don't see anything special about it.")
239
240 (define-mhandler (room-look-at room message direct-obj)
241   "Look at a specific object in the room."
242   (define matching-object
243     (room-find-thing-called room direct-obj))
244
245   (cond
246    (matching-object
247     (let ((obj-desc
248            (message-ref
249             (<-wait room matching-object 'get-desc
250                     #:whos-looking (message-from message))
251             'val)))
252       (if obj-desc
253           (<- room (message-from message) 'tell
254               #:text (string-append obj-desc "\n"))
255           (<- room (message-from message) 'tell
256               #:text (string-append %formless-desc "\n")))))
257    (else
258     (<- room (message-from message) 'tell
259         #:text "You don't see that here, so you can't look at it.\n"))))