d2fc76dd64d1cbb15a4cf079bb3c9de8317761e9
[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-mhandler (player-init player message)
75   ;; Look around the room we're in
76   (<- player (gameobj-loc player) 'look-room))
77
78
79 (define-mhandler (player-handle-input player message 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 send-message player winner-id cmd-action message-args))
93     (#f
94      (<- player (gameobj-gm player) 'write-home
95          #:text "Huh?\n"))))
96
97 (define-mhandler (player-tell player message text)
98   (<- player (gameobj-gm player) 'write-home
99       #:text text))
100
101 (define-mhandler (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-mhandler (player-cmd-inventory player message)
112   "Display the inventory for the player"
113   (define inv-names
114     (map
115      (lambda (inv-item)
116        (message-ref (<-wait player inv-item 'get-name)
117                     'val))
118      (gameobj-occupants player)))
119   (define text-to-show
120     (if (eq? inv-names '())
121         "You aren't carrying anything.\n"
122         (apply string-append
123                "You are carrying:\n"
124                (map (lambda (item-name)
125                       (string-append "  * " item-name "\n"))
126                     inv-names))))
127   (<- player (actor-id player) 'tell #:text text-to-show))
128
129
130 ;;; Command handling
131 ;;; ================
132
133 ;; @@: Hard to know whether this should be in player.scm or here...
134 ;; @@: This could be more efficient as a stream...!?
135 (define (player-gather-command-handlers player verb)
136   (define player-loc
137     (let ((result (gameobj-loc player)))
138       (if result
139           result
140           (throw 'player-has-no-location
141                  "Player ~a has no location!  How'd that happen?\n"
142                  #:player-id (actor-id player)))))
143
144   ;; Ask the room for its commands
145   (define room-commands
146     ;; TODO: Map room id and sort
147     (message-ref
148      (<-wait player player-loc
149              'get-container-commands
150              #:verb verb)
151      'commands))
152
153   ;; All the co-occupants of the room (not including ourself)
154   (define co-occupants
155     (remove
156      (lambda (x) (equal? x (actor-id player)))
157      (message-ref
158       (<-wait player player-loc 'get-occupants)
159       'occupants)))
160
161   ;; @@: There's a race condition here if someone leaves the room
162   ;;   during this, heh...
163   ;;   I'm not sure it can be solved, but "lag" on the race can be
164   ;;   reduced maybe?
165
166   ;; Get all the co-occupants' commands
167   (define co-occupant-commands
168     ;; TODO: Switch this to a fold.  Ignore a result if it
169     ;;   returns false for in the command response
170     (fold
171      (lambda (co-occupant prev)
172        (let* ((result (<-wait player co-occupant 'get-commands
173                               #:verb verb))
174               (commands (message-ref result 'commands))
175               (goes-by (message-ref result 'goes-by)))
176          (append
177           (map (lambda (command)
178                  (list command goes-by co-occupant))
179                commands)
180           prev)))
181      '()
182      co-occupants))
183
184   ;; Append our own command handlers
185   (define our-commands
186     (filter
187      (lambda (cmd)
188        (equal? (command-verbs cmd) verb))
189      (val-or-run
190       (slot-ref player 'self-commands))))
191
192   ;; TODO: Append our inventory's relevant command handlers
193
194   ;; Now return a big ol sorted list of ((actor-id . command))
195   (append
196    (sort-commands-append-actor room-commands
197                                player-loc '()) ; room doesn't go by anything
198    (sort-commands-multi-actors co-occupant-commands)
199    (sort-commands-append-actor our-commands
200                                (actor-id player) '()))) ; nor does player
201
202 (define (sort-commands-append-actor commands actor-id goes-by)
203   (sort-commands-multi-actors
204    (map (lambda (command) (list command goes-by actor-id)) commands)))
205
206 (define (sort-commands-multi-actors actors-and-commands)
207   (sort
208    actors-and-commands
209    (lambda (x y)
210      (> (command-priority (car x))
211         (command-priority (car y))))))
212
213
214 (define (find-command-winner sorted-candidates line)
215   "Find a command winner from a sorted list of candidates"
216   ;; A cache of results from matchers we've already seen
217   ;; TODO: fill in this cache.  This is a *critical* optimization!
218   (define matcher-cache '())
219   (call/ec
220    (lambda (return)
221      (for-each
222       (match-lambda
223         ((command actor-goes-by actor-id)
224          (let* ((matcher (command-matcher command))
225                 (matched (matcher line)))
226            (if (and matched
227                     ;; Great, it matched, but does it also pass
228                     ;; should-handle?
229                     (apply (command-should-handle command)
230                            actor-goes-by
231                            matched))  ; matched is kwargs if truthy
232                (return (list (command-action command)
233                              actor-id matched))
234                #f))))
235       sorted-candidates)
236      #f)))