Update to use build-actions; fix clerk communication
[mudsync.git] / mudsync / player.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 player)
20   #:use-module (mudsync command)
21   #:use-module (mudsync gameobj)
22   #:use-module (mudsync game-master)
23   #:use-module (mudsync parser)
24   #:use-module (8sync actors)
25   #:use-module (8sync agenda)
26   #:use-module (ice-9 control)
27   #:use-module (ice-9 format)
28   #:use-module (ice-9 match)
29   #:use-module (oop goops)
30   #:use-module (srfi srfi-1)
31   #:use-module (srfi srfi-9)
32   #:export (<player>
33             player-self-commands))
34
35 ;;; Players
36 ;;; =======
37
38 (define player-self-commands
39   (list
40    (empty-command "inventory" 'cmd-inventory)
41    ;; aliases...
42    ;; @@: Should use an "alias" system for common aliases?
43    (empty-command "inv" 'cmd-inventory)
44    (empty-command "i" 'cmd-inventory)))
45
46 (define-class <player> (<gameobj>)
47   (username #:init-keyword #:username
48             #:getter player-username)
49
50   (self-commands #:init-value player-self-commands)
51
52   (actions #:allocation #:each-subclass
53            #:init-value
54            (build-actions
55             (init player-init)
56             (handle-input player-handle-input)
57             (tell player-tell)
58             (disconnect-self-destruct player-disconnect-self-destruct)
59             (cmd-inventory player-cmd-inventory))))
60
61
62 ;;; player message handlers
63
64 (define (player-init player message)
65   ;; Look around the room we're in
66   (<- player (gameobj-loc player) 'look-room))
67
68
69 (define* (player-handle-input player message #:key input)
70   (define split-input (split-verb-and-rest input))
71   (define input-verb (car split-input))
72   (define input-rest (cdr split-input))
73
74   (define command-candidates
75     (player-gather-command-handlers player input-verb))
76
77   (define winner
78     (find-command-winner command-candidates input-rest))
79
80   (match winner
81     ((cmd-action winner-id message-args)
82      (apply <- player winner-id cmd-action message-args))
83     (#f
84      (<- player (gameobj-gm player) 'write-home
85          #:text "Huh?\n"))))
86
87 (define* (player-tell player message #:key text)
88   (<- player (gameobj-gm player) 'write-home
89       #:text text))
90
91 (define (player-disconnect-self-destruct player message)
92   "Action routine for being told to disconnect and self destruct."
93   (define loc (gameobj-loc player))
94   (when loc
95     (<- player loc 'tell-room
96         #:exclude (actor-id player)
97         #:text (format #f "~a disappears in a puff of entropy!\n"
98                        (slot-ref player 'name))))
99   (gameobj-self-destruct player))
100
101 (define (player-cmd-inventory player message)
102   "Display the inventory for the player"
103   (define inv-names
104     (map
105      (lambda (inv-item)
106        (msg-val (<-wait player inv-item 'get-name)))
107      (gameobj-occupants player)))
108   (define text-to-show
109     (if (eq? inv-names '())
110         "You aren't carrying anything.\n"
111         (apply string-append
112                "You are carrying:\n"
113                (map (lambda (item-name)
114                       (string-append "  * " item-name "\n"))
115                     inv-names))))
116   (<- player (actor-id player) 'tell #:text text-to-show))
117
118
119 ;;; Command handling
120 ;;; ================
121
122 ;; @@: Hard to know whether this should be in player.scm or here...
123 ;; @@: This could be more efficient as a stream...!?
124 (define (player-gather-command-handlers player verb)
125   (define player-loc
126     (let ((result (gameobj-loc player)))
127       (if result
128           result
129           (throw 'player-has-no-location
130                  "Player ~a has no location!  How'd that happen?\n"
131                  #:player-id (actor-id player)))))
132
133   ;; Ask the room for its commands
134   (define room-commands
135     ;; TODO: Map room id and sort
136     (msg-receive (_ #:key commands)
137         (<-wait player player-loc
138              'get-container-commands
139              #:verb verb)
140       commands))
141
142   ;; All the co-occupants of the room (not including ourself)
143   (define co-occupants
144     (remove
145      (lambda (x) (equal? x (actor-id player)))
146      (msg-receive (_ #:key occupants)
147          (<-wait player player-loc 'get-occupants)
148        occupants)))
149
150   ;; @@: There's a race condition here if someone leaves the room
151   ;;   during this, heh...
152   ;;   I'm not sure it can be solved, but "lag" on the race can be
153   ;;   reduced maybe?
154
155   ;; Get all the co-occupants' commands
156   (define co-occupant-commands
157     (fold
158      (lambda (co-occupant prev)
159        (msg-receive (_ #:key commands goes-by)
160            (<-wait player co-occupant 'get-commands
161                               #:verb verb)
162          (append
163           (map (lambda (command)
164                  (list command goes-by co-occupant))
165                commands)
166           prev)))
167      '()
168      co-occupants))
169
170   ;; Append our own command handlers
171   (define our-commands
172     (filter
173      (lambda (cmd)
174        (equal? (command-verbs cmd) verb))
175      (val-or-run
176       (slot-ref player 'self-commands))))
177
178   ;; Append our inventory's relevant command handlers
179   (define inv-items
180     (gameobj-occupants player))
181   (define inv-item-commands
182     (fold
183      (lambda (inv-item prev)
184        (msg-receive (_ #:key commands goes-by)
185            (<-wait player inv-item
186                    'get-contained-commands
187                    #:verb verb)
188          (append
189           (map (lambda (command)
190                  (list command goes-by inv-item))
191                commands)
192           prev)))
193      '()
194      inv-items))
195
196   ;; Now return a big ol sorted list of ((actor-id . command))
197   (append
198    (sort-commands-append-actor room-commands
199                                player-loc '()) ; room doesn't go by anything
200    (sort-commands-multi-actors co-occupant-commands)
201    (sort-commands-append-actor our-commands
202                                (actor-id player) '()) ; nor does player
203    (sort-commands-multi-actors inv-item-commands)))
204
205 (define (sort-commands-append-actor commands actor-id goes-by)
206   (sort-commands-multi-actors
207    (map (lambda (command) (list command goes-by actor-id)) commands)))
208
209 (define (sort-commands-multi-actors actors-and-commands)
210   (sort
211    actors-and-commands
212    (lambda (x y)
213      (> (command-priority (car x))
214         (command-priority (car y))))))
215
216
217 (define (find-command-winner sorted-candidates line)
218   "Find a command winner from a sorted list of candidates"
219   ;; A cache of results from matchers we've already seen
220   ;; TODO: fill in this cache.  This is a *critical* optimization!
221   (define matcher-cache '())
222   (call/ec
223    (lambda (return)
224      (for-each
225       (match-lambda
226         ((command actor-goes-by actor-id)
227          (let* ((matcher (command-matcher command))
228                 (matched (matcher line)))
229            (if (and matched
230                     ;; Great, it matched, but does it also pass
231                     ;; should-handle?
232                     (apply (command-should-handle command)
233                            actor-goes-by
234                            matched))  ; matched is kwargs if truthy
235                (return (list (command-action command)
236                              actor-id matched))
237                #f))))
238       sorted-candidates)
239      #f)))