gameobj.scm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. ;;; Mudsync --- Live hackable MUD
  2. ;;; Copyright © 2016 Christine Lemmer-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. ;;; Game actor
  19. ;;; ==========
  20. (define-module (mudsync gameobj)
  21. #:use-module (mudsync command)
  22. #:use-module (mudsync utils)
  23. #:use-module (8sync actors)
  24. #:use-module (8sync agenda)
  25. #:use-module (8sync rmeta-slot)
  26. #:use-module (srfi srfi-1)
  27. #:use-module (ice-9 control)
  28. #:use-module (ice-9 format)
  29. #:use-module (ice-9 match)
  30. #:use-module (oop goops)
  31. #:export (<gameobj>
  32. create-gameobj
  33. gameobj-loc
  34. gameobj-gm
  35. gameobj-desc
  36. gameobj-act-init
  37. gameobj-set-loc!
  38. gameobj-occupants
  39. gameobj-self-destruct
  40. slot-ref-maybe-runcheck
  41. val-or-run
  42. build-props
  43. dyn-ref
  44. ;; Some of the more common commands
  45. cmd-look-at
  46. cmd-take cmd-drop
  47. cmd-take-from-no-op cmd-put-in-no-op))
  48. ;;; Gameobj
  49. ;;; =======
  50. (define build-props build-rmeta-slot)
  51. ;;; *all* game components that talk to players should somehow
  52. ;;; derive from this class.
  53. ;;; And all of them need a GM!
  54. (define-class <gameobj> (<actor>)
  55. ;; location id
  56. (loc #:init-value #f
  57. #:getter gameobj-loc)
  58. ;; Uses a hash table like a set (values ignored)
  59. (occupants #:init-thunk make-hash-table)
  60. ;; game master id
  61. (gm #:init-keyword #:gm
  62. #:getter gameobj-gm)
  63. ;; a name to be known by
  64. (name #:init-keyword #:name
  65. #:init-value #f)
  66. (goes-by #:init-keyword #:goes-by
  67. #:init-value #f)
  68. (desc #:init-value #f
  69. #:init-keyword #:desc)
  70. ;; @@: Maybe commands should be renamed to verbs, I dunno
  71. ;; Commands we can handle
  72. (commands #:allocation #:each-subclass
  73. #:init-thunk (build-commands
  74. (("l" "look") ((direct-command cmd-look-at)))
  75. ("take" ((direct-command cmd-take)
  76. (prep-indir-command cmd-take-from
  77. '("from" "out of"))))
  78. ("put" ((prep-indir-command cmd-put-in
  79. '("in" "inside" "into" "on"))))))
  80. ;; Commands we can handle by being something's container
  81. ;; dominant version (goes before everything)
  82. (container-dom-commands #:allocation #:each-subclass
  83. #:init-thunk (build-commands))
  84. ;; subordinate version (goes after everything)
  85. (container-sub-commands #:allocation #:each-subclass
  86. #:init-thunk (build-commands))
  87. ;; Commands we can handle by being contained by something else
  88. (contained-commands #:allocation #:each-subclass
  89. #:init-thunk
  90. (build-commands
  91. (("l" "look") ((direct-command cmd-look-at)))
  92. ("drop" ((direct-command cmd-drop #:obvious? #f)))))
  93. ;; The extremely squishy concept of "props"... properties!
  94. ;; These are flags, etc etc of various types. This is a hashq table.
  95. ;; These have upsides and downsides, but the big upside is that you can
  96. ;; query a "prop" of a prospective gameobj without knowing what type of
  97. ;; gameobj that is, and not fear some kind of breakage.
  98. ;;
  99. ;; props by default only have a 'get-prop read-only action handler;
  100. ;; any coordination of setting a prop between actors must be
  101. ;; added to that actor, to keep things from getting out of control.
  102. (props #:init-thunk make-hash-table
  103. #:init-keyword #:props)
  104. ;; gameobjs may inherit an initial list of these via the
  105. ;; initial-props slot, which must always have its
  106. ;; #:allocation #:each-subclass and use (build-props) for the
  107. ;; #:init-thunk.
  108. ;; The vanilla gameobj has no props, on purpose.
  109. (initial-props #:allocation #:each-subclass
  110. #:init-thunk (build-props '()))
  111. ;; Most objects are generally visible by default
  112. (invisible? #:init-value #f
  113. #:init-keyword #:invisible?)
  114. ;; TODO: Fold this into a procedure in invisible? similar
  115. ;; to take-me? and etc
  116. (visible-to-player?
  117. #:init-value (wrap-apply gameobj-visible-to-player?))
  118. ;; Can be a boolean or a procedure accepting
  119. ;; (gameobj whos-acting #:key from)
  120. (take-me? #:init-value #f
  121. #:init-keyword #:take-me?)
  122. ;; Can be a boolean or a procedure accepting
  123. ;; (gameobj whos-acting where)
  124. (drop-me? #:init-value #t
  125. #:init-keyword #:drop-me?)
  126. ;; TODO: Remove this and use actor-alive? instead.
  127. ;; Set this on self-destruct
  128. ;; (checked by some "long running" game routines)
  129. (destructed #:init-value #f)
  130. (actions #:allocation #:each-subclass
  131. ;;; Actions supported by all gameobj
  132. #:init-thunk
  133. (build-actions
  134. (init gameobj-act-init)
  135. ;; Commands for co-occupants
  136. (get-commands gameobj-get-commands)
  137. ;; Commands for participants in a room
  138. (get-container-dom-commands gameobj-get-container-dom-commands)
  139. (get-container-sub-commands gameobj-get-container-sub-commands)
  140. ;; Commands for inventory items, etc (occupants of the gameobj commanding)
  141. (get-contained-commands gameobj-get-contained-commands)
  142. (get-occupants gameobj-get-occupants)
  143. (add-occupant! gameobj-add-occupant!)
  144. (remove-occupant! gameobj-remove-occupant!)
  145. (get-loc gameobj-act-get-loc)
  146. (set-loc! gameobj-act-set-loc!)
  147. (get-name gameobj-get-name)
  148. (set-name! gameobj-act-set-name!)
  149. (get-desc gameobj-get-desc)
  150. (get-prop gameobj-act-get-prop)
  151. (goes-by gameobj-act-goes-by)
  152. (visible-name gameobj-visible-name)
  153. (self-destruct gameobj-act-self-destruct)
  154. (tell gameobj-tell-no-op)
  155. (assist-replace gameobj-act-assist-replace)
  156. (ok-to-drop-here? (lambda (gameobj message . _)
  157. (<-reply message #t))) ; ok to drop by default
  158. (ok-to-be-taken-from? gameobj-ok-to-be-taken-from)
  159. (ok-to-be-put-in? gameobj-ok-to-be-put-in)
  160. ;; Common commands
  161. (cmd-look-at cmd-look-at)
  162. (cmd-take cmd-take)
  163. (cmd-drop cmd-drop)
  164. (cmd-take-from cmd-take-from-no-op)
  165. (cmd-put-in cmd-put-in-no-op))))
  166. ;;; gameobj message handlers
  167. ;;; ========================
  168. ;; TODO: This init stuff is a mess, and should be redone now that
  169. ;; we have the *init* action stuff. We've really spread out the
  170. ;; logic for creating a gameobj in several places, eg gm-inject-special!
  171. (define (create-gameobj class gm loc . args)
  172. "Create a gameobj of CLASS with GM and set to location LOC, applying rest of ARGS.
  173. Note that this doesn't do any special dyn-ref of the location."
  174. (let ((new-gameobj (apply create-actor (%current-actor) class
  175. #:gm gm args)))
  176. ;; Set the location
  177. (<-wait new-gameobj 'set-loc! #:loc loc)
  178. ;; Initialize the object
  179. (<-wait new-gameobj 'init)))
  180. ;; ;; @@: Should we also dyn-ref the loc here? We can do that, unlike with
  181. ;; ;; create-gameobj.
  182. ;; ;; Another route could be to have set-loc! itself know how to use the
  183. ;; ;; dyn-ref.
  184. ;; (define (gameobj-create-gameobj gameobj class loc . args)
  185. ;; "Like create-gameobj but saves the step of passing in the gm."
  186. ;; (apply create-gameobj class (gameobj-gm gameobj) loc args))
  187. ;; Kind of a useful utility, maybe?
  188. (define (simple-slot-getter slot)
  189. (lambda (actor message)
  190. (<-reply message (slot-ref actor slot))))
  191. (define (gameobj-replace-step-occupants actor occupants)
  192. ;; Snarf all the occupants!
  193. (display "replacing occupant\n")
  194. (when occupants
  195. (for-each
  196. (lambda (occupant)
  197. (<-wait occupant 'set-loc!
  198. #:loc (actor-id actor)))
  199. occupants)))
  200. (define gameobj-replace-steps*
  201. (list gameobj-replace-step-occupants))
  202. (define (run-replacement actor replaces replace-steps)
  203. (when replaces
  204. (mbody-receive (_ #:key occupants)
  205. (<-wait replaces 'assist-replace)
  206. (for-each
  207. (lambda (replace-step)
  208. (replace-step actor occupants))
  209. replace-steps))))
  210. (define %nothing (cons '*the* '*nothing*))
  211. (define (gameobj-setup-props gameobj)
  212. (define class (class-of gameobj))
  213. (define props (slot-ref gameobj 'props))
  214. (maybe-build-rmeta-slot-cache! class 'initial-props
  215. eq? hashq-set! hashq-ref)
  216. ;; Kind of a kludge... we read through the rmeta-slot-cache
  217. ;; and use that to build up the table
  218. (hash-for-each
  219. (lambda (key value)
  220. (when (eq? (hashq-ref props key %nothing) ; don't override init'ed instance values
  221. %nothing)
  222. (hashq-set! props key value)))
  223. (rmeta-slot-cache (class-slot-ref class 'initial-props))))
  224. ;; TODO: Use the *init* action?
  225. ;; We could also use a generic method if they didn't have
  226. ;; what I'm pretty sure is O(n) dispatch in GOOPS...
  227. (define* (gameobj-act-init actor message #:key replace)
  228. "Your most basic game object init procedure."
  229. (gameobj-setup-props actor)
  230. (run-replacement actor replace gameobj-replace-steps*))
  231. (define* (gameobj-get-prop gameobj key #:optional dflt)
  232. (hashq-ref (slot-ref gameobj 'props) key dflt))
  233. (define* (gameobj-set-prop! gameobj key val)
  234. (hashq-set! (slot-ref gameobj 'props) key val))
  235. (define* (gameobj-act-get-prop actor message key #:optional dflt)
  236. (<-reply message (gameobj-get-prop actor key dflt)))
  237. (define (gameobj-goes-by gameobj)
  238. "Find the name we go by. Defaults to #:name if nothing else provided."
  239. (cond ((slot-ref gameobj 'goes-by) =>
  240. identity)
  241. ((slot-ref gameobj 'name) =>
  242. (lambda (name)
  243. (list name)))
  244. (else '())))
  245. (define (gameobj-act-goes-by actor message)
  246. "Reply to a message requesting what we go by."
  247. (<-reply message (gameobj-goes-by actor)))
  248. (define (val-or-run val-or-proc)
  249. "Evaluate if a procedure, or just return otherwise"
  250. (if (procedure? val-or-proc)
  251. (val-or-proc)
  252. val-or-proc))
  253. (define (get-candidate-commands actor rmeta-sym verb)
  254. (class-rmeta-ref (class-of actor) rmeta-sym verb
  255. #:dflt '()))
  256. (define* (gameobj-get-commands actor message #:key verb)
  257. "Get commands a co-occupant of the room might execute for VERB"
  258. (define candidate-commands
  259. (get-candidate-commands actor 'commands verb))
  260. (<-reply message
  261. #:commands candidate-commands
  262. #:goes-by (gameobj-goes-by actor)))
  263. (define* (gameobj-get-container-dom-commands actor message #:key verb)
  264. "Get (dominant) commands as the container / room of message's sender"
  265. (define candidate-commands
  266. (get-candidate-commands actor 'container-dom-commands verb))
  267. (<-reply message #:commands candidate-commands))
  268. (define* (gameobj-get-container-sub-commands actor message #:key verb)
  269. "Get (subordinate) commands as the container / room of message's sender"
  270. (define candidate-commands
  271. (get-candidate-commands actor 'container-sub-commands verb))
  272. (<-reply message #:commands candidate-commands))
  273. (define* (gameobj-get-contained-commands actor message #:key verb)
  274. "Get commands as being contained (eg inventory) of commanding gameobj"
  275. (define candidate-commands
  276. (get-candidate-commands actor 'contained-commands verb))
  277. (<-reply message
  278. #:commands candidate-commands
  279. #:goes-by (gameobj-goes-by actor)))
  280. (define* (gameobj-add-occupant! actor message #:key who)
  281. "Add an actor to our list of present occupants"
  282. (hash-set! (slot-ref actor 'occupants)
  283. who #t))
  284. (define* (gameobj-remove-occupant! actor message #:key who)
  285. "Remove an occupant from the room."
  286. (hash-remove! (slot-ref actor 'occupants) who))
  287. (define* (gameobj-occupants gameobj #:key exclude)
  288. (hash-fold
  289. (lambda (occupant _ prev)
  290. (define exclude-it?
  291. (match exclude
  292. ;; Empty list and #f are non-exclusion
  293. (() #f)
  294. (#f #f)
  295. ;; A list of addresses... since our address object is (annoyingly)
  296. ;; currently a simple cons cell...
  297. ((exclude-1 ... exclude-rest)
  298. (member occupant exclude))
  299. ;; Must be an individual address!
  300. (_ (equal? occupant exclude))))
  301. (if exclude-it?
  302. prev
  303. (cons occupant prev)))
  304. '()
  305. (slot-ref gameobj 'occupants)))
  306. (define* (gameobj-get-occupants actor message #:key exclude)
  307. "Get all present occupants of the room."
  308. (define occupants
  309. (gameobj-occupants actor #:exclude exclude))
  310. (<-reply message occupants))
  311. (define (gameobj-act-get-loc actor message)
  312. (<-reply message (slot-ref actor 'loc)))
  313. (define (gameobj-set-loc! gameobj loc)
  314. "Set the location of this object."
  315. (define old-loc (gameobj-loc gameobj))
  316. (format #t "DEBUG: Location set to ~s for ~s\n"
  317. loc (actor-id-actor gameobj))
  318. (when (not (equal? old-loc loc))
  319. (slot-set! gameobj 'loc loc)
  320. ;; Change registation of where we currently are
  321. (if old-loc
  322. (<-wait old-loc 'remove-occupant! #:who (actor-id gameobj)))
  323. (if loc
  324. (<-wait loc 'add-occupant! #:who (actor-id gameobj)))))
  325. ;; @@: Should it really be #:id ? Maybe #:loc-id or #:loc?
  326. (define* (gameobj-act-set-loc! actor message #:key loc)
  327. "Action routine to set the location."
  328. (gameobj-set-loc! actor loc))
  329. (define (slot-ref-maybe-runcheck gameobj slot whos-asking . other-args)
  330. "Do a slot-ref on gameobj, evaluating it including ourselves
  331. and whos-asking, and see if we should just return it or run it."
  332. (match (slot-ref gameobj slot)
  333. ((? procedure? slot-val-proc)
  334. (apply slot-val-proc gameobj whos-asking other-args))
  335. (anything-else anything-else)))
  336. (define gameobj-get-name (simple-slot-getter 'name))
  337. (define* (gameobj-act-set-name! actor message val)
  338. (slot-set! actor 'name val))
  339. (define* (gameobj-desc gameobj #:key whos-looking)
  340. (match (slot-ref gameobj 'desc)
  341. ((? procedure? desc-proc)
  342. (desc-proc gameobj whos-looking))
  343. (desc desc)))
  344. (define* (gameobj-get-desc actor message #:key whos-looking)
  345. "This is the action equivalent of the gameobj-desc getter"
  346. (<-reply message (gameobj-desc actor #:whos-looking whos-looking)))
  347. (define (gameobj-visible-to-player? gameobj whos-looking)
  348. "Check to see whether we're visible to the player or not.
  349. By default, this is whether or not the generally-visible flag is set."
  350. (not (slot-ref gameobj 'invisible?)))
  351. (define* (gameobj-visible-name actor message #:key whos-looking)
  352. ;; Are we visible?
  353. (define we-are-visible
  354. ((slot-ref actor 'visible-to-player?) actor whos-looking))
  355. (define name-to-return
  356. (if we-are-visible
  357. ;; Return our name
  358. (match (slot-ref actor 'name)
  359. ((? procedure? name-proc)
  360. (name-proc actor whos-looking))
  361. ((? string? name)
  362. name)
  363. (#f #f))
  364. #f))
  365. (<-reply message #:text name-to-return))
  366. (define (gameobj-self-destruct gameobj)
  367. "General gameobj self destruction routine"
  368. ;; Unregister from being in any particular room
  369. (gameobj-set-loc! gameobj #f)
  370. (slot-set! gameobj 'destructed #t)
  371. ;; Boom!
  372. (self-destruct gameobj))
  373. (define* (gameobj-act-self-destruct gameobj message #:key why)
  374. "Action routine for self destruction"
  375. (gameobj-self-destruct gameobj))
  376. ;; Unless an actor has a tell message, we just ignore it
  377. (define gameobj-tell-no-op
  378. (const 'no-op))
  379. (define (gameobj-replace-data-occupants gameobj)
  380. "The general purpose list of replacement data"
  381. (list #:occupants (hash-map->list (lambda (occupant _) occupant)
  382. (slot-ref gameobj 'occupants))))
  383. (define (gameobj-replace-data* gameobj)
  384. ;; For now, just call gameobj-replace-data-occupants.
  385. ;; But there may be more in the future!
  386. (gameobj-replace-data-occupants gameobj))
  387. ;; So sad that objects must assist in their replacement ;_;
  388. ;; But that's life in a live hacked game!
  389. (define (gameobj-act-assist-replace gameobj message)
  390. "Vanilla method for assisting in self-replacement for live hacking"
  391. (apply <-reply message
  392. (gameobj-replace-data* gameobj)))
  393. (define (gameobj-ok-to-be-taken-from gameobj message whos-acting)
  394. (call-with-values (lambda ()
  395. (slot-ref-maybe-runcheck gameobj 'take-me?
  396. whos-acting #:from #t))
  397. ;; This allows this to reply with #:why-not if appropriate
  398. (lambda args
  399. (apply <-reply message args))))
  400. (define (gameobj-ok-to-be-put-in gameobj message whos-acting where)
  401. (call-with-values (lambda ()
  402. (slot-ref-maybe-runcheck gameobj 'drop-me?
  403. whos-acting where))
  404. ;; This allows this to reply with #:why-not if appropriate
  405. (lambda args
  406. (apply <-reply message args))))
  407. ;;; Utilities every gameobj has
  408. ;;; ---------------------------
  409. (define (dyn-ref gameobj special-symbol)
  410. "Dynamically look up a special object from the gm"
  411. (match special-symbol
  412. ;; if it's a symbol, look it up dynamically
  413. ((? symbol? _)
  414. ;; TODO: If we get back an #f at this point, should we throw
  415. ;; an error? Obviously #f is okay, but maybe not if
  416. (mbody-val (<-wait (slot-ref gameobj 'gm) 'lookup-special
  417. #:symbol special-symbol)))
  418. ;; if it's false, return nothing
  419. (#f #f)
  420. ;; otherwise it's probably an address, return it as-is
  421. (_ special-symbol)))
  422. ;;; Basic actions
  423. ;;; -------------
  424. (define %formless-desc
  425. "You don't see anything special.")
  426. (define* (cmd-look-at gameobj message
  427. #:key direct-obj
  428. (player (message-from message)))
  429. (let ((desc
  430. (or (gameobj-desc gameobj #:whos-looking player)
  431. %formless-desc)))
  432. (<- player 'tell #:text desc)))
  433. (define* (cmd-take gameobj message
  434. #:key direct-obj
  435. (player (message-from message)))
  436. (define player-name
  437. (mbody-val (<-wait player 'get-name)))
  438. (define player-loc
  439. (mbody-val (<-wait player 'get-loc)))
  440. (define our-name (slot-ref gameobj 'name))
  441. (define self-should-take
  442. (slot-ref-maybe-runcheck gameobj 'take-me? player))
  443. ;; @@: Is there any reason to allow the room to object in the way
  444. ;; that there is for dropping? It doesn't seem like it.
  445. (call-with-values (lambda ()
  446. (slot-ref-maybe-runcheck gameobj 'take-me? player))
  447. (lambda* (self-should-take #:key (why-not
  448. `("It doesn't seem like you can take "
  449. ,our-name ".")))
  450. (if self-should-take
  451. ;; Set the location to whoever's picking us up
  452. (begin
  453. (gameobj-set-loc! gameobj player)
  454. (<- player 'tell
  455. #:text (format #f "You pick up ~a.\n"
  456. our-name))
  457. (<- player-loc 'tell-room
  458. #:text (format #f "~a picks up ~a.\n"
  459. player-name
  460. our-name)
  461. #:exclude player))
  462. (<- player 'tell #:text why-not)))))
  463. (define* (cmd-drop gameobj message
  464. #:key direct-obj
  465. (player (message-from message)))
  466. (define player-name
  467. (mbody-val (<-wait player 'get-name)))
  468. (define player-loc
  469. (mbody-val (<-wait player 'get-loc)))
  470. (define our-name (slot-ref gameobj 'name))
  471. (define should-drop
  472. (slot-ref-maybe-runcheck gameobj 'drop-me? player))
  473. (define (room-objection-to-drop)
  474. (mbody-receive (_ drop-ok? #:key why-not) ; does the room object to dropping?
  475. (<-wait player-loc 'ok-to-drop-here? player (actor-id gameobj))
  476. (and (not drop-ok?)
  477. ;; Either give the specified reason, or give a boilerplate one
  478. (or why-not
  479. `("You'd love to drop " ,our-name
  480. " but for some reason it doesn't seem like you can"
  481. " do that here.")))))
  482. (cond
  483. ((not player-loc)
  484. (<- player 'tell
  485. #:text `("It doesn't seem like you can drop " ,our-name
  486. " here, because you don't seem to be anywhere?!?")))
  487. ;; TODO: Let ourselves supply a reason why not.
  488. ((not should-drop)
  489. (<- player 'tell
  490. #:text (format #f "It doesn't seem like you can drop ~a.\n"
  491. our-name)))
  492. ((room-objection-to-drop)
  493. (<- player 'tell
  494. #:text room-objection-to-drop))
  495. (else
  496. (gameobj-set-loc! gameobj player-loc)
  497. ;; TODO: Allow more flavortext here.
  498. (<- player 'tell
  499. #:text (format #f "You drop ~a.\n"
  500. our-name))
  501. (<- player-loc 'tell-room
  502. #:text (format #f "~a drops ~a.\n"
  503. player-name
  504. our-name)
  505. #:exclude player))))
  506. (define* (cmd-take-from-no-op gameobj message
  507. #:key direct-obj indir-obj preposition
  508. (player (message-from message)))
  509. (<- player 'tell
  510. #:text `("It doesn't seem like you can take anything "
  511. ,preposition " "
  512. ,(slot-ref gameobj 'name) ".")))
  513. (define* (cmd-put-in-no-op gameobj message
  514. #:key direct-obj indir-obj preposition
  515. (player (message-from message)))
  516. (<- player 'tell
  517. #:text `("It doesn't seem like you can put anything "
  518. ,preposition " "
  519. ,(slot-ref gameobj 'name) ".")))