#:use-module (mudsync gameobj)
#:use-module (mudsync game-master)
#:use-module (mudsync parser)
- #:use-module (8sync systems actors)
+ #:use-module (8sync actors)
#:use-module (8sync agenda)
+ #:use-module (ice-9 control)
#:use-module (ice-9 format)
+ #:use-module (ice-9 match)
#:use-module (oop goops)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
#:export (<player>
player-self-commands))
;;; Players
;;; =======
+(define player-self-commands
+ (list
+ (empty-command "inventory" 'cmd-inventory)
+ ;; aliases...
+ ;; @@: Should use an "alias" system for common aliases?
+ (empty-command "inv" 'cmd-inventory)
+ (empty-command "i" 'cmd-inventory)
+ (empty-command "help" 'cmd-help)))
+
(define-class <player> (<gameobj>)
(username #:init-keyword #:username
- #:accessor player-username)
- ;; Connection id
- (client #:accessor player-client)
-
- (self-commands
- #:init-value '()
- #:accessor player-self-commands)
-
- (message-handler
- #:init-value
- ;; @@: We're gonna need action inheritance real awful soon, huh?
- (make-action-dispatch
- (set-loc! (wrap-apply player-set-loc!))
- (init (wrap-apply player-init!))
- (handle-input (wrap-apply player-handle-input)))))
+ #:getter player-username)
+
+ (self-commands #:init-value (wrap player-self-commands))
+
+ (actions #:allocation #:each-subclass
+ #:init-value
+ (build-actions
+ (init player-init)
+ (handle-input player-handle-input)
+ (tell player-tell)
+ (disconnect-self-destruct player-disconnect-self-destruct)
+ (cmd-inventory player-cmd-inventory)
+ (cmd-help player-cmd-help))))
-;;; player message handlers
-(define-mhandler (player-set-loc! player message id)
- (format #t "DEBUG: Location set to ~s for player ~s\n"
- id (actor-id-actor player))
- (set! (gameobj-loc player) id))
+;;; player message handlers
-(define-mhandler (player-init! player message)
- (player-look-around player))
+(define (player-init player message)
+ ;; Look around the room we're in
+ (<- (gameobj-loc player) 'look-room))
-(define-mhandler (player-handle-input player message input)
+(define* (player-handle-input player message #:key input)
(define split-input (split-verb-and-rest input))
- (define input-verb (pk 'input-verb (car split-input)))
- (define input-rest (pk 'input-rest (cdr split-input)))
+ (define input-verb (car split-input))
+ (define input-rest (cdr split-input))
(define command-candidates
- (pk 'candidates
- (player-gather-command-handlers player input-verb)))
+ (player-gather-command-handlers player input-verb))
(define winner
- (pk 'winner (find-command-winner command-candidates input-rest)))
+ (find-command-winner command-candidates input-rest))
+
+ (match winner
+ ((cmd-action winner-id message-args)
+ (apply <- winner-id cmd-action message-args))
+ (#f
+ (<- (gameobj-gm player) 'write-home
+ #:text "Huh? (type \"help\" for common commands)\n"))))
+
+(define* (player-tell player message #:key text)
+ (<- (gameobj-gm player) 'write-home
+ #:text text))
+
+(define (player-disconnect-self-destruct player message)
+ "Action routine for being told to disconnect and self destruct."
+ (define loc (gameobj-loc player))
+ (when loc
+ (<- loc 'tell-room
+ #:exclude (actor-id player)
+ #:text (format #f "~a disappears in a puff of entropy!\n"
+ (slot-ref player 'name))))
+ (gameobj-self-destruct player))
+
+(define (player-cmd-inventory player message)
+ "Display the inventory for the player"
+ (define inv-names
+ (map
+ (lambda (inv-item)
+ (mbody-val (<-wait inv-item 'get-name)))
+ (gameobj-occupants player)))
+ (define text-to-show
+ (if (eq? inv-names '())
+ "You aren't carrying anything.\n"
+ (apply string-append
+ "You are carrying:\n"
+ (map (lambda (item-name)
+ (string-append " * " item-name "\n"))
+ inv-names))))
+ (<- (actor-id player) 'tell #:text text-to-show))
+
+(define (player-cmd-help player message)
+ (<- (actor-id player) 'tell
+ #:text '((strong "** Mudsync Help **")(br)
+ (p "You're playing Mudsync, a multiplayer text-adventure. "
+ "Type different commands to interact with your surroundings "
+ "and other players.")
+ (p "Some common commands:"
+ (ul (li (strong "say <message>") " -- "
+ "Chat with other players in the same room. "
+ "(Also aliased to the " (b "\"") " character.)")
+ (li (strong "look") " -- "
+ "Look around the room you're in.")
+ (li (strong "look [at] <object>") " -- "
+ "Examine a particular object.")
+ (li (strong "go <exit>") " -- "
+ "Move to another room in <exit> direction.")))
+ (p "Different objects can be interacted with in different ways. "
+ "For example, if there's a bell in the same room as you, "
+ "you might try typing " (em "ring bell")
+ " and see what happens."))))
+
+
+;;; Command handling
+;;; ================
+
+;; @@: Hard to know whether this should be in player.scm or here...
+;; @@: This could be more efficient as a stream...!?
+(define (player-gather-command-handlers player verb)
+ (define player-loc
+ (let ((result (gameobj-loc player)))
+ (if result
+ result
+ (throw 'player-has-no-location
+ "Player ~a has no location! How'd that happen?\n"
+ #:player-id (actor-id player)))))
+
+ ;; Ask the room for its commands
+ (define room-commands
+ ;; TODO: Map room id and sort
+ (mbody-receive (_ #:key commands)
+ (<-wait player-loc 'get-container-commands
+ #:verb verb)
+ commands))
+
+ ;; All the co-occupants of the room (not including ourself)
+ (define co-occupants
+ (remove
+ (lambda (x) (equal? x (actor-id player)))
+ (mbody-receive (_ #:key occupants)
+ (<-wait player-loc 'get-occupants)
+ occupants)))
+
+ ;; @@: There's a race condition here if someone leaves the room
+ ;; during this, heh...
+ ;; I'm not sure it can be solved, but "lag" on the race can be
+ ;; reduced maybe?
- (<- player (gameobj-gm player) 'write-home
- #:text
- (format #f "<~a>: ~s\n"
- (player-username player)
- input)))
+ ;; Get all the co-occupants' commands
+ (define co-occupant-commands
+ (fold
+ (lambda (co-occupant prev)
+ (mbody-receive (_ #:key commands goes-by)
+ (<-wait co-occupant 'get-commands
+ #:verb verb)
+ (append
+ (map (lambda (command)
+ (list command goes-by co-occupant))
+ commands)
+ prev)))
+ '()
+ co-occupants))
+ ;; Append our own command handlers
+ (define our-commands
+ (filter
+ (lambda (cmd)
+ (equal? (command-verbs cmd) verb))
+ (val-or-run
+ (slot-ref player 'self-commands))))
-;;; player methods
+ ;; Append our inventory's relevant command handlers
+ (define inv-items
+ (gameobj-occupants player))
+ (define inv-item-commands
+ (fold
+ (lambda (inv-item prev)
+ (mbody-receive (_ #:key commands goes-by)
+ (<-wait inv-item 'get-contained-commands
+ #:verb verb)
+ (append
+ (map (lambda (command)
+ (list command goes-by inv-item))
+ commands)
+ prev)))
+ '()
+ inv-items))
-(define (player-look-around player)
- (define room-name
- (message-ref
- (<-wait player (gameobj-loc player) 'get-name)
- 'val))
- (define room-desc
- (message-ref
- (<-wait player (gameobj-loc player) 'get-desc)
- 'val))
- (define message-text
- (format #f "**~a**\n~a\n" room-name room-desc))
+ ;; Now return a big ol sorted list of ((actor-id . command))
+ (append
+ (sort-commands-append-actor room-commands
+ player-loc '()) ; room doesn't go by anything
+ (sort-commands-multi-actors co-occupant-commands)
+ (sort-commands-append-actor our-commands
+ (actor-id player) '()) ; nor does player
+ (sort-commands-multi-actors inv-item-commands)))
- (<- player (gameobj-gm player) 'write-home #:text message-text))
+(define (sort-commands-append-actor commands actor-id goes-by)
+ (sort-commands-multi-actors
+ (map (lambda (command) (list command goes-by actor-id)) commands)))
+(define (sort-commands-multi-actors actors-and-commands)
+ (sort
+ actors-and-commands
+ (lambda (x y)
+ (> (command-priority (car x))
+ (command-priority (car y))))))
+(define (find-command-winner sorted-candidates line)
+ "Find a command winner from a sorted list of candidates"
+ ;; A cache of results from matchers we've already seen
+ ;; TODO: fill in this cache. This is a *critical* optimization!
+ (define matcher-cache '())
+ (call/ec
+ (lambda (return)
+ (for-each
+ (match-lambda
+ ((command actor-goes-by actor-id)
+ (let* ((matcher (command-matcher command))
+ (matched (matcher line)))
+ (if (and matched
+ ;; Great, it matched, but does it also pass
+ ;; should-handle?
+ (apply (command-should-handle command)
+ actor-goes-by
+ matched)) ; matched is kwargs if truthy
+ (return (list (command-action command)
+ actor-id matched))
+ #f))))
+ sorted-candidates)
+ #f)))