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