1 /* vi: set ts=2 shiftwidth=2 expandtab:
2  *
3  * Copyright (C) 2003-2008  Simon Baldwin and Mark J. Tilford
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of version 2 of the GNU General Public License
7  * as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
17  * USA
18  */
19 
20 /*
21  * Module notes:
22  *
23  * o Ensure module messages precisely match the real Runner ones.  This
24  *   matters for ALRs.
25  *
26  * o Capacity checks on the player and on containers are implemented, but
27  *   may not be right.
28  */
29 
30 #include <assert.h>
31 #include <limits.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "scare.h"
37 #include "scprotos.h"
38 #include "scgamest.h"
39 
40 
41 /* Assorted definitions and constants. */
42 static const sc_char NUL = '\0';
43 static const sc_char COMMA = ',';
44 enum
45 { SECS_PER_MINUTE = 60,
46   MINS_PER_HOUR = 60,
47   SECS_PER_HOUR = 3600
48 };
49 enum { LIB_ALLOCATION_AVOIDANCE_SIZE = 128 };
50 
51 /* Trace flag, set before running. */
52 static sc_bool lib_trace = FALSE;
53 
54 
55 /*
56  * lib_warn_battle_system()
57  *
58  * Display a warning when the battle system is detected in a game.  Print
59  * directly rather than using the printfilter to avoid possible clashes
60  * with ALRs.
61  */
62 void
lib_warn_battle_system(void)63 lib_warn_battle_system (void)
64 {
65   if_print_tag (SC_TAG_FONT, "size=16");
66   if_print_string ("SCARE WARNING");
67   if_print_tag (SC_TAG_ENDFONT, "");
68 
69   if_print_string (
70     "\n\nThe game uses Adrift's Battle System, something not fully supported"
71     " by this release of SCARE.\n\n");
72 
73   if_print_string (
74     "SCARE will still run the game, but it will not create character"
75     " battles where they would normally occur.  For some games, this may"
76     " be perfectly okay, as the Battle System is sometimes turned on"
77     " by accident in a game, but never actually used.  For others, though,"
78     " the omission of this feature may be more serious.\n\n");
79 
80   if_print_string ("Please press a key to continue...\n\n");
81   if_print_tag (SC_TAG_WAITKEY, "");
82 }
83 
84 
85 /*
86  * lib_random_roomgroup_member()
87  *
88  * Return a random member of a roomgroup.
89  */
90 sc_int
lib_random_roomgroup_member(sc_gameref_t game,sc_int roomgroup)91 lib_random_roomgroup_member (sc_gameref_t game, sc_int roomgroup)
92 {
93   const sc_prop_setref_t bundle = gs_get_bundle (game);
94   sc_vartype_t vt_key[4];
95   sc_int count, room;
96 
97   /* Get the count of rooms in the group. */
98   vt_key[0].string = "RoomGroups";
99   vt_key[1].integer = roomgroup;
100   vt_key[2].string = "List2";
101   count = prop_get_child_count (bundle, "I<-sis", vt_key);
102   if (count == 0)
103     {
104       sc_fatal ("lib_random_roomgroup_member:"
105                 " no rooms in group %ld\n", roomgroup);
106     }
107 
108   /* Pick a room at random and return it. */
109   vt_key[3].integer = sc_randomint (0, count - 1);
110   room = prop_get_integer (bundle, "I<-sisi", vt_key);
111 
112   if (lib_trace)
113     {
114       sc_trace ("Library: random room for group %ld is %ld\n",
115                 roomgroup, room);
116     }
117 
118   return room;
119 }
120 
121 
122 /*
123  * lib_use_room_alt()
124  *
125  * Return TRUE if a particular alternate room description should be used.
126  */
127 static sc_bool
lib_use_room_alt(sc_gameref_t game,sc_int room,sc_int alt)128 lib_use_room_alt (sc_gameref_t game, sc_int room, sc_int alt)
129 {
130   const sc_prop_setref_t bundle = gs_get_bundle (game);
131   sc_vartype_t vt_key[5];
132   sc_int type;
133   sc_bool retval;
134 
135   /* Get alternate type. */
136   vt_key[0].string = "Rooms";
137   vt_key[1].integer = room;
138   vt_key[2].string = "Alts";
139   vt_key[3].integer = alt;
140   vt_key[4].string = "Type";
141   type = prop_get_integer (bundle, "I<-sisis", vt_key);
142 
143   /* Select based on type. */
144   retval = FALSE;
145   switch (type)
146     {
147     case 0:                    /* Task. */
148       {
149         sc_int var2, var3;
150 
151         vt_key[4].string = "Var2";
152         var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
153         if (var2 == 0)          /* No task. */
154           retval = TRUE;
155         else
156           {
157             vt_key[4].string = "Var3";
158             var3 = prop_get_integer (bundle, "I<-sisis", vt_key);
159 
160             retval = gs_task_done (game, var2 - 1) == !(var3 != 0);
161           }
162         break;
163       }
164 
165     case 1:                    /* Stateful object. */
166       {
167         sc_int var2, var3, object;
168 
169         vt_key[4].string = "Var2";
170         var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
171         if (var2 == 0)          /* No object. */
172           retval = TRUE;
173         else
174           {
175             vt_key[4].string = "Var3";
176             var3 = prop_get_integer (bundle, "I<-sisis", vt_key);
177 
178             object = obj_stateful_index (game, var2 - 1);
179             retval = restr_pass_task_object_state (game, object + 1, var3 - 1);
180           }
181         break;
182       }
183 
184     case 2:                    /* Player condition. */
185       {
186         sc_int var2, var3, object;
187 
188         vt_key[4].string = "Var2";
189         var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
190         vt_key[4].string = "Var3";
191         var3 = prop_get_integer (bundle, "I<-sisis", vt_key);
192 
193         if (var3 == 0)
194           {
195             switch (var2)
196               {
197               case 0: case 2: case 5:
198                 retval = TRUE;
199                 break;
200               case 1: case 3: case 4:
201                 retval = FALSE;
202                 break;
203               default:
204                 sc_fatal ("lib_use_room_alt:"
205                           " invalid player condition, %ld\n", var2);
206               }
207             break;
208           }
209 
210         if (var2 == 2 || var2 == 3)
211           object = obj_wearable_object (game, var3 - 1);
212         else
213           object = obj_dynamic_object (game, var3 - 1);
214 
215         switch (var2)
216           {
217           case 0:              /* Isn't holding (or wearing). */
218             retval = gs_object_position (game, object) != OBJ_HELD_PLAYER
219                      && gs_object_position (game, object) != OBJ_WORN_PLAYER;
220             break;
221           case 1:              /* Is holding (or wearing). */
222             retval = gs_object_position (game, object) == OBJ_HELD_PLAYER
223                      || gs_object_position (game, object) == OBJ_WORN_PLAYER;
224             break;
225           case 2:              /* Isn't wearing. */
226             retval = gs_object_position (game, object) != OBJ_WORN_PLAYER;
227             break;
228           case 3:              /* Is wearing. */
229             retval = gs_object_position (game, object) == OBJ_WORN_PLAYER;
230             break;
231           case 4:              /* Isn't in the same room as. */
232             retval = !obj_indirectly_in_room (game,
233                                               object, gs_playerroom (game));
234             break;
235           case 5:              /* Is in the same room as. */
236             retval = obj_indirectly_in_room (game,
237                                              object, gs_playerroom (game));
238             break;
239           default:
240             sc_fatal ("lib_use_room_alt:"
241                       " invalid player condition, %ld\n", var2);
242           }
243         break;
244       }
245 
246     default:
247       sc_fatal ("lib_use_room_alt: invalid type, %ld\n", type);
248     }
249 
250   return retval;
251 }
252 
253 
254 /*
255  * lib_find_starting_alt()
256  *
257  * Return the alt index for the alt at which we need to start running down
258  * the alts list when generating room names or descriptions.  Returns -1 if
259  * no alt overrides the default room long description.
260  */
261 static sc_int
lib_find_starting_alt(sc_gameref_t game,sc_int room)262 lib_find_starting_alt (sc_gameref_t game, sc_int room)
263 {
264   const sc_prop_setref_t bundle = gs_get_bundle (game);
265   sc_vartype_t vt_key[5];
266   sc_int alt_count, alt, retval;
267 
268   /* Get count of room alternates. */
269   vt_key[0].string = "Rooms";
270   vt_key[1].integer = room;
271   vt_key[2].string = "Alts";
272   alt_count = prop_get_child_count (bundle, "I<-sis", vt_key);
273 
274   /* Search backwards for a method-0 or method-1 overriding description. */
275   retval = -1;
276   for (alt = alt_count - 1; alt >= 0; alt--)
277     {
278       sc_int method;
279 
280       vt_key[3].integer = alt;
281       vt_key[4].string = "DisplayRoom";
282       method = prop_get_integer (bundle, "I<-sisis", vt_key);
283 
284       if (!(method == 0 || method == 1))
285         continue;
286 
287       if (lib_use_room_alt (game, room, alt))
288         {
289           const sc_char *m1;
290 
291           vt_key[3].integer = alt;
292           vt_key[4].string = "M1";
293           m1 = prop_get_string (bundle, "S<-sisis", vt_key);
294           if (!sc_strempty (m1))
295             {
296               retval = alt;
297               break;
298             }
299         }
300       else
301         {
302           const sc_char *m2;
303 
304           vt_key[3].integer = alt;
305           vt_key[4].string = "M2";
306           m2 = prop_get_string (bundle, "S<-sisis", vt_key);
307           if (!sc_strempty (m2))
308             {
309               retval = alt;
310               break;
311             }
312         }
313     }
314 
315   /* Return the index of the base alt, or -1 if none found. */
316   return retval;
317 }
318 
319 
320 /*
321  * lib_get_room_name()
322  * lib_print_room_name()
323  *
324  * Get/print out the name for a given room.
325  */
326 const sc_char *
lib_get_room_name(sc_gameref_t game,sc_int room)327 lib_get_room_name (sc_gameref_t game, sc_int room)
328 {
329   const sc_prop_setref_t bundle = gs_get_bundle (game);
330   sc_vartype_t vt_key[5];
331   sc_int alt_count, alt, start;
332   const sc_char *name;
333 
334   /* Get the basic room name, and the count of room alternates. */
335   vt_key[0].string = "Rooms";
336   vt_key[1].integer = room;
337   vt_key[2].string = "Short";
338   name = prop_get_string (bundle, "S<-sis", vt_key);
339 
340   vt_key[2].string = "Alts";
341   alt_count = prop_get_child_count (bundle, "I<-sis", vt_key);
342 
343   /* Get our starting point in the alts list. */
344   start = lib_find_starting_alt (game, room);
345 
346   /*
347    * Run forwards through all alts lower than our starting point, or all alts
348    * if no starting point found.
349    */
350   for (alt = (start != -1) ? start : 0; alt < alt_count; alt++)
351     {
352       /* Ignore all non-method-2 alts except for the starter. */
353       if (alt != start)
354         {
355           sc_int method;
356 
357           vt_key[3].integer = alt;
358           vt_key[4].string = "DisplayRoom";
359           method = prop_get_integer (bundle, "I<-sisis", vt_key);
360 
361           if (method != 2)
362             continue;
363         }
364 
365       /* If this alt offers a name change, note it and continue. */
366       if (lib_use_room_alt (game, room, alt))
367         {
368           const sc_char *changed;
369 
370           vt_key[3].integer = alt;
371           vt_key[4].string = "Changed";
372           changed = prop_get_string (bundle, "S<-sisis", vt_key);
373           if (!sc_strempty (changed))
374             name = changed;
375         }
376     }
377 
378   /* Return the final selected name. */
379   return name;
380 }
381 
382 void
lib_print_room_name(sc_gameref_t game,sc_int room)383 lib_print_room_name (sc_gameref_t game, sc_int room)
384 {
385   const sc_filterref_t filter = gs_get_filter (game);
386   const sc_char *name;
387 
388   /* Print the room name, possibly in bold. */
389   name = lib_get_room_name (game, room);
390   if (game->bold_room_names)
391     {
392       pf_buffer_tag (filter, SC_TAG_BOLD);
393       pf_buffer_string (filter, name);
394       pf_buffer_tag (filter, SC_TAG_ENDBOLD);
395     }
396   else
397     pf_buffer_string (filter, name);
398   pf_buffer_character (filter, '\n');
399 }
400 
401 
402 /*
403  * lib_print_object_np
404  * lib_print_object
405  *
406  * Convenience functions to print out an object's name, with a "normalized"
407  * prefix -- any "a"/"an"/"some" is replaced by "the" -- and with the full
408  * prefix.
409  */
410 static void
lib_print_object_np(sc_gameref_t game,sc_int object)411 lib_print_object_np (sc_gameref_t game, sc_int object)
412 {
413   const sc_filterref_t filter = gs_get_filter (game);
414   const sc_prop_setref_t bundle = gs_get_bundle (game);
415   sc_vartype_t vt_key[3];
416   const sc_char *prefix, *normalized, *name;
417 
418   /* Get the object's prefix. */
419   vt_key[0].string = "Objects";
420   vt_key[1].integer = object;
421   vt_key[2].string = "Prefix";
422   prefix = prop_get_string (bundle, "S<-sis", vt_key);
423 
424   /*
425    * Normalize by skipping any leading "a"/"an"/"some", replacing it instead
426    * with "the", and skipping any odd "the" already present.  If no prefix at
427    * all, add a "the " anyway.
428    *
429    * TODO This is empirical, based on observed Adrift Runner behavior, and
430    * what it's _really_ supposed to do is a mystery.  This routine has been a
431    * real PITA.
432    */
433   normalized = prefix;
434   if (sc_compare_word (prefix, "a", 1))
435     {
436       normalized = prefix + 1;
437       pf_buffer_string (filter, "the");
438     }
439   else if (sc_compare_word (prefix, "an", 2))
440     {
441       normalized = prefix + 2;
442       pf_buffer_string (filter, "the");
443     }
444   else if (sc_compare_word (prefix, "the", 3))
445     {
446       normalized = prefix + 3;
447       pf_buffer_string (filter, "the");
448     }
449   else if (sc_compare_word (prefix, "some", 4))
450     {
451       normalized = prefix + 4;
452       pf_buffer_string (filter, "the");
453     }
454   else if (sc_strempty (prefix))
455     pf_buffer_string (filter, "the ");
456 
457   /*
458    * If the remaining normalized prefix isn't empty, print it, and a space.
459    * If it is, then consider adding a space to any "the" printed above, except
460    * for the one done for empty prefixes, that is.
461    */
462   if (!sc_strempty (normalized))
463     {
464       pf_buffer_string (filter, normalized);
465       pf_buffer_character (filter, ' ');
466     }
467   else if (normalized > prefix)
468     pf_buffer_character (filter, ' ');
469 
470   /*
471    * Print the object's name; here we also look for a leading article and
472    * strip if found -- some games may avoid prefix and do this instead.
473    */
474   vt_key[2].string = "Short";
475   name = prop_get_string (bundle, "S<-sis", vt_key);
476   if (sc_compare_word (name, "a", 1))
477     name += 1;
478   else if (sc_compare_word (name, "an", 2))
479     name += 2;
480   else if (sc_compare_word (name, "the", 3))
481     name += 3;
482   else if (sc_compare_word (name, "some", 4))
483     name += 4;
484   pf_buffer_string (filter, name);
485 }
486 
487 static void
lib_print_object(sc_gameref_t game,sc_int object)488 lib_print_object (sc_gameref_t game, sc_int object)
489 {
490   const sc_filterref_t filter = gs_get_filter (game);
491   const sc_prop_setref_t bundle = gs_get_bundle (game);
492   sc_vartype_t vt_key[3];
493   const sc_char *prefix, *name;
494 
495   /*
496    * Get the object's prefix, and print if not empty, otherwise default to an
497    * "a " prefix, as that's what Adrift seems to do.
498    */
499   vt_key[0].string = "Objects";
500   vt_key[1].integer = object;
501   vt_key[2].string = "Prefix";
502   prefix = prop_get_string (bundle, "S<-sis", vt_key);
503   if (!sc_strempty (prefix))
504     {
505       pf_buffer_string (filter, prefix);
506       pf_buffer_character (filter, ' ');
507     }
508   else
509     pf_buffer_string (filter, "a ");
510 
511   /* Print object name. */
512   vt_key[2].string = "Short";
513   name = prop_get_string (bundle, "S<-sis", vt_key);
514   pf_buffer_string (filter, name);
515 }
516 
517 
518 /*
519  * lib_print_npc_np
520  * lib_print_npc
521  *
522  * Convenience functions to print out an NPC's name, with and without
523  * any prefix.
524  */
525 static void
lib_print_npc_np(sc_gameref_t game,sc_int npc)526 lib_print_npc_np (sc_gameref_t game, sc_int npc)
527 {
528   const sc_filterref_t filter = gs_get_filter (game);
529   const sc_prop_setref_t bundle = gs_get_bundle (game);
530   sc_vartype_t vt_key[3];
531   const sc_char *name;
532 
533   /* Get the NPC's short description, and print it. */
534   vt_key[0].string = "NPCs";
535   vt_key[1].integer = npc;
536   vt_key[2].string = "Name";
537   name = prop_get_string (bundle, "S<-sis", vt_key);
538 
539   pf_buffer_string (filter, name);
540 }
541 
542 #if 0
543 static void
544 lib_print_npc (sc_gameref_t game, sc_int npc)
545 {
546   const sc_filterref_t filter = gs_get_filter (game);
547   const sc_prop_setref_t bundle = gs_get_bundle (game);
548   sc_vartype_t vt_key[3];
549   const sc_char *prefix;
550 
551   /* Get the NPC's prefix. */
552   vt_key[0].string = "NPCs";
553   vt_key[1].integer = npc;
554   vt_key[2].string = "Prefix";
555   prefix = prop_get_string (bundle, "S<-sis", vt_key);
556 
557   /* If the prefix isn't empty, print it, then print NPC name. */
558   if (!sc_strempty (prefix))
559     {
560       pf_buffer_string (filter, prefix);
561       pf_buffer_character (filter, ' ');
562     }
563   lib_print_npc_np (game, npc);
564 }
565 #endif
566 
567 
568 /*
569  * lib_select_response()
570  * lib_select_plurality()
571  *
572  * Convenience functions for multiple handlers.  Returns the appropriate
573  * response string for a game, based on perspective or object plurality.
574  */
575 static const sc_char *
lib_select_response(sc_gameref_t game,const sc_char * second_person,const sc_char * first_person,const sc_char * third_person)576 lib_select_response (sc_gameref_t game,
577                      const sc_char *second_person,
578                      const sc_char *first_person,
579                      const sc_char *third_person)
580 {
581   const sc_prop_setref_t bundle = gs_get_bundle (game);
582   sc_vartype_t vt_key[2];
583   sc_int perspective;
584   const sc_char *response;
585 
586   /* Return the response appropriate for Perspective. */
587   vt_key[0].string = "Globals";
588   vt_key[1].string = "Perspective";
589   perspective = prop_get_integer (bundle, "I<-ss", vt_key);
590   switch (perspective)
591     {
592     case LIB_FIRST_PERSON:
593       response = first_person;
594       break;
595     case LIB_SECOND_PERSON:
596       response = second_person;
597       break;
598     case LIB_THIRD_PERSON:
599       response = third_person;
600       break;
601     default:
602       sc_error ("lib_select_response:"
603                 " unknown perspective, %ld\n", perspective);
604       response = second_person;
605       break;
606     }
607 
608   return response;
609 }
610 
611 static const sc_char *
lib_select_plurality(sc_gameref_t game,sc_int object,const sc_char * singular,const sc_char * plural)612 lib_select_plurality (sc_gameref_t game, sc_int object,
613                       const sc_char *singular, const sc_char *plural)
614 {
615   return obj_appears_plural (game, object) ? plural : singular;
616 }
617 
618 
619 /*
620  * lib_get_npc_inroom_text()
621  *
622  * Returns the inroom description to be use for an NPC; if the NPC has
623  * gone walkabout and offers a changed description, return that; otherwise
624  * return the standard inroom text.
625  */
626 static const sc_char *
lib_get_npc_inroom_text(sc_gameref_t game,sc_int npc)627 lib_get_npc_inroom_text (sc_gameref_t game, sc_int npc)
628 {
629   const sc_prop_setref_t bundle = gs_get_bundle (game);
630   sc_vartype_t vt_key[5];
631   sc_int walk_count, walk;
632   const sc_char *inroomtext;
633 
634   /* Get the count of NPC walks. */
635   vt_key[0].string = "NPCs";
636   vt_key[1].integer = npc;
637   vt_key[2].string = "Walks";
638   walk_count = prop_get_child_count (bundle, "I<-sis", vt_key);
639 
640   /* Check for any active walk with a description, return if found. */
641   for (walk = walk_count - 1; walk >= 0; walk--)
642     {
643       if (gs_npc_walkstep (game, npc, walk) > 0)
644         {
645           const sc_char *changeddesc;
646 
647           /* Get and check any walk active description. */
648           vt_key[3].integer = walk;
649           vt_key[4].string = "ChangedDesc";
650           changeddesc = prop_get_string (bundle, "S<-sisis", vt_key);
651           if (!sc_strempty (changeddesc))
652             return changeddesc;
653         }
654     }
655 
656   /* Return the standard inroom text. */
657   vt_key[2].string = "InRoomText";
658   inroomtext = prop_get_string (bundle, "S<-sis", vt_key);
659   return inroomtext;
660 }
661 
662 
663 /*
664  * lib_print_room_contents()
665  *
666  * Print a list of the contents of a room.
667  */
668 static void
lib_print_room_contents(sc_gameref_t game,sc_int room)669 lib_print_room_contents (sc_gameref_t game, sc_int room)
670 {
671   const sc_filterref_t filter = gs_get_filter (game);
672   const sc_prop_setref_t bundle = gs_get_bundle (game);
673   sc_vartype_t vt_key[4];
674   sc_int object, npc, count, trail;
675 
676   /* List all objects that show their initial description. */
677   count = 0;
678   for (object = 0; object < gs_object_count (game); object++)
679     {
680       if (obj_directly_in_room (game, object, room)
681           && obj_shows_initial_description (game, object))
682         {
683           const sc_char *inroomdesc;
684 
685           /* Find and print in room description. */
686           vt_key[0].string = "Objects";
687           vt_key[1].integer = object;
688           vt_key[2].string = "InRoomDesc";
689           inroomdesc = prop_get_string (bundle, "S<-sis", vt_key);
690           if (!sc_strempty (inroomdesc))
691             {
692               if (count == 0)
693                 pf_buffer_character (filter, '\n');
694               else
695                 pf_buffer_string (filter, "  ");
696               pf_buffer_string (filter, inroomdesc);
697               count++;
698             }
699         }
700     }
701   if (count > 0)
702     pf_buffer_character (filter, '\n');
703 
704   /*
705    * List dynamic objects directly located in the room, and not already listed
706    * above since they lack, or suppress, an in room description.
707    *
708    * If an object sets ListFlag, then if dynamic it's suppressed from the list
709    * where it would normally be included, but if static it's included where it
710    * would normally be excluded.
711    */
712   count = 0;
713   trail = -1;
714   for (object = 0; object < gs_object_count (game); object++)
715     {
716       if (obj_directly_in_room (game, object, room))
717         {
718           const sc_char *inroomdesc;
719 
720           vt_key[0].string = "Objects";
721           vt_key[1].integer = object;
722           vt_key[2].string = "InRoomDesc";
723           inroomdesc = prop_get_string (bundle, "S<-sis", vt_key);
724 
725           if (!obj_shows_initial_description (game, object)
726               || sc_strempty (inroomdesc))
727             {
728               sc_bool listflag;
729 
730               vt_key[2].string = "ListFlag";
731               listflag = prop_get_boolean (bundle, "B<-sis", vt_key);
732 
733               if (listflag == obj_is_static (game, object))
734                 {
735                   if (count > 0)
736                     {
737                       if (count == 1)
738                         pf_buffer_string (filter,
739                                           lib_select_plurality (game, trail,
740                                                            "\nAlso here is ",
741                                                            "\nAlso here are "));
742                       else
743                         pf_buffer_string (filter, ", ");
744                       lib_print_object (game, trail);
745                     }
746                   trail = object;
747                   count++;
748                 }
749             }
750         }
751     }
752   if (count >= 1)
753     {
754       if (count == 1)
755         pf_buffer_string (filter,
756                           lib_select_plurality (game, trail,
757                                                 "\nAlso here is ",
758                                                 "\nAlso here are "));
759       else
760         pf_buffer_string (filter, " and ");
761       lib_print_object (game, trail);
762       pf_buffer_string (filter, ".\n");
763     }
764 
765   /* List NPCs directly in the room that have an in room description. */
766   count = 0;
767   for (npc = 0; npc < gs_npc_count (game); npc++)
768     {
769       if (npc_in_room (game, npc, room))
770         {
771           const sc_char *description;
772 
773           /* Print any non='#' in-room description. */
774           description = lib_get_npc_inroom_text (game, npc);
775           if (!sc_strempty (description) && sc_strcasecmp (description, "#"))
776             {
777               if (count == 0)
778                 pf_buffer_character (filter, '\n');
779               else
780                 pf_buffer_string (filter, "  ");
781               pf_buffer_string (filter, description);
782               count++;
783             }
784         }
785     }
786   if (count > 0)
787     pf_buffer_character (filter, '\n');
788 
789   /*
790    * List NPCs in the room that don't have an in room description and that
791    * request a default "...is here" with "#".
792    *
793    * TODO Is this right?
794    */
795   count = 0;
796   trail = -1;
797   for (npc = 0; npc < gs_npc_count (game); npc++)
798     {
799       if (npc_in_room (game, npc, room))
800         {
801           const sc_char *description;
802 
803           /* Print name for descriptions marked '#'. */
804           description = lib_get_npc_inroom_text (game, npc);
805           if (!sc_strempty (description) && !sc_strcasecmp (description, "#"))
806             {
807               if (count > 0)
808                 {
809                   if (count > 1)
810                     pf_buffer_string (filter, ", ");
811                   else
812                     {
813                       pf_buffer_character (filter, '\n');
814                       pf_new_sentence (filter);
815                     }
816                   lib_print_npc_np (game, trail);
817                 }
818               trail = npc;
819               count++;
820             }
821         }
822     }
823   if (count >= 1)
824     {
825       if (count == 1)
826         {
827           pf_buffer_character (filter, '\n');
828           pf_new_sentence (filter);
829           lib_print_npc_np (game, trail);
830           pf_buffer_string (filter, " is here");
831         }
832       else
833         {
834           pf_buffer_string (filter, " and ");
835           lib_print_npc_np (game, trail);
836           pf_buffer_string (filter, " are here");
837         }
838       pf_buffer_string (filter, ".\n");
839     }
840 }
841 
842 
843 /*
844  * lib_print_room_description()
845  *
846  * Print out the long description for a given room.
847  */
848 void
lib_print_room_description(sc_gameref_t game,sc_int room)849 lib_print_room_description (sc_gameref_t game, sc_int room)
850 {
851   const sc_filterref_t filter = gs_get_filter (game);
852   const sc_prop_setref_t bundle = gs_get_bundle (game);
853   sc_vartype_t vt_key[5];
854   sc_bool showobjects, is_described, is_suppressed;
855   sc_int alt_count, alt, start, event;
856 
857   /* Get count of room alternates. */
858   vt_key[0].string = "Rooms";
859   vt_key[1].integer = room;
860   vt_key[2].string = "Alts";
861   alt_count = prop_get_child_count (bundle, "I<-sis", vt_key);
862 
863   /* Start with no description, and get our starting point in the alts list. */
864   is_described = FALSE;
865   start = lib_find_starting_alt (game, room);
866 
867   /* Print the standard description unless a start alt indicates not. */
868   if (start == -1)
869     is_suppressed = FALSE;
870   else
871     {
872       sc_int method;
873 
874       vt_key[3].integer = start;
875       vt_key[4].string = "DisplayRoom";
876       method = prop_get_integer (bundle, "I<-sisis", vt_key);
877 
878       is_suppressed = (method == 0);
879     }
880   if (!is_suppressed)
881     {
882       const sc_char *description;
883 
884       vt_key[0].string = "Rooms";
885       vt_key[1].integer = room;
886       vt_key[2].string = "Long";
887       description = prop_get_string (bundle, "S<-sis", vt_key);
888       if (!sc_strempty (description))
889         {
890           pf_buffer_string (filter, description);
891           is_described = TRUE;
892         }
893 
894       vt_key[2].string = "Res";
895       res_handle_resource (game, "sis", vt_key);
896     }
897 
898   /* Ensure that we're back to handling room alts. */
899   vt_key[0].string = "Rooms";
900   vt_key[1].integer = room;
901   vt_key[2].string = "Alts";
902 
903   /*
904    * Run forwards through all alts lower than our starting point, or all alts
905    * if no starting point overrider found.
906    */
907   showobjects = TRUE;
908   for (alt = (start != -1) ? start : 0; alt < alt_count; alt++)
909     {
910       /* Ignore all non-method-2 alts except for the starter. */
911       if (alt != start)
912         {
913           sc_int method;
914 
915           vt_key[3].integer = alt;
916           vt_key[4].string = "DisplayRoom";
917           method = prop_get_integer (bundle, "I<-sisis", vt_key);
918 
919           if (method != 2)
920             continue;
921         }
922 
923       if (lib_use_room_alt (game, room, alt))
924         {
925           const sc_char *m1;
926           sc_int hideobjects;
927 
928           vt_key[3].integer = alt;
929           vt_key[4].string = "M1";
930           m1 = prop_get_string (bundle, "S<-sisis", vt_key);
931           if (!sc_strempty (m1))
932             {
933               if (is_described)
934                 pf_buffer_string (filter, "  ");
935               pf_buffer_string (filter, m1);
936               is_described = TRUE;
937             }
938 
939           vt_key[4].string = "Res1";
940           res_handle_resource (game, "sisis", vt_key);
941 
942           vt_key[4].string = "HideObjects";
943           hideobjects = prop_get_integer (bundle, "I<-sisis", vt_key);
944           if (hideobjects == 1)
945             showobjects = FALSE;
946         }
947       else
948         {
949           const sc_char *m2;
950 
951           vt_key[3].integer = alt;
952           vt_key[4].string = "M2";
953           m2 = prop_get_string (bundle, "S<-sisis", vt_key);
954           if (!sc_strempty (m2))
955             {
956               if (is_described)
957                 pf_buffer_string (filter, "  ");
958               pf_buffer_string (filter, m2);
959               is_described = TRUE;
960             }
961 
962           vt_key[4].string = "Res2";
963           res_handle_resource (game, "sisis", vt_key);
964         }
965     }
966 
967   /* Print out any relevant event look text. */
968   for (event = 0; event < gs_event_count (game); event++)
969     {
970       if (gs_event_state (game, event) == ES_RUNNING
971           && evt_can_see_event (game, event))
972         {
973           const sc_char *looktext;
974 
975           vt_key[0].string = "Events";
976           vt_key[1].integer = event;
977           vt_key[2].string = "LookText";
978           looktext = prop_get_string (bundle, "S<-sis", vt_key);
979           if (is_described)
980             pf_buffer_string (filter, "  ");
981           pf_buffer_string (filter, looktext);
982           is_described = TRUE;
983 
984           vt_key[2].string = "Res";
985           vt_key[3].integer = 1;
986           res_handle_resource (game, "sisi", vt_key);
987         }
988     }
989   if (is_described)
990     pf_buffer_character (filter, '\n');
991 
992   /* Finally, print room contents. */
993   if (showobjects)
994     lib_print_room_contents (game, room);
995 }
996 
997 
998 /*
999  * lib_can_go()
1000  *
1001  * Return TRUE if the player can move in the given direction.
1002  */
1003 static sc_bool
lib_can_go(sc_gameref_t game,sc_int room,sc_int direction)1004 lib_can_go (sc_gameref_t game, sc_int room, sc_int direction)
1005 {
1006   const sc_prop_setref_t bundle = gs_get_bundle (game);
1007   sc_vartype_t vt_key[5];
1008   sc_int restriction;
1009   sc_bool is_restricted = FALSE;
1010 
1011   /* Set up invariant parts of key. */
1012   vt_key[0].string = "Rooms";
1013   vt_key[1].integer = room;
1014   vt_key[2].string = "Exits";
1015   vt_key[3].integer = direction;
1016 
1017   /* Check for any movement restrictions. */
1018   vt_key[4].string = "Var1";
1019   restriction = prop_get_integer (bundle, "I<-sisis", vt_key) - 1;
1020   if (restriction >= 0)
1021     {
1022       sc_int type;
1023 
1024       if (lib_trace)
1025         sc_trace ("Library: hit move restriction\n");
1026 
1027       /* Get restriction type. */
1028       vt_key[4].string = "Var3";
1029       type = prop_get_integer (bundle, "I<-sisis", vt_key);
1030       switch (type)
1031         {
1032         case 0:                /* Task type restriction */
1033           {
1034             sc_int check;
1035 
1036             /* Get the expected completion state. */
1037             vt_key[4].string = "Var2";
1038             check = prop_get_integer (bundle, "I<-sisis", vt_key);
1039 
1040             if (lib_trace)
1041               {
1042                 sc_trace ("Library: task %ld, check %ld\n",
1043                           restriction, check);
1044               }
1045 
1046             /* Restrict if task isn't done/not done as expected. */
1047             if ((check != 0) == gs_task_done (game, restriction))
1048               is_restricted = TRUE;
1049             break;
1050           }
1051 
1052         case 1:                /* Object state restriction */
1053           {
1054             sc_int object, check, openable;
1055 
1056             /* Get the target object. */
1057             object = obj_stateful_object (game, restriction);
1058 
1059             /* Get the expected object state. */
1060             vt_key[4].string = "Var2";
1061             check = prop_get_integer (bundle, "I<-sisis", vt_key);
1062 
1063             if (lib_trace)
1064               sc_trace ("Library: object %ld, check %ld\n", object, check);
1065 
1066             /* Check openable and lockable objects. */
1067             vt_key[0].string = "Objects";
1068             vt_key[1].integer = object;
1069             vt_key[2].string = "Openable";
1070             openable = prop_get_integer (bundle, "I<-sis", vt_key);
1071             if (openable > 0)
1072               {
1073                 sc_int lockable;
1074 
1075                 /* See if lockable. */
1076                 vt_key[2].string = "Key";
1077                 lockable = prop_get_integer (bundle, "I<-sis", vt_key);
1078                 if (lockable >= 0)
1079                   {
1080                     /* Lockable. */
1081                     if (check <= 2)
1082                       {
1083                         if (gs_object_openness (game, object) != check + 5)
1084                           is_restricted = TRUE;
1085                       }
1086                     else
1087                       {
1088                         if (gs_object_state (game, object) != check - 2)
1089                           is_restricted = TRUE;
1090                       }
1091                   }
1092                 else
1093                   {
1094                     /* Not lockable, though openable. */
1095                     if (check <= 1)
1096                       {
1097                         if (gs_object_openness (game, object) != check + 5)
1098                           is_restricted = TRUE;
1099                       }
1100                     else
1101                       {
1102                         if (gs_object_state (game, object) != check - 1)
1103                           is_restricted = TRUE;
1104                       }
1105                   }
1106               }
1107             else
1108               {
1109                 /* Not openable. */
1110                 if (gs_object_state (game, object) != check + 1)
1111                   is_restricted = TRUE;
1112               }
1113             break;
1114           }
1115         }
1116     }
1117 
1118   /* Return TRUE if not restricted. */
1119   return !is_restricted;
1120 }
1121 
1122 
1123 /* List of direction names, for printing and counting exits. */
1124 static const sc_char *const DIRNAMES_4[] = {
1125   "north", "east", "south", "west", "up", "down", "in", "out",
1126   NULL
1127 };
1128 static const sc_char *const DIRNAMES_8[] = {
1129   "north", "east", "south", "west", "up", "down", "in", "out",
1130   "northeast", "southeast", "southwest", "northwest",
1131   NULL
1132 };
1133 
1134 
1135 /*
1136  * lib_cmd_print_room_exits()
1137  *
1138  * Print a list of exits from the player room.
1139  */
1140 sc_bool
lib_cmd_print_room_exits(sc_gameref_t game)1141 lib_cmd_print_room_exits (sc_gameref_t game)
1142 {
1143   const sc_filterref_t filter = gs_get_filter (game);
1144   const sc_prop_setref_t bundle = gs_get_bundle (game);
1145   sc_vartype_t vt_key[4];
1146   sc_bool eightpointcompass;
1147   const sc_char *const *dirnames;
1148   sc_int count, index_, trail;
1149 
1150   /* Decide on four or eight point compass names list. */
1151   vt_key[0].string = "Globals";
1152   vt_key[1].string = "EightPointCompass";
1153   eightpointcompass = prop_get_boolean (bundle, "B<-ss", vt_key);
1154   dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4;
1155 
1156   /* Poll for an exit for each valid direction name. */
1157   count = 0;
1158   trail = -1;
1159   for (index_ = 0; dirnames[index_]; index_++)
1160     {
1161       sc_vartype_t vt_rvalue;
1162 
1163       vt_key[0].string = "Rooms";
1164       vt_key[1].integer = gs_playerroom (game);
1165       vt_key[2].string = "Exits";
1166       vt_key[3].integer = index_;
1167       if (prop_get (bundle, "I<-sisi", &vt_rvalue, vt_key)
1168           && lib_can_go (game, gs_playerroom (game), index_))
1169         {
1170           if (count > 0)
1171             {
1172               if (count == 1)
1173                 {
1174                   /* Vary text slightly for DispFirstRoom. */
1175                   if (game->turns == 0)
1176                     pf_buffer_string (filter, "There are exits ");
1177                   else
1178                     pf_buffer_string (filter,
1179                                       lib_select_response (game,
1180                                                          "You can move ",
1181                                                          "I can move ",
1182                                                          "%player% can move "));
1183                 }
1184               else
1185                 pf_buffer_string (filter, ", ");
1186               pf_buffer_string (filter, dirnames[trail]);
1187             }
1188           trail = index_;
1189           count++;
1190         }
1191     }
1192   if (count >= 1)
1193     {
1194       if (count == 1)
1195         {
1196           /* Vary text slightly for DispFirstRoom. */
1197           if (game->turns == 0)
1198             pf_buffer_string (filter, "There is an exit ");
1199           else
1200             pf_buffer_string (filter,
1201                               lib_select_response (game,
1202                                                    "You can only move ",
1203                                                    "I can only move ",
1204                                                    "%player% can only move "));
1205         }
1206       else
1207         pf_buffer_string (filter, " and ");
1208       pf_buffer_string (filter, dirnames[trail]);
1209       pf_buffer_string (filter, ".\n");
1210     }
1211   else
1212     {
1213       pf_buffer_string (filter,
1214                         lib_select_response (game,
1215                                       "You can't go in any direction!\n",
1216                                       "I can't go in any direction!\n",
1217                                       "%player% can't go in any direction!\n"));
1218     }
1219 
1220   return TRUE;
1221 }
1222 
1223 
1224 /*
1225  * lib_describe_player_room()
1226  *
1227  * Print out details of the player room, in brief if verbose not set and the
1228  * room has already been visited.
1229  */
1230 static void
lib_describe_player_room(sc_gameref_t game,sc_bool force_verbose)1231 lib_describe_player_room (sc_gameref_t game, sc_bool force_verbose)
1232 {
1233   const sc_filterref_t filter = gs_get_filter (game);
1234   const sc_prop_setref_t bundle = gs_get_bundle (game);
1235   sc_vartype_t vt_key[2];
1236 
1237   /* Print the room name. */
1238   lib_print_room_name (game, gs_playerroom (game));
1239 
1240   /* Print other room details if applicable. */
1241   if (force_verbose
1242       || game->verbose || !gs_room_seen (game, gs_playerroom (game)))
1243     {
1244       sc_bool showexits;
1245 
1246       /* Print room description, and objects and NPCs. */
1247       lib_print_room_description (game, gs_playerroom (game));
1248 
1249       /* Print exits if the ShowExits global requests it. */
1250       vt_key[0].string = "Globals";
1251       vt_key[1].string = "ShowExits";
1252       showexits = prop_get_boolean (bundle, "B<-ss", vt_key);
1253       if (showexits)
1254         {
1255           pf_buffer_character (filter, '\n');
1256           lib_cmd_print_room_exits (game);
1257         }
1258     }
1259 }
1260 
1261 
1262 /*
1263  * lib_cmd_look()
1264  *
1265  * Command handler for "look" command.
1266  */
1267 sc_bool
lib_cmd_look(sc_gameref_t game)1268 lib_cmd_look (sc_gameref_t game)
1269 {
1270   const sc_filterref_t filter = gs_get_filter (game);
1271 
1272   pf_buffer_character (filter, '\n');
1273   lib_describe_player_room (game, TRUE);
1274   return TRUE;
1275 }
1276 
1277 
1278 /*
1279  * lib_cmd_quit()
1280  *
1281  * Called on "quit".  Exits from the game main loop.
1282  */
1283 sc_bool
lib_cmd_quit(sc_gameref_t game)1284 lib_cmd_quit (sc_gameref_t game)
1285 {
1286   if (if_confirm (SC_CONF_QUIT))
1287     game->is_running = FALSE;
1288 
1289   game->is_admin = TRUE;
1290   return TRUE;
1291 }
1292 
1293 
1294 /*
1295  * lib_cmd_restart()
1296  *
1297  * Called on "restart".  Exits from the game main loop with restart
1298  * request set.
1299  */
1300 sc_bool
lib_cmd_restart(sc_gameref_t game)1301 lib_cmd_restart (sc_gameref_t game)
1302 {
1303   if (if_confirm (SC_CONF_RESTART))
1304     {
1305       game->is_running = FALSE;
1306       game->do_restart = TRUE;
1307     }
1308 
1309   game->is_admin = TRUE;
1310   return TRUE;
1311 }
1312 
1313 
1314 /*
1315  * lib_cmd_undo()
1316  *
1317  * Called on "undo".  Restores any undo game or memo to the main game.
1318  */
1319 sc_bool
lib_cmd_undo(sc_gameref_t game)1320 lib_cmd_undo (sc_gameref_t game)
1321 {
1322   const sc_filterref_t filter = gs_get_filter (game);
1323   const sc_memo_setref_t memento = gs_get_memento (game);
1324 
1325   /* If an undo buffer is available, restore it. */
1326   if (game->undo_available)
1327     {
1328       gs_copy (game, game->undo);
1329       game->undo_available = FALSE;
1330 
1331       lib_print_room_name (game, gs_playerroom (game));
1332       pf_buffer_string (filter, "[The previous turn has been undone.]\n");
1333 
1334       /* Undo can't properly unravel layered sounds... */
1335       game->stop_sound = TRUE;
1336     }
1337 
1338   /*
1339    * If there is no undo buffer, try to restore one saved previously in a
1340    * memo.  If that works, treat as for restore from file, since that's
1341    * effectively what it is.
1342    */
1343   else if (memo_load_game (memento, game))
1344     {
1345       lib_print_room_name (game, gs_playerroom (game));
1346       pf_buffer_string (filter, "[The previous turn has been undone.]\n");
1347 
1348       game->is_running = FALSE;
1349       game->do_restore = TRUE;
1350     }
1351 
1352   /* If no undo buffer and memo restore failed, there's no undo available. */
1353   else if (game->turns == 0)
1354     pf_buffer_string (filter, "You can't undo what hasn't been done.\n");
1355   else
1356     pf_buffer_string (filter, "Sorry, no more undo is available.\n");
1357 
1358   game->is_admin = TRUE;
1359   return TRUE;
1360 }
1361 
1362 
1363 /*
1364  * lib_cmd_history_common()
1365  * lib_cmd_history_number()
1366  * lib_cmd_history()
1367  *
1368  * Prints a history of saved commands for the game.  Print directly rather
1369  * than using the printfilter to avoid possible clashes with ALRs.
1370  */
1371 static sc_bool
lib_cmd_history_common(sc_gameref_t game,sc_int limit)1372 lib_cmd_history_common (sc_gameref_t game, sc_int limit)
1373 {
1374   const sc_var_setref_t vars = gs_get_vars (game);
1375   const sc_memo_setref_t memento = gs_get_memento (game);
1376   sc_int first, count, timestamp;
1377 
1378   /*
1379    * The runner main loop will add an entry for the "history" command that
1380    * got us here, but it hasn't done so yet.  To keep the history list
1381    * accurate for recalling commands, we add a surrogate "history" command
1382    * to the history here, and remove it when we've done listing.  This matches
1383    * the c-shell, which always shows 'history' listed last.
1384    */
1385   timestamp = var_get_elapsed_seconds (vars);
1386   memo_save_command (memento, "[history]", timestamp, game->turns);
1387 
1388   /* Decide on the first history to display; all if limit is 0 or less. */
1389   if (limit > 0)
1390     {
1391       /*
1392        * Get a count of the history length recorded.  Because of the surrogate
1393        * "history" above, this is always at least one.  From this, choose a
1394        * start point for the display; all if not enough history.
1395        */
1396       count = memo_get_command_count (memento);
1397       first = (count > limit) ? count - limit : 0;
1398     }
1399   else
1400     first = 0;
1401 
1402   if_print_string ("These are your most recent game commands:\n\n");
1403 
1404   /* Display history starting at the first entry determined above. */
1405   memo_first_command (memento);
1406   for (count = 0; memo_more_commands (memento); count++)
1407     {
1408       const sc_char *command;
1409       sc_int sequence, turns;
1410 
1411       /* Obtain the history entry, and write if included. */
1412       memo_next_command (memento, &command, &sequence, &timestamp, &turns);
1413       if (count >= first)
1414         {
1415           sc_int hr, min, sec;
1416           sc_char buffer[64];
1417 
1418           /* Write the history entry sequence. */
1419           sprintf (buffer, "%4ld -- Time ", sequence);
1420           if_print_string (buffer);
1421 
1422           /* Separate the timestamp out into components. */
1423           hr = timestamp / SECS_PER_HOUR;
1424           min = (timestamp % SECS_PER_HOUR) / MINS_PER_HOUR;
1425           sec = timestamp % SECS_PER_MINUTE;
1426 
1427           /* Print playing time as "[HHh ][M]Mm SSs". */
1428           if (hr > 0)
1429             sprintf (buffer, "%ldh %02ldm %02lds", hr, min, sec);
1430           else
1431             sprintf (buffer, "%ldm %02lds", min, sec);
1432           if_print_string (buffer);
1433 
1434           /* Follow up with the turns count, and the command string itself. */
1435           sprintf (buffer, ", turn %ld : ", turns);
1436           if_print_string (buffer);
1437           if_print_string (command);
1438           if_print_character ('\n');
1439         }
1440     }
1441 
1442   /* Remove the surrogate "history"; the main loop will add the real one. */
1443   memo_unsave_command (memento);
1444 
1445   game->is_admin = TRUE;
1446   return TRUE;
1447 }
1448 
1449 sc_bool
lib_cmd_history_number(sc_gameref_t game)1450 lib_cmd_history_number (sc_gameref_t game)
1451 {
1452   const sc_var_setref_t vars = gs_get_vars (game);
1453   sc_int limit;
1454 
1455   /* Get requested length of history list, and complain if not valid. */
1456   limit = var_get_ref_number (vars);
1457   if (limit < 1)
1458     {
1459       if_print_string ("That's not a valid history length.\n");
1460 
1461       game->is_admin = TRUE;
1462       return TRUE;
1463     }
1464 
1465   return lib_cmd_history_common (game, limit);
1466 }
1467 
1468 sc_bool
lib_cmd_history(sc_gameref_t game)1469 lib_cmd_history (sc_gameref_t game)
1470 {
1471   return lib_cmd_history_common (game, 0);
1472 }
1473 
1474 
1475 /*
1476  * lib_cmd_again()
1477  * lib_cmd_redo_number()
1478  * lib_cmd_redo_text_last_common()
1479  * lib_cmd_redo_text()
1480  * lib_cmd_redo_last()
1481  *
1482  * The first function is called on "again", and simply sets the game do_again
1483  * flag.  The others allow the user to select a command from the history list
1484  * to re-run.
1485  */
1486 sc_bool
lib_cmd_again(sc_gameref_t game)1487 lib_cmd_again (sc_gameref_t game)
1488 {
1489   game->do_again = TRUE;
1490   game->redo_sequence = 0;
1491 
1492   game->is_admin = TRUE;
1493   return TRUE;
1494 }
1495 
1496 sc_bool
lib_cmd_redo_number(sc_gameref_t game)1497 lib_cmd_redo_number (sc_gameref_t game)
1498 {
1499   const sc_var_setref_t vars = gs_get_vars (game);
1500   const sc_memo_setref_t memento = gs_get_memento (game);
1501   sc_int sequence;
1502 
1503   /*
1504    * Get the history sequence entry requested and validate it.  The sequence
1505    * may be positive (absolute) or negative (relative to history end), but
1506    * not zero.
1507    */
1508   sequence = var_get_ref_number (vars);
1509   if (sequence != 0 && memo_find_command (memento, sequence))
1510     {
1511       game->do_again = TRUE;
1512       game->redo_sequence = sequence;
1513     }
1514   else
1515     {
1516       if_print_string ("No matching entry found in the command history.\n");
1517 
1518       /*
1519        * This is a failed redo, but returning FALSE will cause the game's
1520        * unknown command message to come up.  However, returning TRUE will
1521        * cause the runner main loop to add this to its history, and at some
1522        * point a "redo 7" could cause problems (say, when it's at sequence 7,
1523        * where it'll cause an infinite loop).  To work round this, here we'll
1524        * return a redo_sequence _without_ do_again, and have the runner catch
1525        * that as an indication not to save the command in its history.  Sorry
1526        * for the ugliness.
1527        */
1528       game->do_again = FALSE;
1529       game->redo_sequence = INT_MAX;
1530     }
1531 
1532   game->is_admin = TRUE;
1533   return TRUE;
1534 }
1535 
1536 static sc_bool
lib_cmd_redo_text_last_common(sc_gameref_t game,const sc_char * target)1537 lib_cmd_redo_text_last_common (sc_gameref_t game, const sc_char *target)
1538 {
1539   const sc_memo_setref_t memento = gs_get_memento (game);
1540   sc_bool is_do_last, is_contains;
1541   sc_int length, matched_sequence;
1542 
1543   /* Make a special case of "!!", rerun the final command in the history. */
1544   is_do_last = (strcmp (target, "!") == 0);
1545 
1546   /*
1547    * Differentiate starts-with and contains searches, setting is_contains and
1548    * advancing by one if the target begins '?' (word search).  Note target
1549    * string length.
1550    */
1551   is_contains = (target[0] == '?');
1552   target += is_contains ? 1 : 0;
1553   length = strlen (target);
1554 
1555   /* If there's no text left to search for, reject this call now. */
1556   if (length == 0)
1557     {
1558       if_print_string ("No matching entry found in the command history.\n");
1559 
1560       /* As with failed numeric redo above, special-case this return. */
1561       game->do_again = FALSE;
1562       game->redo_sequence = INT_MAX;
1563 
1564       game->is_admin = TRUE;
1565       return TRUE;
1566     }
1567 
1568   /*
1569    * Search saved commands for one that matches the target string in the
1570    * required way.  We want to return the most recently saved match, so ideally
1571    * we'd search backwards, but the iterator is only forwards, so we do it the
1572    * hard way.
1573    */
1574   matched_sequence = 0;
1575   memo_first_command (memento);
1576   while (memo_more_commands (memento))
1577     {
1578       const sc_char *command;
1579       sc_int sequence, timestamp, turns;
1580       sc_bool is_matched;
1581 
1582       /* Get the command; only command and sequence are relevant. */
1583       memo_next_command (memento, &command, &sequence, &timestamp, &turns);
1584 
1585       /*
1586        * If this is the "!!" special case, match everything.  Otherwise,
1587        * either search the command for the target, or match if the command
1588        * begins with the target.
1589        */
1590       if (is_do_last)
1591         is_matched = TRUE;
1592       else if (is_contains)
1593         {
1594           sc_int index_;
1595 
1596           /* Search this command for an occurrence of target anywhere. */
1597           is_matched = FALSE;
1598           for (index_ = strlen (command) - length; index_ >= 0; index_--)
1599             {
1600               if (sc_strncasecmp (command + index_, target, length) == 0)
1601                 {
1602                   is_matched = TRUE;
1603                   break;
1604                 }
1605             }
1606         }
1607       else
1608         is_matched = (sc_strncasecmp (command, target, length) == 0);
1609 
1610       /* If the command matched the target criteria, note it and continue. */
1611       if (is_matched)
1612         matched_sequence = sequence;
1613     }
1614 
1615   /* If we found a match, set the redo values accordingly. */
1616   if (matched_sequence > 0)
1617     {
1618       game->do_again = TRUE;
1619       game->redo_sequence = matched_sequence;
1620     }
1621   else
1622     {
1623       if_print_string ("No matching entry found in the command history.\n");
1624 
1625       /* As with failed numeric redo above, special-case this return. */
1626       game->do_again = FALSE;
1627       game->redo_sequence = INT_MAX;
1628     }
1629 
1630   game->is_admin = TRUE;
1631   return TRUE;
1632 }
1633 
1634 sc_bool
lib_cmd_redo_text(sc_gameref_t game)1635 lib_cmd_redo_text (sc_gameref_t game)
1636 {
1637   const sc_var_setref_t vars = gs_get_vars (game);
1638 
1639   /* Call the common redo with the referenced text from %text%. */
1640   return lib_cmd_redo_text_last_common (game, var_get_ref_text (vars));
1641 }
1642 
1643 sc_bool
lib_cmd_redo_last(sc_gameref_t game)1644 lib_cmd_redo_last (sc_gameref_t game)
1645 {
1646   /* Call the common redo with, literally, "!", forming "!!" . */
1647   return lib_cmd_redo_text_last_common (game, "!");
1648 }
1649 
1650 
1651 /*
1652  * lib_cmd_hints()
1653  *
1654  * Called on "hints".  Requests the interface to display any available hints.
1655  */
1656 sc_bool
lib_cmd_hints(sc_gameref_t game)1657 lib_cmd_hints (sc_gameref_t game)
1658 {
1659   const sc_filterref_t filter = gs_get_filter (game);
1660   sc_int task;
1661   sc_bool game_has_hints;
1662 
1663   /*
1664    * Check for the presence of any game hints at all, no matter whether the
1665    * task is runnable or not.
1666    */
1667   game_has_hints = FALSE;
1668   for (task = 0; task < gs_task_count (game); task++)
1669     {
1670       if (task_has_hints (game, task))
1671         {
1672           game_has_hints = TRUE;
1673           break;
1674         }
1675     }
1676 
1677   /* If the game has hints, display any relevant ones. */
1678   if (game_has_hints)
1679     {
1680       if (run_hint_iterate (game, NULL))
1681         {
1682           if (if_confirm (SC_CONF_VIEW_HINTS))
1683             if_display_hints (game);
1684         }
1685       else
1686         pf_buffer_string (filter, "There are currently no hints available.\n");
1687     }
1688   else
1689     {
1690       pf_buffer_string (filter,
1691                         "There are no hints available for this adventure.\n");
1692       pf_buffer_string (filter,
1693                         "You're just going to have to work it out for"
1694                         " yourself...\n");
1695     }
1696 
1697   game->is_admin = TRUE;
1698   return TRUE;
1699 }
1700 
1701 
1702 /*
1703  * lib_print_string_bold()
1704  * lib_print_string_italics()
1705  *
1706  * Convenience helpers for printing licensing and game information.
1707  */
1708 static void
lib_print_string_bold(const sc_char * string)1709 lib_print_string_bold (const sc_char *string)
1710 {
1711   if_print_tag (SC_TAG_BOLD, "");
1712   if_print_string (string);
1713   if_print_tag (SC_TAG_ENDBOLD, "");
1714 }
1715 
1716 static void
lib_print_string_italics(const sc_char * string)1717 lib_print_string_italics (const sc_char *string)
1718 {
1719   if_print_tag (SC_TAG_ITALICS, "");
1720   if_print_string (string);
1721   if_print_tag (SC_TAG_ENDITALICS, "");
1722 }
1723 
1724 
1725 /*
1726  * lib_cmd_help()
1727  * lib_cmd_license()
1728  *
1729  * A form of standard help output for games that don't define it themselves,
1730  * and the GPL licensing.  Print directly rather than using the printfilter
1731  * to avoid possible clashes with ALRs.
1732  */
1733 sc_bool
lib_cmd_help(sc_gameref_t game)1734 lib_cmd_help (sc_gameref_t game)
1735 {
1736   if_print_string (
1737     "These are some of the typical commands used in this adventure:\n\n");
1738 
1739   if_print_string (
1740     "  [N]orth, [E]ast, [S]outh, [W]est, [U]p, [D]own, [In], [O]ut,"
1741     " [L]ook, [Exits]\n  E[x]amine <object>, [Get <object>],"
1742     " [Drop <object>], [...it], [...all]\n  [Where is <object>]\n"
1743     "  [Give <object> to  <character>], [Open...], [Close...],"
1744     " [Ask <character> about <subject>]\n"
1745     "  [Wear <object>], [Remove <object>], [I]nventory\n"
1746     "  [Put <object> into <object>], [Put <object> onto <object>]\n");
1747 
1748   if_print_string ("\nUse the ");
1749   lib_print_string_italics ("Save");
1750   if_print_string (", ");
1751   lib_print_string_italics ("Restore");
1752   if_print_string (", ");
1753   lib_print_string_italics ("Undo");
1754   if_print_string (", and ");
1755   lib_print_string_italics ("Quit");
1756   if_print_string (
1757     " commands to save and restore games, undo a move, and leave the "
1758     " game.  Use ");
1759   lib_print_string_italics ("History");
1760   if_print_string (" and ");
1761   lib_print_string_italics ("Redo");
1762   if_print_string (
1763     " to view and repeat recent game commands.\n");
1764 
1765   if_print_string ("\nThe ");
1766   lib_print_string_italics ("Hint");
1767   if_print_string (" command displays any game hints, ");
1768   lib_print_string_italics ("Notify");
1769   if_print_string (" provides score change notification, and ");
1770   lib_print_string_italics ("Verbose");
1771   if_print_string (" and ");
1772   lib_print_string_italics ("Brief");
1773   if_print_string (" control room descriptions.\n");
1774 
1775   if_print_string ("\nUse ");
1776   lib_print_string_italics ("License");
1777   if_print_string (
1778     " to view SCARE's licensing terms and conditions, and ");
1779   lib_print_string_italics ("Version");
1780   if_print_string (
1781     " to print both SCARE's and the game's version number.\n");
1782 
1783   game->is_admin = TRUE;
1784   return TRUE;
1785 }
1786 
1787 sc_bool
lib_cmd_license(sc_gameref_t game)1788 lib_cmd_license (sc_gameref_t game)
1789 {
1790   lib_print_string_bold ("SCARE");
1791   if_print_string (" is ");
1792   lib_print_string_italics (
1793     "Copyright (C) 2003-2008  Simon Baldwin and Mark J. Tilford");
1794   if_print_string (".\n\n");
1795 
1796   if_print_string (
1797     "This program is free software; you can redistribute it and/or modify"
1798     " it under the terms of version 2 of the GNU General Public License"
1799     " as published by the Free Software Foundation.\n\n");
1800 
1801   if_print_string (
1802     "This program is distributed in the hope that it will be useful, but ");
1803   lib_print_string_bold ("WITHOUT ANY WARRANTY");
1804   if_print_string ("; without even the implied warranty of ");
1805   lib_print_string_bold ("MERCHANTABILITY");
1806   if_print_string (" or ");
1807   lib_print_string_bold ("FITNESS FOR A PARTICULAR PURPOSE");
1808   if_print_string (
1809     ".  See the GNU General Public License for more details.\n\n");
1810 
1811   if_print_string (
1812     "You should have received a copy of the GNU General Public License"
1813     " along with this program; if not, write to the Free Software"
1814     " Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307"
1815     " USA\n\n");
1816 
1817   if_print_string ("Please report any bugs, omissions, or misfeatures to ");
1818   lib_print_string_italics ("simon_baldwin@yahoo.com");
1819   if_print_string (".\n");
1820 
1821   game->is_admin = TRUE;
1822   return TRUE;
1823 }
1824 
1825 
1826 /*
1827  * lib_cmd_information()
1828  *
1829  * Display a few small pieces of game information, done by a dialog GUI
1830  * in real Adrift.  Prints directly rather than using the printfilter to
1831  * avoid possible clashes with ALRs.
1832  */
1833 sc_bool
lib_cmd_information(sc_gameref_t game)1834 lib_cmd_information (sc_gameref_t game)
1835 {
1836   const sc_prop_setref_t bundle = gs_get_bundle (game);
1837   const sc_var_setref_t vars = gs_get_vars (game);
1838   sc_vartype_t vt_key[2];
1839   const sc_char *gamename, *compile_date, *gameauthor;
1840   sc_char *filtered;
1841 
1842   vt_key[0].string = "Globals";
1843   vt_key[1].string = "GameName";
1844   gamename = prop_get_string (bundle, "S<-ss", vt_key);
1845   filtered = pf_filter_for_info (gamename, vars);
1846   pf_strip_tags (filtered);
1847 
1848   if_print_string ("\"");
1849   if_print_string (!sc_strempty (filtered) ? filtered : "Untitled");
1850   if_print_string ("\"");
1851   sc_free (filtered);
1852 
1853   vt_key[0].string = "CompileDate";
1854   compile_date = prop_get_string (bundle, "S<-s", vt_key);
1855   if (!sc_strempty (compile_date))
1856     {
1857       if_print_string (", ");
1858       if_print_string (compile_date);
1859     }
1860 
1861   vt_key[0].string = "Globals";
1862   vt_key[1].string = "GameAuthor";
1863   gameauthor = prop_get_string (bundle, "S<-ss", vt_key);
1864   filtered = pf_filter_for_info (gameauthor, vars);
1865   pf_strip_tags (filtered);
1866 
1867   if_print_string (", ");
1868   if_print_string (!sc_strempty (filtered) ? filtered : "Anonymous");
1869   if_print_string (".\n");
1870   sc_free (filtered);
1871 
1872   game->is_admin = TRUE;
1873   return TRUE;
1874 }
1875 
1876 
1877 /*
1878  * lib_cmd_clear()
1879  *
1880  * Clear the main game window (almost).
1881  */
1882 sc_bool
lib_cmd_clear(sc_gameref_t game)1883 lib_cmd_clear (sc_gameref_t game)
1884 {
1885   const sc_filterref_t filter = gs_get_filter (game);
1886 
1887   pf_buffer_tag (filter, SC_TAG_CLS);
1888   pf_buffer_string (filter, "Screen cleared.\n");
1889   game->is_admin = TRUE;
1890   return TRUE;
1891 }
1892 
1893 
1894 /*
1895  * lib_cmd_statusline()
1896  *
1897  * Display the status line as would be shown by the Runner.  Useful for
1898  * interpreter builds that can't offer a true status line.  Prints directly
1899  * rather than using the printfilter to avoid possible clashes with ALRs.
1900  */
1901 sc_bool
lib_cmd_statusline(sc_gameref_t game)1902 lib_cmd_statusline (sc_gameref_t game)
1903 {
1904   const sc_char *name, *author, *room, *status;
1905   sc_int score;
1906 
1907   /*
1908    * Retrieve the game's name and author, the description of the current
1909    * game room, and any formatted game status line.
1910    */
1911   run_get_attributes (game, &name, &author, NULL, NULL,
1912                       &score, NULL, &room, &status, NULL, NULL, NULL, NULL);
1913 
1914   /* If nothing is yet determined, print the game name and author. */
1915   if (!room || sc_strempty (room))
1916     {
1917       if_print_string (name);
1918       if_print_string (" | ");
1919       if_print_string (author);
1920     }
1921   else
1922     {
1923       /* Print the player location, and a separator. */
1924       if_print_string (room);
1925       if_print_string (" | ");
1926 
1927       /* If the game offers a status line, print it, otherwise the score. */
1928       if (status && !sc_strempty (status))
1929         if_print_string (status);
1930       else
1931         {
1932           sc_char buffer[32];
1933 
1934           if_print_string ("Score: ");
1935           sprintf (buffer, "%ld", score);
1936           if_print_string (buffer);
1937         }
1938     }
1939   if_print_character ('\n');
1940 
1941   game->is_admin = TRUE;
1942   return TRUE;
1943 }
1944 
1945 
1946 /*
1947  * lib_cmd_version()
1948  *
1949  * Display the "Runner version".  Prints directly rather than using the
1950  * printfilter to avoid possible clashes with ALRs.
1951  */
1952 sc_bool
lib_cmd_version(sc_gameref_t game)1953 lib_cmd_version (sc_gameref_t game)
1954 {
1955   const sc_prop_setref_t bundle = gs_get_bundle (game);
1956   sc_vartype_t vt_key;
1957   sc_char buffer[64];
1958   sc_int major, minor, point;
1959   const sc_char *version;
1960 
1961   if_print_string ("SCARE version ");
1962   if_print_string (SCARE_VERSION SCARE_PATCH_LEVEL);
1963   if_print_string (" [Adrift ");
1964   major = SCARE_EMULATION / 1000;
1965   minor = (SCARE_EMULATION % 1000) / 100;
1966   point = SCARE_EMULATION % 100;
1967   sprintf (buffer, "%ld.%02ld.%02ld", major, minor, point);
1968   if_print_string (buffer);
1969   if_print_string (" compatible], ");
1970 
1971   vt_key.string = "VersionString";
1972   version = prop_get_string (bundle, "S<-s", &vt_key);
1973   if_print_string ("Generator version ");
1974   if_print_string (version);
1975   if_print_string (".\n");
1976 
1977   game->is_admin = TRUE;
1978   return TRUE;
1979 }
1980 
1981 
1982 /*
1983  * lib_cmd_wait()
1984  * lib_cmd_wait_number()
1985  *
1986  * Set game waitcounter to a count of turns for which the main loop will run
1987  * without taking input.  Many Adrift Runners ignore any WaitTurns setting in
1988  * the game, and use always use one; this might make a game misbehave, so to
1989  * try to cover this case we supply 'wait N' as a player control to override
1990  * the game's setting.  The latter prints directly rather than using the
1991  * printfilter to avoid possible clashes with ALRs.
1992  */
1993 sc_bool
lib_cmd_wait(sc_gameref_t game)1994 lib_cmd_wait (sc_gameref_t game)
1995 {
1996   const sc_filterref_t filter = gs_get_filter (game);
1997   const sc_prop_setref_t bundle = gs_get_bundle (game);
1998   sc_vartype_t vt_key[2];
1999   sc_int waitturns;
2000 
2001   /* Note if wait turns is different from the game's setting. */
2002   vt_key[0].string = "Globals";
2003   vt_key[1].string = "WaitTurns";
2004   waitturns = prop_get_integer (bundle, "I<-ss", vt_key);
2005   if (waitturns != game->waitturns)
2006     {
2007       sc_char buffer[32];
2008 
2009       pf_buffer_string (filter, "(");
2010       sprintf (buffer, "%ld", game->waitturns);
2011       pf_buffer_string (filter, buffer);
2012       pf_buffer_string (filter,
2013                         game->waitturns == 1 ? " turn)\n" : " turns)\n");
2014     }
2015 
2016   /* Reset the wait counter to the current waitturns setting. */
2017   game->waitcounter = game->waitturns;
2018 
2019   pf_buffer_string (filter, "Time passes...\n");
2020   return TRUE;
2021 }
2022 
2023 sc_bool
lib_cmd_wait_number(sc_gameref_t game)2024 lib_cmd_wait_number (sc_gameref_t game)
2025 {
2026   const sc_var_setref_t vars = gs_get_vars (game);
2027   sc_int waitturns;
2028   sc_char buffer[32];
2029 
2030   /* Get and validate the waitturns setting. */
2031   waitturns = var_get_ref_number (vars);
2032   if (waitturns < 1 || waitturns > 20)
2033     {
2034       if_print_string ("You can only wait between 1 and 20 turns.\n");
2035       game->is_admin = TRUE;
2036       return TRUE;
2037     }
2038 
2039   /* Update the game setting, and confirm for the player. */
2040   game->waitturns = waitturns;
2041 
2042   if_print_string ("The game will now wait ");
2043   sprintf (buffer, "%ld", waitturns);
2044   if_print_string (buffer);
2045   if_print_string (waitturns == 1 ? " turn" : " turns");
2046   if_print_string (" for each 'wait' command you enter.\n");
2047 
2048   game->is_admin = TRUE;
2049   return TRUE;
2050 }
2051 
2052 
2053 /*
2054  * lib_cmd_verbose()
2055  * lib_cmd_brief()
2056  *
2057  * Set/clear game verbose flag.  Print directly rather than using the
2058  * printfilter to avoid possible clashes with ALRs.
2059  */
2060 sc_bool
lib_cmd_verbose(sc_gameref_t game)2061 lib_cmd_verbose (sc_gameref_t game)
2062 {
2063   /* Set game verbose flag and return. */
2064   game->verbose = TRUE;
2065   if_print_string ("The game is now in its ");
2066   if_print_tag (SC_TAG_ITALICS, "");
2067   if_print_string ("verbose");
2068   if_print_tag (SC_TAG_ENDITALICS, "");
2069   if_print_string (" mode, which always gives long descriptions of locations"
2070                    " (even if you've been there before).\n");
2071 
2072   game->is_admin = TRUE;
2073   return TRUE;
2074 }
2075 
2076 sc_bool
lib_cmd_brief(sc_gameref_t game)2077 lib_cmd_brief (sc_gameref_t game)
2078 {
2079   /* Clear game verbose flag and return. */
2080   game->verbose = FALSE;
2081   if_print_string ("The game is now in its ");
2082   if_print_tag (SC_TAG_ITALICS, "");
2083   if_print_string ("brief");
2084   if_print_tag (SC_TAG_ENDITALICS, "");
2085   if_print_string (" mode, which gives long descriptions of places never"
2086                    " before visited and short descriptions otherwise.\n");
2087 
2088   game->is_admin = TRUE;
2089   return TRUE;
2090 }
2091 
2092 
2093 /*
2094  * lib_cmd_notify_on_off()
2095  * lib_cmd_notify()
2096  *
2097  * Set/clear/query game score change notification flag.  Print directly
2098  * rather than using the printfilter to avoid possible clashes with ALRs.
2099  */
2100 sc_bool
lib_cmd_notify_on_off(sc_gameref_t game)2101 lib_cmd_notify_on_off (sc_gameref_t game)
2102 {
2103   const sc_var_setref_t vars = gs_get_vars (game);
2104   const sc_char *control;
2105 
2106   /* Get the text following the notify command, and check for "on"/"off". */
2107   control = var_get_ref_text (vars);
2108   if (sc_strcasecmp (control, "on") == 0)
2109     {
2110       /* Set score change notification. */
2111       game->notify_score_change = TRUE;
2112       if_print_string ("Game score change notification is now ");
2113       if_print_tag (SC_TAG_ITALICS, "");
2114       if_print_string ("on");
2115       if_print_tag (SC_TAG_ENDITALICS, "");
2116       if_print_string (", and the game will tell you of any changes in the"
2117                        " score.\n");
2118     }
2119   else if (sc_strcasecmp (control, "off") == 0)
2120     {
2121       /* Clear score change notification. */
2122       game->notify_score_change = FALSE;
2123       if_print_string ("Game score change notification is now ");
2124       if_print_tag (SC_TAG_ITALICS, "");
2125       if_print_string ("off");
2126       if_print_tag (SC_TAG_ENDITALICS, "");
2127       if_print_string (", and the game will be silent on changes in the"
2128                        " score.\n");
2129     }
2130   else
2131     {
2132       if_print_string ("Use 'notify on' or 'notify off' to control game"
2133                        " score notification.\n");
2134     }
2135 
2136   game->is_admin = TRUE;
2137   return TRUE;
2138 }
2139 
2140 sc_bool
lib_cmd_notify(sc_gameref_t game)2141 lib_cmd_notify (sc_gameref_t game)
2142 {
2143   /* Report the current state of notification. */
2144   if_print_string ("Game score change notification is ");
2145   if_print_tag (SC_TAG_ITALICS, "");
2146   if_print_string (game->notify_score_change ? "on" : "off");
2147   if_print_tag (SC_TAG_ENDITALICS, "");
2148 
2149   if (game->notify_score_change)
2150     {
2151       if_print_string (", and the game will tell you of any changes in the"
2152                        " score.\n");
2153     }
2154   else
2155     {
2156       if_print_string (", and the game will be silent on changes in the"
2157                        " score.\n");
2158     }
2159 
2160   game->is_admin = TRUE;
2161   return TRUE;
2162 }
2163 
2164 
2165 /*
2166  * lib_cmd_time()
2167  * lib_cmd_date()
2168  *
2169  * Print elapsed game time, and smart-alec "date" response.  The Adrift
2170  * Runner responds here with the system time and date, but we'll do something
2171  * different.
2172  */
2173 sc_bool
lib_cmd_time(sc_gameref_t game)2174 lib_cmd_time (sc_gameref_t game)
2175 {
2176   const sc_var_setref_t vars = gs_get_vars (game);
2177   sc_uint timestamp;
2178   sc_int hr, min, sec;
2179   sc_char buffer[64];
2180 
2181   /* Get elapsed game time and convert to hour, minutes, and seconds. */
2182   timestamp = var_get_elapsed_seconds (vars);
2183   hr = timestamp / SECS_PER_HOUR;
2184   min = (timestamp % SECS_PER_HOUR) / MINS_PER_HOUR;
2185   sec = timestamp % SECS_PER_MINUTE;
2186   if (hr > 0)
2187     sprintf (buffer, "%ldh %02ldm %02lds", hr, min, sec);
2188   else
2189     sprintf (buffer, "%ldm %02lds", min, sec);
2190 
2191   /* Print the game's elapsed time. */
2192   if_print_string ("You have been running the game for ");
2193   if_print_string (buffer);
2194   if_print_string (".\n");
2195 
2196   game->is_admin = TRUE;
2197   return TRUE;
2198 }
2199 
2200 sc_bool
lib_cmd_date(sc_gameref_t game)2201 lib_cmd_date (sc_gameref_t game)
2202 {
2203   const sc_filterref_t filter = gs_get_filter (game);
2204 
2205   pf_buffer_string (filter, "Maybe we should just be good friends.\n");
2206   return TRUE;
2207 }
2208 
2209 
2210 /*
2211  * Direction enumeration.  Used by movement commands, to multiplex them all
2212  * into a single function.  The values are explicit to ensure they match
2213  * enumerations in the game data.
2214  */
2215 enum
2216 { DIR_NORTH = 0, DIR_EAST = 1, DIR_SOUTH = 2, DIR_WEST = 3,
2217   DIR_UP = 4, DIR_DOWN = 5, DIR_IN = 6, DIR_OUT = 7,
2218   DIR_NORTHEAST = 8, DIR_SOUTHEAST = 9, DIR_SOUTHWEST = 10, DIR_NORTHWEST = 11
2219 };
2220 
2221 
2222 /*
2223  * lib_go()
2224  *
2225  * Central movement command, called by all movement handlers.
2226  */
2227 static sc_bool
lib_go(sc_gameref_t game,sc_int direction)2228 lib_go (sc_gameref_t game, sc_int direction)
2229 {
2230   const sc_filterref_t filter = gs_get_filter (game);
2231   const sc_prop_setref_t bundle = gs_get_bundle (game);
2232   sc_vartype_t vt_key[5], vt_rvalue;
2233   sc_bool eightpointcompass, is_trapped, is_exitable[12];
2234   sc_int destination, index_;
2235   const sc_char *const *dirnames;
2236 
2237   /* Decide on four or eight point compass names list. */
2238   vt_key[0].string = "Globals";
2239   vt_key[1].string = "EightPointCompass";
2240   eightpointcompass = prop_get_boolean (bundle, "B<-ss", vt_key);
2241   dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4;
2242 
2243   /* Start by seeing if there are any exits at all available. */
2244   is_trapped = TRUE;
2245   for (index_ = 0; dirnames[index_]; index_++)
2246     {
2247       vt_key[0].string = "Rooms";
2248       vt_key[1].integer = gs_playerroom (game);
2249       vt_key[2].string = "Exits";
2250       vt_key[3].integer = index_;
2251       if (prop_get (bundle, "I<-sisi", &vt_rvalue, vt_key)
2252           && lib_can_go (game, gs_playerroom (game), index_))
2253         {
2254           is_exitable[index_] = TRUE;
2255           is_trapped = FALSE;
2256         }
2257       else
2258         is_exitable[index_] = FALSE;
2259     }
2260   if (is_trapped)
2261     {
2262       pf_buffer_string (filter,
2263                         lib_select_response (game,
2264                                       "You can't go in any direction!\n",
2265                                       "I can't go in any direction!\n",
2266                                       "%player% can't go in any direction!\n"));
2267       return TRUE;
2268     }
2269 
2270   /*
2271    * Check for the exit, and if it doesn't exist, refuse, and list the possible
2272    * options.
2273    */
2274   vt_key[0].string = "Rooms";
2275   vt_key[1].integer = gs_playerroom (game);
2276   vt_key[2].string = "Exits";
2277   vt_key[3].integer = direction;
2278   vt_key[4].string = "Dest";
2279   if (prop_get (bundle, "I<-sisis", &vt_rvalue, vt_key))
2280     destination = vt_rvalue.integer - 1;
2281   else
2282     {
2283       sc_int count, trail;
2284 
2285       pf_buffer_string (filter,
2286                         lib_select_response (game,
2287                          "You can't go in that direction, but you can move ",
2288                          "I can't go in that direction, but I can move ",
2289                          "%player% can't go in that direction, but can move "));
2290 
2291       /* List available exits, found in exit test loop earlier. */
2292       count = 0;
2293       trail = -1;
2294       for (index_ = 0; dirnames[index_]; index_++)
2295         {
2296           if (is_exitable[index_])
2297             {
2298               if (count > 0)
2299                 {
2300                   if (count > 1)
2301                     pf_buffer_string (filter, ", ");
2302                   pf_buffer_string (filter, dirnames[trail]);
2303                 }
2304               trail = index_;
2305               count++;
2306             }
2307         }
2308       if (count >= 1)
2309         {
2310           if (count > 1)
2311             pf_buffer_string (filter, " and ");
2312           pf_buffer_string (filter, dirnames[trail]);
2313         }
2314       pf_buffer_string (filter, ".\n");
2315       return TRUE;
2316     }
2317 
2318   /* Check for any movement restrictions. */
2319   if (!lib_can_go (game, gs_playerroom (game), direction))
2320     {
2321       pf_buffer_string (filter,
2322                         lib_select_response (game,
2323                         "You can't go in that direction (at present).\n",
2324                         "I can't go in that direction (at present).\n",
2325                         "%player% can't go in that direction (at present).\n"));
2326       return TRUE;
2327     }
2328 
2329   if (lib_trace)
2330     {
2331       sc_trace ("Library: moving player from %ld to %ld\n",
2332                 gs_playerroom (game), destination);
2333     }
2334 
2335   /* Indicate if getting off something or standing up first. */
2336   if (gs_playerparent (game) != -1)
2337     {
2338       pf_buffer_string (filter, "(Getting off ");
2339       lib_print_object_np (game, gs_playerparent (game));
2340       pf_buffer_string (filter, " first)\n");
2341     }
2342   else if (gs_playerposition (game) != 0)
2343     pf_buffer_string (filter, "(Standing up first)\n");
2344 
2345   /* Confirm and then make move. */
2346   pf_buffer_string (filter,
2347                     lib_select_response (game,
2348                                          "You move ",
2349                                          "I move ",
2350                                          "%player% moves "));
2351   pf_buffer_string (filter, dirnames[direction]);
2352   pf_buffer_string (filter, ".\n");
2353 
2354   gs_move_player_to_room (game, destination);
2355 
2356   /* Describe the new room and return. */
2357   lib_describe_player_room (game, FALSE);
2358   return TRUE;
2359 }
2360 
2361 
2362 /*
2363  * lib_cmd_go_*()
2364  *
2365  * Direction-specific movement commands.
2366  */
2367 sc_bool
lib_cmd_go_north(sc_gameref_t game)2368 lib_cmd_go_north (sc_gameref_t game)
2369 {
2370   return lib_go (game, DIR_NORTH);
2371 }
2372 
2373 sc_bool
lib_cmd_go_east(sc_gameref_t game)2374 lib_cmd_go_east (sc_gameref_t game)
2375 {
2376   return lib_go (game, DIR_EAST);
2377 }
2378 
2379 sc_bool
lib_cmd_go_south(sc_gameref_t game)2380 lib_cmd_go_south (sc_gameref_t game)
2381 {
2382   return lib_go (game, DIR_SOUTH);
2383 }
2384 
2385 sc_bool
lib_cmd_go_west(sc_gameref_t game)2386 lib_cmd_go_west (sc_gameref_t game)
2387 {
2388   return lib_go (game, DIR_WEST);
2389 }
2390 
2391 sc_bool
lib_cmd_go_up(sc_gameref_t game)2392 lib_cmd_go_up (sc_gameref_t game)
2393 {
2394   return lib_go (game, DIR_UP);
2395 }
2396 
2397 sc_bool
lib_cmd_go_down(sc_gameref_t game)2398 lib_cmd_go_down (sc_gameref_t game)
2399 {
2400   return lib_go (game, DIR_DOWN);
2401 }
2402 
2403 sc_bool
lib_cmd_go_in(sc_gameref_t game)2404 lib_cmd_go_in (sc_gameref_t game)
2405 {
2406   return lib_go (game, DIR_IN);
2407 }
2408 
2409 sc_bool
lib_cmd_go_out(sc_gameref_t game)2410 lib_cmd_go_out (sc_gameref_t game)
2411 {
2412   return lib_go (game, DIR_OUT);
2413 }
2414 
2415 sc_bool
lib_cmd_go_northeast(sc_gameref_t game)2416 lib_cmd_go_northeast (sc_gameref_t game)
2417 {
2418   return lib_go (game, DIR_NORTHEAST);
2419 }
2420 
2421 sc_bool
lib_cmd_go_southeast(sc_gameref_t game)2422 lib_cmd_go_southeast (sc_gameref_t game)
2423 {
2424   return lib_go (game, DIR_SOUTHEAST);
2425 }
2426 
2427 sc_bool
lib_cmd_go_northwest(sc_gameref_t game)2428 lib_cmd_go_northwest (sc_gameref_t game)
2429 {
2430   return lib_go (game, DIR_NORTHWEST);
2431 }
2432 
2433 sc_bool
lib_cmd_go_southwest(sc_gameref_t game)2434 lib_cmd_go_southwest (sc_gameref_t game)
2435 {
2436   return lib_go (game, DIR_SOUTHWEST);
2437 }
2438 
2439 
2440 /*
2441  * lib_compare_rooms()
2442  *
2443  * Helper for lib_cmd_go_room().  Compare the name of the passed in room
2444  * with the string passed in, and return TRUE if they match.  The routine
2445  * requires that string is filtered, stripped, trimmed and normalized.
2446  */
2447 static sc_bool
lib_compare_rooms(sc_gameref_t game,sc_int room,const sc_char * string)2448 lib_compare_rooms (sc_gameref_t game, sc_int room, const sc_char *string)
2449 {
2450   const sc_var_setref_t vars = gs_get_vars (game);
2451   const sc_prop_setref_t bundle = gs_get_bundle (game);
2452   sc_char *name, *compare_name;
2453   sc_bool status;
2454 
2455   /* Get the name of the room, and filter it down to a plain string. */
2456   name = pf_filter (lib_get_room_name (game, room), vars, bundle);
2457   pf_strip_tags (name);
2458   sc_normalize_string (sc_trim_string (name));
2459 
2460   /* Bypass any prefix on the room name. */
2461   if (sc_compare_word (name, "a", 1))
2462     compare_name = name + 1;
2463   else if (sc_compare_word (name, "an", 2))
2464     compare_name = name + 2;
2465   else if (sc_compare_word (name, "the", 3))
2466     compare_name = name + 3;
2467   else
2468     compare_name = name;
2469   sc_trim_string (compare_name);
2470 
2471   /* Compare strings, then free the allocated name. */
2472   status = sc_strcasecmp (compare_name, string) == 0;
2473   sc_free (name);
2474 
2475   return status;
2476 }
2477 
2478 
2479 /*
2480  * lib_cmd_go_room()
2481  *
2482  * A weak replica of the Runner's claimed ability to go to a named room via
2483  * rooms that have already been visited using a shortest-path search.  This
2484  * version scans adjacent rooms for accessibility, and then generates the
2485  * required directional move for any unique match.
2486  *
2487  * Note that rooms can have the same name after they've been cleaned up for
2488  * text comparisons, for example, two "Manor Grounds" at the start of Humbug,
2489  * differentiated within the game with trailing "<some_tag>" components.
2490  */
2491 sc_bool
lib_cmd_go_room(sc_gameref_t game)2492 lib_cmd_go_room (sc_gameref_t game)
2493 {
2494   const sc_filterref_t filter = gs_get_filter (game);
2495   const sc_var_setref_t vars = gs_get_vars (game);
2496   const sc_prop_setref_t bundle = gs_get_bundle (game);
2497   sc_vartype_t vt_key[5], vt_rvalue;
2498   sc_bool eightpointcompass, is_trapped, is_ambiguous;
2499   sc_int direction, destination, index_;
2500   const sc_char *const *dirnames;
2501   sc_char *name, *compare_name;
2502 
2503   /* Determine the requested room, and filter it down to a plain string. */
2504   name = pf_filter (var_get_ref_text (vars), vars, bundle);
2505   pf_strip_tags (name);
2506   sc_normalize_string (sc_trim_string (name));
2507 
2508   /* Bypass any prefix on the request room name. */
2509   if (sc_compare_word (name, "a", 1))
2510     compare_name = name + 1;
2511   else if (sc_compare_word (name, "an", 2))
2512     compare_name = name + 2;
2513   else if (sc_compare_word (name, "the", 3))
2514     compare_name = name + 3;
2515   else
2516     compare_name = name;
2517   sc_trim_string (compare_name);
2518 
2519   /* See if the named room is the current player room. */
2520   if (lib_compare_rooms (game, gs_playerroom (game), compare_name))
2521     {
2522       pf_buffer_string (filter, "You are already there!\n");
2523       sc_free (name);
2524       return TRUE;
2525     }
2526 
2527   /* Decide on four or eight point compass names list. */
2528   vt_key[0].string = "Globals";
2529   vt_key[1].string = "EightPointCompass";
2530   eightpointcompass = prop_get_boolean (bundle, "B<-ss", vt_key);
2531   dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4;
2532 
2533   /* Search adjacent and available rooms for a name match. */
2534   is_trapped = TRUE;
2535   is_ambiguous = FALSE;
2536   direction = -1;
2537   destination = -1;
2538   for (index_ = 0; dirnames[index_]; index_++)
2539     {
2540       vt_key[0].string = "Rooms";
2541       vt_key[1].integer = gs_playerroom (game);
2542       vt_key[2].string = "Exits";
2543       vt_key[3].integer = index_;
2544       if (prop_get (bundle, "I<-sisi", &vt_rvalue, vt_key)
2545           && lib_can_go (game, gs_playerroom (game), index_))
2546         {
2547           is_trapped = FALSE;
2548 
2549           /*
2550            * Room is available.  Compare its name with that requested provided
2551            * that it's a location we've not already accepted (that is, some
2552            * rooms are reachable by multiple directions, such as both "south"
2553            * and "out").
2554            */
2555           vt_key[4].string = "Dest";
2556           if (prop_get (bundle, "I<-sisis", &vt_rvalue, vt_key))
2557             {
2558               sc_int location;
2559 
2560               location = vt_rvalue.integer - 1;
2561               if (location != destination
2562                   && lib_compare_rooms (game, location, compare_name))
2563                 {
2564                   if (direction != -1)
2565                     is_ambiguous = TRUE;
2566                   direction = index_;
2567                   destination = location;
2568                 }
2569             }
2570         }
2571     }
2572   sc_free (name);
2573 
2574   /* If trapped or it's unclear where to go, handle these cases. */
2575   if (is_trapped)
2576     {
2577       pf_buffer_string (filter,
2578                         lib_select_response (game,
2579                                       "You can't go in any direction!\n",
2580                                       "I can't go in any direction!\n",
2581                                       "%player% can't go in any direction!\n"));
2582       return TRUE;
2583     }
2584   else if (is_ambiguous)
2585     {
2586       pf_buffer_string (filter,
2587                         "I'm not clear about where you want to go."
2588                         "  Please try using just a direction.\n");
2589       pf_buffer_character (filter, '\n');
2590       lib_cmd_print_room_exits (game);
2591       return TRUE;
2592      }
2593 
2594   /* If no match, note it, otherwise handle as standard directional move. */
2595   if (direction == -1)
2596     {
2597       pf_buffer_string (filter, "I don't know how to get there from here.\n");
2598       pf_buffer_character (filter, '\n');
2599       lib_cmd_print_room_exits (game);
2600       return TRUE;
2601     }
2602 
2603   return lib_go (game, direction);
2604 }
2605 
2606 
2607 /*
2608  * lib_cmd_examine_self()
2609  *
2610  * Show the long description of a player.
2611  */
2612 sc_bool
lib_cmd_examine_self(sc_gameref_t game)2613 lib_cmd_examine_self (sc_gameref_t game)
2614 {
2615   const sc_filterref_t filter = gs_get_filter (game);
2616   const sc_prop_setref_t bundle = gs_get_bundle (game);
2617   sc_vartype_t vt_key[2];
2618   sc_int task, object, count, trail;
2619   const sc_char *description, *position = NULL;
2620 
2621   /* Get selection task. */
2622   vt_key[0].string = "Globals";
2623   vt_key[1].string = "Task";
2624   task = prop_get_integer (bundle, "I<-ss", vt_key) - 1;
2625 
2626   /* Select either the main or the alternate description. */
2627   if (task >= 0 && gs_task_done (game, task))
2628     vt_key[1].string = "AltDesc";
2629   else
2630     vt_key[1].string = "PlayerDesc";
2631 
2632   /* Print the description, or default response. */
2633   description = prop_get_string (bundle, "S<-ss", vt_key);
2634   if (!sc_strempty (description))
2635     pf_buffer_string (filter, description);
2636   else
2637     {
2638       pf_buffer_string (filter,
2639                         lib_select_response (game,
2640                                        "You are as well as can be expected,"
2641                                        " considering the circumstances.",
2642                                        "I am as well as can be expected,"
2643                                        " considering the circumstances.",
2644                                        "%player% is as well as can be expected,"
2645                                        " considering the circumstances."));
2646     }
2647 
2648   /* If not just standing on the floor, say more. */
2649   switch (gs_playerposition (game))
2650     {
2651     case 0:
2652       position = lib_select_response (game,
2653                                       "You are standing",
2654                                       "I am standing",
2655                                       "%player% is standing");
2656       break;
2657     case 1:
2658       position = lib_select_response (game,
2659                                       "You are sitting down",
2660                                       "I am sitting down",
2661                                       "%player% is sitting down");
2662       break;
2663     case 2:
2664       position = lib_select_response (game,
2665                                       "You are lying down",
2666                                       "I am lying down",
2667                                       "%player% is lying down");
2668       break;
2669     }
2670 
2671   if (position
2672       && !(gs_playerposition (game) == 0 && gs_playerparent (game) == -1))
2673     {
2674       pf_buffer_string (filter, "  ");
2675       pf_buffer_string (filter, position);
2676       if (gs_playerparent (game) != -1)
2677         {
2678           pf_buffer_string (filter, " on ");
2679           lib_print_object_np (game, gs_playerparent (game));
2680         }
2681       pf_buffer_character (filter, '.');
2682     }
2683 
2684   /* Find and list each object worn by the player. */
2685   count = 0;
2686   trail = -1;
2687   for (object = 0; object < gs_object_count (game); object++)
2688     {
2689       if (gs_object_position (game, object) == OBJ_WORN_PLAYER)
2690         {
2691           if (count > 0)
2692             {
2693               if (count == 1)
2694                 {
2695                   pf_buffer_string (filter,
2696                                     lib_select_response (game,
2697                                                      "  You are wearing ",
2698                                                      "  I am wearing ",
2699                                                      "  %player% is wearing "));
2700                 }
2701               else
2702                 pf_buffer_string (filter, ", ");
2703               lib_print_object (game, trail);
2704             }
2705           trail = object;
2706           count++;
2707         }
2708     }
2709   if (count >= 1)
2710     {
2711       /* Print out final listed object. */
2712       if (count == 1)
2713         {
2714           pf_buffer_string (filter,
2715                             lib_select_response (game,
2716                                                  "  You are wearing ",
2717                                                  "  I am wearing ",
2718                                                  "  %player% is wearing "));
2719         }
2720       else
2721         pf_buffer_string (filter, " and ");
2722       lib_print_object (game, trail);
2723       pf_buffer_character (filter, '.');
2724     }
2725 
2726   pf_buffer_character (filter, '\n');
2727   return TRUE;
2728 }
2729 
2730 
2731 /*
2732  * lib_disambiguate_npc()
2733  *
2734  * Filter, then search the set of NPC matches.  If only one matched, note
2735  * and return it.  If multiple matched, print a disambiguation message and
2736  * the list, and return -1 with *is_ambiguous TRUE.  If none matched, return
2737  * -1 with *is_ambiguous FALSE if requested, otherwise print a message then
2738  * return -1.
2739  */
2740 static sc_int
lib_disambiguate_npc(sc_gameref_t game,const sc_char * verb,sc_bool * is_ambiguous)2741 lib_disambiguate_npc (sc_gameref_t game,
2742                       const sc_char *verb, sc_bool *is_ambiguous)
2743 {
2744   const sc_filterref_t filter = gs_get_filter (game);
2745   const sc_var_setref_t vars = gs_get_vars (game);
2746   sc_int count, index_, npc, listed;
2747 
2748   /*
2749    * Filter out all referenced NPCs not actually visible or seen.  Count the
2750    * number of NPCs remaining as referenced by the last command, and note the
2751    * last referenced NPC, for where count is 1.
2752    */
2753   count = 0;
2754   npc = -1;
2755   for (index_ = 0; index_ < gs_npc_count (game); index_++)
2756     {
2757       if (game->npc_references[index_]
2758           && gs_npc_seen (game, index_)
2759           && npc_in_room (game, index_, gs_playerroom (game)))
2760         {
2761           count++;
2762           npc = index_;
2763         }
2764       else
2765         game->npc_references[index_] = FALSE;
2766     }
2767 
2768   /* If the reference is unambiguous, set in variables and return it. */
2769   if (count == 1)
2770     {
2771       /* Set this NPC as the referenced character. */
2772       var_set_ref_character (vars, npc);
2773 
2774       /* Return, setting no ambiguity. */
2775       if (is_ambiguous)
2776         *is_ambiguous = FALSE;
2777       return npc;
2778     }
2779 
2780   /* If nothing referenced, return no NPC. */
2781   if (count == 0)
2782     {
2783       if (is_ambiguous)
2784         *is_ambiguous = FALSE;
2785       else
2786         {
2787           pf_buffer_string (filter,
2788                             "Please be more clear, who do you want to ");
2789           pf_buffer_string (filter, verb);
2790           pf_buffer_string (filter, "?\n");
2791         }
2792       return -1;
2793     }
2794 
2795   /* The NPC reference is ambiguous, so list the choices. */
2796   pf_buffer_string (filter, "Please be more clear, who do you want to ");
2797   pf_buffer_string (filter, verb);
2798   pf_buffer_string (filter, "?  ");
2799 
2800   pf_new_sentence (filter);
2801   listed = 0;
2802   for (index_ = 0; index_ < gs_npc_count (game); index_++)
2803     {
2804       if (game->npc_references[index_])
2805         {
2806           lib_print_npc_np (game, index_);
2807           listed++;
2808           if (listed < count)
2809             pf_buffer_string (filter, (listed < count - 1) ? ", " : " or ");
2810         }
2811     }
2812   pf_buffer_string (filter, "?\n");
2813 
2814   /* Return no NPC for an ambiguous reference. */
2815   if (is_ambiguous)
2816     *is_ambiguous = TRUE;
2817   return -1;
2818 }
2819 
2820 
2821 /*
2822  * lib_disambiguate_object_common()
2823  * lib_disambiguate_object()
2824  * lib_disambiguate_object_extended()
2825  *
2826  * Filter, then search the set of object matches.  If only one matched, note
2827  * and return it.  If multiple matched, print a disambiguation message and
2828  * the list, and return -1 with *is_ambiguous TRUE.  If none matched, return
2829  * -1 with *is_ambiguous FALSE if requested, otherwise print a message then
2830  * return -1.
2831  *
2832  * Extended disambiguation operates as normal disambiguation, except that if
2833  * normal disambiguation returns more than one object, the resolver function,
2834  * if supplied, is used to see if the multiple objects can be resolved into
2835  * just one object.  The resolver function can normally be the same as the
2836  * function used to filter objects for multiple references.
2837  */
2838 static sc_int
lib_disambiguate_object_common(sc_gameref_t game,const sc_char * verb,sc_bool (* resolver)(sc_gameref_t,sc_int,sc_int),sc_int resolver_arg,sc_bool * is_ambiguous)2839 lib_disambiguate_object_common (sc_gameref_t game, const sc_char *verb,
2840                                sc_bool (*resolver)
2841                                    (sc_gameref_t, sc_int, sc_int),
2842                                sc_int resolver_arg,
2843                                sc_bool *is_ambiguous)
2844 {
2845   const sc_filterref_t filter = gs_get_filter (game);
2846   const sc_var_setref_t vars = gs_get_vars (game);
2847   sc_int count, index_, object, listed;
2848 
2849   /*
2850    * Filter out all referenced objects not actually visible or seen.  Count
2851    * the number of objects remaining as referenced by the last command, and
2852    * note the last referenced object, for where count is 1.
2853    */
2854   count = 0;
2855   object = -1;
2856   for (index_ = 0; index_ < gs_object_count (game); index_++)
2857     {
2858       if (game->object_references[index_]
2859           && gs_object_seen (game, index_)
2860           && obj_indirectly_in_room (game, index_, gs_playerroom (game)))
2861         {
2862           count++;
2863           object = index_;
2864         }
2865       else
2866         game->object_references[index_] = FALSE;
2867     }
2868 
2869   /*
2870    * If this reference is ambiguous and a resolver was supplied, try to
2871    * resolve it unambiguously by calling the resolver filter on the remaining
2872    * set references.
2873    */
2874   if (resolver && count > 1)
2875     {
2876       sc_int retry_count;
2877 
2878       /*
2879        * Search for objects accepted by the resolver filter, but don't filter
2880        * references just yet.  Again, note the last referenced.
2881        */
2882       retry_count = 0;
2883       object = -1;
2884       for (index_ = 0; index_ < gs_object_count (game); index_++)
2885         {
2886           if (game->object_references[index_]
2887               && resolver (game, index_, resolver_arg))
2888             {
2889               retry_count++;
2890               object = index_;
2891             }
2892         }
2893 
2894       /* See if we narrowed the field without eliminating every object. */
2895       if (retry_count > 0 && retry_count < count)
2896         {
2897           /*
2898            * If we got down to a single object, the ambiguity is resolved.
2899            * In this case, set count to 1 so that 'object' is returned.
2900            */
2901           if (retry_count == 1)
2902             count = retry_count;
2903           else
2904             {
2905               /*
2906                * We got down to fewer objects; reduce references so that the
2907                * disambiguation message is clearer.  Note that here we still
2908                * leave with count greater than 1.
2909                */
2910               count = 0;
2911               for (index_ = 0; index_ < gs_object_count (game); index_++)
2912                 {
2913                   if (game->object_references[index_]
2914                       && resolver (game, index_, resolver_arg))
2915                     count++;
2916                   else
2917                     game->object_references[index_] = FALSE;
2918                 }
2919             }
2920         }
2921     }
2922 
2923   /* If the reference is unambiguous, set in variables and return it. */
2924   if (count == 1)
2925     {
2926       /* Set this object as referenced. */
2927       var_set_ref_object (vars, object);
2928 
2929       /* Return, setting no ambiguity. */
2930       if (is_ambiguous)
2931         *is_ambiguous = FALSE;
2932       return object;
2933     }
2934 
2935   /* If nothing referenced, return no object. */
2936   if (count == 0)
2937     {
2938       if (is_ambiguous)
2939         *is_ambiguous = FALSE;
2940       else
2941         {
2942           pf_buffer_string (filter,
2943                             "Please be more clear, what do you want to ");
2944           pf_buffer_string (filter, verb);
2945           pf_buffer_string (filter, "?\n");
2946         }
2947       return -1;
2948     }
2949 
2950   /* The object reference is ambiguous, so list the choices. */
2951   pf_buffer_string (filter, "Please be more clear, what do you want to ");
2952   pf_buffer_string (filter, verb);
2953   pf_buffer_string (filter, "?  ");
2954 
2955   pf_new_sentence (filter);
2956   listed = 0;
2957   for (index_ = 0; index_ < gs_object_count (game); index_++)
2958     {
2959       if (game->object_references[index_])
2960         {
2961           lib_print_object_np (game, index_);
2962           listed++;
2963           if (listed < count)
2964             pf_buffer_string (filter, (listed < count - 1) ? ", " : " or ");
2965         }
2966     }
2967   pf_buffer_string (filter, "?\n");
2968 
2969   /* Return no object for an ambiguous reference. */
2970   if (is_ambiguous)
2971     *is_ambiguous = TRUE;
2972   return -1;
2973 }
2974 
2975 static sc_int
lib_disambiguate_object(sc_gameref_t game,const sc_char * verb,sc_bool * is_ambiguous)2976 lib_disambiguate_object (sc_gameref_t game,
2977                          const sc_char *verb, sc_bool *is_ambiguous)
2978 {
2979   return lib_disambiguate_object_common (game, verb, NULL, -1, is_ambiguous);
2980 }
2981 
2982 static sc_int
lib_disambiguate_object_extended(sc_gameref_t game,const sc_char * verb,sc_bool (* resolver)(sc_gameref_t,sc_int,sc_int),sc_int resolver_arg,sc_bool * is_ambiguous)2983 lib_disambiguate_object_extended (sc_gameref_t game, const sc_char *verb,
2984                                   sc_bool (*resolver)
2985                                       (sc_gameref_t, sc_int, sc_int),
2986                                   sc_int resolver_arg,
2987                                   sc_bool *is_ambiguous)
2988 {
2989   return lib_disambiguate_object_common (game, verb,
2990                                          resolver, resolver_arg, is_ambiguous);
2991 }
2992 
2993 
2994 /*
2995  * lib_list_npc_inventory()
2996  *
2997  * List objects carried and worn by an NPC.
2998  */
2999 static sc_bool
lib_list_npc_inventory(sc_gameref_t game,sc_int npc,sc_bool is_described)3000 lib_list_npc_inventory (sc_gameref_t game, sc_int npc, sc_bool is_described)
3001 {
3002   const sc_filterref_t filter = gs_get_filter (game);
3003   sc_int object, count, trail;
3004   sc_bool wearing;
3005 
3006   /* Find and list each object worn by the NPC. */
3007   count = 0;
3008   trail = -1;
3009   wearing = FALSE;
3010   for (object = 0; object < gs_object_count (game); object++)
3011     {
3012       if (gs_object_position (game, object) == OBJ_WORN_NPC
3013           && gs_object_parent (game, object) == npc)
3014         {
3015           if (count > 0)
3016             {
3017               if (count == 1)
3018                 {
3019                   if (is_described)
3020                     pf_buffer_string (filter, "  ");
3021                   pf_new_sentence (filter);
3022                   lib_print_npc_np (game, npc);
3023                   pf_buffer_string (filter, " is wearing ");
3024                 }
3025               else
3026                 pf_buffer_string (filter, ", ");
3027               lib_print_object (game, trail);
3028             }
3029           trail = object;
3030           count++;
3031         }
3032     }
3033   if (count >= 1)
3034     {
3035       /* Print out final listed object. */
3036       if (count == 1)
3037         {
3038           if (is_described)
3039             pf_buffer_string (filter, "  ");
3040           pf_new_sentence (filter);
3041           lib_print_npc_np (game, npc);
3042           pf_buffer_string (filter, " is wearing ");
3043         }
3044       else
3045         pf_buffer_string (filter, " and ");
3046       lib_print_object (game, trail);
3047       wearing = TRUE;
3048     }
3049 
3050   /* Find and list each object owned by the NPC. */
3051   count = 0;
3052   trail = -1;
3053   for (object = 0; object < gs_object_count (game); object++)
3054     {
3055       if (gs_object_position (game, object) == OBJ_HELD_NPC
3056           && gs_object_parent (game, object) == npc)
3057         {
3058           if (count > 0)
3059             {
3060               if (count == 1)
3061                 {
3062                   if (!wearing)
3063                     {
3064                       if (is_described)
3065                         pf_buffer_string (filter, "  ");
3066                       pf_new_sentence (filter);
3067                       lib_print_npc_np (game, npc);
3068                     }
3069                   else
3070                     pf_buffer_string (filter, ", and");
3071                   pf_buffer_string (filter, " is carrying ");
3072                 }
3073               else
3074                 pf_buffer_string (filter, ", ");
3075               lib_print_object (game, trail);
3076             }
3077           trail = object;
3078           count++;
3079         }
3080     }
3081   if (count >= 1)
3082     {
3083       /* Print out final listed object. */
3084       if (count == 1)
3085         {
3086           if (!wearing)
3087             {
3088               if (is_described)
3089                 pf_buffer_string (filter, "  ");
3090               pf_new_sentence (filter);
3091               lib_print_npc_np (game, npc);
3092             }
3093           else
3094             pf_buffer_string (filter, ", and");
3095           pf_buffer_string (filter, " is carrying ");
3096         }
3097       else
3098         pf_buffer_string (filter, " and ");
3099       lib_print_object (game, trail);
3100       pf_buffer_character (filter, '.');
3101     }
3102   else
3103     {
3104       if (wearing)
3105         pf_buffer_character (filter, '.');
3106     }
3107 
3108   /* Return TRUE if anything worn or carried. */
3109   return wearing || count > 0;
3110 }
3111 
3112 
3113 /*
3114  * lib_cmd_examine_npc()
3115  *
3116  * Show the long description of the most recently referenced NPC, and a
3117  * list of what they're wearing and carrying.
3118  */
3119 sc_bool
lib_cmd_examine_npc(sc_gameref_t game)3120 lib_cmd_examine_npc (sc_gameref_t game)
3121 {
3122   const sc_filterref_t filter = gs_get_filter (game);
3123   const sc_prop_setref_t bundle = gs_get_bundle (game);
3124   sc_vartype_t vt_key[4];
3125   sc_int npc, task, resource;
3126   sc_bool is_ambiguous;
3127   const sc_char *description;
3128 
3129   /* Get the referenced npc, and if none, consider complete. */
3130   npc = lib_disambiguate_npc (game, "examine", &is_ambiguous);
3131   if (npc == -1)
3132     return is_ambiguous;
3133 
3134   /* Get selection task. */
3135   vt_key[0].string = "NPCs";
3136   vt_key[1].integer = npc;
3137   vt_key[2].string = "Task";
3138   task = prop_get_integer (bundle, "I<-sis", vt_key) - 1;
3139 
3140   /* Select either the main or the alternate description. */
3141   if (task >= 0 && gs_task_done (game, task))
3142     {
3143       vt_key[2].string = "AltText";
3144       resource = 1;
3145     }
3146   else
3147     {
3148       vt_key[2].string = "Descr";
3149       resource = 0;
3150     }
3151 
3152   /* Print the description, or a default message if none. */
3153   description = prop_get_string (bundle, "S<-sis", vt_key);
3154   if (!sc_strempty (description))
3155     pf_buffer_string (filter, description);
3156   else
3157     {
3158       pf_buffer_string (filter, "There's nothing special about ");
3159       lib_print_npc_np (game, npc);
3160       pf_buffer_character (filter, '.');
3161     }
3162 
3163   /* Handle any associated resource. */
3164   vt_key[2].string = "Res";
3165   vt_key[3].integer = resource;
3166   res_handle_resource (game, "sisi", vt_key);
3167 
3168   /* Print what the NPC is wearing and carrying. */
3169   lib_list_npc_inventory (game, npc, TRUE);
3170 
3171   pf_buffer_character (filter, '\n');
3172   return TRUE;
3173 }
3174 
3175 
3176 /*
3177  * lib_list_in_object_normal()
3178  *
3179  * List the objects in a given container object, normal format listing.
3180  */
3181 static sc_bool
lib_list_in_object_normal(sc_gameref_t game,sc_int container,sc_bool is_described)3182 lib_list_in_object_normal (sc_gameref_t game,
3183                            sc_int container, sc_bool is_described)
3184 {
3185   const sc_filterref_t filter = gs_get_filter (game);
3186   sc_int object, count, trail;
3187 
3188   /* List out the containers contained in this container. */
3189   count = 0;
3190   trail = -1;
3191   for (object = 0; object < gs_object_count (game); object++)
3192     {
3193       /* Contained? */
3194       if (gs_object_position (game, object) == OBJ_IN_OBJECT
3195           && gs_object_parent (game, object) == container)
3196         {
3197           if (count > 0)
3198             {
3199               if (count == 1)
3200                 {
3201                   if (is_described)
3202                     pf_buffer_string (filter, "  ");
3203                   pf_buffer_string (filter, "Inside ");
3204                   lib_print_object_np (game, container);
3205                   pf_buffer_string (filter,
3206                                     lib_select_plurality (game, trail,
3207                                                           " is ", " are "));
3208                 }
3209               else
3210                 pf_buffer_string (filter, ", ");
3211 
3212               /* Print out the current list object. */
3213               lib_print_object (game, trail);
3214             }
3215           trail = object;
3216           count++;
3217         }
3218     }
3219   if (count >= 1)
3220     {
3221       /* Print out final listed object. */
3222       if (count == 1)
3223         {
3224           if (is_described)
3225             pf_buffer_string (filter, "  ");
3226           pf_buffer_string (filter, "Inside ");
3227           lib_print_object_np (game, container);
3228           pf_buffer_string (filter,
3229                             lib_select_plurality (game, trail,
3230                                                   " is ", " are "));
3231         }
3232       else
3233         pf_buffer_string (filter, " and ");
3234 
3235       /* Print out the final object. */
3236       lib_print_object (game, trail);
3237       pf_buffer_character (filter, '.');
3238     }
3239 
3240   /* Return TRUE if anything listed. */
3241   return count > 0;
3242 }
3243 
3244 
3245 /*
3246  * lib_list_in_object_alternate()
3247  *
3248  * List the objects in a given container object, alternate format listing.
3249  */
3250 static sc_bool
lib_list_in_object_alternate(sc_gameref_t game,sc_int container,sc_bool is_described)3251 lib_list_in_object_alternate (sc_gameref_t game,
3252                               sc_int container, sc_bool is_described)
3253 {
3254   const sc_filterref_t filter = gs_get_filter (game);
3255   sc_int object, count, trail;
3256 
3257   /* List out the objects contained in this object. */
3258   count = 0;
3259   trail = -1;
3260   for (object = 0; object < gs_object_count (game); object++)
3261     {
3262       /* Contained? */
3263       if (gs_object_position (game, object) == OBJ_IN_OBJECT
3264           && gs_object_parent (game, object) == container)
3265         {
3266           if (count > 0)
3267             {
3268               if (count == 1)
3269                 {
3270                   if (is_described)
3271                     pf_buffer_string (filter, "  ");
3272                   pf_new_sentence (filter);
3273                 }
3274               else
3275                 pf_buffer_string (filter, ", ");
3276 
3277               /* Print out the current list object. */
3278               lib_print_object (game, trail);
3279             }
3280           trail = object;
3281           count++;
3282         }
3283     }
3284   if (count >= 1)
3285     {
3286       /* Print out final listed object. */
3287       if (count == 1)
3288         {
3289           if (is_described)
3290             pf_buffer_string (filter, "  ");
3291           pf_new_sentence (filter);
3292           lib_print_object (game, trail);
3293           pf_buffer_string (filter,
3294                             lib_select_plurality (game, trail,
3295                                                   " is inside ",
3296                                                   " are inside "));
3297         }
3298       else
3299         {
3300           pf_buffer_string (filter, " and ");
3301           lib_print_object (game, trail);
3302           pf_buffer_string (filter, " are inside ");
3303         }
3304 
3305       /* Print out the container. */
3306       lib_print_object_np (game, container);
3307       pf_buffer_character (filter, '.');
3308     }
3309 
3310   /* Return TRUE if anything listed. */
3311   return count > 0;
3312 }
3313 
3314 
3315 /*
3316  * lib_list_in_object()
3317  *
3318  * List the objects in a given container object.
3319  *
3320  * TODO The Adrift Runner has two distinct styles it uses for listing objects
3321  * within a container, but which it picks at any one point is, frankly, a
3322  * mystery.  The selection below seems to work with the few games checked for
3323  * this, and in particular works with the ALR magic in "To Hell in a Hamper",
3324  * but it's almost certainly wrong.  Or, at minimum, incomplete.
3325  */
3326 static sc_bool
lib_list_in_object(sc_gameref_t game,sc_int container,sc_bool is_described)3327 lib_list_in_object (sc_gameref_t game, sc_int container, sc_bool is_described)
3328 {
3329   sc_bool use_alternate_format = FALSE;
3330 
3331   /*
3332    * Switch if the object is static and part of an NPC or the player, or if
3333    * the count of contained objects in a dynamic container is exactly one.
3334    */
3335   if (obj_is_static (game, container))
3336     {
3337       sc_int object_position;
3338 
3339       object_position = gs_object_position (game, container);
3340 
3341       if (object_position == OBJ_PART_NPC || object_position == OBJ_PART_PLAYER)
3342         use_alternate_format = TRUE;
3343     }
3344   else
3345     {
3346       sc_int object, count;
3347 
3348       count = 0;
3349       for (object = 0; object < gs_object_count (game); object++)
3350         {
3351           if (gs_object_position (game, object) == OBJ_IN_OBJECT
3352               && gs_object_parent (game, object) == container)
3353             count++;
3354           if (count > 1)
3355             break;
3356         }
3357 
3358       if (count == 1)
3359         use_alternate_format = TRUE;
3360     }
3361 
3362   /* List contained objects using the selected handler. */
3363   return use_alternate_format
3364          ? lib_list_in_object_alternate (game, container, is_described)
3365          : lib_list_in_object_normal (game, container, is_described);
3366 }
3367 
3368 
3369 /*
3370  * lib_list_on_object()
3371  *
3372  * List the objects on a given surface object.
3373  */
3374 static sc_bool
lib_list_on_object(sc_gameref_t game,sc_int supporter,sc_bool is_described)3375 lib_list_on_object (sc_gameref_t game, sc_int supporter, sc_bool is_described)
3376 {
3377   const sc_filterref_t filter = gs_get_filter (game);
3378   sc_int object, count, trail;
3379 
3380   /* List out the objects standing on this object. */
3381   count = 0;
3382   trail = -1;
3383   for (object = 0; object < gs_object_count (game); object++)
3384     {
3385       /* Standing on? */
3386       if (gs_object_position (game, object) == OBJ_ON_OBJECT
3387           && gs_object_parent (game, object) == supporter)
3388         {
3389           if (count > 0)
3390             {
3391               if (count == 1)
3392                 {
3393                   if (is_described)
3394                     pf_buffer_string (filter, "  ");
3395                   pf_new_sentence (filter);
3396                 }
3397               else
3398                 pf_buffer_string (filter, ", ");
3399 
3400               /* Print out the current list object. */
3401               lib_print_object (game, trail);
3402             }
3403           trail = object;
3404           count++;
3405         }
3406     }
3407   if (count >= 1)
3408     {
3409       /* Print out final listed object. */
3410       if (count == 1)
3411         {
3412           if (is_described)
3413             pf_buffer_string (filter, "  ");
3414           pf_new_sentence (filter);
3415           lib_print_object (game, trail);
3416           pf_buffer_string (filter,
3417                             lib_select_plurality (game, trail,
3418                                                   " is on ",
3419                                                   " are on "));
3420         }
3421       else
3422         {
3423           pf_buffer_string (filter, " and ");
3424           lib_print_object (game, trail);
3425           pf_buffer_string (filter, " are on ");
3426         }
3427 
3428       /* Print out the surface. */
3429       lib_print_object_np (game, supporter);
3430       pf_buffer_character (filter, '.');
3431     }
3432 
3433   /* Return TRUE if anything listed. */
3434   return count > 0;
3435 }
3436 
3437 
3438 /*
3439  * lib_list_object_state()
3440  *
3441  * Describe the state of a stateful object.
3442  */
3443 static sc_bool
lib_list_object_state(sc_gameref_t game,sc_int object,sc_bool is_described)3444 lib_list_object_state (sc_gameref_t game, sc_int object, sc_bool is_described)
3445 {
3446   const sc_filterref_t filter = gs_get_filter (game);
3447   const sc_prop_setref_t bundle = gs_get_bundle (game);
3448   sc_vartype_t vt_key[3];
3449   sc_bool is_statussed;
3450   sc_char *state;
3451 
3452   /* Get object statefulness. */
3453   vt_key[0].string = "Objects";
3454   vt_key[1].integer = object;
3455   vt_key[2].string = "CurrentState";
3456   is_statussed = prop_get_integer (bundle, "I<-sis", vt_key) != 0;
3457 
3458   /* Ensure this is a stateful object. */
3459   if (is_statussed)
3460     {
3461       if (is_described)
3462         pf_buffer_string (filter, "  ");
3463       pf_new_sentence (filter);
3464       lib_print_object_np (game, object);
3465       pf_buffer_string (filter,
3466                         lib_select_plurality (game, object, " is ", " are "));
3467 
3468       /* Add object state string. */
3469       state = obj_state_name (game, object);
3470       if (state)
3471         {
3472           pf_buffer_string (filter, state);
3473           sc_free (state);
3474           pf_buffer_string (filter, ".");
3475         }
3476       else
3477         {
3478           sc_error ("lib_list_object_state: invalid object state\n");
3479           pf_buffer_string (filter, "[invalid state].");
3480         }
3481     }
3482 
3483   /* Return TRUE if a state was printed. */
3484   return is_statussed;
3485 }
3486 
3487 
3488 /*
3489  * lib_cmd_examine_object()
3490  *
3491  * Show the long description of the most recently referenced object.
3492  */
3493 sc_bool
lib_cmd_examine_object(sc_gameref_t game)3494 lib_cmd_examine_object (sc_gameref_t game)
3495 {
3496   const sc_filterref_t filter = gs_get_filter (game);
3497   const sc_prop_setref_t bundle = gs_get_bundle (game);
3498   sc_vartype_t vt_key[3];
3499   sc_int object, task, openness;
3500   sc_bool is_described, is_statussed, is_mentioned, is_ambiguous, should_be;
3501   const sc_char *description, *resource;
3502 
3503   /* Get the referenced object, and if none, consider complete. */
3504   object = lib_disambiguate_object (game, "examine", &is_ambiguous);
3505   if (object == -1)
3506     return is_ambiguous;
3507 
3508   /* Begin assuming no description printed. */
3509   is_described = FALSE;
3510 
3511   /*
3512    * Get selection task and expected state; for the expected task state, FALSE
3513    * indicates task completed, TRUE not completed.
3514    */
3515   vt_key[0].string = "Objects";
3516   vt_key[1].integer = object;
3517   vt_key[2].string = "Task";
3518   task = prop_get_integer (bundle, "I<-sis", vt_key) - 1;
3519   vt_key[2].string = "TaskNotDone";
3520   should_be = !prop_get_boolean (bundle, "B<-sis", vt_key);
3521 
3522   /* Select either the main or the alternate description. */
3523   if (task >= 0 && gs_task_done (game, task) == should_be)
3524     {
3525       vt_key[2].string = "AltDesc";
3526       resource = "Res2";
3527     }
3528   else
3529     {
3530       vt_key[2].string = "Description";
3531       resource = "Res1";
3532     }
3533 
3534   /* Print the description, or a default response. */
3535   description = prop_get_string (bundle, "S<-sis", vt_key);
3536   if (!sc_strempty (description))
3537     {
3538       pf_buffer_string (filter, description);
3539       is_described |= TRUE;
3540     }
3541 
3542   /* Handle any associated resource. */
3543   vt_key[2].string = resource;
3544   res_handle_resource (game, "sis", vt_key);
3545 
3546   /* If the object is openable, print its openness state. */
3547   openness = gs_object_openness (game, object);
3548   switch (openness)
3549     {
3550     case OBJ_OPEN:
3551       if (is_described)
3552         pf_buffer_string (filter, "  ");
3553       pf_new_sentence (filter);
3554       lib_print_object_np (game, object);
3555       pf_buffer_string (filter,
3556                         lib_select_plurality (game, object,
3557                                               " is open.", " are open."));
3558       is_described |= TRUE;
3559       break;
3560 
3561     case OBJ_CLOSED:
3562       if (is_described)
3563         pf_buffer_string (filter, "  ");
3564       pf_new_sentence (filter);
3565       lib_print_object_np (game, object);
3566       pf_buffer_string (filter,
3567                         lib_select_plurality (game, object,
3568                                               " is closed.", " are closed."));
3569       is_described |= TRUE;
3570       break;
3571 
3572     case OBJ_LOCKED:
3573       if (is_described)
3574         pf_buffer_string (filter, "  ");
3575       pf_new_sentence (filter);
3576       lib_print_object_np (game, object);
3577       pf_buffer_string (filter,
3578                         lib_select_plurality (game, object,
3579                                               " is locked.", " are locked."));
3580       is_described |= TRUE;
3581       break;
3582 
3583     default:
3584       break;
3585     }
3586 
3587   /* Add any extra details for stateful objects. */
3588   vt_key[1].integer = object;
3589   vt_key[2].string = "CurrentState";
3590   is_statussed = prop_get_integer (bundle, "I<-sis", vt_key) != 0;
3591   if (is_statussed)
3592     {
3593       vt_key[2].string = "StateListed";
3594       is_mentioned = prop_get_boolean (bundle, "B<-sis", vt_key);
3595       if (is_mentioned)
3596         is_described |= lib_list_object_state (game, object, is_described);
3597     }
3598 
3599   /* For open container objects, list out what's in them. */
3600   if (obj_is_container (game, object) && openness <= OBJ_OPEN)
3601     is_described |= lib_list_in_object (game, object, is_described);
3602 
3603   /* For surface objects, list out what's on them. */
3604   if (obj_is_surface (game, object))
3605     is_described |= lib_list_on_object (game, object, is_described);
3606 
3607   /* If nothing yet said, print a default response. */
3608   if (!is_described)
3609     {
3610       pf_buffer_string (filter,
3611                         lib_select_response (game,
3612                                        "You see nothing special about ",
3613                                        "I see nothing special about ",
3614                                        "%player% sees nothing special about "));
3615       lib_print_object_np (game, object);
3616       pf_buffer_character (filter, '.');
3617     }
3618 
3619   pf_buffer_character (filter, '\n');
3620   return TRUE;
3621 }
3622 
3623 
3624 /*
3625  * lib_save_game_references()
3626  * lib_restore_game_references()
3627  *
3628  * Helpers for trying game commands.  Save and restore game references
3629  * so that parsing game commands doesn't interfere with backend loops that
3630  * are working through game references set by prior commands.  Saving
3631  * references uses the buffer passed in if possible, otherwise allocates
3632  * its own buffer; testing the return value shows which happened.
3633  */
3634 static sc_bool *
lib_save_object_references(sc_gameref_t game,sc_bool buffer[],sc_int length)3635 lib_save_object_references (sc_gameref_t game, sc_bool buffer[], sc_int length)
3636 {
3637   sc_int required, available;
3638   sc_bool *references;
3639 
3640   /*
3641    * Calculate the required bytes for references, and then either allocate or
3642    * use the buffer supplied.
3643    */
3644   required = gs_object_count (game) * sizeof (*references);
3645   available = length * sizeof (buffer[0]);
3646   references = required > available ? sc_malloc (required) : buffer;
3647 
3648   /* Copy over references from the game, and return the saved copy. */
3649   memcpy (references, game->object_references, required);
3650   return references;
3651 }
3652 
3653 static void
lib_restore_object_references(sc_gameref_t game,const sc_bool references[])3654 lib_restore_object_references (sc_gameref_t game, const sc_bool references[])
3655 {
3656   sc_int bytes;
3657 
3658   /* Calculate the bytes in the references array, and copy back to the game. */
3659   bytes = gs_object_count (game) * sizeof (references[0]);
3660   memcpy (game->object_references, references, bytes);
3661 }
3662 
3663 
3664 /*
3665  * lib_try_game_command_common()
3666  * lib_try_game_command_short()
3667  * lib_try_game_command_with_object()
3668  * lib_try_game_command_with_npc()
3669  *
3670  * Try a game command with a standard verb.  Used by get and drop handlers
3671  * to retry game commands using standard "get " and "drop " commands.  This
3672  * makes "take/pick up/put down" work with a game's overridden get/drop.
3673  */
3674 static sc_bool
lib_try_game_command_common(sc_gameref_t game,const sc_char * verb,sc_int object,const sc_char * preposition,sc_int associate,sc_bool is_associate_object,sc_bool is_associate_npc)3675 lib_try_game_command_common (sc_gameref_t game,
3676                              const sc_char *verb, sc_int object,
3677                              const sc_char *preposition,
3678                              sc_int associate,
3679                              sc_bool is_associate_object,
3680                              sc_bool is_associate_npc)
3681 {
3682   const sc_prop_setref_t bundle = gs_get_bundle (game);
3683   sc_vartype_t vt_key[3];
3684   sc_char buffer[LIB_ALLOCATION_AVOIDANCE_SIZE];
3685   sc_bool references_buffer[LIB_ALLOCATION_AVOIDANCE_SIZE];
3686   const sc_char *prefix, *name;
3687   sc_char *command;
3688   sc_bool *references, status;
3689   assert (!is_associate_object || !is_associate_npc);
3690 
3691   /* Save the game's references, for restore later on. */
3692   references = lib_save_object_references (game, references_buffer,
3693                                            LIB_ALLOCATION_AVOIDANCE_SIZE);
3694 
3695   /* Get the addressed object's prefix and main name. */
3696   vt_key[0].string = "Objects";
3697   vt_key[1].integer = object;
3698   vt_key[2].string = "Prefix";
3699   prefix = prop_get_string (bundle, "S<-sis", vt_key);
3700   vt_key[2].string = "Short";
3701   name = prop_get_string (bundle, "S<-sis", vt_key);
3702 
3703   /* Construct and try for game commands with a standard verb. */
3704   if (is_associate_object || is_associate_npc)
3705     {
3706       const sc_char *associate_prefix, *associate_name;
3707       sc_int required;
3708 
3709       /* Get the associate's prefix and main name. */
3710       if (is_associate_object)
3711         {
3712           vt_key[0].string = "Objects";
3713           vt_key[1].integer = associate;
3714           vt_key[2].string = "Prefix";
3715           associate_prefix = prop_get_string (bundle, "S<-sis", vt_key);
3716           vt_key[2].string = "Short";
3717           associate_name = prop_get_string (bundle, "S<-sis", vt_key);
3718         }
3719       else
3720         {
3721           assert (is_associate_npc);
3722           vt_key[0].string = "NPCs";
3723           vt_key[1].integer = associate;
3724           vt_key[2].string = "Prefix";
3725           associate_prefix = prop_get_string (bundle, "S<-sis", vt_key);
3726           vt_key[2].string = "Name";
3727           associate_name = prop_get_string (bundle, "S<-sis", vt_key);
3728         }
3729 
3730       assert (preposition);
3731       required = strlen (verb) + strlen (prefix) + strlen (name)
3732                  + strlen (preposition) + strlen (associate_prefix)
3733                  + strlen (associate_name) + 6;
3734       command = required > (sc_int) sizeof (buffer)
3735                 ? sc_malloc (required) : buffer;
3736 
3737       /*
3738        * Try the command with and without prefixes on both the target object
3739        * and the associate.
3740        */
3741       sprintf (command, "%s %s %s %s %s %s", verb,
3742                prefix, name, preposition, associate_prefix, associate_name);
3743       status = run_game_task_commands (game, command);
3744       if (!status)
3745         {
3746           sprintf (command, "%s %s %s %s %s",
3747                    verb, prefix, name, preposition, associate_name);
3748           status = run_game_task_commands (game, command);
3749         }
3750       if (!status)
3751         {
3752           sprintf (command, "%s %s %s %s %s",
3753                    verb, name, preposition, associate_prefix, associate_name);
3754           status = run_game_task_commands (game, command);
3755         }
3756       if (!status)
3757         {
3758           sprintf (command, "%s %s %s %s",
3759                    verb, name, preposition, associate_name);
3760           status = run_game_task_commands (game, command);
3761         }
3762     }
3763   else
3764     {
3765       sc_int required;
3766 
3767       required = strlen (verb) + strlen (prefix) + strlen (name) + 3;
3768       command = required > (sc_int) sizeof (buffer)
3769                 ? sc_malloc (required) : buffer;
3770 
3771       /* Try the command with and without prefixes on the addressed object. */
3772       sprintf (command, "%s %s %s", verb, prefix, name);
3773       status = run_game_task_commands (game, command);
3774       if (!status)
3775         {
3776           sprintf (command, "%s %s", verb, name);
3777           status = run_game_task_commands (game, command);
3778         }
3779     }
3780 
3781   /* Restore the game object references back to their state on entry. */
3782   lib_restore_object_references (game, references);
3783 
3784   /* Free any allocations, and return the game command status. */
3785   if (command != buffer)
3786     sc_free (command);
3787   if (references != references_buffer)
3788     sc_free (references);
3789   return status;
3790 }
3791 
3792 static sc_bool
lib_try_game_command_short(sc_gameref_t game,const sc_char * verb,sc_int object)3793 lib_try_game_command_short (sc_gameref_t game,
3794                             const sc_char *verb, sc_int object)
3795 {
3796   return lib_try_game_command_common (game, verb, object,
3797                                       NULL, -1, FALSE, FALSE);
3798 }
3799 
3800 static sc_bool
lib_try_game_command_with_object(sc_gameref_t game,const sc_char * verb,sc_int object,const sc_char * preposition,sc_int other_object)3801 lib_try_game_command_with_object (sc_gameref_t game,
3802                                   const sc_char *verb, sc_int object,
3803                                   const sc_char *preposition,
3804                                   sc_int other_object)
3805 {
3806   return lib_try_game_command_common (game, verb, object,
3807                                       preposition, other_object, TRUE, FALSE);
3808 }
3809 
3810 static sc_bool
lib_try_game_command_with_npc(sc_gameref_t game,const sc_char * verb,sc_int object,const sc_char * preposition,sc_int npc)3811 lib_try_game_command_with_npc (sc_gameref_t game,
3812                                const sc_char *verb, sc_int object,
3813                                const sc_char *preposition, sc_int npc)
3814 {
3815   return lib_try_game_command_common (game, verb, object,
3816                                       preposition, npc, FALSE, TRUE);
3817 }
3818 
3819 
3820 /*
3821  * lib_parse_next_object()
3822  *
3823  * Helper for lib_parse_multiple_objects().  Extracts the next object, if any,
3824  * from referenced text, and returns it.  Disambiguates any ambiguous objects
3825  * using the verb supplied, and sets are_more_objects if we found an object
3826  * but there appear to be more following it.
3827  */
3828 static sc_bool
lib_parse_next_object(sc_gameref_t game,const sc_char * verb,sc_bool (* resolver)(sc_gameref_t,sc_int,sc_int),sc_int resolver_arg,sc_int * object,sc_bool * are_more_objects,sc_bool * is_ambiguous)3829 lib_parse_next_object (sc_gameref_t game, const sc_char *verb,
3830                        sc_bool (*resolver) (sc_gameref_t, sc_int, sc_int),
3831                        sc_int resolver_arg,
3832                        sc_int *object,
3833                        sc_bool *are_more_objects, sc_bool *is_ambiguous)
3834 {
3835   const sc_var_setref_t vars = gs_get_vars (game);
3836   const sc_char *list;
3837   sc_bool is_matched;
3838 
3839   /* Look for "object" or "object and ...", and set match and more flags. */
3840   list = var_get_ref_text (vars);
3841   if (uip_match ("%object%", list, game))
3842     {
3843       *are_more_objects = FALSE;
3844       is_matched = TRUE;
3845     }
3846   else if (uip_match ("%object% and %text%", list, game))
3847     {
3848       *are_more_objects = TRUE;
3849       is_matched = TRUE;
3850     }
3851   else
3852     is_matched = FALSE;
3853 
3854   /* If we extracted an object from referenced text, disambiguate. */
3855   if (is_matched)
3856     *object = lib_disambiguate_object_extended (game, verb,
3857                                                 resolver, resolver_arg,
3858                                                 is_ambiguous);
3859   else
3860     *is_ambiguous = FALSE;
3861 
3862   /* Return TRUE if we matched anything. */
3863   return is_matched;
3864 }
3865 
3866 
3867 /*
3868  * lib_parse_multiple_objects()
3869  *
3870  * Parser for commands that take multiple object targets from a %text% match.
3871  * Parses object lists such as "object" and "object and object" and returns
3872  * the multiple objects in the game's multiple_references.
3873  */
3874 static sc_bool
lib_parse_multiple_objects(sc_gameref_t game,const sc_char * verb,sc_bool (* resolver)(sc_gameref_t,sc_int,sc_int),sc_int resolver_arg,sc_int * count)3875 lib_parse_multiple_objects (sc_gameref_t game, const sc_char *verb,
3876                             sc_bool (*resolver) (sc_gameref_t, sc_int, sc_int),
3877                             sc_int resolver_arg,
3878                             sc_int *count)
3879 {
3880   const sc_filterref_t filter = gs_get_filter (game);
3881   sc_int count_, object;
3882   sc_bool are_more_objects, is_ambiguous;
3883 
3884   /* Initialize variables to avoid gcc warnings. */
3885   object = -1;
3886   are_more_objects = FALSE;
3887 
3888   /* Clear all current multiple object references, and the count. */
3889   gs_clear_multiple_references (game);
3890   count_ = 0;
3891 
3892   /*
3893    * Parse the first object from the list.  If we get nothing here, return
3894    * FALSE if it didn't look like a multiple object list, TRUE if ambiguous.
3895    * Beyond here, we always return TRUE, since after this point _something_
3896    * looked believable...
3897    */
3898   if (!lib_parse_next_object (game, verb,
3899                               resolver, resolver_arg,
3900                               &object, &are_more_objects, &is_ambiguous))
3901     return FALSE;
3902   else if (object == -1)
3903     {
3904       if (is_ambiguous)
3905         {
3906           /*
3907            * Return TRUE, with zero count, to cause caller to return.  We get
3908            * here if the first parsed object was ambiguous.  In this case,
3909            * the disambiguation has printed a message, so we want our caller
3910            * to simply return TRUE to indicate that the command was handled,
3911            * albeit not fully successfully.
3912            */
3913           *count = count_;
3914           return TRUE;
3915         }
3916       else
3917         {
3918           /*
3919            * No object matched after disambiguation, so return FALSE to have
3920            * our caller ignore the command.
3921            */
3922           return FALSE;
3923         }
3924     }
3925 
3926   /* Mark this first object as referenced in the return array. */
3927   game->multiple_references[object] = TRUE;
3928   count_++;
3929 
3930   /* Now parse each additional object from the list. */
3931   while (are_more_objects)
3932     {
3933       sc_int last_object;
3934 
3935       /*
3936        * If no next object, leave the loop.  If no disambiguation message
3937        * then it was probably garble, so print a message for that case.  We
3938        * also catch repeated objects here.
3939        */
3940       last_object = object;
3941       if (!lib_parse_next_object (game, verb,
3942                                   resolver, resolver_arg,
3943                                   &object, &are_more_objects, &is_ambiguous)
3944           || object == -1
3945           || game->multiple_references[object])
3946         {
3947           if (!is_ambiguous)
3948             {
3949               pf_buffer_string (filter,
3950                                 "I only understood you as far as wanting to ");
3951               pf_buffer_string (filter, verb);
3952               pf_buffer_character (filter, ' ');
3953               lib_print_object_np (game, last_object);
3954               pf_buffer_string (filter, ".\n");
3955             }
3956 
3957           /* Zero count to indicate an error somewhere in the list. */
3958           count_ = 0;
3959           break;
3960         }
3961 
3962       /* Mark the object as referenced in the return array. */
3963       game->multiple_references[object] = TRUE;
3964       count_++;
3965     }
3966 
3967   /* We found at least enough of an object list to say we matched. */
3968   *count = count_;
3969   return TRUE;
3970 }
3971 
3972 
3973 /*
3974  * lib_apply_multiple_filter()
3975  * lib_apply_except_filter()
3976  *
3977  * Apply filters for multiple object frontends.  Transfer multiple object
3978  * references into standard object references, using the supplied filter.
3979  * The first is inclusive, the second exclusive.
3980  */
3981 static sc_int
lib_apply_multiple_filter(sc_gameref_t game,sc_bool (* filter)(sc_gameref_t,sc_int,sc_int),sc_int filter_arg,sc_int * references)3982 lib_apply_multiple_filter (sc_gameref_t game,
3983                            sc_bool (*filter) (sc_gameref_t, sc_int, sc_int),
3984                            sc_int filter_arg, sc_int *references)
3985 {
3986   sc_int count, object, references_;
3987 
3988   /* Clear all object references initially. */
3989   gs_clear_object_references (game);
3990 
3991   /*
3992    * Find objects included by the filter, and transfer the reference of each
3993    * from the multiple references into standard references.
3994    */
3995   count = 0;
3996   references_ = references ? *references : 0;
3997   for (object = 0; object < gs_object_count (game); object++)
3998     {
3999       if (filter (game, object, filter_arg))
4000         {
4001           /* Transfer the reference. */
4002           if (game->multiple_references[object])
4003             {
4004               game->object_references[object] = TRUE;
4005               count++;
4006               game->multiple_references[object] = FALSE;
4007               references_--;
4008             }
4009         }
4010     }
4011 
4012   /* Copy back the updated reference count, return count. */
4013   if (references)
4014     *references = references_;
4015   return count;
4016 }
4017 
4018 static sc_int
lib_apply_except_filter(sc_gameref_t game,sc_bool (* filter)(sc_gameref_t,sc_int,sc_int),sc_int filter_arg,sc_int * references)4019 lib_apply_except_filter (sc_gameref_t game,
4020                          sc_bool (*filter) (sc_gameref_t, sc_int, sc_int),
4021                          sc_int filter_arg, sc_int *references)
4022 {
4023   sc_int count, object, references_;
4024 
4025   /* Clear all object references initially. */
4026   gs_clear_object_references (game);
4027 
4028   /*
4029    * Find objects included by the filter, and transfer the reference of each
4030    * from the multiple references into standard references.
4031    */
4032   count = 0;
4033   references_ = references ? *references : 0;
4034   for (object = 0; object < gs_object_count (game); object++)
4035     {
4036       if (filter (game, object, filter_arg))
4037         {
4038           /* If excepted, remove from exceptions, else add to references. */
4039           if (game->multiple_references[object])
4040             {
4041               game->multiple_references[object] = FALSE;
4042               references_--;
4043             }
4044           else
4045             {
4046               game->object_references[object] = TRUE;
4047               count++;
4048             }
4049         }
4050     }
4051 
4052   /* Copy back the updated reference count, return count. */
4053   if (references)
4054     *references = references_;
4055   return count;
4056 }
4057 
4058 
4059 /*
4060  * lib_cmd_count()
4061  *
4062  * Display player weight and size limits and amounts currently carried.
4063  */
4064 sc_bool
lib_cmd_count(sc_gameref_t game)4065 lib_cmd_count (sc_gameref_t game)
4066 {
4067   const sc_filterref_t filter = gs_get_filter (game);
4068   sc_int index_, size, weight;
4069   sc_char buffer[32];
4070 
4071   /* Sum sizes for objects currently held or worn by player. */
4072   size = 0;
4073   for (index_ = 0; index_ < gs_object_count (game); index_++)
4074     {
4075       if (gs_object_position (game, index_) == OBJ_HELD_PLAYER
4076           || gs_object_position (game, index_) == OBJ_WORN_PLAYER)
4077         size += obj_get_size (game, index_);
4078     }
4079 
4080   /* Sum weights for objects currently held or worn by player. */
4081   weight = 0;
4082   for (index_ = 0; index_ < gs_object_count (game); index_++)
4083     {
4084       if (gs_object_position (game, index_) == OBJ_HELD_PLAYER
4085           || gs_object_position (game, index_) == OBJ_WORN_PLAYER)
4086         weight += obj_get_weight (game, index_);
4087     }
4088 
4089   /* Print the player limits and amounts used. */
4090   pf_buffer_string (filter, "Size:    You have ");
4091   sprintf (buffer, "%ld", size);
4092   pf_buffer_string (filter, buffer);
4093   pf_buffer_string (filter, ".  The most you can hold is ");
4094   sprintf (buffer, "%ld", obj_get_player_size_limit (game));
4095   pf_buffer_string (filter, buffer);
4096   pf_buffer_string (filter, ".\n");
4097 
4098   pf_buffer_string (filter, "Weight:  You have ");
4099   sprintf (buffer, "%ld", weight);
4100   pf_buffer_string (filter, buffer);
4101   pf_buffer_string (filter, ".  The most you can hold is ");
4102   sprintf (buffer, "%ld", obj_get_player_weight_limit (game));
4103   pf_buffer_string (filter, buffer);
4104   pf_buffer_string (filter, ".\n");
4105 
4106   game->is_admin = TRUE;
4107   return TRUE;
4108 }
4109 
4110 
4111 /*
4112  * lib_object_too_heavy()
4113  *
4114  * Return TRUE if the given object is too heavy for the player to carry.
4115  */
4116 static sc_bool
lib_object_too_heavy(sc_gameref_t game,sc_int object,sc_bool * is_portable)4117 lib_object_too_heavy (sc_gameref_t game, sc_int object, sc_bool *is_portable)
4118 {
4119   sc_int player_limit, index_, weight, object_weight;
4120 
4121   /* Get the player limit and the given object weight. */
4122   player_limit = obj_get_player_weight_limit (game);
4123   object_weight = obj_get_weight (game, object);
4124 
4125   /* Sum weights for objects currently held or worn by player. */
4126   weight = 0;
4127   for (index_ = 0; index_ < gs_object_count (game); index_++)
4128     {
4129       if (gs_object_position (game, index_) == OBJ_HELD_PLAYER
4130           || gs_object_position (game, index_) == OBJ_WORN_PLAYER)
4131         weight += obj_get_weight (game, index_);
4132     }
4133 
4134   /* If requested, return object portability. */
4135   if (is_portable)
4136     *is_portable = !(object_weight > player_limit);
4137 
4138   /* Return TRUE if the new object exceeds limit. */
4139   return weight + object_weight > player_limit;
4140 }
4141 
4142 
4143 /*
4144  * lib_object_too_large()
4145  *
4146  * Return TRUE if the given object is too large for the player to carry.
4147  */
4148 static sc_bool
lib_object_too_large(sc_gameref_t game,sc_int object,sc_bool * is_portable)4149 lib_object_too_large (sc_gameref_t game, sc_int object, sc_bool *is_portable)
4150 {
4151   sc_int player_limit, index_, size, object_size;
4152 
4153   /* Get the player limit and the given object size. */
4154   player_limit = obj_get_player_size_limit (game);
4155   object_size = obj_get_size (game, object);
4156 
4157   /* Sum sizes for objects currently held or worn by player. */
4158   size = 0;
4159   for (index_ = 0; index_ < gs_object_count (game); index_++)
4160     {
4161       if (gs_object_position (game, index_) == OBJ_HELD_PLAYER
4162           || gs_object_position (game, index_) == OBJ_WORN_PLAYER)
4163         size += obj_get_size (game, index_);
4164     }
4165 
4166   /* If requested, return object portability. */
4167   if (is_portable)
4168     *is_portable = !(object_size > player_limit);
4169 
4170   /* Return TRUE if the new object exceeds limit. */
4171   return size + object_size > player_limit;
4172 }
4173 
4174 
4175 /*
4176  * lib_cmd_take_npc()
4177  *
4178  * Reject attempts to take an npc.
4179  */
4180 sc_bool
lib_cmd_take_npc(sc_gameref_t game)4181 lib_cmd_take_npc (sc_gameref_t game)
4182 {
4183   const sc_filterref_t filter = gs_get_filter (game);
4184   sc_int npc;
4185   sc_bool is_ambiguous;
4186 
4187   /* Get the referenced npc, and if none, consider complete. */
4188   npc = lib_disambiguate_npc (game, "take", &is_ambiguous);
4189   if (npc == -1)
4190     return is_ambiguous;
4191 
4192   /* Reject this attempt. */
4193   pf_buffer_string (filter, "I don't think ");
4194   lib_print_npc_np (game, npc);
4195   pf_buffer_string (filter, " would appreciate being handled.\n");
4196   return TRUE;
4197 }
4198 
4199 
4200 /*
4201  * lib_take_backend_common()
4202  *
4203  * Common backend handler for taking objects.  Takes all objects currently
4204  * referenced in the game, trying game commands first, and then moving other
4205  * unhandled objects to the player inventory.
4206  *
4207  * Objects to action are flagged in object_references; objects requested but
4208  * deemed not actionable are flagged in multiple_references.
4209  */
4210 static void
lib_take_backend_common(sc_gameref_t game,sc_int associate,sc_bool is_associate_object,sc_bool is_associate_npc)4211 lib_take_backend_common (sc_gameref_t game, sc_int associate,
4212                          sc_bool is_associate_object, sc_bool is_associate_npc)
4213 {
4214   const sc_filterref_t filter = gs_get_filter (game);
4215   sc_int object_count, object, count, trail, total, npc;
4216   sc_int too_heavy, too_large;
4217   sc_bool too_heavy_portable, too_large_portable, has_printed;
4218   assert (!is_associate_object || !is_associate_npc);
4219 
4220   /* Initialize our notions of anything exceeding player capacity. */
4221   too_heavy_portable = too_large_portable = FALSE;
4222   too_large = too_heavy = -1;
4223 
4224   /*
4225    * Try game commands for all referenced objects first.  If any succeed,
4226    * remove that reference from the list.  At the same time, filter out and
4227    * flag any object that takes us over the player's capacity.  We report
4228    * only the first.
4229    */
4230   has_printed = FALSE;
4231   object_count = gs_object_count (game);
4232   for (object = 0; object < object_count; object++)
4233     {
4234       sc_bool status;
4235 
4236       if (!game->object_references[object])
4237         continue;
4238 
4239       /*
4240        * If the object is inside or on something already held by the player,
4241        * capacity checks are meaningless.
4242        */
4243       if (!((gs_object_position (game, object) == OBJ_IN_OBJECT
4244             || gs_object_position (game, object) == OBJ_ON_OBJECT)
4245             && obj_indirectly_held_by_player (game,
4246                                               gs_object_parent (game, object))))
4247         {
4248           sc_bool is_portable;
4249 
4250           /*
4251            * See if the object takes us beyond capacity.  If it does and it's
4252            * the first of its kind, note it and continue.
4253            */
4254           if (lib_object_too_heavy (game, object, &is_portable))
4255             {
4256               if (too_heavy == -1)
4257                 {
4258                   too_heavy = object;
4259                   too_heavy_portable = is_portable;
4260                 }
4261               game->object_references[object] = FALSE;
4262               continue;
4263             }
4264           if (lib_object_too_large (game, object, &is_portable))
4265             {
4266               if (too_large == -1)
4267                 {
4268                   too_large = object;
4269                   too_large_portable = is_portable;
4270                 }
4271               game->object_references[object] = FALSE;
4272               continue;
4273             }
4274         }
4275 
4276       /* Now try for a game command, using the associate if supplied. */
4277       if (is_associate_object)
4278         status = lib_try_game_command_with_object (game, "get",
4279                                                    object, "from", associate);
4280       else if (is_associate_npc)
4281         status = lib_try_game_command_with_npc (game, "get",
4282                                                 object, "from", associate);
4283       else
4284         status = lib_try_game_command_short (game, "get", object);
4285       if (status)
4286         {
4287           game->object_references[object] = FALSE;
4288           has_printed = TRUE;
4289         }
4290     }
4291 
4292   /*
4293    * We attempt acquisition of get-able objects here only for cases where
4294    * there is either no associate, or where the associate is an object.  If
4295    * the associate is an NPC, we're going to refuse all acquisitions later
4296    * on, by forcing object references.
4297    */
4298   total = 0;
4299   if (!is_associate_npc)
4300     {
4301       sc_int parent, start, limit;
4302 
4303       /*
4304        * Attempt to acquire each remaining get-able object in turn, looping
4305        * on each possible parent object in turn, with an initial parent of
4306        * -1 for objects not contained or supported.
4307        *
4308        * If we're dealing with only objects from a known container or
4309        * supporter, eliminate all but one iteration of the parent search.
4310        */
4311       start = is_associate_object ? associate : -1;
4312       limit = is_associate_object ? associate : object_count - 1;
4313 
4314       for (parent = start; parent <= limit; parent++)
4315         {
4316           count = 0;
4317           trail = -1;
4318           for (object = 0; object < object_count; object++)
4319             {
4320               sc_bool is_portable;
4321 
4322               if (!game->object_references[object])
4323                 continue;
4324 
4325               /*
4326                * If parent is -1, ignore contained objects, otherwise ignore
4327                * objects not contained, or if contained, not contained by the
4328                * current parent.
4329                */
4330               if (parent == -1)
4331                 {
4332                   if (gs_object_position (game, object) == OBJ_IN_OBJECT
4333                       || gs_object_position (game, object) == OBJ_ON_OBJECT)
4334                     continue;
4335                 }
4336               else
4337                 {
4338                   if (!(gs_object_position (game, object) == OBJ_IN_OBJECT
4339                         || gs_object_position (game, object) == OBJ_ON_OBJECT))
4340                     continue;
4341                   if (gs_object_parent (game, object) != parent)
4342                     continue;
4343                 }
4344 
4345               /*
4346                * Here we have to repeat capacity checks.  As objects are
4347                * acquired more and more of the player's capacity gets used up.
4348                * This means a check directly before each acquisition.
4349                */
4350                if (parent == -1
4351                    || !obj_indirectly_held_by_player (game, parent))
4352                 {
4353                   if (lib_object_too_heavy (game, object, &is_portable))
4354                     {
4355                       if (too_heavy == -1)
4356                         {
4357                           too_heavy = object;
4358                           too_heavy_portable = is_portable;
4359                         }
4360                       continue;
4361                     }
4362                   if (lib_object_too_large (game, object, &is_portable))
4363                     {
4364                       if (too_large == -1)
4365                         {
4366                           too_large = object;
4367                           too_large_portable = is_portable;
4368                         }
4369                       continue;
4370                     }
4371                 }
4372 
4373               if (count > 0)
4374                 {
4375                   if (count == 1)
4376                     {
4377                       if (has_printed)
4378                         pf_buffer_string (filter, total == 0 ? "\n" : "  ");
4379                       if (parent == -1)
4380                         pf_buffer_string (filter,
4381                                           lib_select_response (game,
4382                                                          "You pick up ",
4383                                                          "I pick up ",
4384                                                          "%player% picks up "));
4385                       else
4386                         pf_buffer_string (filter,
4387                                           lib_select_response (game,
4388                                                            "You take ",
4389                                                            "I take ",
4390                                                            "%player% takes "));
4391                     }
4392                   else
4393                     pf_buffer_string (filter, ", ");
4394                   lib_print_object_np (game, trail);
4395                 }
4396               trail = object;
4397               count++;
4398 
4399               gs_object_player_get (game, object);
4400             }
4401 
4402           if (count >= 1)
4403             {
4404               if (count == 1)
4405                 {
4406                   if (has_printed)
4407                     pf_buffer_string (filter, total == 0 ? "\n" : "  ");
4408                   if (parent == -1)
4409                     pf_buffer_string (filter,
4410                                       lib_select_response (game,
4411                                                          "You pick up ",
4412                                                          "I pick up ",
4413                                                          "%player% picks up "));
4414                   else
4415                     pf_buffer_string (filter,
4416                                       lib_select_response (game,
4417                                                            "You take ",
4418                                                            "I take ",
4419                                                            "%player% takes "));
4420                 }
4421               else
4422                 pf_buffer_string (filter, " and ");
4423               lib_print_object_np (game, trail);
4424               if (parent != -1)
4425                 {
4426                   pf_buffer_string (filter, " from ");
4427                   lib_print_object_np (game, parent);
4428                 }
4429               pf_buffer_character (filter, '.');
4430             }
4431           total += count;
4432           has_printed |= count > 0;
4433         }
4434     }
4435 
4436   /*
4437    * If we ran out of capacity, either in weight or in size, print the
4438    * details.  Note that we currently only report the first object of any
4439    * type to go over capacity.
4440    */
4441   if (too_heavy != -1)
4442     {
4443       if (has_printed)
4444         pf_buffer_string (filter, "  ");
4445       pf_new_sentence (filter);
4446       lib_print_object_np (game, too_heavy);
4447       pf_buffer_string (filter,
4448                         lib_select_plurality (game, too_heavy, " is", " are"));
4449       pf_buffer_string (filter,
4450                         lib_select_response (game,
4451                                            " too heavy for you to carry",
4452                                            " too heavy for me to carry",
4453                                            " too heavy for %player% to carry"));
4454       if (too_heavy_portable)
4455         pf_buffer_string (filter, " at the moment");
4456       pf_buffer_character (filter, '.');
4457       has_printed |= TRUE;
4458     }
4459   else if (too_large != -1)
4460     {
4461       if (has_printed)
4462         pf_buffer_string (filter, "  ");
4463       pf_buffer_string (filter,
4464                         lib_select_response (game,
4465                                              "Your hands are full",
4466                                              "My hands are full",
4467                                              "%player%'s hands are full"));
4468       if (too_large_portable)
4469         pf_buffer_string (filter, " at the moment");
4470       pf_buffer_character (filter, '.');
4471       has_printed |= TRUE;
4472     }
4473 
4474   /*
4475    * Note any remaining multiple references left out of the take operation.
4476    * This is some workload...
4477    *
4478    * First, deal with the case where we have an associated object.
4479    */
4480   if (is_associate_object)
4481     {
4482       count = 0;
4483       trail = -1;
4484       for (object = 0; object < object_count; object++)
4485         {
4486           if (!game->multiple_references[object])
4487             continue;
4488 
4489           if (gs_object_position (game, object) == OBJ_HELD_PLAYER
4490               || gs_object_position (game, object) == OBJ_WORN_PLAYER)
4491             continue;
4492 
4493           if (count > 0)
4494             {
4495               if (count == 1)
4496                 {
4497                   if (has_printed)
4498                     pf_buffer_string (filter, "  ");
4499                   pf_new_sentence (filter);
4500                   lib_print_object_np (game, trail);
4501                 }
4502               else
4503                 pf_buffer_string (filter, ", ");
4504             }
4505           trail = object;
4506           count++;
4507 
4508           game->multiple_references[object] = FALSE;
4509         }
4510 
4511       if (count >= 1)
4512         {
4513           if (count == 1)
4514             {
4515               if (has_printed)
4516                 pf_buffer_string (filter, "  ");
4517               pf_new_sentence (filter);
4518               lib_print_object_np (game, trail);
4519               pf_buffer_string (filter,
4520                                 lib_select_plurality (game, trail,
4521                                                       " is not ",
4522                                                       " are not "));
4523             }
4524           else
4525             {
4526               pf_buffer_string (filter, " and ");
4527               lib_print_object_np (game, trail);
4528               pf_buffer_string (filter, " are not ");
4529             }
4530           if (obj_is_container (game, associate))
4531             {
4532               pf_buffer_string (filter, "in ");
4533               if (obj_is_surface (game, associate))
4534                 pf_buffer_string (filter, "or on ");
4535             }
4536           else
4537             pf_buffer_string (filter, "on ");
4538           lib_print_object_np (game, associate);
4539           pf_buffer_character (filter, '.');
4540         }
4541       has_printed |= count > 0;
4542     }
4543 
4544   /*
4545    * Now, deal with the case where we have an associated NPC.  Once this
4546    * case is handled, we can force the object references so that the code
4547    * that follows on from here will report errors taking all objects.
4548    *
4549    * Note that this means that we can never successfully take an object
4550    * from an NPC; that'll have to happen via a game's own commands.
4551    */
4552   if (is_associate_npc)
4553     {
4554       count = 0;
4555       trail = -1;
4556       for (object = 0; object < object_count; object++)
4557         {
4558           if (!game->multiple_references[object])
4559             continue;
4560 
4561           if (gs_object_position (game, object) == OBJ_PART_NPC)
4562             continue;
4563 
4564           if (count > 0)
4565             {
4566               if (count == 1)
4567                 {
4568                   if (has_printed)
4569                     pf_buffer_string (filter, "  ");
4570                   pf_new_sentence (filter);
4571                   lib_print_npc_np (game, associate);
4572                   pf_buffer_string (filter, " is not carrying ");
4573                 }
4574               else
4575                 pf_buffer_string (filter, ", ");
4576               lib_print_object_np (game, trail);
4577             }
4578           trail = object;
4579           count++;
4580 
4581           game->multiple_references[object] = FALSE;
4582         }
4583 
4584       if (count >= 1)
4585         {
4586           if (count == 1)
4587             {
4588               if (has_printed)
4589                 pf_buffer_string (filter, "  ");
4590               pf_new_sentence (filter);
4591               lib_print_npc_np (game, associate);
4592               pf_buffer_string (filter, " is not carrying ");
4593               lib_print_object_np (game, trail);
4594             }
4595           else
4596             {
4597               pf_buffer_string (filter, " or ");
4598               lib_print_object_np (game, trail);
4599             }
4600           pf_buffer_character (filter, '!');
4601         }
4602       has_printed |= count > 0;
4603 
4604       /*
4605        * Merge any remaining object references into multiple references,
4606        * so that succeeding code complains about the inability to acquire
4607        * these objects.
4608        */
4609       for (object = 0; object < object_count; object++)
4610         {
4611           game->multiple_references[object] |= game->object_references[object];
4612           game->object_references[object] = FALSE;
4613         }
4614     }
4615 
4616   /*
4617    * The remainder of this routine is common error reporting for both object
4618    * and NPC associates (and also for no associates).
4619    */
4620   count = 0;
4621   trail = -1;
4622   for (object = 0; object < object_count; object++)
4623     {
4624       if (!game->multiple_references[object])
4625         continue;
4626 
4627       if (gs_object_position (game, object) != OBJ_HELD_PLAYER)
4628         continue;
4629 
4630       if (count > 0)
4631         {
4632           if (count == 1)
4633             {
4634               if (has_printed)
4635                 pf_buffer_string (filter, "  ");
4636               pf_buffer_string (filter,
4637                                 lib_select_response (game,
4638                                                      "You've already got ",
4639                                                      "I've already got ",
4640                                                      "%player% already has "));
4641             }
4642           else
4643             pf_buffer_string (filter, ", ");
4644           lib_print_object_np (game, trail);
4645         }
4646       trail = object;
4647       count++;
4648 
4649       game->multiple_references[object] = FALSE;
4650     }
4651 
4652   if (count >= 1)
4653     {
4654       if (count == 1)
4655         {
4656           if (has_printed)
4657             pf_buffer_string (filter, "  ");
4658           pf_buffer_string (filter,
4659                             lib_select_response (game,
4660                                                  "You've already got ",
4661                                                  "I've already got ",
4662                                                  "%player% already has "));
4663         }
4664       else
4665         pf_buffer_string (filter, " and ");
4666       lib_print_object_np (game, trail);
4667       pf_buffer_character (filter, '!');
4668     }
4669   has_printed |= count > 0;
4670 
4671   count = 0;
4672   trail = -1;
4673   for (object = 0; object < object_count; object++)
4674     {
4675       if (!game->multiple_references[object])
4676         continue;
4677 
4678       if (gs_object_position (game, object) != OBJ_WORN_PLAYER)
4679         continue;
4680 
4681       if (count > 0)
4682         {
4683           if (count == 1)
4684             {
4685               if (has_printed)
4686                 pf_buffer_string (filter, "  ");
4687               pf_buffer_string (filter,
4688                                 lib_select_response (game,
4689                                                "You're already wearing ",
4690                                                "I'm already wearing ",
4691                                                "%player% is already wearing "));
4692             }
4693           else
4694             pf_buffer_string (filter, ", ");
4695           lib_print_object_np (game, trail);
4696         }
4697       trail = object;
4698       count++;
4699 
4700       game->multiple_references[object] = FALSE;
4701     }
4702 
4703   if (count >= 1)
4704     {
4705       if (count == 1)
4706         {
4707           if (has_printed)
4708             pf_buffer_string (filter, "  ");
4709           pf_buffer_string (filter,
4710                             lib_select_response (game,
4711                                                "You're already wearing ",
4712                                                "I'm already wearing ",
4713                                                "%player% is already wearing "));
4714         }
4715       else
4716         pf_buffer_string (filter, " and ");
4717       lib_print_object_np (game, trail);
4718       pf_buffer_character (filter, '!');
4719     }
4720   has_printed |= count > 0;
4721 
4722   for (npc = 0; npc < gs_npc_count (game); npc++)
4723     {
4724       count = 0;
4725       trail = -1;
4726       for (object = 0; object < object_count; object++)
4727         {
4728           if (!game->multiple_references[object])
4729             continue;
4730 
4731           if (gs_object_position (game, object) != OBJ_HELD_NPC
4732               && gs_object_position (game, object) != OBJ_WORN_NPC)
4733             continue;
4734           if (gs_object_parent (game, object) != npc)
4735             continue;
4736 
4737           if (count > 0)
4738             {
4739               if (count == 1)
4740                 {
4741                   if (has_printed)
4742                     pf_buffer_string (filter, "  ");
4743                   pf_new_sentence (filter);
4744                   lib_print_npc_np (game, gs_object_parent (game, trail));
4745                   pf_buffer_string (filter,
4746                                     lib_select_response (game,
4747                                                  " refuses to give you ",
4748                                                  " refuses to give me ",
4749                                                  " refuses to give %player% "));
4750                 }
4751               else
4752                 pf_buffer_string (filter, ", ");
4753               lib_print_object_np (game, trail);
4754             }
4755           trail = object;
4756           count++;
4757 
4758           game->multiple_references[object] = FALSE;
4759         }
4760 
4761       if (count >= 1)
4762         {
4763           if (count == 1)
4764             {
4765               if (has_printed)
4766                 pf_buffer_string (filter, "  ");
4767               pf_new_sentence (filter);
4768               lib_print_npc_np (game, gs_object_parent (game, trail));
4769               pf_buffer_string (filter,
4770                                 lib_select_response (game,
4771                                                  " refuses to give you ",
4772                                                  " refuses to give me ",
4773                                                  " refuses to give %player% "));
4774             }
4775           else
4776             pf_buffer_string (filter, " and ");
4777           lib_print_object_np (game, trail);
4778           pf_buffer_character (filter, '!');
4779         }
4780       has_printed |= count > 0;
4781     }
4782 
4783   count = 0;
4784   trail = -1;
4785   for (object = 0; object < object_count; object++)
4786     {
4787       if (!game->multiple_references[object])
4788         continue;
4789 
4790       if (count > 0)
4791         {
4792           if (count == 1)
4793             {
4794               if (has_printed)
4795                 pf_buffer_string (filter, "  ");
4796               pf_buffer_string (filter,
4797                                 lib_select_response (game,
4798                                                      "You can't take ",
4799                                                      "I can't take ",
4800                                                      "%player% can't take "));
4801             }
4802           else
4803             pf_buffer_string (filter, ", ");
4804           lib_print_object_np (game, trail);
4805         }
4806       trail = object;
4807       count++;
4808 
4809       game->multiple_references[object] = FALSE;
4810     }
4811 
4812   if (count >= 1)
4813     {
4814       if (count == 1)
4815         {
4816           if (has_printed)
4817             pf_buffer_string (filter, "  ");
4818           pf_buffer_string (filter,
4819                             lib_select_response (game,
4820                                                  "You can't take ",
4821                                                  "I can't take ",
4822                                                  "%player% can't take "));
4823         }
4824       else
4825         pf_buffer_string (filter, " and ");
4826       lib_print_object_np (game, trail);
4827       pf_buffer_character (filter, '!');
4828     }
4829 }
4830 
4831 
4832 /*
4833  * lib_take_backend()
4834  * lib_take_from_object_backend()
4835  * lib_take_from_npc_backend()
4836  *
4837  * Facets of lib_take_backend_common().  Provide backend handling for either
4838  * the plain "take" handlers, or the "take from <something>" handlers.
4839  */
4840 static void
lib_take_backend(sc_gameref_t game)4841 lib_take_backend (sc_gameref_t game)
4842 {
4843   lib_take_backend_common (game, -1, FALSE, FALSE);
4844 }
4845 
4846 static void
lib_take_from_object_backend(sc_gameref_t game,sc_int associate)4847 lib_take_from_object_backend (sc_gameref_t game, sc_int associate)
4848 {
4849   lib_take_backend_common (game, associate, TRUE, FALSE);
4850 }
4851 
4852 static void
lib_take_from_npc_backend(sc_gameref_t game,sc_int associate)4853 lib_take_from_npc_backend (sc_gameref_t game, sc_int associate)
4854 {
4855   lib_take_backend_common (game, associate, FALSE, TRUE);
4856 }
4857 
4858 
4859 /*
4860  * lib_take_filter()
4861  * lib_take_not_associated_filter()
4862  *
4863  * Helper functions for deciding if an object may be acquired in this context.
4864  * Returns TRUE if an object may be acquired, FALSE otherwise.
4865  */
4866 static sc_bool
lib_take_filter(sc_gameref_t game,sc_int object,sc_int unused)4867 lib_take_filter (sc_gameref_t game, sc_int object, sc_int unused)
4868 {
4869   assert (unused == -1);
4870 
4871   /*
4872    * To be take-able, an object must be visible in the room, not static,
4873    * and not already held or worn by the player or an NPC.
4874    */
4875   return obj_indirectly_in_room (game, object, gs_playerroom (game))
4876          && !obj_is_static (game, object)
4877          && !(gs_object_position (game, object) == OBJ_HELD_PLAYER
4878               || gs_object_position (game, object) == OBJ_WORN_PLAYER)
4879          && !(gs_object_position (game, object) == OBJ_HELD_NPC
4880               || gs_object_position (game, object) == OBJ_WORN_NPC);
4881 }
4882 
4883 static sc_bool
lib_take_not_associated_filter(sc_gameref_t game,sc_int object,sc_int unused)4884 lib_take_not_associated_filter (sc_gameref_t game,
4885                                 sc_int object, sc_int unused)
4886 {
4887   assert (unused == -1);
4888 
4889   /* In addition to other checks, the object may not be in or on an object. */
4890   return lib_take_filter (game, object, -1)
4891          && !(gs_object_position (game, object) == OBJ_ON_OBJECT
4892               || gs_object_position (game, object) == OBJ_IN_OBJECT);
4893 }
4894 
4895 
4896 /*
4897  * lib_cmd_take_all()
4898  *
4899  * Attempt to take all objects currently visible to the player.
4900  */
4901 sc_bool
lib_cmd_take_all(sc_gameref_t game)4902 lib_cmd_take_all (sc_gameref_t game)
4903 {
4904   const sc_filterref_t filter = gs_get_filter (game);
4905   sc_int objects;
4906 
4907   /* Filter objects into references, then handle with the backend. */
4908   gs_set_multiple_references (game);
4909   objects = lib_apply_multiple_filter (game,
4910                                        lib_take_not_associated_filter, -1,
4911                                        NULL);
4912   gs_clear_multiple_references (game);
4913   if (objects > 0)
4914     lib_take_backend (game);
4915   else
4916     pf_buffer_string (filter, "There is nothing to pick up here.");
4917 
4918   pf_buffer_character (filter, '\n');
4919   return TRUE;
4920 }
4921 
4922 
4923 /*
4924  * lib_cmd_take_except_multiple()
4925  *
4926  * Take all objects currently available to the player, excepting those listed
4927  * in %text%.
4928  */
4929 sc_bool
lib_cmd_take_except_multiple(sc_gameref_t game)4930 lib_cmd_take_except_multiple (sc_gameref_t game)
4931 {
4932   const sc_filterref_t filter = gs_get_filter (game);
4933   sc_int objects, references;
4934 
4935   /* Parse the multiple objects list to find leave target objects. */
4936   if (!lib_parse_multiple_objects (game, "leave",
4937                                    lib_take_not_associated_filter, -1,
4938                                    &references))
4939     return FALSE;
4940   else if (references == 0)
4941     return TRUE;
4942 
4943   /* Filter objects into references, then handle with the backend. */
4944   objects = lib_apply_except_filter (game,
4945                                      lib_take_not_associated_filter, -1,
4946                                      &references);
4947   if (objects > 0 || references > 0)
4948     lib_take_backend (game);
4949   else
4950     {
4951       if (objects == 0)
4952         pf_buffer_string (filter, "There is nothing else to pick up here.");
4953       else
4954         pf_buffer_string (filter, "There is nothing to pick up here.");
4955     }
4956 
4957   pf_buffer_character (filter, '\n');
4958   return TRUE;
4959 }
4960 
4961 
4962 /*
4963  * lib_cmd_take_multiple()
4964  *
4965  * Take all objects currently available to the player and listed in %text%.
4966  */
4967 sc_bool
lib_cmd_take_multiple(sc_gameref_t game)4968 lib_cmd_take_multiple (sc_gameref_t game)
4969 {
4970   const sc_filterref_t filter = gs_get_filter (game);
4971   sc_int objects, references;
4972 
4973   /* Parse the multiple objects list to find take target objects. */
4974   if (!lib_parse_multiple_objects (game, "take",
4975                                    lib_take_filter, -1,
4976                                    &references))
4977     return FALSE;
4978   else if (references == 0)
4979     return TRUE;
4980 
4981   /* Filter objects into references, then handle with the backend. */
4982   objects = lib_apply_multiple_filter (game,
4983                                        lib_take_filter, -1,
4984                                        &references);
4985   if (objects > 0 || references > 0)
4986     lib_take_backend (game);
4987   else
4988     pf_buffer_string (filter, "There is nothing to pick up here.");
4989 
4990   pf_buffer_character (filter, '\n');
4991   return TRUE;
4992 }
4993 
4994 
4995 /*
4996  * lib_take_from_filter()
4997  *
4998  * Helper function for deciding if an object may be acquired in this context.
4999  * Returns TRUE if an object may be acquired, FALSE otherwise.
5000  */
5001 static sc_bool
lib_take_from_filter(sc_gameref_t game,sc_int object,sc_int associate)5002 lib_take_from_filter (sc_gameref_t game, sc_int object, sc_int associate)
5003 {
5004   /*
5005    * To be take-able, an object must be either inside or on the specified
5006    * object.
5007    */
5008   return (gs_object_position (game, object) == OBJ_IN_OBJECT
5009           || gs_object_position (game, object) == OBJ_ON_OBJECT)
5010          && !obj_is_static (game, object)
5011          && gs_object_parent (game, object) == associate;
5012 }
5013 
5014 
5015 /*
5016  * lib_take_from_empty()
5017  *
5018  * Common error handling for when nothing is taken from a container or
5019  * supporter object.
5020  */
5021 static void
lib_take_from_empty(sc_gameref_t game,sc_int associate,sc_bool is_except)5022 lib_take_from_empty (sc_gameref_t game, sc_int associate, sc_bool is_except)
5023 {
5024   const sc_filterref_t filter = gs_get_filter (game);
5025 
5026   if (obj_is_container (game, associate) && obj_is_surface (game, associate))
5027     {
5028       if (gs_object_openness (game, associate) <= OBJ_OPEN)
5029         {
5030           if (is_except)
5031             pf_buffer_string (filter, "There is nothing else in or on ");
5032           else
5033             pf_buffer_string (filter, "There is nothing in or on ");
5034           lib_print_object_np (game, associate);
5035           pf_buffer_character (filter, '.');
5036         }
5037       else
5038         {
5039           if (is_except)
5040             pf_buffer_string (filter, "There is nothing else on ");
5041           else
5042             pf_buffer_string (filter, "There is nothing on ");
5043           lib_print_object_np (game, associate);
5044           if (gs_object_openness (game, associate) == OBJ_LOCKED)
5045             pf_buffer_string (filter, " and it is locked.");
5046           else
5047             pf_buffer_string (filter, " and it is closed.");
5048         }
5049     }
5050   else
5051     {
5052       if (obj_is_container (game, associate))
5053         {
5054           if (gs_object_openness (game, associate) <= OBJ_OPEN)
5055             {
5056               if (is_except)
5057                 pf_buffer_string (filter, "There is nothing else inside ");
5058               else
5059                 pf_buffer_string (filter, "There is nothing inside ");
5060               lib_print_object_np (game, associate);
5061               pf_buffer_character (filter, '.');
5062             }
5063           else
5064             {
5065               pf_new_sentence (filter);
5066               lib_print_object_np (game, associate);
5067               pf_buffer_string (filter,
5068                                 lib_select_plurality (game, associate,
5069                                                       " is ", " are "));
5070               if (gs_object_openness (game, associate) == OBJ_LOCKED)
5071                 pf_buffer_string (filter, "locked.");
5072               else
5073                 pf_buffer_string (filter, "closed.");
5074             }
5075         }
5076       else
5077         {
5078           if (is_except)
5079             pf_buffer_string (filter, "There is nothing else on ");
5080           else
5081             pf_buffer_string (filter, "There is nothing on ");
5082           lib_print_object_np (game, associate);
5083           pf_buffer_character (filter, '.');
5084         }
5085     }
5086 }
5087 
5088 
5089 /*
5090  * lib_take_from_is_valid()
5091  *
5092  * Validate the supporter requested in "take from" commands.
5093  */
5094 static sc_bool
lib_take_from_is_valid(sc_gameref_t game,sc_int associate)5095 lib_take_from_is_valid (sc_gameref_t game, sc_int associate)
5096 {
5097   const sc_filterref_t filter = gs_get_filter (game);
5098 
5099   /* Disallow emptying non-container/non-surface objects. */
5100   if (!(obj_is_container (game, associate)
5101         || obj_is_surface (game, associate)))
5102     {
5103       pf_buffer_string (filter,
5104                         lib_select_response (game,
5105                                          "You can't take anything from ",
5106                                          "I can't take anything from ",
5107                                          "%player% can't take anything from "));
5108       lib_print_object_np (game, associate);
5109       pf_buffer_string (filter, ".\n");
5110       return FALSE;
5111     }
5112 
5113   /* If object is a container, and is closed, reject now. */
5114   if (obj_is_container (game, associate)
5115       && gs_object_openness (game, associate) > OBJ_OPEN)
5116     {
5117       pf_new_sentence (filter);
5118       lib_print_object_np (game, associate);
5119       pf_buffer_string (filter,
5120                         lib_select_plurality (game, associate,
5121                                              " is closed.\n",
5122                                              " are closed.\n"));
5123       return FALSE;
5124     }
5125 
5126   /* Associate is a valid target for "take from". */
5127   return TRUE;
5128 }
5129 
5130 
5131 /*
5132  * lib_cmd_take_all_from()
5133  *
5134  * Attempt to take all objects contained in or supported by a given object.
5135  */
5136 sc_bool
lib_cmd_take_all_from(sc_gameref_t game)5137 lib_cmd_take_all_from (sc_gameref_t game)
5138 {
5139   const sc_filterref_t filter = gs_get_filter (game);
5140   sc_int associate, objects;
5141   sc_bool is_ambiguous;
5142 
5143   /* Get the referenced object, and if none, consider complete. */
5144   associate = lib_disambiguate_object (game, "take from", &is_ambiguous);
5145   if (associate == -1)
5146     return is_ambiguous;
5147 
5148   /* Validate the associate object to take from. */
5149   if (!lib_take_from_is_valid (game, associate))
5150     return TRUE;
5151 
5152   /* Filter objects into references, then handle with the backend. */
5153   gs_set_multiple_references (game);
5154   objects = lib_apply_multiple_filter (game,
5155                                        lib_take_from_filter, associate,
5156                                        NULL);
5157   gs_clear_multiple_references (game);
5158   if (objects > 0)
5159     lib_take_from_object_backend (game, associate);
5160   else
5161     lib_take_from_empty (game, associate, FALSE);
5162 
5163   pf_buffer_character (filter, '\n');
5164   return TRUE;
5165 }
5166 
5167 
5168 /*
5169  * lib_cmd_take_from_except_multiple()
5170  *
5171  * Take all objects contained in or supported by a given object, excepting
5172  * those listed in %text%.
5173  */
5174 sc_bool
lib_cmd_take_from_except_multiple(sc_gameref_t game)5175 lib_cmd_take_from_except_multiple (sc_gameref_t game)
5176 {
5177   const sc_filterref_t filter = gs_get_filter (game);
5178   sc_int associate, objects, references;
5179   sc_bool is_ambiguous;
5180 
5181   /* Get the referenced object, and if none, consider complete. */
5182   associate = lib_disambiguate_object (game, "take from", &is_ambiguous);
5183   if (associate == -1)
5184     return is_ambiguous;
5185 
5186   /* Parse the multiple objects list to find leave target objects. */
5187   if (!lib_parse_multiple_objects (game, "leave",
5188                                    lib_take_from_filter, associate,
5189                                    &references))
5190     return FALSE;
5191   else if (references == 0)
5192     return TRUE;
5193 
5194   /* Validate the associate object to take from. */
5195   if (!lib_take_from_is_valid (game, associate))
5196     return TRUE;
5197 
5198   /* As a special case, complain about requests to retain the associate. */
5199   if (game->multiple_references[associate])
5200     {
5201       pf_buffer_string (filter,
5202                         "I only understood you as far as wanting to leave ");
5203       lib_print_object_np (game, associate);
5204       pf_buffer_string (filter, ".\n");
5205       return TRUE;
5206     }
5207 
5208   /* Filter objects into references, then handle with the backend. */
5209   objects = lib_apply_except_filter (game,
5210                                      lib_take_from_filter, associate,
5211                                      &references);
5212   if (objects > 0 || references > 0)
5213     lib_take_from_object_backend (game, associate);
5214   else
5215     lib_take_from_empty (game, associate, TRUE);
5216 
5217   pf_buffer_character (filter, '\n');
5218   return TRUE;
5219 }
5220 
5221 
5222 /*
5223  * lib_cmd_take_from_multiple()
5224  *
5225  * Take the objects currently inside an object and listed in %text%.  This
5226  * function isn't mandatory -- plain "take <object>" works fine with contain-
5227  * ers and surfaces, but it's a standard in Adrift so here it is.
5228  */
5229 sc_bool
lib_cmd_take_from_multiple(sc_gameref_t game)5230 lib_cmd_take_from_multiple (sc_gameref_t game)
5231 {
5232   const sc_filterref_t filter = gs_get_filter (game);
5233   sc_int associate, objects, references;
5234   sc_bool is_ambiguous;
5235 
5236   /* Get the referenced object, and if none, consider complete. */
5237   associate = lib_disambiguate_object (game, "take from", &is_ambiguous);
5238   if (associate == -1)
5239     return is_ambiguous;
5240 
5241   /* Parse the multiple objects list to find take target objects. */
5242   if (!lib_parse_multiple_objects (game, "take",
5243                                    lib_take_from_filter, associate,
5244                                    &references))
5245     return FALSE;
5246   else if (references == 0)
5247     return TRUE;
5248 
5249   /* Validate the associate object to take from. */
5250   if (!lib_take_from_is_valid (game, associate))
5251     return TRUE;
5252 
5253   /* Filter objects into references, then handle with the backend. */
5254   objects = lib_apply_multiple_filter (game,
5255                                        lib_take_from_filter, associate,
5256                                        &references);
5257   if (objects > 0 || references > 0)
5258     lib_take_from_object_backend (game, associate);
5259   else
5260     lib_take_from_empty (game, associate, FALSE);
5261 
5262   pf_buffer_character (filter, '\n');
5263   return TRUE;
5264 }
5265 
5266 
5267 /*
5268  * lib_take_from_npc_filter()
5269  *
5270  * Helper function for deciding if an object may be acquired in this context.
5271  * Returns TRUE if an object may be acquired, FALSE otherwise.
5272  */
5273 static sc_bool
lib_take_from_npc_filter(sc_gameref_t game,sc_int object,sc_int associate)5274 lib_take_from_npc_filter (sc_gameref_t game, sc_int object, sc_int associate)
5275 {
5276   /*
5277    * To be take-able, an object must be either held or worn by the specified
5278    * NPC.
5279    */
5280   return (gs_object_position (game, object) == OBJ_HELD_NPC
5281           || gs_object_position (game, object) == OBJ_WORN_NPC)
5282          && !obj_is_static (game, object)
5283          && gs_object_parent (game, object) == associate;
5284 }
5285 
5286 
5287 /*
5288  * lib_cmd_take_all_from_npc()
5289  *
5290  * Attempt to take all objects held or worn by a given NPC.
5291  */
5292 sc_bool
lib_cmd_take_all_from_npc(sc_gameref_t game)5293 lib_cmd_take_all_from_npc (sc_gameref_t game)
5294 {
5295   const sc_filterref_t filter = gs_get_filter (game);
5296   sc_int associate, objects;
5297   sc_bool is_ambiguous;
5298 
5299   /* Get the referenced NPC, and if none, consider complete. */
5300   associate = lib_disambiguate_npc (game, "take from", &is_ambiguous);
5301   if (associate == -1)
5302     return is_ambiguous;
5303 
5304   /* Filter objects into references, then handle with the backend. */
5305   gs_set_multiple_references (game);
5306   objects = lib_apply_multiple_filter (game,
5307                                        lib_take_from_npc_filter, associate,
5308                                        NULL);
5309   gs_clear_multiple_references (game);
5310   if (objects > 0)
5311     lib_take_from_npc_backend (game, associate);
5312   else
5313     {
5314       pf_new_sentence (filter);
5315       lib_print_npc_np (game, associate);
5316       pf_buffer_string (filter, " is not carrying anything!");
5317     }
5318 
5319   pf_buffer_character (filter, '\n');
5320   return TRUE;
5321 }
5322 
5323 
5324 /*
5325  * lib_cmd_take_from_npc_except_multiple()
5326  *
5327  * Attempt to take all objects held or worn by a given NPC, excepting those
5328  * listed in %text%.
5329  */
5330 sc_bool
lib_cmd_take_from_npc_except_multiple(sc_gameref_t game)5331 lib_cmd_take_from_npc_except_multiple (sc_gameref_t game)
5332 {
5333   const sc_filterref_t filter = gs_get_filter (game);
5334   sc_int associate, objects, references;
5335   sc_bool is_ambiguous;
5336 
5337   /* Get the referenced NPC, and if none, consider complete. */
5338   associate = lib_disambiguate_npc (game, "take from", &is_ambiguous);
5339   if (associate == -1)
5340     return is_ambiguous;
5341 
5342   /* Parse the multiple objects list to find leave target objects. */
5343   if (!lib_parse_multiple_objects (game, "leave",
5344                                    lib_take_from_npc_filter, associate,
5345                                    &references))
5346     return FALSE;
5347   else if (references == 0)
5348     return TRUE;
5349 
5350   /* Filter objects into references, then handle with the backend. */
5351   objects = lib_apply_except_filter (game,
5352                                      lib_take_from_npc_filter, associate,
5353                                      &references);
5354   if (objects > 0 || references > 0)
5355     lib_take_from_npc_backend (game, associate);
5356   else
5357     {
5358       pf_new_sentence (filter);
5359       lib_print_npc_np (game, associate);
5360       pf_buffer_string (filter, " is not carrying anything else!");
5361     }
5362 
5363   pf_buffer_character (filter, '\n');
5364   return TRUE;
5365 }
5366 
5367 
5368 /*
5369  * lib_cmd_take_from_npc_multiple()
5370  *
5371  * Attempt to take the objects currently held or worn by an NPC and listed
5372  * in %text%.
5373  */
5374 sc_bool
lib_cmd_take_from_npc_multiple(sc_gameref_t game)5375 lib_cmd_take_from_npc_multiple (sc_gameref_t game)
5376 {
5377   const sc_filterref_t filter = gs_get_filter (game);
5378   sc_int associate, objects, references;
5379   sc_bool is_ambiguous;
5380 
5381   /* Get the referenced NPC, and if none, consider complete. */
5382   associate = lib_disambiguate_npc (game, "take from", &is_ambiguous);
5383   if (associate == -1)
5384     return is_ambiguous;
5385 
5386   /* Parse the multiple objects list to find take target objects. */
5387   if (!lib_parse_multiple_objects (game, "take",
5388                                    lib_take_from_npc_filter, associate,
5389                                    &references))
5390     return FALSE;
5391   else if (references == 0)
5392     return TRUE;
5393 
5394   /* Filter objects into references, then handle with the backend. */
5395   objects = lib_apply_multiple_filter (game,
5396                                        lib_take_from_npc_filter, associate,
5397                                        &references);
5398   if (objects > 0 || references > 0)
5399     lib_take_from_npc_backend (game, associate);
5400   else
5401     {
5402       pf_new_sentence (filter);
5403       lib_print_npc_np (game, associate);
5404       pf_buffer_string (filter, " is not carrying anything!");
5405     }
5406 
5407   pf_buffer_character (filter, '\n');
5408   return TRUE;
5409 }
5410 
5411 
5412 /*
5413  * lib_drop_backend()
5414  *
5415  * Common backend handler for dropping objects.  Drops all objects currently
5416  * referenced in the game, trying game commands first, and then moving other
5417  * unhandled objects to the player room floor.
5418  *
5419  * Objects to action are flagged in object_references; objects requested but
5420  * deemed not actionable are flagged in multiple_references.
5421  */
5422 static void
lib_drop_backend(sc_gameref_t game)5423 lib_drop_backend (sc_gameref_t game)
5424 {
5425   const sc_filterref_t filter = gs_get_filter (game);
5426   sc_int object_count, object, count, trail;
5427   sc_bool has_printed;
5428 
5429   /*
5430    * Try game commands for all referenced objects first.  If any succeed,
5431    * remove that reference from the list.
5432    */
5433   has_printed = FALSE;
5434   object_count = gs_object_count (game);
5435   for (object = 0; object < object_count; object++)
5436     {
5437       if (!game->object_references[object])
5438         continue;
5439 
5440       if (lib_try_game_command_short (game, "drop", object))
5441         {
5442           game->object_references[object] = FALSE;
5443           has_printed = TRUE;
5444         }
5445     }
5446 
5447   /* Drop every object that remains referenced. */
5448   count = 0;
5449   trail = -1;
5450   for (object = 0; object < object_count; object++)
5451     {
5452       if (!game->object_references[object])
5453         continue;
5454 
5455       if (count > 0)
5456         {
5457           if (count == 1)
5458             {
5459               if (has_printed)
5460                 pf_buffer_string (filter, "  ");
5461               pf_buffer_string (filter,
5462                                 lib_select_response (game,
5463                                                      "You drop ",
5464                                                      "I drop ",
5465                                                      "%player% drops "));
5466             }
5467           else
5468             pf_buffer_string (filter, ", ");
5469           lib_print_object_np (game, trail);
5470         }
5471       trail = object;
5472       count++;
5473 
5474       gs_object_to_room (game, object, gs_playerroom (game));
5475     }
5476 
5477   if (count >= 1)
5478     {
5479       if (count == 1)
5480         {
5481           if (has_printed)
5482             pf_buffer_string (filter, "  ");
5483           pf_buffer_string (filter,
5484                             lib_select_response (game,
5485                                                  "You drop ",
5486                                                  "I drop ",
5487                                                  "%player% drops "));
5488         }
5489       else
5490         pf_buffer_string (filter, " and ");
5491       lib_print_object_np (game, trail);
5492       pf_buffer_character (filter, '.');
5493     }
5494   has_printed |= count > 0;
5495 
5496   /* Note any remaining multiple references left out of the drop operation. */
5497   count = 0;
5498   trail = -1;
5499   for (object = 0; object < object_count; object++)
5500     {
5501       if (!game->multiple_references[object])
5502         continue;
5503 
5504       if (count > 0)
5505         {
5506           if (count == 1)
5507             {
5508               if (has_printed)
5509                 pf_buffer_string (filter, "  ");
5510               pf_buffer_string (filter,
5511                                 lib_select_response (game,
5512                                                    "You are not holding ",
5513                                                    "I am not holding ",
5514                                                    "%player% is not holding "));
5515             }
5516           else
5517             pf_buffer_string (filter, ", ");
5518           lib_print_object_np (game, trail);
5519         }
5520       trail = object;
5521       count++;
5522 
5523       game->multiple_references[object] = FALSE;
5524     }
5525 
5526   if (count >= 1)
5527     {
5528       if (count == 1)
5529         {
5530           if (has_printed)
5531             pf_buffer_string (filter, "  ");
5532           pf_buffer_string (filter,
5533                             lib_select_response (game,
5534                                                  "You are not holding ",
5535                                                  "I am not holding ",
5536                                                  "%player% is not holding "));
5537         }
5538       else
5539         pf_buffer_string (filter, " or ");
5540       lib_print_object_np (game, trail);
5541       pf_buffer_character (filter, '.');
5542     }
5543 }
5544 
5545 
5546 /*
5547  * lib_drop_filter()
5548  *
5549  * Helper function for deciding if an object may be dropped in this context.
5550  * Returns TRUE if an object may be dropped, FALSE otherwise.
5551  */
5552 static sc_bool
lib_drop_filter(sc_gameref_t game,sc_int object,sc_int unused)5553 lib_drop_filter (sc_gameref_t game, sc_int object, sc_int unused)
5554 {
5555   assert (unused == -1);
5556 
5557   return !obj_is_static (game, object)
5558          && gs_object_position (game, object) == OBJ_HELD_PLAYER;
5559 }
5560 
5561 
5562 /*
5563  * lib_cmd_drop_all()
5564  *
5565  * Drop all objects currently held by the player.
5566  */
5567 sc_bool
lib_cmd_drop_all(sc_gameref_t game)5568 lib_cmd_drop_all (sc_gameref_t game)
5569 {
5570   const sc_filterref_t filter = gs_get_filter (game);
5571   sc_int objects;
5572 
5573   /* Filter objects into references, then handle with the backend. */
5574   gs_set_multiple_references (game);
5575   objects = lib_apply_multiple_filter (game,
5576                                        lib_drop_filter, -1,
5577                                        NULL);
5578   gs_clear_multiple_references (game);
5579   if (objects > 0)
5580     lib_drop_backend (game);
5581   else
5582     {
5583       pf_buffer_string (filter,
5584                         lib_select_response (game,
5585                                           "You're not carrying anything.",
5586                                           "I'm not carrying anything.",
5587                                           "%player%'s not carrying anything."));
5588     }
5589 
5590   pf_buffer_character (filter, '\n');
5591   return TRUE;
5592 }
5593 
5594 
5595 /*
5596  * lib_cmd_drop_except_multiple()
5597  *
5598  * Drop all objects currently held by the player, excepting those listed in
5599  * %text%.
5600  */
5601 sc_bool
lib_cmd_drop_except_multiple(sc_gameref_t game)5602 lib_cmd_drop_except_multiple (sc_gameref_t game)
5603 {
5604   const sc_filterref_t filter = gs_get_filter (game);
5605   sc_int objects, references;
5606 
5607   /* Parse the multiple objects list to find retain target objects. */
5608   if (!lib_parse_multiple_objects (game, "retain",
5609                                    lib_drop_filter, -1,
5610                                    &references))
5611     return FALSE;
5612   else if (references == 0)
5613     return TRUE;
5614 
5615   /* Filter objects into references, then handle with the backend. */
5616   objects = lib_apply_except_filter (game,
5617                                      lib_drop_filter, -1,
5618                                      &references);
5619   if (objects > 0 || references > 0)
5620     lib_drop_backend (game);
5621   else
5622     {
5623       pf_buffer_string (filter,
5624                         lib_select_response (game,
5625                                            "You are not holding anything",
5626                                            "I am not holding anything",
5627                                            "%player% is not holding anything"));
5628       if (objects == 0)
5629         pf_buffer_string (filter, " else");
5630       pf_buffer_character (filter, '.');
5631     }
5632 
5633   pf_buffer_character (filter, '\n');
5634   return TRUE;
5635 }
5636 
5637 
5638 /*
5639  * lib_cmd_drop_multiple()
5640  *
5641  * Drop all objects currently held by the player and listed in %text%.
5642  */
5643 sc_bool
lib_cmd_drop_multiple(sc_gameref_t game)5644 lib_cmd_drop_multiple (sc_gameref_t game)
5645 {
5646   const sc_filterref_t filter = gs_get_filter (game);
5647   sc_int objects, references;
5648 
5649   /* Parse the multiple objects list to find drop target objects. */
5650   if (!lib_parse_multiple_objects (game, "drop",
5651                                    lib_drop_filter, -1,
5652                                    &references))
5653     return FALSE;
5654   else if (references == 0)
5655     return TRUE;
5656 
5657   /* Filter objects into references, then handle with the backend. */
5658   objects = lib_apply_multiple_filter (game,
5659                                        lib_drop_filter, -1,
5660                                        &references);
5661   if (objects > 0 || references > 0)
5662     lib_drop_backend (game);
5663   else
5664     {
5665       pf_buffer_string (filter,
5666                         lib_select_response (game,
5667                                           "You are not holding anything.",
5668                                           "I am not holding anything.",
5669                                           "%player% is not holding anything."));
5670     }
5671 
5672   pf_buffer_character (filter, '\n');
5673   return TRUE;
5674 }
5675 
5676 
5677 /*
5678  * lib_cmd_give_object_npc()
5679  * lib_cmd_give_object()
5680  *
5681  * Attempt to give an object to an NPC.
5682  */
5683 sc_bool
lib_cmd_give_object_npc(sc_gameref_t game)5684 lib_cmd_give_object_npc (sc_gameref_t game)
5685 {
5686   const sc_filterref_t filter = gs_get_filter (game);
5687   sc_int object, npc;
5688   sc_bool is_ambiguous;
5689 
5690   /* Get the referenced object, and if none, consider complete. */
5691   object = lib_disambiguate_object (game, "give", &is_ambiguous);
5692   if (object == -1)
5693     return is_ambiguous;
5694 
5695   /* Get the referenced npc, and if none, consider complete. */
5696   npc = lib_disambiguate_npc (game, "give to", NULL);
5697   if (npc == -1)
5698     return TRUE;
5699 
5700   /* Reject if not holding the object offered. */
5701   if (gs_object_position (game, object) != OBJ_HELD_PLAYER)
5702     {
5703       pf_buffer_string (filter,
5704                         lib_select_response (game,
5705                                              "You don't have ",
5706                                              "I don't have ",
5707                                              "%player% doesn't have "));
5708       lib_print_object_np (game, object);
5709       pf_buffer_string (filter, "!\n");
5710       return TRUE;
5711     }
5712 
5713   /* After all that, the npc is disinterested. */
5714   pf_new_sentence (filter);
5715   lib_print_npc_np (game, npc);
5716   pf_buffer_string (filter, " doesn't seem interested in ");
5717   lib_print_object_np (game, object);
5718   pf_buffer_string (filter, ".\n");
5719   return TRUE;
5720 }
5721 
5722 sc_bool
lib_cmd_give_object(sc_gameref_t game)5723 lib_cmd_give_object (sc_gameref_t game)
5724 {
5725   const sc_filterref_t filter = gs_get_filter (game);
5726   sc_int object;
5727   sc_bool is_ambiguous;
5728 
5729   /* Get the referenced object, and if none, consider complete. */
5730   object = lib_disambiguate_object (game, "give", &is_ambiguous);
5731   if (object == -1)
5732     return is_ambiguous;
5733 
5734   /* Reject if not holding the object offered. */
5735   if (gs_object_position (game, object) != OBJ_HELD_PLAYER)
5736     {
5737       pf_buffer_string (filter,
5738                         lib_select_response (game,
5739                                              "You don't have ",
5740                                              "I don't have ",
5741                                              "%player% doesn't have "));
5742       lib_print_object_np (game, object);
5743       pf_buffer_string (filter, "!\n");
5744       return TRUE;
5745     }
5746 
5747   /* After all that, we have to ask (and shouldn't this be "to whom?"). */
5748   pf_buffer_string (filter, "Give ");
5749   lib_print_object_np (game, object);
5750   pf_buffer_string (filter, " to who?\n");
5751   return TRUE;
5752 }
5753 
5754 
5755 /*
5756  * lib_wear_backend()
5757  *
5758  * Common backend handler for wearing objects.  Puts on all objects currently
5759  * referenced in the game, moving objects to worn by player.
5760  *
5761  * Objects to action are flagged in object_references; objects requested but
5762  * deemed not actionable are flagged in multiple_references.
5763  */
5764 static void
lib_wear_backend(sc_gameref_t game)5765 lib_wear_backend (sc_gameref_t game)
5766 {
5767   const sc_filterref_t filter = gs_get_filter (game);
5768   sc_int object_count, object, count, trail;
5769   sc_bool has_printed;
5770 
5771   /*
5772    * Try game commands for all referenced objects first.  If any succeed,
5773    * remove that reference from the list.
5774    */
5775   has_printed = FALSE;
5776   object_count = gs_object_count (game);
5777   for (object = 0; object < object_count; object++)
5778     {
5779       if (!game->object_references[object])
5780         continue;
5781 
5782       if (lib_try_game_command_short (game, "wear", object))
5783         {
5784           game->object_references[object] = FALSE;
5785           has_printed = TRUE;
5786         }
5787     }
5788 
5789   /* Wear every object referenced. */
5790   count = 0;
5791   trail = -1;
5792   for (object = 0; object < object_count; object++)
5793     {
5794       if (!game->object_references[object])
5795         continue;
5796 
5797       if (count > 0)
5798         {
5799           if (count == 1)
5800             {
5801               if (has_printed)
5802                 pf_buffer_string (filter, "  ");
5803               pf_buffer_string (filter,
5804                                 lib_select_response (game,
5805                                                      "You put on ",
5806                                                      "I put on ",
5807                                                      "%player% puts on "));
5808             }
5809           else
5810             pf_buffer_string (filter, ", ");
5811           lib_print_object_np (game, trail);
5812         }
5813       trail = object;
5814       count++;
5815 
5816       gs_object_player_wear (game, object);
5817     }
5818 
5819   if (count >= 1)
5820     {
5821       if (count == 1)
5822         {
5823           if (has_printed)
5824             pf_buffer_string (filter, "  ");
5825           pf_buffer_string (filter,
5826                             lib_select_response (game,
5827                                                  "You put on ",
5828                                                  "I put on ",
5829                                                  "%player% puts on "));
5830         }
5831       else
5832         pf_buffer_string (filter, " and ");
5833       lib_print_object_np (game, trail);
5834       pf_buffer_character (filter, '.');
5835     }
5836   has_printed |= count > 0;
5837 
5838   /* Note any remaining multiple references left out of the wear operation. */
5839   count = 0;
5840   trail = -1;
5841   for (object = 0; object < object_count; object++)
5842     {
5843       if (!game->multiple_references[object])
5844         continue;
5845 
5846       if (gs_object_position (game, object) != OBJ_WORN_PLAYER)
5847         continue;
5848 
5849       if (count > 0)
5850         {
5851           if (count == 1)
5852             {
5853               if (has_printed)
5854                 pf_buffer_string (filter, "  ");
5855               pf_buffer_string (filter,
5856                                 lib_select_response (game,
5857                                                "You are already wearing ",
5858                                                "I am already wearing ",
5859                                                "%player% is already wearing "));
5860             }
5861           else
5862             pf_buffer_string (filter, ", ");
5863           lib_print_object_np (game, trail);
5864         }
5865       trail = object;
5866       count++;
5867 
5868       game->multiple_references[object] = FALSE;
5869     }
5870 
5871   if (count >= 1)
5872     {
5873       if (count == 1)
5874         {
5875           if (has_printed)
5876             pf_buffer_string (filter, "  ");
5877           pf_buffer_string (filter,
5878                             lib_select_response (game,
5879                                                "You are already wearing ",
5880                                                "I am already wearing ",
5881                                                "%player% is already wearing "));
5882         }
5883       else
5884         pf_buffer_string (filter, " and ");
5885       lib_print_object_np (game, trail);
5886       pf_buffer_character (filter, '.');
5887     }
5888   has_printed |= count > 0;
5889 
5890   count = 0;
5891   trail = -1;
5892   for (object = 0; object < object_count; object++)
5893     {
5894       if (!game->multiple_references[object])
5895         continue;
5896 
5897       if (gs_object_position (game, object) == OBJ_HELD_PLAYER)
5898         continue;
5899 
5900       if (count > 0)
5901         {
5902           if (count == 1)
5903             {
5904               if (has_printed)
5905                 pf_buffer_string (filter, "  ");
5906               pf_buffer_string (filter,
5907                                 lib_select_response (game,
5908                                                    "You are not holding ",
5909                                                    "I am not holding ",
5910                                                    "%player% is not holding "));
5911             }
5912           else
5913             pf_buffer_string (filter, ", ");
5914           lib_print_object_np (game, trail);
5915         }
5916       trail = object;
5917       count++;
5918 
5919       game->multiple_references[object] = FALSE;
5920     }
5921 
5922   if (count >= 1)
5923     {
5924       if (count == 1)
5925         {
5926           if (has_printed)
5927             pf_buffer_string (filter, "  ");
5928           pf_buffer_string (filter,
5929                             lib_select_response (game,
5930                                                  "You are not holding ",
5931                                                  "I am not holding ",
5932                                                  "%player% is not holding "));
5933         }
5934       else
5935         pf_buffer_string (filter, " or ");
5936       lib_print_object_np (game, trail);
5937       pf_buffer_character (filter, '.');
5938     }
5939   has_printed |= count > 0;
5940 
5941   count = 0;
5942   trail = -1;
5943   for (object = 0; object < object_count; object++)
5944     {
5945       if (!game->multiple_references[object])
5946         continue;
5947 
5948       if (count > 0)
5949         {
5950           if (count == 1)
5951             {
5952               if (has_printed)
5953                 pf_buffer_string (filter, "  ");
5954               pf_buffer_string (filter,
5955                                 lib_select_response (game,
5956                                                      "You can't wear ",
5957                                                      "I can't wear ",
5958                                                      "%player% can't wear "));
5959             }
5960           else
5961             pf_buffer_string (filter, ", ");
5962           lib_print_object_np (game, trail);
5963         }
5964       trail = object;
5965       count++;
5966 
5967       game->multiple_references[object] = FALSE;
5968     }
5969 
5970   if (count >= 1)
5971     {
5972       if (count == 1)
5973         {
5974           if (has_printed)
5975             pf_buffer_string (filter, "  ");
5976           pf_buffer_string (filter,
5977                             lib_select_response (game,
5978                                                  "You can't wear ",
5979                                                  "I can't wear ",
5980                                                  "%player% can't wear "));
5981         }
5982       else
5983         pf_buffer_string (filter, " or ");
5984       lib_print_object_np (game, trail);
5985       pf_buffer_character (filter, '.');
5986     }
5987 }
5988 
5989 
5990 /*
5991  * lib_wear_filter()
5992  *
5993  * Helper function for deciding if an object may be worn in this context.
5994  * Returns TRUE if an object may be worn, FALSE otherwise.
5995  */
5996 static sc_bool
lib_wear_filter(sc_gameref_t game,sc_int object,sc_int unused)5997 lib_wear_filter (sc_gameref_t game, sc_int object, sc_int unused)
5998 {
5999   const sc_prop_setref_t bundle = gs_get_bundle (game);
6000   assert (unused == -1);
6001 
6002   /*
6003    * The object is wearable if the player is holding it, and it's not static
6004    * (static moved to player inventory by event), and if it's marked wearable
6005    * in properties.
6006    */
6007   if (gs_object_position (game, object) == OBJ_HELD_PLAYER
6008       && !obj_is_static (game, object))
6009     {
6010       sc_vartype_t vt_key[3];
6011 
6012       /* Return wearability from the object properties. */
6013       vt_key[0].string = "Objects";
6014       vt_key[1].integer = object;
6015       vt_key[2].string = "Wearable";
6016       return prop_get_boolean (bundle, "B<-sis", vt_key);
6017     }
6018 
6019   return FALSE;
6020 }
6021 
6022 
6023 /*
6024  * lib_cmd_wear_all()
6025  *
6026  * Wear all wearable objects currently held by the player.
6027  */
6028 sc_bool
lib_cmd_wear_all(sc_gameref_t game)6029 lib_cmd_wear_all (sc_gameref_t game)
6030 {
6031   const sc_filterref_t filter = gs_get_filter (game);
6032   sc_int objects;
6033 
6034   /* Filter objects into references, then handle with the backend. */
6035   gs_set_multiple_references (game);
6036   objects = lib_apply_multiple_filter (game,
6037                                        lib_wear_filter, -1,
6038                                        NULL);
6039   gs_clear_multiple_references (game);
6040   if (objects > 0)
6041     lib_wear_backend (game);
6042   else
6043     {
6044       pf_buffer_string (filter,
6045                         lib_select_response (game,
6046                                            "You're not carrying anything",
6047                                            "I'm not carrying anything",
6048                                            "%player%'s not carrying anything"));
6049       pf_buffer_string (filter, " that can be worn.");
6050     }
6051 
6052   pf_buffer_character (filter, '\n');
6053   return TRUE;
6054 }
6055 
6056 
6057 /*
6058  * lib_cmd_wear_except_multiple()
6059  *
6060  * Wear all wearable objects currently held by the player, excepting those
6061  * listed in %text%.
6062  */
6063 sc_bool
lib_cmd_wear_except_multiple(sc_gameref_t game)6064 lib_cmd_wear_except_multiple (sc_gameref_t game)
6065 {
6066   const sc_filterref_t filter = gs_get_filter (game);
6067   sc_int objects, references;
6068 
6069   /* Parse the multiple objects list to find retain target objects. */
6070   if (!lib_parse_multiple_objects (game, "retain",
6071                                    lib_wear_filter, -1,
6072                                    &references))
6073     return FALSE;
6074   else if (references == 0)
6075     return TRUE;
6076 
6077   /* Filter objects into references, then handle with the backend. */
6078   objects = lib_apply_except_filter (game,
6079                                      lib_wear_filter, -1,
6080                                      &references);
6081   if (objects > 0 || references > 0)
6082     lib_wear_backend (game);
6083   else
6084     {
6085       pf_buffer_string (filter,
6086                         lib_select_response (game,
6087                                            "You are not holding anything",
6088                                            "I am not holding anything",
6089                                            "%player% is not holding anything"));
6090       if (objects == 0)
6091         pf_buffer_string (filter, " else");
6092       pf_buffer_string (filter, " that can be worn.");
6093     }
6094 
6095   pf_buffer_character (filter, '\n');
6096   return TRUE;
6097 }
6098 
6099 
6100 /*
6101  * lib_cmd_wear_multiple()
6102  *
6103  * Wear all objects currently held by the player, wearable, and listed
6104  * in %text%.
6105  */
6106 sc_bool
lib_cmd_wear_multiple(sc_gameref_t game)6107 lib_cmd_wear_multiple (sc_gameref_t game)
6108 {
6109   const sc_filterref_t filter = gs_get_filter (game);
6110   sc_int objects, references;
6111 
6112   /* Parse the multiple objects list to find wear target objects. */
6113   if (!lib_parse_multiple_objects (game, "wear",
6114                                    lib_wear_filter, -1,
6115                                    &references))
6116     return FALSE;
6117   else if (references == 0)
6118     return TRUE;
6119 
6120   /* Filter objects into references, then handle with the backend. */
6121   objects = lib_apply_multiple_filter (game,
6122                                        lib_wear_filter, -1,
6123                                        &references);
6124   if (objects > 0 || references > 0)
6125     lib_wear_backend (game);
6126   else
6127     {
6128       pf_buffer_string (filter,
6129                         lib_select_response (game,
6130                                            "You are not holding anything",
6131                                            "I am not holding anything",
6132                                            "%player% is not holding anything"));
6133       pf_buffer_string (filter, " that can be worn.");
6134     }
6135 
6136   pf_buffer_character (filter, '\n');
6137   return TRUE;
6138 }
6139 
6140 
6141 /*
6142  * lib_remove_backend()
6143  *
6144  * Common backend handler for removing objects.  Takes off on all objects
6145  * currently referenced in the game, moving objects to held by player.
6146  *
6147  * Objects to action are flagged in object_references; objects requested but
6148  * deemed not actionable are flagged in multiple_references.
6149  */
6150 static void
lib_remove_backend(sc_gameref_t game)6151 lib_remove_backend (sc_gameref_t game)
6152 {
6153   const sc_filterref_t filter = gs_get_filter (game);
6154   sc_int object_count, object, count, trail;
6155   sc_bool has_printed;
6156 
6157   /*
6158    * Try game commands for all referenced objects first.  If any succeed,
6159    * remove that reference from the list.
6160    */
6161   has_printed = FALSE;
6162   object_count = gs_object_count (game);
6163   for (object = 0; object < object_count; object++)
6164     {
6165       if (!game->object_references[object])
6166         continue;
6167 
6168       if (lib_try_game_command_short (game, "remove", object))
6169         {
6170           game->object_references[object] = FALSE;
6171           has_printed = TRUE;
6172         }
6173     }
6174 
6175   /* Remove every object referenced. */
6176   count = 0;
6177   trail = -1;
6178   for (object = 0; object < object_count; object++)
6179     {
6180       if (!game->object_references[object])
6181         continue;
6182 
6183       if (count > 0)
6184         {
6185           if (count == 1)
6186             {
6187               if (has_printed)
6188                 pf_buffer_string (filter, "  ");
6189               pf_buffer_string (filter,
6190                                 lib_select_response (game,
6191                                                      "You remove ",
6192                                                      "I remove ",
6193                                                      "%player% removes "));
6194             }
6195           else
6196             pf_buffer_string (filter, ", ");
6197           lib_print_object_np (game, trail);
6198         }
6199       trail = object;
6200       count++;
6201 
6202       gs_object_player_get (game, object);
6203     }
6204 
6205   if (count >= 1)
6206     {
6207       if (count == 1)
6208         {
6209           if (has_printed)
6210             pf_buffer_string (filter, "  ");
6211           pf_buffer_string (filter,
6212                             lib_select_response (game,
6213                                                  "You remove ",
6214                                                  "I remove ",
6215                                                  "%player% removes "));
6216         }
6217       else
6218         pf_buffer_string (filter, " and ");
6219       lib_print_object_np (game, trail);
6220       pf_buffer_character (filter, '.');
6221     }
6222   has_printed |= count > 0;
6223 
6224   /* Note any remaining multiple references left out of the remove operation. */
6225   count = 0;
6226   trail = -1;
6227   for (object = 0; object < object_count; object++)
6228     {
6229       if (!game->multiple_references[object])
6230         continue;
6231 
6232       if (count > 0)
6233         {
6234           if (count == 1)
6235             {
6236               if (has_printed)
6237                 pf_buffer_string (filter, "  ");
6238               pf_buffer_string (filter,
6239                                 lib_select_response (game,
6240                                                    "You are not wearing ",
6241                                                    "I am not wearing ",
6242                                                    "%player% is not wearing "));
6243             }
6244           else
6245             pf_buffer_string (filter, ", ");
6246           lib_print_object_np (game, trail);
6247         }
6248       trail = object;
6249       count++;
6250 
6251       game->multiple_references[object] = FALSE;
6252     }
6253 
6254   if (count >= 1)
6255     {
6256       if (count == 1)
6257         {
6258           if (has_printed)
6259             pf_buffer_string (filter, "  ");
6260           pf_buffer_string (filter,
6261                             lib_select_response (game,
6262                                                  "You are not wearing ",
6263                                                  "I am not wearing ",
6264                                                  "%player% is not wearing "));
6265         }
6266       else
6267         pf_buffer_string (filter, " or ");
6268       lib_print_object_np (game, trail);
6269       pf_buffer_character (filter, '!');
6270     }
6271 }
6272 
6273 
6274 /*
6275  * lib_remove_filter()
6276  *
6277  * Helper function for deciding if an object may be removed in this context.
6278  * Returns TRUE if an object is currently being worn, FALSE otherwise.
6279  */
6280 static sc_bool
lib_remove_filter(sc_gameref_t game,sc_int object,sc_int unused)6281 lib_remove_filter (sc_gameref_t game, sc_int object, sc_int unused)
6282 {
6283   assert (unused == -1);
6284 
6285   return !obj_is_static (game, object)
6286          && gs_object_position (game, object) == OBJ_WORN_PLAYER;
6287 }
6288 
6289 
6290 /*
6291  * lib_cmd_remove_all()
6292  *
6293  * Remove all objects currently held by the player.
6294  */
6295 sc_bool
lib_cmd_remove_all(sc_gameref_t game)6296 lib_cmd_remove_all (sc_gameref_t game)
6297 {
6298   const sc_filterref_t filter = gs_get_filter (game);
6299   sc_int objects;
6300 
6301   /* Filter objects into references, then handle with the backend. */
6302   gs_set_multiple_references (game);
6303   objects = lib_apply_multiple_filter (game,
6304                                        lib_remove_filter, -1,
6305                                        NULL);
6306   gs_clear_multiple_references (game);
6307   if (objects > 0)
6308     lib_remove_backend (game);
6309   else
6310     {
6311       pf_buffer_string (filter,
6312                         lib_select_response (game,
6313                                            "You're not wearing anything",
6314                                            "I'm not wearing anything",
6315                                            "%player%'s not wearing anything"));
6316       pf_buffer_string (filter, " that can be removed.");
6317     }
6318 
6319   pf_buffer_character (filter, '\n');
6320   return TRUE;
6321 }
6322 
6323 
6324 /*
6325  * lib_cmd_remove_except_multiple()
6326  *
6327  * Remove all objects currently worn by the player, excepting those listed
6328  * in %text%.
6329  */
6330 sc_bool
lib_cmd_remove_except_multiple(sc_gameref_t game)6331 lib_cmd_remove_except_multiple (sc_gameref_t game)
6332 {
6333   const sc_filterref_t filter = gs_get_filter (game);
6334   sc_int objects, references;
6335 
6336   /* Parse the multiple objects list to find retain target objects. */
6337   if (!lib_parse_multiple_objects (game, "retain",
6338                                    lib_remove_filter, -1,
6339                                    &references))
6340     return FALSE;
6341   else if (references == 0)
6342     return TRUE;
6343 
6344   /* Filter objects into references, then handle with the backend. */
6345   objects = lib_apply_except_filter (game,
6346                                      lib_remove_filter, -1,
6347                                      &references);
6348   if (objects > 0 || references > 0)
6349     lib_remove_backend (game);
6350   else
6351     {
6352       pf_buffer_string (filter,
6353                         lib_select_response (game,
6354                                            "You are not wearing anything",
6355                                            "I am not wearing anything",
6356                                            "%player% is not wearing anything"));
6357       if (objects == 0)
6358         pf_buffer_string (filter, " else");
6359       pf_buffer_string (filter, " that can be removed.");
6360     }
6361 
6362   pf_buffer_character (filter, '\n');
6363   return TRUE;
6364 }
6365 
6366 
6367 /*
6368  * lib_cmd_remove_multiple()
6369  *
6370  * Remove all objects currently worn by the player, and listed in %text%.
6371  */
6372 sc_bool
lib_cmd_remove_multiple(sc_gameref_t game)6373 lib_cmd_remove_multiple (sc_gameref_t game)
6374 {
6375   const sc_filterref_t filter = gs_get_filter (game);
6376   sc_int objects, references;
6377 
6378   /* Parse the multiple objects list to find remove target objects. */
6379   if (!lib_parse_multiple_objects (game, "remove",
6380                                    lib_remove_filter, -1,
6381                                    &references))
6382     return FALSE;
6383   else if (references == 0)
6384     return TRUE;
6385 
6386   /* Filter objects into references, then handle with the backend. */
6387   objects = lib_apply_multiple_filter (game,
6388                                        lib_remove_filter, -1,
6389                                        &references);
6390   if (objects > 0 || references > 0)
6391     lib_remove_backend (game);
6392   else
6393     {
6394       pf_buffer_string (filter,
6395                         lib_select_response (game,
6396                                            "You are not holding anything",
6397                                            "I am not holding anything",
6398                                            "%player% is not holding anything"));
6399       pf_buffer_string (filter, " that can be removed.");
6400     }
6401 
6402   pf_buffer_character (filter, '\n');
6403   return TRUE;
6404 }
6405 
6406 
6407 /*
6408  * lib_cmd_inventory()
6409  *
6410  * List objects carried and worn by the player.
6411  */
6412 sc_bool
lib_cmd_inventory(sc_gameref_t game)6413 lib_cmd_inventory (sc_gameref_t game)
6414 {
6415   const sc_filterref_t filter = gs_get_filter (game);
6416   sc_int object, count, trail;
6417   sc_bool wearing;
6418 
6419   /* Find and list each object worn by the player. */
6420   count = 0;
6421   trail = -1;
6422   wearing = FALSE;
6423   for (object = 0; object < gs_object_count (game); object++)
6424     {
6425       if (gs_object_position (game, object) == OBJ_WORN_PLAYER)
6426         {
6427           if (count > 0)
6428             {
6429               if (count == 1)
6430                 {
6431                   pf_buffer_string (filter,
6432                                     lib_select_response (game,
6433                                                        "You are wearing ",
6434                                                        "I am wearing ",
6435                                                        "%player% is wearing "));
6436                 }
6437               else
6438                 pf_buffer_string (filter, ", ");
6439               lib_print_object (game, trail);
6440             }
6441           trail = object;
6442           count++;
6443         }
6444     }
6445   if (count >= 1)
6446     {
6447       /* Print out final listed object. */
6448       if (count == 1)
6449         {
6450           pf_buffer_string (filter,
6451                             lib_select_response (game,
6452                                                  "You are wearing ",
6453                                                  "I am wearing ",
6454                                                  "%player% is wearing "));
6455         }
6456       else
6457         pf_buffer_string (filter, " and ");
6458       lib_print_object (game, trail);
6459       wearing = TRUE;
6460     }
6461 
6462   /* Find and list each object owned by the player. */
6463   count = 0;
6464   for (object = 0; object < gs_object_count (game); object++)
6465     {
6466       if (gs_object_position (game, object) == OBJ_HELD_PLAYER)
6467         {
6468           if (count > 0)
6469             {
6470               if (count == 1)
6471                 {
6472                   if (wearing)
6473                     {
6474                       pf_buffer_string (filter,
6475                                         lib_select_response (game,
6476                                                 ", and you are carrying ",
6477                                                 ", and I am carrying ",
6478                                                 ", and %player% is carrying "));
6479                     }
6480                   else
6481                     {
6482                       pf_buffer_string (filter,
6483                                         lib_select_response (game,
6484                                                       "You are carrying ",
6485                                                       "I am carrying ",
6486                                                       "%player% is carrying "));
6487                     }
6488                 }
6489               else
6490                 pf_buffer_string (filter, ", ");
6491               lib_print_object (game, trail);
6492             }
6493           trail = object;
6494           count++;
6495         }
6496     }
6497   if (count >= 1)
6498     {
6499       /* Print out final listed object. */
6500       if (count == 1)
6501         {
6502           if (wearing)
6503             {
6504               pf_buffer_string (filter,
6505                                 lib_select_response (game,
6506                                                 ", and you are carrying ",
6507                                                 ", and I am carrying ",
6508                                                 ", and %player% is carrying "));
6509             }
6510           else
6511             {
6512               pf_buffer_string (filter,
6513                                 lib_select_response (game,
6514                                                      "You are carrying ",
6515                                                      "I am carrying ",
6516                                                      "%player% is carrying "));
6517             }
6518         }
6519       else
6520         pf_buffer_string (filter, " and ");
6521       lib_print_object (game, trail);
6522       pf_buffer_character (filter, '.');
6523 
6524       /* Print contents of every container and surface carried. */
6525       for (object = 0; object < gs_object_count (game); object++)
6526         {
6527           if (gs_object_position (game, object) == OBJ_HELD_PLAYER)
6528             {
6529               if (obj_is_container (game, object)
6530                   && gs_object_openness (game, object) <= OBJ_OPEN)
6531                 lib_list_in_object (game, object, TRUE);
6532 
6533               if (obj_is_surface (game, object))
6534                 lib_list_on_object (game, object, TRUE);
6535             }
6536         }
6537       pf_buffer_character (filter, '\n');
6538     }
6539   else
6540     {
6541       if (wearing)
6542         {
6543           pf_buffer_string (filter, ", and ");
6544           pf_buffer_string (filter,
6545                             lib_select_response (game,
6546                                             "you are carrying nothing.\n",
6547                                             "I am carrying nothing.\n",
6548                                             "%player% is carrying nothing.\n"));
6549         }
6550       else
6551         {
6552           pf_buffer_string (filter,
6553                             lib_select_response (game,
6554                                             "You are carrying nothing.\n",
6555                                             "I am carrying nothing.\n",
6556                                             "%player% is carrying nothing.\n"));
6557         }
6558     }
6559 
6560   /* Successful command. */
6561   return TRUE;
6562 }
6563 
6564 
6565 /*
6566  * lib_cmd_open_object()
6567  *
6568  * Attempt to open the referenced object.
6569  */
6570 sc_bool
lib_cmd_open_object(sc_gameref_t game)6571 lib_cmd_open_object (sc_gameref_t game)
6572 {
6573   const sc_filterref_t filter = gs_get_filter (game);
6574   sc_int object, openness;
6575   sc_bool is_ambiguous;
6576 
6577   /* Get the referenced object, and if none, consider complete. */
6578   object = lib_disambiguate_object (game, "open", &is_ambiguous);
6579   if (object == -1)
6580     return is_ambiguous;
6581 
6582   /* Get the current object openness. */
6583   openness = gs_object_openness (game, object);
6584 
6585   /* React to the request based on openness state. */
6586   switch (openness)
6587     {
6588     case OBJ_OPEN:
6589       pf_new_sentence (filter);
6590       lib_print_object_np (game, object);
6591       pf_buffer_string (filter,
6592                         lib_select_plurality (game, object,
6593                                               " is already open!\n",
6594                                               " are already open!\n"));
6595       return TRUE;
6596 
6597     case OBJ_CLOSED:
6598       pf_buffer_string (filter,
6599                         lib_select_response (game,
6600                                              "You open ",
6601                                              "I open ",
6602                                              "%player% opens "));
6603       lib_print_object_np (game, object);
6604       pf_buffer_character (filter, '.');
6605 
6606       /* Set open state, and list contents. */
6607       gs_set_object_openness (game, object, OBJ_OPEN);
6608       lib_list_in_object (game, object, TRUE);
6609       pf_buffer_character (filter, '\n');
6610       return TRUE;
6611 
6612     case OBJ_LOCKED:
6613       pf_buffer_string (filter,
6614                         lib_select_response (game,
6615                                              "You can't open ",
6616                                              "I can't open ",
6617                                              "%player% can't open "));
6618       lib_print_object_np (game, object);
6619       pf_buffer_string (filter, " as it is locked!\n");
6620       return TRUE;
6621 
6622     default:
6623       break;
6624     }
6625 
6626   /* The object isn't openable. */
6627   pf_buffer_string (filter,
6628                     lib_select_response (game,
6629                                          "You can't open ",
6630                                          "I can't open ",
6631                                          "%player% can't open "));
6632   lib_print_object_np (game, object);
6633   pf_buffer_string (filter, "!\n");
6634   return TRUE;
6635 }
6636 
6637 
6638 /*
6639  * lib_cmd_close_object()
6640  *
6641  * Attempt to close the referenced object.
6642  */
6643 sc_bool
lib_cmd_close_object(sc_gameref_t game)6644 lib_cmd_close_object (sc_gameref_t game)
6645 {
6646   const sc_filterref_t filter = gs_get_filter (game);
6647   sc_int object, openness;
6648   sc_bool is_ambiguous;
6649 
6650   /* Get the referenced object, and if none, consider complete. */
6651   object = lib_disambiguate_object (game, "close", &is_ambiguous);
6652   if (object == -1)
6653     return is_ambiguous;
6654 
6655   /* Get the current object openness. */
6656   openness = gs_object_openness (game, object);
6657 
6658   /* React to the request based on openness state. */
6659   switch (openness)
6660     {
6661     case OBJ_OPEN:
6662       pf_buffer_string (filter,
6663                         lib_select_response (game,
6664                                              "You close ",
6665                                              "I close ",
6666                                              "%player% closes "));
6667       lib_print_object_np (game, object);
6668       pf_buffer_string (filter, ".\n");
6669 
6670       /* Set closed state. */
6671       gs_set_object_openness (game, object, OBJ_CLOSED);
6672       return TRUE;
6673 
6674     case OBJ_CLOSED:
6675     case OBJ_LOCKED:
6676       pf_new_sentence (filter);
6677       lib_print_object_np (game, object);
6678       pf_buffer_string (filter,
6679                         lib_select_plurality (game, object,
6680                                               " is already closed!\n",
6681                                               " are already closed!\n"));
6682       return TRUE;
6683 
6684     default:
6685       break;
6686     }
6687 
6688   /* The object isn't closeable. */
6689   pf_buffer_string (filter,
6690                         lib_select_response (game,
6691                                          "You can't close ",
6692                                          "I can't close ",
6693                                          "%player% can't close "));
6694   lib_print_object_np (game, object);
6695   pf_buffer_string (filter, "!\n");
6696   return TRUE;
6697 }
6698 
6699 
6700 /*
6701  * lib_attempt_key_acquisition()
6702  *
6703  * Automatically get an object being used as a key, if possible.
6704  */
6705 static void
lib_attempt_key_acquisition(sc_gameref_t game,sc_int object)6706 lib_attempt_key_acquisition (sc_gameref_t game, sc_int object)
6707 {
6708   const sc_filterref_t filter = gs_get_filter (game);
6709 
6710   /* Disallow getting static objects. */
6711   if (obj_is_static (game, object))
6712     return;
6713 
6714   /* If the object is not seen or available, reject the attempt. */
6715   if (!(gs_object_seen (game, object)
6716         && obj_indirectly_in_room (game, object, gs_playerroom (game))))
6717     return;
6718 
6719   /*
6720    * Check if we already have it, or are wearing it, or if a NPC has or is
6721    * wearing it.
6722    */
6723   if (gs_object_position (game, object) == OBJ_HELD_PLAYER
6724       || gs_object_position (game, object) == OBJ_WORN_PLAYER
6725       || gs_object_position (game, object) == OBJ_HELD_NPC
6726       || gs_object_position (game, object) == OBJ_WORN_NPC)
6727     return;
6728 
6729   /*
6730    * If the object is contained in or on something we're already holding,
6731    * capacity checks are meaningless.
6732    */
6733   if (!obj_indirectly_held_by_player (game, object))
6734     {
6735       if (lib_object_too_heavy (game, object, NULL)
6736           || lib_object_too_large (game, object, NULL))
6737         return;
6738     }
6739 
6740   /* Retry game commands for the object with a standard "get". */
6741   if (lib_try_game_command_short (game, "get", object))
6742     return;
6743 
6744   /* Note what we're doing. */
6745   if (gs_object_position (game, object) == OBJ_IN_OBJECT
6746       || gs_object_position (game, object) == OBJ_ON_OBJECT)
6747     {
6748       pf_buffer_string (filter, "(Taking ");
6749       lib_print_object_np (game, object);
6750 
6751       pf_buffer_string (filter, " from ");
6752       lib_print_object_np (game, gs_object_parent (game, object));
6753       pf_buffer_string (filter, " first)\n");
6754     }
6755   else
6756     {
6757       pf_buffer_string (filter, "(Picking up ");
6758       lib_print_object_np (game, object);
6759       pf_buffer_string (filter, " first)\n");
6760     }
6761 
6762   /* Take possession of the object. */
6763   gs_object_player_get (game, object);
6764 }
6765 
6766 
6767 /*
6768  * lib_cmd_unlock_object_with()
6769  *
6770  * Attempt to unlock the referenced object.
6771  */
6772 sc_bool
lib_cmd_unlock_object_with(sc_gameref_t game)6773 lib_cmd_unlock_object_with (sc_gameref_t game)
6774 {
6775   const sc_filterref_t filter = gs_get_filter (game);
6776   const sc_var_setref_t vars = gs_get_vars (game);
6777   const sc_prop_setref_t bundle = gs_get_bundle (game);
6778   sc_int object, key, openness;
6779   sc_bool is_ambiguous;
6780 
6781   /* Get the referenced object, and if none, consider complete. */
6782   object = lib_disambiguate_object (game, "unlock", &is_ambiguous);
6783   if (object == -1)
6784     return is_ambiguous;
6785 
6786   /*
6787    * Now try to get the key from referenced text, and disambiguate as usual.
6788    */
6789   if (!uip_match ("%object%", var_get_ref_text (vars), game))
6790     {
6791       pf_buffer_string (filter, "What do you want to unlock that with?\n");
6792       return TRUE;
6793     }
6794   key = lib_disambiguate_object (game, "unlock that with", NULL);
6795   if (key == -1)
6796     return TRUE;
6797 
6798   /* React to the request based on openness state. */
6799   openness = gs_object_openness (game, object);
6800   switch (openness)
6801     {
6802     case OBJ_OPEN:
6803     case OBJ_CLOSED:
6804       pf_new_sentence (filter);
6805       lib_print_object_np (game, object);
6806       pf_buffer_string (filter,
6807                         lib_select_plurality (game, object,
6808                                               " is not locked!\n",
6809                                               " are not locked!\n"));
6810       return TRUE;
6811 
6812     case OBJ_LOCKED:
6813       {
6814         sc_vartype_t vt_key[3];
6815         sc_int key_index, the_key;
6816 
6817         vt_key[0].string = "Objects";
6818         vt_key[1].integer = object;
6819         vt_key[2].string = "Key";
6820         key_index = prop_get_integer (bundle, "I<-sis", vt_key);
6821         if (key_index == -1)
6822           break;
6823 
6824         the_key = obj_dynamic_object (game, key_index);
6825         if (the_key != key)
6826           {
6827             pf_buffer_string (filter,
6828                               lib_select_response (game,
6829                                                    "You can't unlock ",
6830                                                    "I can't unlock ",
6831                                                    "%player% can't unlock "));
6832             lib_print_object_np (game, object);
6833             pf_buffer_string (filter, " with ");
6834             lib_print_object_np (game, key);
6835             pf_buffer_string (filter, ".\n");
6836             return TRUE;
6837           }
6838 
6839         if (gs_object_position (game, key) != OBJ_HELD_PLAYER)
6840           {
6841             pf_buffer_string (filter,
6842                               lib_select_response (game,
6843                                                    "You are not holding ",
6844                                                    "I am not holding ",
6845                                                    "%player% is not holding "));
6846             lib_print_object_np (game, key);
6847             pf_buffer_string (filter, ".\n");
6848             return TRUE;
6849           }
6850 
6851         gs_set_object_openness (game, object, OBJ_CLOSED);
6852         pf_buffer_string (filter,
6853                           lib_select_response (game,
6854                                                "You unlock ",
6855                                                "I unlock ",
6856                                                "%player% unlocks "));
6857         lib_print_object_np (game, object);
6858         pf_buffer_string (filter, " with ");
6859         lib_print_object_np (game, key);
6860         pf_buffer_string (filter, ".\n");
6861         return TRUE;
6862       }
6863 
6864     default:
6865       break;
6866     }
6867 
6868   /* The object isn't lockable. */
6869   pf_buffer_string (filter,
6870                     lib_select_response (game,
6871                                          "You can't unlock ",
6872                                          "I can't unlock ",
6873                                          "%player% can't unlock "));
6874   lib_print_object_np (game, object);
6875   pf_buffer_string (filter, ".\n");
6876   return TRUE;
6877 }
6878 
6879 
6880 /*
6881  * lib_cmd_unlock_object()
6882  *
6883  * Attempt to unlock the referenced object, automatically selecting key.
6884  */
6885 sc_bool
lib_cmd_unlock_object(sc_gameref_t game)6886 lib_cmd_unlock_object (sc_gameref_t game)
6887 {
6888   const sc_filterref_t filter = gs_get_filter (game);
6889   const sc_prop_setref_t bundle = gs_get_bundle (game);
6890   sc_int object, openness;
6891   sc_bool is_ambiguous;
6892 
6893   /* Get the referenced object, and if none, consider complete. */
6894   object = lib_disambiguate_object (game, "unlock", &is_ambiguous);
6895   if (object == -1)
6896     return is_ambiguous;
6897 
6898   /* React to the request based on openness state. */
6899   openness = gs_object_openness (game, object);
6900   switch (openness)
6901     {
6902     case OBJ_OPEN:
6903     case OBJ_CLOSED:
6904       pf_new_sentence (filter);
6905       lib_print_object_np (game, object);
6906       pf_buffer_string (filter,
6907                         lib_select_plurality (game, object,
6908                                               " is not locked!\n",
6909                                               " are not locked!\n"));
6910       return TRUE;
6911 
6912     case OBJ_LOCKED:
6913       {
6914         sc_vartype_t vt_key[3];
6915         sc_int key_index, key;
6916 
6917         vt_key[0].string = "Objects";
6918         vt_key[1].integer = object;
6919         vt_key[2].string = "Key";
6920         key_index = prop_get_integer (bundle, "I<-sis", vt_key);
6921         if (key_index == -1)
6922           break;
6923 
6924         key = obj_dynamic_object (game, key_index);
6925         lib_attempt_key_acquisition (game, key);
6926         if (gs_object_position (game, key) != OBJ_HELD_PLAYER)
6927           {
6928             pf_buffer_string (filter,
6929                               lib_select_response (game,
6930                                                    "You don't have",
6931                                                    "I don't have",
6932                                                    "%player% doesn't have"));
6933             pf_buffer_string (filter, " anything to unlock ");
6934             lib_print_object_np (game, object);
6935             pf_buffer_string (filter, " with!\n");
6936             return TRUE;
6937           }
6938 
6939         gs_set_object_openness (game, object, OBJ_CLOSED);
6940         pf_buffer_string (filter,
6941                           lib_select_response (game,
6942                                                "You unlock ",
6943                                                "I unlock ",
6944                                                "%player% unlocks "));
6945         lib_print_object_np (game, object);
6946         pf_buffer_string (filter, " with ");
6947         lib_print_object_np (game, key);
6948         pf_buffer_string (filter, ".\n");
6949         return TRUE;
6950       }
6951 
6952     default:
6953       break;
6954     }
6955 
6956   /* The object isn't lockable. */
6957   pf_buffer_string (filter,
6958                     lib_select_response (game,
6959                                          "You can't unlock ",
6960                                          "I can't unlock ",
6961                                          "%player% can't unlock "));
6962   lib_print_object_np (game, object);
6963   pf_buffer_string (filter, ".\n");
6964   return TRUE;
6965 }
6966 
6967 
6968 /*
6969  * lib_cmd_lock_object_with()
6970  *
6971  * Attempt to lock the referenced object.
6972  */
6973 sc_bool
lib_cmd_lock_object_with(sc_gameref_t game)6974 lib_cmd_lock_object_with (sc_gameref_t game)
6975 {
6976   const sc_filterref_t filter = gs_get_filter (game);
6977   const sc_var_setref_t vars = gs_get_vars (game);
6978   const sc_prop_setref_t bundle = gs_get_bundle (game);
6979   sc_int object, key, openness;
6980   sc_bool is_ambiguous;
6981 
6982   /* Get the referenced object, and if none, consider complete. */
6983   object = lib_disambiguate_object (game, "lock", &is_ambiguous);
6984   if (object == -1)
6985     return is_ambiguous;
6986 
6987   /*
6988    * Now try to get the key from referenced text, and disambiguate as usual.
6989    */
6990   if (!uip_match ("%object%", var_get_ref_text (vars), game))
6991     {
6992       pf_buffer_string (filter, "What do you want to lock that with?\n");
6993       return TRUE;
6994     }
6995   key = lib_disambiguate_object (game, "lock that with", NULL);
6996   if (key == -1)
6997     return TRUE;
6998 
6999   /* React to the request based on openness state. */
7000   openness = gs_object_openness (game, object);
7001   switch (openness)
7002     {
7003     case OBJ_OPEN:
7004       pf_buffer_string (filter,
7005                         lib_select_response (game,
7006                                              "You can't lock ",
7007                                              "I can't lock ",
7008                                              "%player% can't lock "));
7009       lib_print_object_np (game, object);
7010       pf_buffer_string (filter, " as it is open.\n");
7011       return TRUE;
7012 
7013     case OBJ_CLOSED:
7014       {
7015         sc_vartype_t vt_key[3];
7016         sc_int key_index, the_key;
7017 
7018         vt_key[0].string = "Objects";
7019         vt_key[1].integer = object;
7020         vt_key[2].string = "Key";
7021         key_index = prop_get_integer (bundle, "I<-sis", vt_key);
7022         if (key_index == -1)
7023           break;
7024 
7025         the_key = obj_dynamic_object (game, key_index);
7026         if (the_key != key)
7027           {
7028             pf_buffer_string (filter,
7029                               lib_select_response (game,
7030                                                    "You can't lock ",
7031                                                    "I can't lock ",
7032                                                    "%player% can't lock "));
7033             lib_print_object_np (game, object);
7034             pf_buffer_string (filter, " with ");
7035             lib_print_object_np (game, key);
7036             pf_buffer_string (filter, ".\n");
7037             return TRUE;
7038           }
7039 
7040         if (gs_object_position (game, key) != OBJ_HELD_PLAYER)
7041           {
7042             pf_buffer_string (filter,
7043                               lib_select_response (game,
7044                                                    "You are not holding ",
7045                                                    "I am not holding ",
7046                                                    "%player% is not holding "));
7047             lib_print_object_np (game, key);
7048             pf_buffer_string (filter, ".\n");
7049             return TRUE;
7050           }
7051 
7052         gs_set_object_openness (game, object, OBJ_LOCKED);
7053         pf_buffer_string (filter, lib_select_response (game,
7054                                                        "You lock ",
7055                                                        "I lock ",
7056                                                        "%player% locks "));
7057         lib_print_object_np (game, object);
7058         pf_buffer_string (filter, " with ");
7059         lib_print_object_np (game, key);
7060         pf_buffer_string (filter, ".\n");
7061         return TRUE;
7062       }
7063 
7064     case OBJ_LOCKED:
7065       pf_new_sentence (filter);
7066       lib_print_object_np (game, object);
7067       pf_buffer_string (filter,
7068                         lib_select_plurality (game, object,
7069                                               " is already locked!\n",
7070                                               " are already locked!\n"));
7071       return TRUE;
7072 
7073     default:
7074       break;
7075     }
7076 
7077   /* The object isn't lockable. */
7078   pf_buffer_string (filter,
7079                     lib_select_response (game,
7080                                          "You can't lock ",
7081                                          "I can't lock ",
7082                                          "%player% can't lock "));
7083   lib_print_object_np (game, object);
7084   pf_buffer_string (filter, ".\n");
7085   return TRUE;
7086 }
7087 
7088 
7089 /*
7090  * lib_cmd_lock_object()
7091  *
7092  * Attempt to lock the referenced object, automatically selecting key.
7093  */
7094 sc_bool
lib_cmd_lock_object(sc_gameref_t game)7095 lib_cmd_lock_object (sc_gameref_t game)
7096 {
7097   const sc_filterref_t filter = gs_get_filter (game);
7098   const sc_prop_setref_t bundle = gs_get_bundle (game);
7099   sc_int object, openness;
7100   sc_bool is_ambiguous;
7101 
7102   /* Get the referenced object, and if none, consider complete. */
7103   object = lib_disambiguate_object (game, "lock", &is_ambiguous);
7104   if (object == -1)
7105     return is_ambiguous;
7106 
7107   /* React to the request based on openness state. */
7108   openness = gs_object_openness (game, object);
7109   switch (openness)
7110     {
7111     case OBJ_OPEN:
7112       pf_buffer_string (filter,
7113                         lib_select_response (game,
7114                                              "You can't lock ",
7115                                              "I can't lock ",
7116                                              "%player% can't lock "));
7117       lib_print_object_np (game, object);
7118       pf_buffer_string (filter, " as it is open.\n");
7119       return TRUE;
7120 
7121     case OBJ_CLOSED:
7122       {
7123         sc_vartype_t vt_key[3];
7124         sc_int key_index, key;
7125 
7126         vt_key[0].string = "Objects";
7127         vt_key[1].integer = object;
7128         vt_key[2].string = "Key";
7129         key_index = prop_get_integer (bundle, "I<-sis", vt_key);
7130         if (key_index == -1)
7131           break;
7132 
7133         key = obj_dynamic_object (game, key_index);
7134         lib_attempt_key_acquisition (game, key);
7135         if (gs_object_position (game, key) != OBJ_HELD_PLAYER)
7136           {
7137             pf_buffer_string (filter,
7138                               lib_select_response (game,
7139                                                    "You don't have",
7140                                                    "I don't have",
7141                                                    "%player% doesn't have"));
7142             pf_buffer_string (filter, " anything to lock ");
7143             lib_print_object_np (game, object);
7144             pf_buffer_string (filter, " with!\n");
7145             return TRUE;
7146           }
7147 
7148         gs_set_object_openness (game, object, OBJ_LOCKED);
7149         pf_buffer_string (filter,
7150                           lib_select_response (game,
7151                                                "You lock ",
7152                                                "I lock ",
7153                                                "%player% locks "));
7154         lib_print_object_np (game, object);
7155         pf_buffer_string (filter, " with ");
7156         lib_print_object_np (game, key);
7157         pf_buffer_string (filter, ".\n");
7158         return TRUE;
7159       }
7160 
7161     case OBJ_LOCKED:
7162       pf_new_sentence (filter);
7163       lib_print_object_np (game, object);
7164       pf_buffer_string (filter,
7165                         lib_select_plurality (game, object,
7166                                               " is already locked!\n",
7167                                               " are already locked!\n"));
7168       return TRUE;
7169 
7170     default:
7171       break;
7172     }
7173 
7174   /* The object isn't lockable. */
7175   pf_buffer_string (filter,
7176                     lib_select_response (game,
7177                                          "You can't lock ",
7178                                          "I can't lock ",
7179                                          "%player% can't lock "));
7180   lib_print_object_np (game, object);
7181   pf_buffer_string (filter, ".\n");
7182   return TRUE;
7183 }
7184 
7185 
7186 /*
7187  * lib_compare_subject()
7188  *
7189  * Compare a subject, comma or NUL terminated.  Helper for ask.
7190  */
7191 static sc_bool
lib_compare_subject(const sc_char * subject,sc_int posn,const sc_char * string)7192 lib_compare_subject (const sc_char *subject, sc_int posn,
7193                      const sc_char *string)
7194 {
7195   sc_int word_posn, string_posn;
7196 
7197   /* Skip any leading subject spaces. */
7198   for (word_posn = posn;
7199        subject[word_posn] != NUL && sc_isspace (subject[word_posn]);)
7200     word_posn++;
7201   for (string_posn = 0;
7202        string[string_posn] != NUL && sc_isspace (string[string_posn]);)
7203     string_posn++;
7204 
7205   /* Match characters from words with the string at position. */
7206   while (TRUE)
7207     {
7208       /* Any character mismatch means no match. */
7209       if (sc_tolower (subject[word_posn]) != sc_tolower (string[string_posn]))
7210         return FALSE;
7211 
7212       /* Move to next character in each. */
7213       word_posn++;
7214       string_posn++;
7215 
7216       /*
7217        * If at space, advance over whitespace in subjects list.  Stop when we
7218        * hit the end of the element or list.
7219        */
7220       while (sc_isspace (subject[word_posn])
7221              && subject[word_posn] != COMMA && subject[word_posn] != NUL)
7222         subject++;
7223 
7224       /* Advance over whitespace in the current string too. */
7225       while (sc_isspace (string[string_posn]) && string[string_posn] != NUL)
7226         string_posn++;
7227 
7228       /*
7229        * If we found the end of the subject, and the end of the current string,
7230        * we've matched.  If not at the end of the current string, though, only
7231        * a partial match.
7232        */
7233       if (subject[word_posn] == NUL || subject[word_posn] == COMMA)
7234         {
7235           if (string[string_posn] == NUL)
7236             break;
7237           else
7238             return FALSE;
7239         }
7240     }
7241 
7242   /* Matched in the loop; return TRUE. */
7243   return TRUE;
7244 }
7245 
7246 
7247 /*
7248  * lib_npc_reply_to()
7249  *
7250  * Reply for an NPC on a given topic.  Helper for ask.
7251  */
7252 static sc_bool
lib_npc_reply_to(sc_gameref_t game,sc_int npc,sc_int topic)7253 lib_npc_reply_to (sc_gameref_t game, sc_int npc, sc_int topic)
7254 {
7255   const sc_filterref_t filter = gs_get_filter (game);
7256   const sc_prop_setref_t bundle = gs_get_bundle (game);
7257   sc_vartype_t vt_key[5];
7258   sc_int task;
7259   const sc_char *response;
7260 
7261   /* Find any associated task to control response. */
7262   vt_key[0].string = "NPCs";
7263   vt_key[1].integer = npc;
7264   vt_key[2].string = "Topics";
7265   vt_key[3].integer = topic;
7266   vt_key[4].string = "Task";
7267   task = prop_get_integer (bundle, "I<-sisis", vt_key);
7268 
7269   /* Get the response, and print if anything there. */
7270   if (task > 0 && gs_task_done (game, task - 1))
7271     vt_key[4].string = "AltReply";
7272   else
7273     vt_key[4].string = "Reply";
7274   response = prop_get_string (bundle, "S<-sisis", vt_key);
7275   if (!sc_strempty (response))
7276     {
7277       pf_buffer_string (filter, response);
7278       pf_buffer_character (filter, '\n');
7279       return TRUE;
7280     }
7281 
7282   /* No response to this combination. */
7283   return FALSE;
7284 }
7285 
7286 
7287 /*
7288  * lib_cmd_ask_npc_about()
7289  *
7290  * Converse with NPC.
7291  */
7292 sc_bool
lib_cmd_ask_npc_about(sc_gameref_t game)7293 lib_cmd_ask_npc_about (sc_gameref_t game)
7294 {
7295   const sc_filterref_t filter = gs_get_filter (game);
7296   const sc_var_setref_t vars = gs_get_vars (game);
7297   const sc_prop_setref_t bundle = gs_get_bundle (game);
7298   sc_vartype_t vt_key[5];
7299   sc_int npc, topic_count, topic, topic_match, default_topic;
7300   sc_bool found, default_found, is_ambiguous;
7301 
7302   /* Get the referenced npc, and if none, consider complete. */
7303   npc = lib_disambiguate_npc (game, "ask", &is_ambiguous);
7304   if (npc == -1)
7305     return is_ambiguous;
7306 
7307   if (lib_trace)
7308     sc_trace ("Library: asking NPC %ld\n", npc);
7309 
7310   /* Get the topics the NPC converses about. */
7311   vt_key[0].string = "NPCs";
7312   vt_key[1].integer = npc;
7313   vt_key[2].string = "Topics";
7314   topic_count = prop_get_child_count (bundle, "I<-sis", vt_key);
7315   topic_match = default_topic = -1;
7316   found = default_found = FALSE;
7317   for (topic = 0; topic < topic_count; topic++)
7318     {
7319       const sc_char *subjects;
7320       sc_int posn;
7321 
7322       /* Get subject list for this topic. */
7323       vt_key[3].integer = topic;
7324       vt_key[4].string = "Subject";
7325       subjects = prop_get_string (bundle, "S<-sisis", vt_key);
7326 
7327       /* If this is the special "*" topic, note and continue. */
7328       if (!sc_strcasecmp (subjects, "*"))
7329         {
7330           if (lib_trace)
7331             sc_trace ("Library: \"*\" is %ld\n", topic);
7332 
7333           default_topic = topic;
7334           default_found = TRUE;
7335           continue;
7336         }
7337 
7338       /* Split into subjects by comma delimiter. */
7339       for (posn = 0; subjects[posn] != NUL;)
7340         {
7341           if (lib_trace)
7342             sc_trace ("Library: subject %s[%ld]\n", subjects, posn);
7343 
7344           /* See if this subject matches. */
7345           if (lib_compare_subject (subjects, posn, var_get_ref_text (vars)))
7346             {
7347               if (lib_trace)
7348                 sc_trace ("Library: matched\n");
7349 
7350               topic_match = topic;
7351               found = TRUE;
7352               break;
7353             }
7354 
7355           /* Move to next subject, or end of list. */
7356           while (subjects[posn] != COMMA && subjects[posn] != NUL)
7357             posn++;
7358           if (subjects[posn] == COMMA)
7359             posn++;
7360         }
7361     }
7362 
7363   /* Handle any matched subject first, and "*" second. */
7364   if (found && lib_npc_reply_to (game, npc, topic_match))
7365     return TRUE;
7366   else if (default_found && lib_npc_reply_to (game, npc, default_topic))
7367     return TRUE;
7368 
7369   /* NPC has no response. */
7370   pf_new_sentence (filter);
7371   lib_print_npc_np (game, npc);
7372   pf_buffer_string (filter,
7373                     lib_select_response (game,
7374                                 " does not respond to your question.\n",
7375                                 " does not respond to my question.\n",
7376                                 " does not respond to %player%'s question.\n"));
7377   return TRUE;
7378 }
7379 
7380 
7381 /*
7382  * lib_check_put_in_recursion()
7383  *
7384  * Checks for infinite recursion when placing an object in an object.  Returns
7385  * TRUE if no recursion detected.
7386  */
7387 static sc_bool
lib_check_put_in_recursion(sc_gameref_t game,sc_int object,sc_int container,sc_bool report)7388 lib_check_put_in_recursion (sc_gameref_t game,
7389                             sc_int object, sc_int container, sc_bool report)
7390 {
7391   const sc_filterref_t filter = gs_get_filter (game);
7392   sc_int check;
7393 
7394   /* Avoid the obvious possibility of infinite recursion. */
7395   if (container == object)
7396     {
7397       if (report)
7398         {
7399           pf_buffer_string (filter,
7400                             lib_select_response (game,
7401                                 "You can't put an object inside itself!",
7402                                 "I can't put an object inside itself!",
7403                                 "%player% can't put an object inside itself!"));
7404         }
7405       return FALSE;
7406     }
7407 
7408   /* Avoid the subtle possibility of infinite recursion. */
7409   check = container;
7410   while (gs_object_position (game, check) == OBJ_ON_OBJECT
7411          || gs_object_position (game, check) == OBJ_IN_OBJECT)
7412     {
7413       check = gs_object_parent (game, check);
7414       if (check == object)
7415         {
7416           if (report)
7417             {
7418               pf_buffer_string (filter,
7419                                 lib_select_response (game,
7420                                     "You can't put an object inside one",
7421                                     "I can't put an object inside one",
7422                                     "%player% can't put an object inside one"));
7423               pf_buffer_string (filter, " it's on or in!");
7424             }
7425           return FALSE;
7426         }
7427     }
7428 
7429   /* No infinite recursion detected. */
7430   return TRUE;
7431 }
7432 
7433 
7434 /*
7435  * lib_put_in_backend()
7436  *
7437  * Common backend handler for placing objects in containers.  Places all
7438  * objects currently referenced in the game into a container, trying game
7439  * commands first, and then moving other unhandled objects into the container.
7440  *
7441  * Objects to action are flagged in object_references; objects requested but
7442  * deemed not actionable are flagged in multiple_references.
7443  */
7444 static void
lib_put_in_backend(sc_gameref_t game,sc_int container)7445 lib_put_in_backend (sc_gameref_t game, sc_int container)
7446 {
7447   const sc_filterref_t filter = gs_get_filter (game);
7448   sc_int object_count, object, count, trail, capacity, maxsize;
7449   sc_bool has_printed;
7450 
7451   /*
7452    * Try game commands for all referenced objects first.  If any succeed,
7453    * remove that reference from the list.  At the same time, check for and
7454    * weed out any moves that result in infinite recursion.
7455    */
7456   has_printed = FALSE;
7457   object_count = gs_object_count (game);
7458   for (object = 0; object < object_count; object++)
7459     {
7460       if (!game->object_references[object])
7461         continue;
7462 
7463       /* Reject and remove attempts to place objects in themselves. */
7464       if (!lib_check_put_in_recursion (game, object, container, !has_printed))
7465         {
7466           game->object_references[object] = FALSE;
7467           has_printed = TRUE;
7468           continue;
7469         }
7470 
7471       if (lib_try_game_command_with_object (game,
7472                                             "put", object, "in", container))
7473         {
7474           game->object_references[object] = FALSE;
7475           has_printed = TRUE;
7476         }
7477     }
7478 
7479   /* Retrieve the container's limits. */
7480   maxsize = obj_get_container_maxsize (game, container);
7481   capacity = obj_get_container_capacity (game, container);
7482 
7483   /* Put in every object that remains referenced. */
7484   count = 0;
7485   trail = -1;
7486   for (object = 0; object < object_count; object++)
7487     {
7488       if (!game->object_references[object])
7489         continue;
7490 
7491       /* If too big, or exceeds container limits, ignore for now. */
7492       if (obj_get_size (game, object) > maxsize)
7493         continue;
7494       else
7495         {
7496           sc_int other, contains;
7497 
7498           contains = 0;
7499           for (other = 0; other < gs_object_count (game); other++)
7500             {
7501               if (gs_object_position (game, other) == OBJ_IN_OBJECT
7502                   && gs_object_parent (game, other) == container)
7503                 contains++;
7504             }
7505           if (contains >= capacity)
7506             continue;
7507         }
7508 
7509       if (count > 0)
7510         {
7511           if (count == 1)
7512             {
7513               if (has_printed)
7514                 pf_buffer_string (filter, "  ");
7515               pf_buffer_string (filter,
7516                                 lib_select_response (game,
7517                                                      "You put ",
7518                                                      "I put ",
7519                                                      "%player% puts "));
7520             }
7521           else
7522             pf_buffer_string (filter, ", ");
7523           lib_print_object_np (game, trail);
7524         }
7525       trail = object;
7526       count++;
7527 
7528       gs_object_move_into (game, object, container);
7529       game->object_references[object] = FALSE;
7530     }
7531 
7532   if (count >= 1)
7533     {
7534       if (count == 1)
7535         {
7536           if (has_printed)
7537             pf_buffer_string (filter, "  ");
7538           pf_buffer_string (filter,
7539                             lib_select_response (game,
7540                                                  "You put ",
7541                                                  "I put ",
7542                                                  "%player% puts "));
7543         }
7544       else
7545         pf_buffer_string (filter, " and ");
7546       lib_print_object_np (game, trail);
7547       pf_buffer_string (filter, " inside ");
7548       lib_print_object_np (game, container);
7549       pf_buffer_character (filter, '.');
7550     }
7551   has_printed |= count > 0;
7552 
7553   /*
7554    * Report objects not put in because of their size.  These objects remain in
7555    * standard references, as do objects rejected because of capacity limits.
7556    * By removing too large objects in this loop, we're left later on with just
7557    * the objects rejected by capacity limits.
7558    */
7559   count = 0;
7560   trail = -1;
7561   for (object = 0; object < object_count; object++)
7562     {
7563       if (!game->object_references[object])
7564         continue;
7565 
7566       if (!(obj_get_size (game, object) > maxsize))
7567         continue;
7568 
7569       if (count > 0)
7570         {
7571           if (count == 1)
7572             {
7573               if (has_printed)
7574                 pf_buffer_string (filter, "  ");
7575               pf_new_sentence (filter);
7576               lib_print_object_np (game, trail);
7577             }
7578           else
7579             pf_buffer_string (filter, ", ");
7580         }
7581       trail = object;
7582       count++;
7583 
7584       game->object_references[object] = FALSE;
7585     }
7586 
7587   if (count >= 1)
7588     {
7589       if (count == 1)
7590         {
7591           if (has_printed)
7592             pf_buffer_string (filter, "  ");
7593           pf_new_sentence (filter);
7594           lib_print_object_np (game, trail);
7595           pf_buffer_string (filter,
7596                             lib_select_plurality (game, trail,
7597                                                   " is too big",
7598                                                   " are too big"));
7599         }
7600       else
7601         {
7602           pf_buffer_string (filter, " and ");
7603           lib_print_object_np (game, trail);
7604           pf_buffer_string (filter, " are too big");
7605         }
7606       pf_buffer_string (filter, " to fit inside ");
7607       lib_print_object_np (game, container);
7608       pf_buffer_character (filter, '.');
7609     }
7610   has_printed |= count > 0;
7611 
7612   /*
7613    * Report objects not put in because the container is too full.  This should
7614    * be all remaining objects in standard references.
7615    */
7616   count = 0;
7617   trail = -1;
7618   for (object = 0; object < object_count; object++)
7619     {
7620       if (!game->object_references[object])
7621         continue;
7622 
7623       if (count > 0)
7624         {
7625           if (count == 1)
7626             {
7627               if (has_printed)
7628                 pf_buffer_string (filter, "  ");
7629               pf_new_sentence (filter);
7630             }
7631           else
7632             pf_buffer_string (filter, ", ");
7633           lib_print_object_np (game, trail);
7634         }
7635       trail = object;
7636       count++;
7637 
7638       game->object_references[object] = FALSE;
7639     }
7640 
7641   if (count >= 1)
7642     {
7643       if (count == 1)
7644         {
7645           if (has_printed)
7646             pf_buffer_string (filter, "  ");
7647           pf_new_sentence (filter);
7648           lib_print_object_np (game, trail);
7649         }
7650       else
7651         {
7652           pf_buffer_string (filter, " and ");
7653           lib_print_object_np (game, trail);
7654         }
7655       pf_buffer_string (filter, " can't fit inside ");
7656       lib_print_object_np (game, container);
7657       pf_buffer_string (filter, " at the moment.");
7658     }
7659   has_printed |= count > 0;
7660 
7661   /* Note any remaining multiple references left out of the operation. */
7662   count = 0;
7663   trail = -1;
7664   for (object = 0; object < object_count; object++)
7665     {
7666       if (!game->multiple_references[object])
7667         continue;
7668 
7669       if (count > 0)
7670         {
7671           if (count == 1)
7672             {
7673               if (has_printed)
7674                 pf_buffer_string (filter, "  ");
7675               pf_buffer_string (filter,
7676                                 lib_select_response (game,
7677                                                    "You are not holding ",
7678                                                    "I am not holding ",
7679                                                    "%player% is not holding "));
7680             }
7681           else
7682             pf_buffer_string (filter, ", ");
7683           lib_print_object_np (game, trail);
7684         }
7685       trail = object;
7686       count++;
7687 
7688       game->multiple_references[object] = FALSE;
7689     }
7690 
7691   if (count >= 1)
7692     {
7693       if (count == 1)
7694         {
7695           if (has_printed)
7696             pf_buffer_string (filter, "  ");
7697           pf_buffer_string (filter,
7698                             lib_select_response (game,
7699                                                  "You are not holding ",
7700                                                  "I am not holding ",
7701                                                  "%player% is not holding "));
7702         }
7703       else
7704         pf_buffer_string (filter, " or ");
7705       lib_print_object_np (game, trail);
7706       pf_buffer_character (filter, '.');
7707     }
7708 }
7709 
7710 
7711 /*
7712  * lib_put_in_filter()
7713  * lib_put_in_not_container_filter()
7714  *
7715  * Helper functions for deciding if an object may be put in another this
7716  * context.  Returns TRUE if an object may be manipulated, FALSE otherwise.
7717  */
7718 static sc_bool
lib_put_in_filter(sc_gameref_t game,sc_int object,sc_int unused)7719 lib_put_in_filter (sc_gameref_t game, sc_int object, sc_int unused)
7720 {
7721   assert (unused == -1);
7722 
7723   return !obj_is_static (game, object)
7724          && gs_object_position (game, object) == OBJ_HELD_PLAYER;
7725 }
7726 
7727 static sc_bool
lib_put_in_not_container_filter(sc_gameref_t game,sc_int object,sc_int container)7728 lib_put_in_not_container_filter (sc_gameref_t game,
7729                                  sc_int object, sc_int container)
7730 {
7731   return lib_put_in_filter (game, object, -1) && object != container;
7732 }
7733 
7734 
7735 /*
7736  * lib_put_in_is_valid()
7737  *
7738  * Validate the container requested in "put in" commands.
7739  */
7740 static sc_bool
lib_put_in_is_valid(sc_gameref_t game,sc_int container)7741 lib_put_in_is_valid (sc_gameref_t game, sc_int container)
7742 {
7743   const sc_filterref_t filter = gs_get_filter (game);
7744 
7745   /* Verify that the container object is a container. */
7746   if (!obj_is_container (game, container))
7747     {
7748       pf_buffer_string (filter,
7749                         lib_select_response (game,
7750                                         "You can't put anything inside ",
7751                                         "I can't put anything inside ",
7752                                         "%player% can't put anything inside "));
7753       lib_print_object_np (game, container);
7754       pf_buffer_string (filter, "!\n");
7755       return FALSE;
7756     }
7757 
7758   /* If the container is closed, reject now. */
7759   if (gs_object_openness (game, container) > OBJ_OPEN)
7760     {
7761       pf_new_sentence (filter);
7762       lib_print_object_np (game, container);
7763       pf_buffer_string (filter,
7764                         lib_select_plurality (game, container, " is", " are"));
7765       if (gs_object_openness (game, container) == OBJ_LOCKED)
7766         pf_buffer_string (filter, " locked!\n");
7767       else
7768         pf_buffer_string (filter, " closed!\n");
7769       return FALSE;
7770     }
7771 
7772   /* Container is a valid target for "put in". */
7773   return TRUE;
7774 }
7775 
7776 
7777 /*
7778  * lib_cmd_put_all_in()
7779  *
7780  * Put all objects currently held by the player into a container.
7781  */
7782 sc_bool
lib_cmd_put_all_in(sc_gameref_t game)7783 lib_cmd_put_all_in (sc_gameref_t game)
7784 {
7785   const sc_filterref_t filter = gs_get_filter (game);
7786   sc_int container, objects;
7787   sc_bool is_ambiguous;
7788 
7789   /* Get the referenced object, and if none, consider complete. */
7790   container = lib_disambiguate_object (game, "put that into", &is_ambiguous);
7791   if (container == -1)
7792     return is_ambiguous;
7793 
7794   /* Validate the container object to take from. */
7795   if (!lib_put_in_is_valid (game, container))
7796     return TRUE;
7797 
7798   /* Filter objects into references, then handle with the backend. */
7799   gs_set_multiple_references (game);
7800   objects = lib_apply_multiple_filter (game,
7801                                        lib_put_in_not_container_filter,
7802                                        container, NULL);
7803   gs_clear_multiple_references (game);
7804   if (objects > 0)
7805     lib_put_in_backend (game, container);
7806   else
7807     {
7808       pf_buffer_string (filter,
7809                         lib_select_response (game,
7810                                            "You're not carrying anything",
7811                                            "I'm not carrying anything",
7812                                            "%player%'s not carrying anything"));
7813       if (obj_indirectly_held_by_player (game, container))
7814         pf_buffer_string (filter, " else");
7815       pf_buffer_character (filter, '.');
7816     }
7817 
7818   pf_buffer_character (filter, '\n');
7819   return TRUE;
7820 }
7821 
7822 
7823 /*
7824  * lib_cmd_put_in_except_multiple()
7825  *
7826  * Put all objects currently held by the player into an object, excepting
7827  * those listed in %text%.
7828  */
7829 sc_bool
lib_cmd_put_in_except_multiple(sc_gameref_t game)7830 lib_cmd_put_in_except_multiple (sc_gameref_t game)
7831 {
7832   const sc_filterref_t filter = gs_get_filter (game);
7833   sc_int container, objects, references;
7834   sc_bool is_ambiguous;
7835 
7836   /* Get the referenced object, and if none, consider complete. */
7837   container = lib_disambiguate_object (game, "put that into", &is_ambiguous);
7838   if (container == -1)
7839     return is_ambiguous;
7840 
7841   /* Parse the multiple objects list to find retain target objects. */
7842   if (!lib_parse_multiple_objects (game, "retain",
7843                                    lib_put_in_not_container_filter,
7844                                    container, &references))
7845     return FALSE;
7846   else if (references == 0)
7847     return TRUE;
7848 
7849   /* Validate the container object to put into. */
7850   if (!lib_put_in_is_valid (game, container))
7851     return TRUE;
7852 
7853   /* As a special case, complain about requests to retain the container. */
7854   if (game->multiple_references[container])
7855     {
7856       pf_buffer_string (filter,
7857                         "I only understood you as far as wanting to retain ");
7858       lib_print_object_np (game, container);
7859       pf_buffer_string (filter, ".\n");
7860       return TRUE;
7861     }
7862 
7863   /* Filter objects into references, then handle with the backend. */
7864   objects = lib_apply_except_filter (game,
7865                                      lib_put_in_not_container_filter,
7866                                      container, &references);
7867   if (objects > 0 || references > 0)
7868     lib_put_in_backend (game, container);
7869   else
7870     {
7871       pf_buffer_string (filter,
7872                         lib_select_response (game,
7873                                            "You are not holding anything",
7874                                            "I am not holding anything",
7875                                            "%player% is not holding anything"));
7876       if (objects == 0)
7877         pf_buffer_string (filter, " else");
7878       pf_buffer_character (filter, '.');
7879     }
7880 
7881   pf_buffer_character (filter, '\n');
7882   return TRUE;
7883 }
7884 
7885 
7886 /*
7887  * lib_cmd_put_in_multiple()
7888  *
7889  * Put all objects currently held by the player and listed in %text% into an
7890  * object.
7891  */
7892 sc_bool
lib_cmd_put_in_multiple(sc_gameref_t game)7893 lib_cmd_put_in_multiple (sc_gameref_t game)
7894 {
7895   const sc_filterref_t filter = gs_get_filter (game);
7896   sc_int container, objects, references;
7897   sc_bool is_ambiguous;
7898 
7899   /* Get the referenced object, and if none, consider complete. */
7900   container = lib_disambiguate_object (game, "put that into", &is_ambiguous);
7901   if (container == -1)
7902     return is_ambiguous;
7903 
7904   /* Parse the multiple objects list to find retain target objects. */
7905   if (!lib_parse_multiple_objects (game, "move",
7906                                    lib_put_in_filter, -1,
7907                                    &references))
7908     return FALSE;
7909   else if (references == 0)
7910     return TRUE;
7911 
7912   /* Validate the container object to put into. */
7913   if (!lib_put_in_is_valid (game, container))
7914     return TRUE;
7915 
7916   /* Filter objects into references, then handle with the backend. */
7917   objects = lib_apply_multiple_filter (game,
7918                                        lib_put_in_filter, -1,
7919                                        &references);
7920   if (objects > 0 || references > 0)
7921     lib_put_in_backend (game, container);
7922   else
7923     {
7924       pf_buffer_string (filter,
7925                         lib_select_response (game,
7926                                           "You are not holding anything.",
7927                                           "I am not holding anything.",
7928                                           "%player% is not holding anything."));
7929     }
7930 
7931   pf_buffer_character (filter, '\n');
7932   return TRUE;
7933 }
7934 
7935 
7936 /*
7937  * lib_check_put_on_recursion()
7938  *
7939  * Checks for infinite recursion when placing an object on an object.  Returns
7940  * TRUE if no recursion detected.
7941  */
7942 static sc_bool
lib_check_put_on_recursion(sc_gameref_t game,sc_int object,sc_int supporter,sc_bool report)7943 lib_check_put_on_recursion (sc_gameref_t game,
7944                             sc_int object, sc_int supporter, sc_bool report)
7945 {
7946   const sc_filterref_t filter = gs_get_filter (game);
7947   sc_int check;
7948 
7949   /* Avoid the obvious possibility of infinite recursion. */
7950   if (supporter == object)
7951     {
7952       if (report)
7953         {
7954           pf_buffer_string (filter,
7955                             lib_select_response (game,
7956                                   "You can't put an object onto itself!",
7957                                   "I can't put an object onto itself!",
7958                                   "%player% can't put an object onto itself!"));
7959         }
7960       return FALSE;
7961     }
7962 
7963   /* Avoid the subtle possibility of infinite recursion. */
7964   check = supporter;
7965   while (gs_object_position (game, check) == OBJ_ON_OBJECT
7966          || gs_object_position (game, check) == OBJ_IN_OBJECT)
7967     {
7968       check = gs_object_parent (game, check);
7969       if (check == object)
7970         {
7971           if (report)
7972             {
7973               pf_buffer_string (filter,
7974                                 lib_select_response (game,
7975                                       "You can't put an object onto one",
7976                                       "I can't put an object onto one",
7977                                       "%player% can't put an object onto one"));
7978               pf_buffer_string (filter, " it's on or in!");
7979             }
7980           return FALSE;
7981         }
7982     }
7983 
7984   /* No infinite recursion detected. */
7985   return TRUE;
7986 }
7987 
7988 
7989 /*
7990  * lib_put_on_backend()
7991  *
7992  * Common backend handler for placing objects on supporters.  Places all
7993  * objects currently referenced in the game onto a supporter, trying game
7994  * commands first, and then moving other unhandled objects onto the supporter.
7995  *
7996  * Objects to action are flagged in object_references; objects requested but
7997  * deemed not actionable are flagged in multiple_references.
7998  */
7999 static void
lib_put_on_backend(sc_gameref_t game,sc_int supporter)8000 lib_put_on_backend (sc_gameref_t game, sc_int supporter)
8001 {
8002   const sc_filterref_t filter = gs_get_filter (game);
8003   sc_int object_count, object, count, trail;
8004   sc_bool has_printed;
8005 
8006   /*
8007    * Try game commands for all referenced objects first.  If any succeed,
8008    * remove that reference from the list.  At the same time, check for and
8009    * weed out any moves that result in infinite recursion.
8010    */
8011   has_printed = FALSE;
8012   object_count = gs_object_count (game);
8013   for (object = 0; object < object_count; object++)
8014     {
8015       if (!game->object_references[object])
8016         continue;
8017 
8018       /* Reject and remove attempts to place objects on themselves. */
8019       if (!lib_check_put_on_recursion (game, object, supporter, !has_printed))
8020         {
8021           game->object_references[object] = FALSE;
8022           has_printed = TRUE;
8023           continue;
8024         }
8025 
8026       if (lib_try_game_command_with_object (game,
8027                                             "put", object, "on", supporter))
8028         {
8029           game->object_references[object] = FALSE;
8030           has_printed = TRUE;
8031         }
8032     }
8033 
8034   /* Put on every object that remains referenced. */
8035   count = 0;
8036   trail = -1;
8037   for (object = 0; object < object_count; object++)
8038     {
8039       if (!game->object_references[object])
8040         continue;
8041 
8042       if (count > 0)
8043         {
8044           if (count == 1)
8045             {
8046               if (has_printed)
8047                 pf_buffer_string (filter, "  ");
8048               pf_buffer_string (filter,
8049                                 lib_select_response (game,
8050                                                      "You put ",
8051                                                      "I put ",
8052                                                      "%player% puts "));
8053             }
8054           else
8055             pf_buffer_string (filter, ", ");
8056           lib_print_object_np (game, trail);
8057         }
8058       trail = object;
8059       count++;
8060 
8061       gs_object_move_onto (game, object, supporter);
8062     }
8063 
8064   if (count >= 1)
8065     {
8066       if (count == 1)
8067         {
8068           if (has_printed)
8069             pf_buffer_string (filter, "  ");
8070           pf_buffer_string (filter,
8071                             lib_select_response (game,
8072                                                  "You put ",
8073                                                  "I put ",
8074                                                  "%player% puts "));
8075         }
8076       else
8077         pf_buffer_string (filter, " and ");
8078       lib_print_object_np (game, trail);
8079       pf_buffer_string (filter, " onto ");
8080       lib_print_object_np (game, supporter);
8081       pf_buffer_character (filter, '.');
8082     }
8083   has_printed |= count > 0;
8084 
8085   /* Note any remaining multiple references left out of the operation. */
8086   count = 0;
8087   trail = -1;
8088   for (object = 0; object < object_count; object++)
8089     {
8090       if (!game->multiple_references[object])
8091         continue;
8092 
8093       if (count > 0)
8094         {
8095           if (count == 1)
8096             {
8097               if (has_printed)
8098                 pf_buffer_string (filter, "  ");
8099               pf_buffer_string (filter,
8100                                 lib_select_response (game,
8101                                                    "You are not holding ",
8102                                                    "I am not holding ",
8103                                                    "%player% is not holding "));
8104             }
8105           else
8106             pf_buffer_string (filter, ", ");
8107           lib_print_object_np (game, trail);
8108         }
8109       trail = object;
8110       count++;
8111 
8112       game->multiple_references[object] = FALSE;
8113     }
8114 
8115   if (count >= 1)
8116     {
8117       if (count == 1)
8118         {
8119           if (has_printed)
8120             pf_buffer_string (filter, "  ");
8121           pf_buffer_string (filter,
8122                             lib_select_response (game,
8123                                                  "You are not holding ",
8124                                                  "I am not holding ",
8125                                                  "%player% is not holding "));
8126         }
8127       else
8128         pf_buffer_string (filter, " or ");
8129       lib_print_object_np (game, trail);
8130       pf_buffer_character (filter, '.');
8131     }
8132 }
8133 
8134 
8135 /*
8136  * lib_put_on_filter()
8137  * lib_put_on_not_supporter_filter()
8138  *
8139  * Helper functions for deciding if an object may be put on another this
8140  * context.  Returns TRUE if an object may be manipulated, FALSE otherwise.
8141  */
8142 static sc_bool
lib_put_on_filter(sc_gameref_t game,sc_int object,sc_int unused)8143 lib_put_on_filter (sc_gameref_t game, sc_int object, sc_int unused)
8144 {
8145   assert (unused == -1);
8146 
8147   return !obj_is_static (game, object)
8148          && gs_object_position (game, object) == OBJ_HELD_PLAYER;
8149 }
8150 
8151 static sc_bool
lib_put_on_not_supporter_filter(sc_gameref_t game,sc_int object,sc_int supporter)8152 lib_put_on_not_supporter_filter (sc_gameref_t game,
8153                                  sc_int object, sc_int supporter)
8154 {
8155   return lib_put_on_filter (game, object, -1) && object != supporter;
8156 }
8157 
8158 
8159 /*
8160  * lib_put_on_is_valid()
8161  *
8162  * Validate the supporter requested in "put on" commands.
8163  */
8164 static sc_bool
lib_put_on_is_valid(sc_gameref_t game,sc_int supporter)8165 lib_put_on_is_valid (sc_gameref_t game, sc_int supporter)
8166 {
8167   const sc_filterref_t filter = gs_get_filter (game);
8168 
8169   /* Verify that the supporter object is a supporter. */
8170   if (!obj_is_surface (game, supporter))
8171     {
8172       pf_buffer_string (filter,
8173                         lib_select_response (game,
8174                                             "You can't put anything on ",
8175                                             "I can't put anything on ",
8176                                             "%player% can't put anything on "));
8177       lib_print_object_np (game, supporter);
8178       pf_buffer_string (filter, "!\n");
8179       return FALSE;
8180     }
8181 
8182   /* Surface is a valid target for "put on". */
8183   return TRUE;
8184 }
8185 
8186 
8187 /*
8188  * lib_cmd_put_all_on()
8189  *
8190  * Put all objects currently held by the player onto a supporter.
8191  */
8192 sc_bool
lib_cmd_put_all_on(sc_gameref_t game)8193 lib_cmd_put_all_on (sc_gameref_t game)
8194 {
8195   const sc_filterref_t filter = gs_get_filter (game);
8196   sc_int supporter, objects;
8197   sc_bool is_ambiguous;
8198 
8199   /* Get the referenced object, and if none, consider complete. */
8200   supporter = lib_disambiguate_object (game, "put that onto", &is_ambiguous);
8201   if (supporter == -1)
8202     return is_ambiguous;
8203 
8204   /* Validate the supporter object to take from. */
8205   if (!lib_put_on_is_valid (game, supporter))
8206     return TRUE;
8207 
8208   /* Filter objects into references, then handle with the backend. */
8209   gs_set_multiple_references (game);
8210   objects = lib_apply_multiple_filter (game,
8211                                        lib_put_on_not_supporter_filter,
8212                                        supporter, NULL);
8213   gs_clear_multiple_references (game);
8214   if (objects > 0)
8215     lib_put_on_backend (game, supporter);
8216   else
8217     {
8218       pf_buffer_string (filter,
8219                         lib_select_response (game,
8220                                            "You're not carrying anything",
8221                                            "I'm not carrying anything",
8222                                            "%player%'s not carrying anything"));
8223       if (obj_indirectly_held_by_player (game, supporter))
8224         pf_buffer_string (filter, " else");
8225       pf_buffer_character (filter, '.');
8226     }
8227 
8228   pf_buffer_character (filter, '\n');
8229   return TRUE;
8230 }
8231 
8232 
8233 /*
8234  * lib_cmd_put_on_except_multiple()
8235  *
8236  * Put all objects currently held by the player onto an object, excepting
8237  * those listed in %text%.
8238  */
8239 sc_bool
lib_cmd_put_on_except_multiple(sc_gameref_t game)8240 lib_cmd_put_on_except_multiple (sc_gameref_t game)
8241 {
8242   const sc_filterref_t filter = gs_get_filter (game);
8243   sc_int supporter, objects, references;
8244   sc_bool is_ambiguous;
8245 
8246   /* Get the referenced object, and if none, consider complete. */
8247   supporter = lib_disambiguate_object (game, "put that onto", &is_ambiguous);
8248   if (supporter == -1)
8249     return is_ambiguous;
8250 
8251   /* Parse the multiple objects list to find retain target objects. */
8252   if (!lib_parse_multiple_objects (game, "retain",
8253                                    lib_put_on_not_supporter_filter,
8254                                    supporter, &references))
8255     return FALSE;
8256   else if (references == 0)
8257     return TRUE;
8258 
8259   /* Validate the supporter object to put into. */
8260   if (!lib_put_on_is_valid (game, supporter))
8261     return TRUE;
8262 
8263   /* As a special case, complain about requests to retain the supporter. */
8264   if (game->multiple_references[supporter])
8265     {
8266       pf_buffer_string (filter,
8267                         "I only understood you as far as wanting to retain ");
8268       lib_print_object_np (game, supporter);
8269       pf_buffer_string (filter, ".\n");
8270       return TRUE;
8271     }
8272 
8273   /* Filter objects into references, then handle with the backend. */
8274   objects = lib_apply_except_filter (game,
8275                                      lib_put_on_not_supporter_filter,
8276                                      supporter, &references);
8277   if (objects > 0 || references > 0)
8278     lib_put_on_backend (game, supporter);
8279   else
8280     {
8281       pf_buffer_string (filter,
8282                         lib_select_response (game,
8283                                            "You are not holding anything",
8284                                            "I am not holding anything",
8285                                            "%player% is not holding anything"));
8286       if (objects == 0)
8287         pf_buffer_string (filter, " else");
8288       pf_buffer_character (filter, '.');
8289     }
8290 
8291   pf_buffer_character (filter, '\n');
8292   return TRUE;
8293 }
8294 
8295 
8296 /*
8297  * lib_cmd_put_on_multiple()
8298  *
8299  * Put all objects currently held by the player and listed in %text% onto an
8300  * object.
8301  */
8302 sc_bool
lib_cmd_put_on_multiple(sc_gameref_t game)8303 lib_cmd_put_on_multiple (sc_gameref_t game)
8304 {
8305   const sc_filterref_t filter = gs_get_filter (game);
8306   sc_int supporter, objects, references;
8307   sc_bool is_ambiguous;
8308 
8309   /* Get the referenced object, and if none, consider complete. */
8310   supporter = lib_disambiguate_object (game, "put that onto", &is_ambiguous);
8311   if (supporter == -1)
8312     return is_ambiguous;
8313 
8314   /* Parse the multiple objects list to find retain target objects. */
8315   if (!lib_parse_multiple_objects (game, "move",
8316                                    lib_put_on_filter, -1,
8317                                    &references))
8318     return FALSE;
8319   else if (references == 0)
8320     return TRUE;
8321 
8322   /* Validate the supporter object to put into. */
8323   if (!lib_put_on_is_valid (game, supporter))
8324     return TRUE;
8325 
8326   /* Filter objects into references, then handle with the backend. */
8327   objects = lib_apply_multiple_filter (game,
8328                                        lib_put_on_filter, -1,
8329                                        &references);
8330   if (objects > 0 || references > 0)
8331     lib_put_on_backend (game, supporter);
8332   else
8333     {
8334       pf_buffer_string (filter,
8335                         lib_select_response (game,
8336                                           "You are not holding anything.",
8337                                           "I am not holding anything.",
8338                                           "%player% is not holding anything."));
8339     }
8340 
8341   pf_buffer_character (filter, '\n');
8342   return TRUE;
8343 }
8344 
8345 
8346 /*
8347  * lib_cmd_read_object()
8348  * lib_cmd_read_other()
8349  *
8350  * Attempt to read the referenced object, or something else.
8351  */
8352 sc_bool
lib_cmd_read_object(sc_gameref_t game)8353 lib_cmd_read_object (sc_gameref_t game)
8354 {
8355   const sc_filterref_t filter = gs_get_filter (game);
8356   const sc_prop_setref_t bundle = gs_get_bundle (game);
8357   sc_vartype_t vt_key[3];
8358   sc_int object, task;
8359   sc_bool is_readable, is_ambiguous;
8360   const sc_char *readtext, *description;
8361 
8362   /* Get the referenced object, and if none, consider complete. */
8363   object = lib_disambiguate_object (game, "read", &is_ambiguous);
8364   if (object == -1)
8365     return is_ambiguous;
8366 
8367   /* Verify that the object is readable. */
8368   vt_key[0].string = "Objects";
8369   vt_key[1].integer = object;
8370   vt_key[2].string = "Readable";
8371   is_readable = prop_get_boolean (bundle, "B<-sis", vt_key);
8372   if (!is_readable)
8373     {
8374       pf_buffer_string (filter,
8375                         lib_select_response (game,
8376                                              "You can't read ",
8377                                              "I can't read ",
8378                                              "%player% can't read "));
8379       lib_print_object_np (game, object);
8380       pf_buffer_string (filter, "!\n");
8381       return TRUE;
8382     }
8383 
8384   /* Get and print the object's read text, if any. */
8385   vt_key[2].string = "ReadText";
8386   readtext = prop_get_string (bundle, "S<-sis", vt_key);
8387   if (!sc_strempty (readtext))
8388     {
8389       pf_buffer_string (filter, readtext);
8390       pf_buffer_character (filter, '\n');
8391       return TRUE;
8392     }
8393 
8394   /* Degrade to a shortened object examine. */
8395   vt_key[2].string = "Task";
8396   task = prop_get_integer (bundle, "I<-sis", vt_key) - 1;
8397 
8398   /* Select either the main or the alternate description. */
8399   if (task >= 0 && gs_task_done (game, task))
8400     vt_key[2].string = "AltDesc";
8401   else
8402     vt_key[2].string = "Description";
8403 
8404   /* Print the description, or a "nothing special" default. */
8405   description = prop_get_string (bundle, "S<-sis", vt_key);
8406   if (!sc_strempty (description))
8407     pf_buffer_string (filter, description);
8408   else
8409     {
8410       pf_buffer_string (filter, "There is nothing special about ");
8411       lib_print_object_np (game, object);
8412       pf_buffer_character (filter, '.');
8413     }
8414 
8415   pf_buffer_character (filter, '\n');
8416   return TRUE;
8417 }
8418 
8419 sc_bool
lib_cmd_read_other(sc_gameref_t game)8420 lib_cmd_read_other (sc_gameref_t game)
8421 {
8422   const sc_filterref_t filter = gs_get_filter (game);
8423 
8424   /* Reject the attempt. */
8425   pf_buffer_string (filter,
8426                     lib_select_response (game,
8427                                          "You see no such thing.\n",
8428                                          "I see no such thing.\n",
8429                                          "%player% sees no such thing.\n"));
8430   return TRUE;
8431 }
8432 
8433 
8434 /*
8435  * lib_cmd_attack_npc()
8436  * lib_cmd_attack_npc_with()
8437  *
8438  * Attempt to attack an NPC, with and without weaponry.
8439  */
8440 sc_bool
lib_cmd_attack_npc(sc_gameref_t game)8441 lib_cmd_attack_npc (sc_gameref_t game)
8442 {
8443   const sc_filterref_t filter = gs_get_filter (game);
8444   sc_int npc;
8445   sc_bool is_ambiguous;
8446 
8447   /* Get the referenced npc, and if none, consider complete. */
8448   npc = lib_disambiguate_npc (game, "attack", &is_ambiguous);
8449   if (npc == -1)
8450     return is_ambiguous;
8451 
8452   /* Print a standard response. */
8453   pf_new_sentence (filter);
8454   lib_print_npc_np (game, npc);
8455   pf_buffer_string (filter,
8456                     lib_select_response (game,
8457                                       " avoids your feeble attempts.\n",
8458                                       " avoids my feeble attempts.\n",
8459                                       " avoids %player%'s feeble attempts.\n"));
8460   return TRUE;
8461 }
8462 
8463 sc_bool
lib_cmd_attack_npc_with(sc_gameref_t game)8464 lib_cmd_attack_npc_with (sc_gameref_t game)
8465 {
8466   const sc_filterref_t filter = gs_get_filter (game);
8467   const sc_prop_setref_t bundle = gs_get_bundle (game);
8468   sc_int object, npc;
8469   sc_vartype_t vt_key[3];
8470   sc_bool weapon, is_ambiguous;
8471 
8472   /* Get the referenced npc, and if none, consider complete. */
8473   npc = lib_disambiguate_npc (game, "attack", &is_ambiguous);
8474   if (npc == -1)
8475     return is_ambiguous;
8476 
8477   /* Get the referenced object, and if none, consider complete. */
8478   object = lib_disambiguate_object (game, "attack with", NULL);
8479   if (object == -1)
8480     return TRUE;
8481 
8482   /* Ensure the referenced object is held. */
8483   if (gs_object_position (game, object) != OBJ_HELD_PLAYER)
8484     {
8485       pf_buffer_string (filter,
8486                         lib_select_response (game,
8487                                              "You are not holding ",
8488                                              "I am not holding ",
8489                                              "%player% is not holding "));
8490       lib_print_object_np (game, object);
8491       pf_buffer_string (filter, ".\n");
8492       return TRUE;
8493     }
8494 
8495   /* Check for static object moved to player by event. */
8496   if (obj_is_static (game, object))
8497     {
8498       pf_new_sentence (filter);
8499       lib_print_object_np (game, object);
8500       pf_buffer_string (filter,
8501                         lib_select_plurality (game, object, " is", " are"));
8502       pf_buffer_string (filter, " not a weapon.\n");
8503       return TRUE;
8504     }
8505 
8506   /* Print standard response depending on if the object is a weapon. */
8507   vt_key[0].string = "Objects";
8508   vt_key[1].integer = object;
8509   vt_key[2].string = "Weapon";
8510   weapon = prop_get_boolean (bundle, "B<-sis", vt_key);
8511   if (weapon)
8512     {
8513       pf_buffer_string (filter,
8514                         lib_select_response (game,
8515                                              "You swing at ",
8516                                              "I swing at ",
8517                                              "%player% swings at "));
8518       lib_print_npc_np (game, npc);
8519       pf_buffer_string (filter, " with ");
8520       lib_print_object_np (game, object);
8521       pf_buffer_string (filter,
8522                         lib_select_response (game,
8523                                              " but you miss.\n",
8524                                              " but I miss.\n",
8525                                              " but misses.\n"));
8526     }
8527   else
8528     {
8529       /*
8530        * TODO Adrift uses "affective" [sic] here.  Should SCARE be right, or
8531        * bug-compatible?
8532        */
8533       pf_buffer_string (filter, "I don't think ");
8534       lib_print_object_np (game, object);
8535       pf_buffer_string (filter, " would be a very effective weapon.\n");
8536     }
8537   return TRUE;
8538 }
8539 
8540 
8541 /*
8542  * lib_cmd_kiss_npc()
8543  * lib_cmd_kiss_object()
8544  * lib_cmd_kiss_other()
8545  *
8546  * Reject romantic advances in all cases.
8547  */
8548 sc_bool
lib_cmd_kiss_npc(sc_gameref_t game)8549 lib_cmd_kiss_npc (sc_gameref_t game)
8550 {
8551   const sc_filterref_t filter = gs_get_filter (game);
8552   const sc_prop_setref_t bundle = gs_get_bundle (game);
8553   sc_vartype_t vt_key[3];
8554   sc_int npc, gender;
8555   sc_bool is_ambiguous;
8556 
8557   /* Get the referenced npc, and if none, consider complete. */
8558   npc = lib_disambiguate_npc (game, "kiss", &is_ambiguous);
8559   if (npc == -1)
8560     return is_ambiguous;
8561 
8562   /* Reject this attempt. */
8563   vt_key[0].string = "NPCs";
8564   vt_key[1].integer = npc;
8565   vt_key[2].string = "Gender";
8566   gender = prop_get_integer (bundle, "I<-sis", vt_key);
8567 
8568   switch (gender)
8569     {
8570     case NPC_MALE:
8571       pf_buffer_string (filter, "I'm not sure he would appreciate that!\n");
8572       break;
8573 
8574     case NPC_FEMALE:
8575       pf_buffer_string (filter, "I'm not sure she would appreciate that!\n");
8576       break;
8577 
8578     case NPC_NEUTER:
8579       pf_buffer_string (filter, "I'm not sure it would appreciate that!\n");
8580       break;
8581 
8582     default:
8583       sc_error ("lib_cmd_kiss_npc: unknown gender, %ld\n", gender);
8584     }
8585   return TRUE;
8586 }
8587 
8588 sc_bool
lib_cmd_kiss_object(sc_gameref_t game)8589 lib_cmd_kiss_object (sc_gameref_t game)
8590 {
8591   const sc_filterref_t filter = gs_get_filter (game);
8592   sc_int object;
8593   sc_bool is_ambiguous;
8594 
8595   /* Get the referenced object, and if none, consider complete. */
8596   object = lib_disambiguate_object (game, "kiss", &is_ambiguous);
8597   if (object == -1)
8598     return is_ambiguous;
8599 
8600   /* Reject this attempt. */
8601   pf_buffer_string (filter, "I'm not sure ");
8602   lib_print_object_np (game, object);
8603   pf_buffer_string (filter, " would appreciate that.\n");
8604   return TRUE;
8605 }
8606 
8607 sc_bool
lib_cmd_kiss_other(sc_gameref_t game)8608 lib_cmd_kiss_other (sc_gameref_t game)
8609 {
8610   const sc_filterref_t filter = gs_get_filter (game);
8611 
8612   /* Reject this attempt. */
8613   pf_buffer_string (filter, "I'm not sure it would appreciate that.\n");
8614   return TRUE;
8615 }
8616 
8617 
8618 /*
8619  * lib_cmd_buy_object()
8620  * lib_cmd_buy_other()
8621  *
8622  * Standard responses to attempts to buy something.
8623  */
8624 sc_bool
lib_cmd_buy_object(sc_gameref_t game)8625 lib_cmd_buy_object (sc_gameref_t game)
8626 {
8627   const sc_filterref_t filter = gs_get_filter (game);
8628   sc_int object;
8629   sc_bool is_ambiguous;
8630 
8631   /* Get the referenced object, and if none, consider complete. */
8632   object = lib_disambiguate_object (game, "buy", &is_ambiguous);
8633   if (object == -1)
8634     return is_ambiguous;
8635 
8636   /* Reject this attempt. */
8637   pf_buffer_string (filter, "I don't think ");
8638   lib_print_object_np (game, object);
8639   pf_buffer_string (filter,
8640                     lib_select_plurality (game, object, " is", " are"));
8641   pf_buffer_string (filter, " for sale.\n");
8642   return TRUE;
8643 }
8644 
8645 sc_bool
lib_cmd_buy_other(sc_gameref_t game)8646 lib_cmd_buy_other (sc_gameref_t game)
8647 {
8648   const sc_filterref_t filter = gs_get_filter (game);
8649 
8650   /* Reject this attempt. */
8651   pf_buffer_string (filter, "I don't think that is for sale.\n");
8652   return TRUE;
8653 }
8654 
8655 
8656 /*
8657  * lib_cmd_break_object()
8658  * lib_cmd_break_other()
8659  *
8660  * Standard responses to attempts to break something.
8661  */
8662 sc_bool
lib_cmd_break_object(sc_gameref_t game)8663 lib_cmd_break_object (sc_gameref_t game)
8664 {
8665   const sc_filterref_t filter = gs_get_filter (game);
8666   sc_int object;
8667   sc_bool is_ambiguous;
8668 
8669   /* Get the referenced object, and if none, consider complete. */
8670   object = lib_disambiguate_object (game, "break", &is_ambiguous);
8671   if (object == -1)
8672     return is_ambiguous;
8673 
8674   /* Reject this attempt. */
8675   pf_buffer_string (filter,
8676                     lib_select_response (game,
8677                                          "You might need ",
8678                                          "I might need ",
8679                                          "%player% might need "));
8680   lib_print_object_np (game, object);
8681   pf_buffer_string (filter, ".\n");
8682   return TRUE;
8683 }
8684 
8685 sc_bool
lib_cmd_break_other(sc_gameref_t game)8686 lib_cmd_break_other (sc_gameref_t game)
8687 {
8688   const sc_filterref_t filter = gs_get_filter (game);
8689 
8690   /* Reject this attempt. */
8691   pf_buffer_string (filter,
8692                     lib_select_response (game,
8693                                          "You might need that.\n",
8694                                          "I might need that.\n",
8695                                          "%player% might need that.\n"));
8696   return TRUE;
8697 }
8698 
8699 
8700 /*
8701  * lib_cmd_smell_object()
8702  * lib_cmd_smell_other()
8703  *
8704  * Standard responses to attempts to smell something.
8705  */
8706 sc_bool
lib_cmd_smell_object(sc_gameref_t game)8707 lib_cmd_smell_object (sc_gameref_t game)
8708 {
8709   const sc_filterref_t filter = gs_get_filter (game);
8710   sc_int object;
8711   sc_bool is_ambiguous;
8712 
8713   /* Get the referenced object, and if none, consider complete. */
8714   object = lib_disambiguate_object (game, "smell", &is_ambiguous);
8715   if (object == -1)
8716     return is_ambiguous;
8717 
8718   /* Reject this attempt. */
8719   pf_new_sentence (filter);
8720   lib_print_object_np (game, object);
8721   pf_buffer_string (filter, " smells normal.\n");
8722   return TRUE;
8723 }
8724 
8725 sc_bool
lib_cmd_smell_other(sc_gameref_t game)8726 lib_cmd_smell_other (sc_gameref_t game)
8727 {
8728   const sc_filterref_t filter = gs_get_filter (game);
8729 
8730   /* Reject this attempt. */
8731   pf_buffer_string (filter, "That smells normal.\n");
8732   return TRUE;
8733 }
8734 
8735 
8736 /*
8737  * lib_cmd_sell_object()
8738  * lib_cmd_sell_other()
8739  *
8740  * Standard responses to attempts to sell something.
8741  */
8742 sc_bool
lib_cmd_sell_object(sc_gameref_t game)8743 lib_cmd_sell_object (sc_gameref_t game)
8744 {
8745   const sc_filterref_t filter = gs_get_filter (game);
8746   sc_int object;
8747   sc_bool is_ambiguous;
8748 
8749   /* Get the referenced object, and if none, consider complete. */
8750   object = lib_disambiguate_object (game, "sell", &is_ambiguous);
8751   if (object == -1)
8752     return is_ambiguous;
8753 
8754   /* Reject this attempt. */
8755   pf_buffer_string (filter, "No-one is interested in buying ");
8756   lib_print_object_np (game, object);
8757   pf_buffer_string (filter, ".\n");
8758   return TRUE;
8759 }
8760 
8761 sc_bool
lib_cmd_sell_other(sc_gameref_t game)8762 lib_cmd_sell_other (sc_gameref_t game)
8763 {
8764   const sc_filterref_t filter = gs_get_filter (game);
8765 
8766   pf_buffer_string (filter, "No-one is interested in buying that.\n");
8767   return TRUE;
8768 }
8769 
8770 
8771 /*
8772  * lib_cmd_eat_object()
8773  *
8774  * Consume edible objects.
8775  */
8776 sc_bool
lib_cmd_eat_object(sc_gameref_t game)8777 lib_cmd_eat_object (sc_gameref_t game)
8778 {
8779   const sc_filterref_t filter = gs_get_filter (game);
8780   const sc_prop_setref_t bundle = gs_get_bundle (game);
8781   sc_vartype_t vt_key[3];
8782   sc_int object;
8783   sc_bool edible, is_ambiguous;
8784 
8785   /* Get the referenced object, and if none, consider complete. */
8786   object = lib_disambiguate_object (game, "eat", &is_ambiguous);
8787   if (object == -1)
8788     return is_ambiguous;
8789 
8790   /* Check that we have the object to eat. */
8791   if (gs_object_position (game, object) != OBJ_HELD_PLAYER)
8792     {
8793       pf_buffer_string (filter,
8794                         lib_select_response (game,
8795                                              "You are not holding ",
8796                                              "I am not holding ",
8797                                              "%player% is not holding "));
8798       lib_print_object_np (game, object);
8799       pf_buffer_string (filter, ".\n");
8800       return TRUE;
8801     }
8802 
8803   /* Check for static object moved to player by event. */
8804   if (obj_is_static (game, object))
8805     {
8806       pf_buffer_string (filter,
8807                         lib_select_response (game,
8808                                              "You can't eat ",
8809                                              "I can't eat ",
8810                                              "%player% can't eat "));
8811       lib_print_object_np (game, object);
8812       pf_buffer_string (filter, ".\n");
8813       return TRUE;
8814     }
8815 
8816   /* Is this object inedible? */
8817   vt_key[0].string = "Objects";
8818   vt_key[1].integer = object;
8819   vt_key[2].string = "Edible";
8820   edible = prop_get_boolean (bundle, "B<-sis", vt_key);
8821   if (!edible)
8822     {
8823       pf_buffer_string (filter,
8824                         lib_select_response (game,
8825                                              "You can't eat ",
8826                                              "I can't eat ",
8827                                              "%player% can't eat "));
8828       lib_print_object_np (game, object);
8829       pf_buffer_string (filter, ".\n");
8830       return TRUE;
8831     }
8832 
8833   /* Confirm, and hide the object. */
8834   pf_buffer_string (filter,
8835                     lib_select_response (game,
8836                                          "You eat ",
8837                                          "I eat ", "%player% eats "));
8838   lib_print_object_np (game, object);
8839   pf_buffer_string (filter,
8840                     ".  Not bad, but it could do with a pinch of salt!\n");
8841   gs_object_make_hidden (game, object);
8842   return TRUE;
8843 }
8844 
8845 
8846 /* Enumerated sit/stand/lie types. */
8847 enum
8848 { OBJ_STANDABLE_MASK = 1 << 0,
8849   OBJ_LIEABLE_MASK = 1 << 1
8850 };
8851 enum
8852 { MOVE_SIT, MOVE_SIT_FLOOR,
8853   MOVE_STAND, MOVE_STAND_FLOOR, MOVE_LIE, MOVE_LIE_FLOOR
8854 };
8855 
8856 /*
8857  * lib_stand_sit_lie()
8858  *
8859  * Central handler for stand, sit, and lie commands.
8860  */
8861 static sc_bool
lib_stand_sit_lie(sc_gameref_t game,sc_int movement)8862 lib_stand_sit_lie (sc_gameref_t game, sc_int movement)
8863 {
8864   const sc_filterref_t filter = gs_get_filter (game);
8865   const sc_prop_setref_t bundle = gs_get_bundle (game);
8866   sc_int object, position;
8867   const sc_char *already_doing_that, *success_message;
8868 
8869   /* Initialize variables to avoid gcc warnings. */
8870   object = -1;
8871   already_doing_that = FALSE;
8872   success_message = FALSE;
8873   position = 0;
8874 
8875   /* Get a target object for movement, -1 if floor. */
8876   switch (movement)
8877     {
8878     case MOVE_STAND:
8879     case MOVE_SIT:
8880     case MOVE_LIE:
8881       {
8882         const sc_char *disambiguate, *cant_do_that;
8883         sc_int sit_lie_flags, movement_mask;
8884         sc_vartype_t vt_key[3];
8885         sc_bool is_ambiguous;
8886 
8887         /* Initialize variables to avoid gcc warnings. */
8888         disambiguate = NULL;
8889         cant_do_that = NULL;
8890         movement_mask = 0;
8891 
8892         /* Set disambiguation and not amenable messages. */
8893         switch (movement)
8894           {
8895           case MOVE_STAND:
8896             disambiguate = "stand on";
8897             cant_do_that = lib_select_response (game,
8898                                                 "You can't stand on ",
8899                                                 "I can't stand on ",
8900                                                 "%player% can't stand on ");
8901             movement_mask = OBJ_STANDABLE_MASK;
8902             break;
8903           case MOVE_SIT:
8904             disambiguate = "sit on";
8905             cant_do_that = lib_select_response (game,
8906                                                 "You can't sit on ",
8907                                                 "I can't sit on ",
8908                                                 "%player% can't sit on ");
8909             movement_mask = OBJ_STANDABLE_MASK;
8910             break;
8911           case MOVE_LIE:
8912             disambiguate = "lie on";
8913             cant_do_that = lib_select_response (game,
8914                                                 "You can't lie on ",
8915                                                 "I can't lie on ",
8916                                                 "%player% can't lie on ");
8917             movement_mask = OBJ_LIEABLE_MASK;
8918             break;
8919           default:
8920             sc_fatal ("lib_sit_stand_lie: movement error, %ld\n", movement);
8921           }
8922 
8923         /* Get the referenced object; if none, consider complete. */
8924         object = lib_disambiguate_object (game, disambiguate, &is_ambiguous);
8925         if (object == -1)
8926           return is_ambiguous;
8927 
8928         /* Verify the referenced object is amenable. */
8929         vt_key[0].string = "Objects";
8930         vt_key[1].integer = object;
8931         vt_key[2].string = "SitLie";
8932         sit_lie_flags = prop_get_integer (bundle, "I<-sis", vt_key);
8933         if (!(sit_lie_flags & movement_mask))
8934           {
8935             pf_buffer_string (filter, cant_do_that);
8936             lib_print_object_np (game, object);
8937             pf_buffer_string (filter, ".\n");
8938             return TRUE;
8939           }
8940         break;
8941       }
8942 
8943     case MOVE_STAND_FLOOR:
8944     case MOVE_SIT_FLOOR:
8945     case MOVE_LIE_FLOOR:
8946       object = -1;
8947       break;
8948 
8949     default:
8950       sc_fatal ("lib_sit_stand_lie: movement error, %ld\n", movement);
8951     }
8952 
8953   /* Set up confirmation messages and position. */
8954   switch (movement)
8955     {
8956     case MOVE_STAND:
8957       already_doing_that = lib_select_response (game,
8958                                             "You are already standing on ",
8959                                             "I am already standing on ",
8960                                             "%player% is already standing on ");
8961       success_message = lib_select_response (game,
8962                                              "You stand on ",
8963                                              "I stand on ",
8964                                              "%player% stands on ");
8965       position = 0;
8966       break;
8967 
8968     case MOVE_STAND_FLOOR:
8969       already_doing_that = lib_select_response (game,
8970                                              "You are already standing!\n",
8971                                              "I am already standing!\n",
8972                                              "%player% is already standing!\n");
8973       success_message = lib_select_response (game,
8974                                              "You stand up",
8975                                              "I stand up",
8976                                              "%player% stands up");
8977       position = 0;
8978       break;
8979 
8980     case MOVE_SIT:
8981       already_doing_that = lib_select_response (game,
8982                                              "You are already sitting on ",
8983                                              "I am already sitting on ",
8984                                              "%player% is already sitting on ");
8985       if (gs_playerposition (game) == 2)
8986         success_message = lib_select_response (game,
8987                                                "You sit up on ",
8988                                                "I sit up on ",
8989                                                "%player% sits up on ");
8990       else
8991         success_message = lib_select_response (game,
8992                                                "You sit down on ",
8993                                                "I sit down on ",
8994                                                "%player% sits down on ");
8995       position = 1;
8996       break;
8997 
8998     case MOVE_SIT_FLOOR:
8999       already_doing_that = lib_select_response (game,
9000                                          "You are already sitting down.\n",
9001                                          "I am already sitting down.\n",
9002                                          "%player% is already sitting down.\n");
9003       if (gs_playerposition (game) == 2)
9004         success_message = lib_select_response (game,
9005                                            "You sit up on the ground.\n",
9006                                            "I sit up on the ground.\n",
9007                                            "%player% sits up on the ground.\n");
9008       else
9009         success_message = lib_select_response (game,
9010                                          "You sit down on the ground.\n",
9011                                          "I sit down on the ground.\n",
9012                                          "%player% sits down on the ground.\n");
9013       position = 1;
9014       break;
9015 
9016     case MOVE_LIE:
9017       already_doing_that = lib_select_response (game,
9018                                                "You are already lying on ",
9019                                                "I am already lying on ",
9020                                                "%player% is already lying on ");
9021       success_message = lib_select_response (game,
9022                                              "You lie down on ",
9023                                              "I lie down on ",
9024                                              "%player% lies down on ");
9025       position = 2;
9026       break;
9027 
9028     case MOVE_LIE_FLOOR:
9029       already_doing_that = lib_select_response (game,
9030                                            "You are already lying down.\n",
9031                                            "I am already lying down.\n",
9032                                            "%player% is already lying down.\n");
9033       success_message = lib_select_response (game,
9034                                          "You lie down on the ground.\n",
9035                                          "I lie down on the ground.\n",
9036                                          "%player% lies down on the ground.\n");
9037       position = 2;
9038       break;
9039 
9040     default:
9041       sc_fatal ("lib_sit_stand_lie: movement error, %ld\n", movement);
9042     }
9043 
9044   /* See if already doing this. */
9045   if (gs_playerposition (game) == position && gs_playerparent (game) == object)
9046     {
9047       pf_buffer_string (filter, already_doing_that);
9048       if (object != -1)
9049         {
9050           lib_print_object_np (game, object);
9051           pf_buffer_string (filter, ".\n");
9052         }
9053       return TRUE;
9054     }
9055 
9056   /* Confirm movement, with special case for getting off an object. */
9057   pf_buffer_string (filter, success_message);
9058   if (movement == MOVE_STAND_FLOOR)
9059     {
9060       if (gs_playerparent (game) != -1)
9061         {
9062           pf_buffer_string (filter, " from ");
9063           lib_print_object_np (game, gs_playerparent (game));
9064         }
9065       pf_buffer_string (filter, ".\n");
9066     }
9067   else if (object != -1)
9068     {
9069       lib_print_object_np (game, object);
9070       pf_buffer_string (filter, ".\n");
9071     }
9072 
9073   /* Adjust player position and parent. */
9074   gs_set_playerposition (game, position);
9075   gs_set_playerparent (game, object);
9076   return TRUE;
9077 }
9078 
9079 
9080 /*
9081  * lib_cmd_stand_*
9082  * lib_cmd_sit_*
9083  * lib_cmd_lie_*
9084  *
9085  * Stand, sit, or lie on an object, or on the floor.
9086  */
9087 sc_bool
lib_cmd_stand_on_object(sc_gameref_t game)9088 lib_cmd_stand_on_object (sc_gameref_t game)
9089 {
9090   return lib_stand_sit_lie (game, MOVE_STAND);
9091 }
9092 
9093 sc_bool
lib_cmd_stand_on_floor(sc_gameref_t game)9094 lib_cmd_stand_on_floor (sc_gameref_t game)
9095 {
9096   return lib_stand_sit_lie (game, MOVE_STAND_FLOOR);
9097 }
9098 
9099 sc_bool
lib_cmd_sit_on_object(sc_gameref_t game)9100 lib_cmd_sit_on_object (sc_gameref_t game)
9101 {
9102   return lib_stand_sit_lie (game, MOVE_SIT);
9103 }
9104 
9105 sc_bool
lib_cmd_sit_on_floor(sc_gameref_t game)9106 lib_cmd_sit_on_floor (sc_gameref_t game)
9107 {
9108   return lib_stand_sit_lie (game, MOVE_SIT_FLOOR);
9109 }
9110 
9111 sc_bool
lib_cmd_lie_on_object(sc_gameref_t game)9112 lib_cmd_lie_on_object (sc_gameref_t game)
9113 {
9114   return lib_stand_sit_lie (game, MOVE_LIE);
9115 }
9116 
9117 sc_bool
lib_cmd_lie_on_floor(sc_gameref_t game)9118 lib_cmd_lie_on_floor (sc_gameref_t game)
9119 {
9120   return lib_stand_sit_lie (game, MOVE_LIE_FLOOR);
9121 }
9122 
9123 
9124 /*
9125  * lib_cmd_get_off_object()
9126  * lib_cmd_get_off()
9127  *
9128  * Get off whatever supporter the player rests on.
9129  */
9130 sc_bool
lib_cmd_get_off_object(sc_gameref_t game)9131 lib_cmd_get_off_object (sc_gameref_t game)
9132 {
9133   const sc_filterref_t filter = gs_get_filter (game);
9134   sc_int object;
9135   sc_bool is_ambiguous;
9136 
9137   /* Get the referenced object; if none, consider complete. */
9138   object = lib_disambiguate_object (game, "get off", &is_ambiguous);
9139   if (object == -1)
9140     return is_ambiguous;
9141 
9142   /* Reject the attempt if the player is not on the given object. */
9143   if (gs_playerparent (game) != object)
9144     {
9145       pf_buffer_string (filter,
9146                         lib_select_response (game,
9147                                              "You are not on ",
9148                                              "I am not on ",
9149                                              "%player% is not on "));
9150       lib_print_object_np (game, object);
9151       pf_buffer_string (filter, "!\n");
9152       return TRUE;
9153     }
9154 
9155   /* Confirm movement. */
9156   pf_buffer_string (filter,
9157                     lib_select_response (game,
9158                                          "You get off ", "I get off ",
9159                                          "%player% gets off "));
9160   lib_print_object_np (game, object);
9161   pf_buffer_string (filter, ".\n");
9162 
9163   /* Adjust player position and parent. */
9164   gs_set_playerposition (game, 0);
9165   gs_set_playerparent (game, -1);
9166   return TRUE;
9167 }
9168 
9169 sc_bool
lib_cmd_get_off(sc_gameref_t game)9170 lib_cmd_get_off (sc_gameref_t game)
9171 {
9172   const sc_filterref_t filter = gs_get_filter (game);
9173 
9174   /* Reject the attempt if the player is not on anything. */
9175   if (gs_playerparent (game) == -1)
9176     {
9177       pf_buffer_string (filter,
9178                         lib_select_response (game,
9179                                              "You are not on anything!\n",
9180                                              "I am not on anything!\n",
9181                                              "%player% is not on anything!\n"));
9182       return TRUE;
9183     }
9184 
9185   /* Confirm movement. */
9186   pf_buffer_string (filter,
9187                     lib_select_response (game,
9188                                          "You get off ", "I get off ",
9189                                          "%player% gets off "));
9190   lib_print_object_np (game, gs_playerparent (game));
9191   pf_buffer_string (filter, ".\n");
9192 
9193   /* Adjust player position and parent. */
9194   gs_set_playerposition (game, 0);
9195   gs_set_playerparent (game, -1);
9196   return TRUE;
9197 }
9198 
9199 
9200 /*
9201  * lib_cmd_save()
9202  * lib_cmd_restore()
9203  *
9204  * Save/restore a game.
9205  */
9206 sc_bool
lib_cmd_save(sc_gameref_t game)9207 lib_cmd_save (sc_gameref_t game)
9208 {
9209   if (if_confirm (SC_CONF_SAVE))
9210     {
9211       if (ser_save_game_prompted (game))
9212         if_print_string ("Ok.\n");
9213       else
9214         if_print_string ("Save failed.\n");
9215     }
9216 
9217   game->is_admin = TRUE;
9218   return TRUE;
9219 }
9220 
9221 sc_bool
lib_cmd_restore(sc_gameref_t game)9222 lib_cmd_restore (sc_gameref_t game)
9223 {
9224   if (if_confirm (SC_CONF_RESTORE))
9225     {
9226       if (ser_load_game_prompted (game))
9227         {
9228           if_print_string ("Ok.\n");
9229           game->is_running = FALSE;
9230           game->do_restore = TRUE;
9231         }
9232       else
9233         if_print_string ("Restore failed.\n");
9234     }
9235 
9236   game->is_admin = TRUE;
9237   return TRUE;
9238 }
9239 
9240 
9241 /*
9242  * lib_cmd_locate_object()
9243  * lib_cmd_locate_npc()
9244  *
9245  * Display the location of a selected object, and selected NPC.
9246  */
9247 sc_bool
lib_cmd_locate_object(sc_gameref_t game)9248 lib_cmd_locate_object (sc_gameref_t game)
9249 {
9250   const sc_filterref_t filter = gs_get_filter (game);
9251   const sc_var_setref_t vars = gs_get_vars (game);
9252   sc_int index_, count, object, room, position, parent;
9253 
9254   game->is_admin = TRUE;
9255 
9256   /*
9257    * Filter to remove unseen object references.  Note that this is different
9258    * from NPCs, who we acknowledge even when unseen.
9259    */
9260   for (index_ = 0; index_ < gs_object_count (game); index_++)
9261     {
9262       if (!gs_object_seen (game, index_))
9263         game->object_references[index_] = FALSE;
9264     }
9265 
9266   /* Count the number of objects referenced by the last command. */
9267   count = 0;
9268   object = -1;
9269   for (index_ = 0; index_ < gs_object_count (game); index_++)
9270     {
9271       if (game->object_references[index_])
9272         {
9273           count++;
9274           object = index_;
9275         }
9276     }
9277 
9278   /*
9279    * If no objects identified, be coy about revealing anything; if more than
9280    * one, be vague.
9281    */
9282   if (count == 0)
9283     {
9284       pf_buffer_string (filter, "I don't know where that is.\n");
9285       return TRUE;
9286     }
9287   else if (count > 1)
9288     {
9289       pf_buffer_string (filter,
9290                         "Please be more clear about what you want to"
9291                         " locate.\n");
9292       return TRUE;
9293     }
9294 
9295   /*
9296    * The reference is unambiguous, so we're responsible for noting it in
9297    * variables.  Disambiguation would normally do this for us, but we just
9298    * bypassed it.
9299    */
9300   var_set_ref_object (vars, object);
9301 
9302   /* See if we can print a message based on position and parent. */
9303   position = gs_object_position (game, object);
9304   parent = gs_object_parent (game, object);
9305   switch (position)
9306     {
9307     case OBJ_HIDDEN:
9308       if (!obj_is_static (game, object))
9309         {
9310           pf_buffer_string (filter, "I don't know where that is.\n");
9311           return TRUE;
9312         }
9313       break;
9314 
9315     case OBJ_HELD_PLAYER:
9316       pf_new_sentence (filter);
9317       pf_buffer_string (filter,
9318                         lib_select_response (game,
9319                                              "You are carrying ",
9320                                              "I am carrying ",
9321                                              "%player% is carrying "));
9322       lib_print_object_np (game, object);
9323       pf_buffer_string (filter, "!\n");
9324       return TRUE;
9325 
9326     case OBJ_WORN_PLAYER:
9327       pf_new_sentence (filter);
9328       pf_buffer_string (filter,
9329                         lib_select_response (game,
9330                                              "You are wearing ",
9331                                              "I am wearing ",
9332                                              "%player% is wearing "));
9333       lib_print_object_np (game, object);
9334       pf_buffer_string (filter, "!\n");
9335       return TRUE;
9336 
9337     case OBJ_HELD_NPC:
9338     case OBJ_WORN_NPC:
9339       if (gs_npc_seen (game, parent))
9340         {
9341           pf_new_sentence (filter);
9342           lib_print_npc_np (game, parent);
9343           pf_buffer_string (filter,
9344                             (position == OBJ_HELD_NPC)
9345                               ? " is holding " : " is wearing ");
9346           lib_print_object_np (game, object);
9347           pf_buffer_string (filter, ".\n");
9348         }
9349       else
9350         pf_buffer_string (filter, "I don't know where that is.\n");
9351       return TRUE;
9352 
9353     case OBJ_PART_NPC:
9354       if (parent == -1)
9355         {
9356           pf_new_sentence (filter);
9357           lib_print_object_np (game, object);
9358           pf_buffer_string (filter,
9359                             lib_select_plurality (game, object, " is", " are"));
9360           pf_buffer_string (filter,
9361                             lib_select_response (game,
9362                                                  " a part of you!\n",
9363                                                  " a part of me!\n",
9364                                                  " a part of %player%!\n"));
9365         }
9366       else
9367         {
9368           if (gs_npc_seen (game, parent))
9369             {
9370               pf_new_sentence (filter);
9371               lib_print_object_np (game, object);
9372               pf_buffer_string (filter,
9373                                 lib_select_plurality (game, object,
9374                                                       " is", " are"));
9375               pf_buffer_string (filter, " a part of ");
9376               lib_print_npc_np (game, parent);
9377               pf_buffer_string (filter, ".\n");
9378             }
9379           else
9380             pf_buffer_string (filter, "I don't know where that is.\n");
9381         }
9382       return TRUE;
9383 
9384     case OBJ_ON_OBJECT:
9385     case OBJ_IN_OBJECT:
9386       if (gs_object_seen (game, parent))
9387         {
9388           pf_new_sentence (filter);
9389           lib_print_object_np (game, object);
9390           pf_buffer_string (filter,
9391                             lib_select_plurality (game, object, " is", " are"));
9392           pf_buffer_string (filter,
9393                             (position == OBJ_ON_OBJECT) ? " on " : " inside ");
9394           lib_print_object_np (game, parent);
9395           pf_buffer_string (filter, ".\n");
9396         }
9397       else
9398         pf_buffer_string (filter, "I don't know where that is.\n");
9399       return TRUE;
9400     }
9401 
9402   /*
9403    * Object is either static unmoved, or dynamic and on the floor of a room.
9404    * Check each room for the object, stopping on first found.
9405    */
9406   for (room = 0; room < gs_room_count (game); room++)
9407     {
9408       if (obj_indirectly_in_room (game, object, room))
9409         break;
9410     }
9411   if (room == gs_room_count (game))
9412     {
9413       pf_buffer_string (filter, "I don't know where that is.\n");
9414       return TRUE;
9415     }
9416 
9417   /* Check that this room's been visited by the player. */
9418   if (!gs_room_seen (game, room))
9419     {
9420       pf_new_sentence (filter);
9421       lib_print_object_np (game, object);
9422       pf_buffer_string (filter,
9423                         lib_select_plurality (game, object, " is", " are"));
9424       pf_buffer_string (filter,
9425                         lib_select_response (game,
9426                              " somewhere that you haven't been yet.\n",
9427                              " somewhere that I haven't been yet.\n",
9428                              " somewhere that %player% hasn't been yet.\n"));
9429       return TRUE;
9430     }
9431 
9432   /* Print the details of the object's room. */
9433   pf_new_sentence (filter);
9434   lib_print_object_np (game, object);
9435   pf_buffer_string (filter, " -- ");
9436   pf_buffer_string (filter, lib_get_room_name (game, room));
9437   pf_buffer_string (filter, ".\n");
9438   return TRUE;
9439 }
9440 
9441 sc_bool
lib_cmd_locate_npc(sc_gameref_t game)9442 lib_cmd_locate_npc (sc_gameref_t game)
9443 {
9444   const sc_filterref_t filter = gs_get_filter (game);
9445   const sc_var_setref_t vars = gs_get_vars (game);
9446   sc_int index_, count, npc, room;
9447 
9448   game->is_admin = TRUE;
9449 
9450   /* Count the number of NPCs referenced by the last command. */
9451   count = 0;
9452   npc = -1;
9453   for (index_ = 0; index_ < gs_npc_count (game); index_++)
9454     {
9455       if (game->npc_references[index_])
9456         {
9457           count++;
9458           npc = index_;
9459         }
9460     }
9461 
9462   /*
9463    * If no NPCs identified, be coy about revealing anything; if more than one,
9464    * be vague.  The "... where that is..." is the correct message even for
9465    * NPCs -- it's the same response as for lib_locate_other().
9466    */
9467   if (count == 0)
9468     {
9469       pf_buffer_string (filter, "I don't know where that is.\n");
9470       return TRUE;
9471     }
9472   else if (count > 1)
9473     {
9474       pf_buffer_string (filter,
9475                         "Please be more clear about who you want to locate.\n");
9476       return TRUE;
9477     }
9478 
9479   /*
9480    * The reference is unambiguous, so we're responsible for noting it in
9481    * variables.  Disambiguation would normally do this for us, but we just
9482    * bypassed it.
9483    */
9484   var_set_ref_character (vars, npc);
9485 
9486   /* See if this NPC has been seen yet. */
9487   if (!gs_npc_seen (game, npc))
9488     {
9489       pf_buffer_string (filter,
9490                         lib_select_response (game,
9491                                              "You haven't seen ",
9492                                              "I haven't seen ",
9493                                              "%player% hasn't seen "));
9494       lib_print_npc_np (game, npc);
9495       pf_buffer_string (filter, " yet!\n");
9496       return TRUE;
9497     }
9498 
9499   /* Check each room for the NPC, stopping on first found. */
9500   for (room = 0; room < gs_room_count (game); room++)
9501     {
9502       if (npc_in_room (game, npc, room))
9503         break;
9504     }
9505   if (room == gs_room_count (game))
9506     {
9507       pf_buffer_string (filter, "I don't know where ");
9508       lib_print_npc_np (game, npc);
9509       pf_buffer_string (filter, " is.\n");
9510       return TRUE;
9511     }
9512 
9513   /* Check that this room's been visited by the player. */
9514   if (!gs_room_seen (game, room))
9515     {
9516       lib_print_npc_np (game, npc);
9517       pf_buffer_string (filter,
9518                         lib_select_response (game,
9519                              " is somewhere that you haven't been yet.\n",
9520                              " is somewhere that I haven't been yet.\n",
9521                              " is somewhere that %player% hasn't been yet.\n"));
9522       return TRUE;
9523     }
9524 
9525   /* Print the location, and smart-alec response. */
9526   pf_new_sentence (filter);
9527   lib_print_npc_np (game, npc);
9528   pf_buffer_string (filter, " -- ");
9529   pf_buffer_string (filter, lib_get_room_name (game, room));
9530 #if 0
9531   if (room == gs_playerroom (game))
9532     {
9533       pf_buffer_string (filter,
9534                         lib_select_response (game,
9535                                          "  (Right next to you, silly!)",
9536                                          "  (Right next to me, silly!)",
9537                                          "  (Right next to %player%, silly!)"));
9538     }
9539 #endif
9540   pf_buffer_string (filter, ".\n");
9541   return TRUE;
9542 }
9543 
9544 
9545 /*
9546  * lib_cmd_turns()
9547  * lib_cmd_score()
9548  *
9549  * Display turns taken and score so far.
9550  */
9551 sc_bool
lib_cmd_turns(sc_gameref_t game)9552 lib_cmd_turns (sc_gameref_t game)
9553 {
9554   const sc_filterref_t filter = gs_get_filter (game);
9555   sc_char buffer[32];
9556 
9557   pf_buffer_string (filter, "You have taken ");
9558   sprintf (buffer, "%ld", game->turns);
9559   pf_buffer_string (filter, buffer);
9560   if (game->turns == 1)
9561     pf_buffer_string (filter, " turn so far.\n");
9562   else
9563     pf_buffer_string (filter, " turns so far.\n");
9564 
9565   game->is_admin = TRUE;
9566   return TRUE;
9567 }
9568 
9569 sc_bool
lib_cmd_score(sc_gameref_t game)9570 lib_cmd_score (sc_gameref_t game)
9571 {
9572   const sc_filterref_t filter = gs_get_filter (game);
9573   const sc_prop_setref_t bundle = gs_get_bundle (game);
9574   sc_vartype_t vt_key[2];
9575   sc_int max_score, percent;
9576   sc_char buffer[32];
9577 
9578   /* Get max score, and calculate score as a percentage. */
9579   vt_key[0].string = "Globals";
9580   vt_key[1].string = "MaxScore";
9581   max_score = prop_get_integer (bundle, "I<-ss", vt_key);
9582   if (game->score > 0 && max_score > 0)
9583     percent = (game->score * 100) / max_score;
9584   else
9585     percent = 0;
9586 
9587   /* Output carefully formatted response. */
9588   pf_buffer_string (filter,
9589                     lib_select_response (game,
9590                                          "Your score is ",
9591                                          "My score is ",
9592                                          "%player%'s score is "));
9593   sprintf (buffer, "%ld", game->score);
9594   pf_buffer_string (filter, buffer);
9595   pf_buffer_string (filter, " out of a maximum of ");
9596   sprintf (buffer, "%ld", max_score);
9597   pf_buffer_string (filter, buffer);
9598   pf_buffer_string (filter, ".  (");
9599   sprintf (buffer, "%ld", percent);
9600   pf_buffer_string (filter, buffer);
9601   pf_buffer_string (filter, "%)\n");
9602 
9603   game->is_admin = TRUE;
9604   return TRUE;
9605 }
9606 
9607 
9608 /*
9609  * lib_cmd_*()
9610  *
9611  * Standard response commands.  These are uninteresting catch-all cases,
9612  * but it's good to make then right as game ALRs may look for them.
9613  */
9614 sc_bool
lib_cmd_profanity(sc_gameref_t game)9615 lib_cmd_profanity (sc_gameref_t game)
9616 {
9617   const sc_filterref_t filter = gs_get_filter (game);
9618 
9619   pf_buffer_string (filter,
9620                     "I really don't think there's any need for language like"
9621                     " that!\n");
9622   return TRUE;
9623 }
9624 
9625 sc_bool
lib_cmd_examine_all(sc_gameref_t game)9626 lib_cmd_examine_all (sc_gameref_t game)
9627 {
9628   const sc_filterref_t filter = gs_get_filter (game);
9629 
9630   pf_buffer_string (filter, "Please examine one object at a time.\n");
9631   return TRUE;
9632 }
9633 
9634 sc_bool
lib_cmd_examine_other(sc_gameref_t game)9635 lib_cmd_examine_other (sc_gameref_t game)
9636 {
9637   const sc_filterref_t filter = gs_get_filter (game);
9638 
9639   pf_buffer_string (filter,
9640                     lib_select_response (game,
9641                                          "You see no such thing.\n",
9642                                          "I see no such thing.\n",
9643                                          "%player% sees no such thing.\n"));
9644   return TRUE;
9645 }
9646 
9647 sc_bool
lib_cmd_locate_other(sc_gameref_t game)9648 lib_cmd_locate_other (sc_gameref_t game)
9649 {
9650   const sc_filterref_t filter = gs_get_filter (game);
9651 
9652   pf_buffer_string (filter, "I don't know where that is!\n");
9653   game->is_admin = TRUE;
9654   return TRUE;
9655 }
9656 
9657 sc_bool
lib_cmd_unix_like(sc_gameref_t game)9658 lib_cmd_unix_like (sc_gameref_t game)
9659 {
9660   const sc_filterref_t filter = gs_get_filter (game);
9661 
9662   pf_buffer_string (filter, "This isn't Unix you know!\n");
9663   return TRUE;
9664 }
9665 
9666 sc_bool
lib_cmd_dos_like(sc_gameref_t game)9667 lib_cmd_dos_like (sc_gameref_t game)
9668 {
9669   const sc_filterref_t filter = gs_get_filter (game);
9670 
9671   pf_buffer_string (filter, "This isn't Dos you know!\n");
9672   return TRUE;
9673 }
9674 
9675 sc_bool
lib_cmd_cry(sc_gameref_t game)9676 lib_cmd_cry (sc_gameref_t game)
9677 {
9678   const sc_filterref_t filter = gs_get_filter (game);
9679 
9680   pf_buffer_string (filter, "There's no need for that!\n");
9681   return TRUE;
9682 }
9683 
9684 sc_bool
lib_cmd_dance(sc_gameref_t game)9685 lib_cmd_dance (sc_gameref_t game)
9686 {
9687   const sc_filterref_t filter = gs_get_filter (game);
9688 
9689   pf_buffer_string (filter,
9690                     lib_select_response (game,
9691                                          "You do a little dance.\n",
9692                                          "I do a little dance.\n",
9693                                          "%player% does a little dance.\n"));
9694   return TRUE;
9695 }
9696 
9697 sc_bool
lib_cmd_eat_other(sc_gameref_t game)9698 lib_cmd_eat_other (sc_gameref_t game)
9699 {
9700   const sc_filterref_t filter = gs_get_filter (game);
9701 
9702   pf_buffer_string (filter, "I don't understand what you are trying to eat.\n");
9703   return TRUE;
9704 }
9705 
9706 sc_bool
lib_cmd_fight(sc_gameref_t game)9707 lib_cmd_fight (sc_gameref_t game)
9708 {
9709   const sc_filterref_t filter = gs_get_filter (game);
9710 
9711   pf_buffer_string (filter, "There is nothing worth fighting here.\n");
9712   return TRUE;
9713 }
9714 
9715 sc_bool
lib_cmd_feed(sc_gameref_t game)9716 lib_cmd_feed (sc_gameref_t game)
9717 {
9718   const sc_filterref_t filter = gs_get_filter (game);
9719 
9720   pf_buffer_string (filter, "There is nothing worth feeding here.\n");
9721   return TRUE;
9722 }
9723 
9724 sc_bool
lib_cmd_feel(sc_gameref_t game)9725 lib_cmd_feel (sc_gameref_t game)
9726 {
9727   const sc_filterref_t filter = gs_get_filter (game);
9728 
9729   pf_buffer_string (filter,
9730                     lib_select_response (game,
9731                               "You feel nothing out of the ordinary.\n",
9732                               "I feel nothing out of the ordinary.\n",
9733                               "%player% feels nothing out of the ordinary.\n"));
9734   return TRUE;
9735 }
9736 
9737 sc_bool
lib_cmd_fly(sc_gameref_t game)9738 lib_cmd_fly (sc_gameref_t game)
9739 {
9740   const sc_filterref_t filter = gs_get_filter (game);
9741 
9742   pf_buffer_string (filter,
9743                     lib_select_response (game,
9744                                          "You can't fly.\n",
9745                                          "I can't fly.\n",
9746                                          "%player% can't fly.\n"));
9747   return TRUE;
9748 }
9749 
9750 sc_bool
lib_cmd_hint(sc_gameref_t game)9751 lib_cmd_hint (sc_gameref_t game)
9752 {
9753   const sc_filterref_t filter = gs_get_filter (game);
9754 
9755   pf_buffer_string (filter,
9756                     "You're just going to have to work it out for"
9757                     " yourself...\n");
9758   return TRUE;
9759 }
9760 
9761 sc_bool
lib_cmd_hum(sc_gameref_t game)9762 lib_cmd_hum (sc_gameref_t game)
9763 {
9764   const sc_filterref_t filter = gs_get_filter (game);
9765 
9766   pf_buffer_string (filter,
9767                     lib_select_response (game,
9768                                          "You hum a little tune.\n",
9769                                          "I hum a little tune.\n",
9770                                          "%player% hums a little tune.\n"));
9771   return TRUE;
9772 }
9773 
9774 sc_bool
lib_cmd_jump(sc_gameref_t game)9775 lib_cmd_jump (sc_gameref_t game)
9776 {
9777   const sc_filterref_t filter = gs_get_filter (game);
9778 
9779   pf_buffer_string (filter, "Wheee-boinng.\n");
9780   return TRUE;
9781 }
9782 
9783 sc_bool
lib_cmd_listen(sc_gameref_t game)9784 lib_cmd_listen (sc_gameref_t game)
9785 {
9786   const sc_filterref_t filter = gs_get_filter (game);
9787 
9788   pf_buffer_string (filter,
9789                     lib_select_response (game,
9790                               "You hear nothing out of the ordinary.\n",
9791                               "I hear nothing out of the ordinary.\n",
9792                               "%player% hears nothing out of the ordinary.\n"));
9793   return TRUE;
9794 }
9795 
9796 sc_bool
lib_cmd_please(sc_gameref_t game)9797 lib_cmd_please (sc_gameref_t game)
9798 {
9799   const sc_filterref_t filter = gs_get_filter (game);
9800 
9801   pf_buffer_string (filter,
9802                     lib_select_response (game,
9803                                         "Your kindness gets you nowhere.\n",
9804                                         "My kindness gets me nowhere.\n",
9805                                         "%player%'s kindness gets nowhere.\n"));
9806   return TRUE;
9807 }
9808 
9809 sc_bool
lib_cmd_punch(sc_gameref_t game)9810 lib_cmd_punch (sc_gameref_t game)
9811 {
9812   const sc_filterref_t filter = gs_get_filter (game);
9813 
9814   pf_buffer_string (filter, "Who do you think you are, Mike Tyson?\n");
9815   return TRUE;
9816 }
9817 
9818 sc_bool
lib_cmd_run(sc_gameref_t game)9819 lib_cmd_run (sc_gameref_t game)
9820 {
9821   const sc_filterref_t filter = gs_get_filter (game);
9822 
9823   pf_buffer_string (filter,
9824                     lib_select_response (game,
9825                                          "Why would you want to run?\n",
9826                                          "Why would I want to run?\n",
9827                                          "Why would %player% want to run?\n"));
9828   return TRUE;
9829 }
9830 
9831 sc_bool
lib_cmd_shout(sc_gameref_t game)9832 lib_cmd_shout (sc_gameref_t game)
9833 {
9834   const sc_filterref_t filter = gs_get_filter (game);
9835 
9836   pf_buffer_string (filter, "Aaarrrrgggghhhhhh!\n");
9837   return TRUE;
9838 }
9839 
9840 sc_bool
lib_cmd_say(sc_gameref_t game)9841 lib_cmd_say (sc_gameref_t game)
9842 {
9843   const sc_filterref_t filter = gs_get_filter (game);
9844   const sc_char *string = NULL;
9845 
9846   switch (sc_randomint (1, 5))
9847     {
9848     case 1:
9849       string = "Gosh, that was very impressive.\n";
9850       break;
9851     case 2:
9852       string = lib_select_response (game,
9853                                     "Not surprisingly, no-one takes any notice"
9854                                     " of you.\n",
9855                                     "Not surprisingly, no-one takes any notice"
9856                                     " of me.\n",
9857                                     "Not surprisingly, no-one takes any notice"
9858                                     " of %player%.\n");
9859       break;
9860     case 3:
9861       string = "Wow!  That achieved a lot.\n";
9862       break;
9863     case 4:
9864       string = "Uh huh, yes, very interesting.\n";
9865       break;
9866     default:
9867       string = "That's the most interesting thing I've ever heard!\n";
9868       break;
9869     }
9870 
9871   pf_buffer_string (filter, string);
9872   return TRUE;
9873 }
9874 
9875 sc_bool
lib_cmd_sing(sc_gameref_t game)9876 lib_cmd_sing (sc_gameref_t game)
9877 {
9878   const sc_filterref_t filter = gs_get_filter (game);
9879 
9880   pf_buffer_string (filter,
9881                     lib_select_response (game,
9882                                          "You sing a little song.\n",
9883                                          "I sing a little song.\n",
9884                                          "%player% sings a little song.\n"));
9885   return TRUE;
9886 }
9887 
9888 sc_bool
lib_cmd_sleep(sc_gameref_t game)9889 lib_cmd_sleep (sc_gameref_t game)
9890 {
9891   const sc_filterref_t filter = gs_get_filter (game);
9892 
9893   pf_buffer_string (filter, "Zzzzz.  Bored are you?\n");
9894   return TRUE;
9895 }
9896 
9897 sc_bool
lib_cmd_talk(sc_gameref_t game)9898 lib_cmd_talk (sc_gameref_t game)
9899 {
9900   const sc_filterref_t filter = gs_get_filter (game);
9901 
9902   pf_buffer_string (filter,
9903                     lib_select_response (game,
9904                                   "No-one listens to your rabblings.\n",
9905                                   "No-one listens to my rabblings.\n",
9906                                   "No-one listens to %player%'s rabblings.\n"));
9907   return TRUE;
9908 }
9909 
9910 sc_bool
lib_cmd_thank(sc_gameref_t game)9911 lib_cmd_thank (sc_gameref_t game)
9912 {
9913   const sc_filterref_t filter = gs_get_filter (game);
9914 
9915   pf_buffer_string (filter, "You're welcome.\n");
9916   return TRUE;
9917 }
9918 
9919 sc_bool
lib_cmd_whistle(sc_gameref_t game)9920 lib_cmd_whistle (sc_gameref_t game)
9921 {
9922   const sc_filterref_t filter = gs_get_filter (game);
9923 
9924   pf_buffer_string (filter,
9925                     lib_select_response (game,
9926                                          "You whistle a little tune.\n",
9927                                          "I whistle a little tune.\n",
9928                                          "%player% whistles a little tune.\n"));
9929   return TRUE;
9930 }
9931 
9932 sc_bool
lib_cmd_interrogation(sc_gameref_t game)9933 lib_cmd_interrogation (sc_gameref_t game)
9934 {
9935   const sc_filterref_t filter = gs_get_filter (game);
9936   const sc_char *string = NULL;
9937 
9938   switch (sc_randomint (1, 17))
9939     {
9940     case 1:
9941       string = "Why do you want to know?\n";
9942       break;
9943     case 2:
9944       string = "Interesting question.\n";
9945       break;
9946     case 3:
9947       string = "Let me think about that one...\n";
9948       break;
9949     case 4:
9950       string = "I haven't a clue!\n";
9951       break;
9952     case 5:
9953       string = "All these questions are hurting my head.\n";
9954       break;
9955     case 6:
9956       string = "I'm not going to tell you.\n";
9957       break;
9958     case 7:
9959       string = "Someday I'll know the answer to that one.\n";
9960       break;
9961     case 8:
9962       string = "I could tell you, but then I'd have to kill you.\n";
9963       break;
9964     case 9:
9965       string = "Ha, as if I'd tell you!\n";
9966       break;
9967     case 10:
9968       string = "Ask me again later.\n";
9969       break;
9970     case 11:
9971       string = "I don't know - could you ask anyone else?\n";
9972       break;
9973     case 12:
9974       string = "Err, yes?!?\n";
9975       break;
9976     case 13:
9977       string = "Let me just check my memory banks...\n";
9978       break;
9979     case 14:
9980       string = "Because that's just the way it is.\n";
9981       break;
9982     case 15:
9983       string = "Do I ask you all sorts of awkward questions?\n";
9984       break;
9985     case 16:
9986       string = "Questions, questions...\n";
9987       break;
9988     default:
9989       string = "Who cares.\n";
9990       break;
9991     }
9992 
9993   pf_buffer_string (filter, string);
9994   return TRUE;
9995 }
9996 
9997 sc_bool
lib_cmd_xyzzy(sc_gameref_t game)9998 lib_cmd_xyzzy (sc_gameref_t game)
9999 {
10000   const sc_filterref_t filter = gs_get_filter (game);
10001 
10002   pf_buffer_string (filter,
10003                     "I'm sorry, but XYZZY doesn't do anything special in"
10004                     " this game!\n");
10005   return TRUE;
10006 }
10007 
10008 sc_bool
lib_cmd_egotistic(sc_gameref_t game)10009 lib_cmd_egotistic (sc_gameref_t game)
10010 {
10011   const sc_filterref_t filter = gs_get_filter (game);
10012 
10013 #if 0
10014   pf_buffer_string (filter,
10015                     "Campbell wrote this Adrift Runner.  It's pretty"
10016                     " good huh!\n");
10017 #else
10018   pf_buffer_string (filter, "No comment.\n");
10019 #endif
10020 
10021   return TRUE;
10022 }
10023 
10024 sc_bool
lib_cmd_yes_or_no(sc_gameref_t game)10025 lib_cmd_yes_or_no (sc_gameref_t game)
10026 {
10027   const sc_filterref_t filter = gs_get_filter (game);
10028 
10029   pf_buffer_string (filter,
10030                     "That's interesting, but it doesn't mean much.\n");
10031   return TRUE;
10032 }
10033 
10034 
10035 /*
10036  * lib_cmd_ask_npc()
10037  * lib_cmd_ask_object()
10038  * lib_cmd_ask_other()
10039  *
10040  * Malformed and rhetorical question responses.
10041  */
10042 sc_bool
lib_cmd_ask_npc(sc_gameref_t game)10043 lib_cmd_ask_npc (sc_gameref_t game)
10044 {
10045   const sc_filterref_t filter = gs_get_filter (game);
10046   sc_int npc;
10047   sc_bool is_ambiguous;
10048 
10049   /* Get the referenced npc, and if none, consider complete. */
10050   npc = lib_disambiguate_npc (game, "ask", &is_ambiguous);
10051   if (npc == -1)
10052     return is_ambiguous;
10053 
10054   /* Incomplete ask command, so offer help and return. */
10055   pf_buffer_string (filter, "Use the format \"ask ");
10056   lib_print_npc_np (game, npc);
10057   pf_buffer_string (filter, " about [subject]\".\n");
10058   return TRUE;
10059 }
10060 
10061 sc_bool
lib_cmd_ask_object(sc_gameref_t game)10062 lib_cmd_ask_object (sc_gameref_t game)
10063 {
10064   const sc_filterref_t filter = gs_get_filter (game);
10065   sc_int object;
10066   sc_bool is_ambiguous;
10067 
10068   /* Get the referenced object, and if none, consider complete. */
10069   object = lib_disambiguate_object (game, "ask", &is_ambiguous);
10070   if (object == -1)
10071     return is_ambiguous;
10072 
10073   /* No reply. */
10074   pf_buffer_string (filter,
10075                     lib_select_response (game,
10076                                          "You get no reply from ",
10077                                          "I get no reply from ",
10078                                          "%player% gets no reply from "));
10079   lib_print_object_np (game, object);
10080   pf_buffer_string (filter, ".\n");
10081   return TRUE;
10082 }
10083 
10084 sc_bool
lib_cmd_ask_other(sc_gameref_t game)10085 lib_cmd_ask_other (sc_gameref_t game)
10086 {
10087   const sc_filterref_t filter = gs_get_filter (game);
10088 
10089   /* Incomplete ask command, so offer help and return. */
10090   pf_buffer_string (filter,
10091                     "Use the format \"ask [character] about [subject]\".\n");
10092   return TRUE;
10093 }
10094 
10095 
10096 /*
10097  * lib_cmd_kill_other()
10098  *
10099  * Uninteresting kill message when no weaponry is involved.
10100  */
10101 sc_bool
lib_cmd_kill_other(sc_gameref_t game)10102 lib_cmd_kill_other (sc_gameref_t game)
10103 {
10104   const sc_filterref_t filter = gs_get_filter (game);
10105 
10106   pf_buffer_string (filter, "Now that isn't very nice.\n");
10107   return TRUE;
10108 }
10109 
10110 
10111 /*
10112  * lib_nothing_happens_common()
10113  * lib_nothing_happens_object()
10114  * lib_nothing_happens_other()
10115  *
10116  * Central handler for a range of nothing-happens messages.  More
10117  * uninteresting responses.
10118  */
10119 static sc_bool
lib_nothing_happens_common(sc_gameref_t game,const sc_char * verb_general,const sc_char * verb_third_person,sc_bool is_object)10120 lib_nothing_happens_common (sc_gameref_t game,
10121                             const sc_char *verb_general,
10122                             const sc_char *verb_third_person,
10123                             sc_bool is_object)
10124 {
10125   const sc_filterref_t filter = gs_get_filter (game);
10126   const sc_prop_setref_t bundle = gs_get_bundle (game);
10127   sc_vartype_t vt_key[2];
10128   sc_int perspective, object;
10129   const sc_char *person, *verb;
10130   sc_bool is_ambiguous;
10131 
10132   /* Use person and verb tense according to perspective. */
10133   vt_key[0].string = "Globals";
10134   vt_key[1].string = "Perspective";
10135   perspective = prop_get_integer (bundle, "I<-ss", vt_key);
10136   switch (perspective)
10137     {
10138     case LIB_FIRST_PERSON:
10139       person = "I ";
10140       verb = verb_general;
10141       break;
10142     case LIB_SECOND_PERSON:
10143       person = "You ";
10144       verb = verb_general;
10145       break;
10146     case LIB_THIRD_PERSON:
10147       person = "%player% ";
10148       verb = verb_third_person;
10149       break;
10150     default:
10151       sc_error ("lib_nothing_happens: unknown perspective, %ld\n", perspective);
10152       person = "You ";
10153       verb = verb_general;
10154       break;
10155     }
10156 
10157   /* If the command target was not an object, end it here. */
10158   if (!is_object)
10159     {
10160       pf_buffer_string (filter, person);
10161       pf_buffer_string (filter, verb);
10162       pf_buffer_string (filter, ", but nothing happens.\n");
10163       return TRUE;
10164     }
10165 
10166   /* Get the referenced object.  If none, return immediately. */
10167   object = lib_disambiguate_object (game, verb_general, &is_ambiguous);
10168   if (object == -1)
10169     return is_ambiguous;
10170 
10171   /* Nothing happens. */
10172   pf_buffer_string (filter, person);
10173   pf_buffer_string (filter, verb);
10174   pf_buffer_character (filter, ' ');
10175   lib_print_object_np (game, object);
10176   pf_buffer_string (filter, ", but nothing happens.\n");
10177   return TRUE;
10178 }
10179 
10180 static sc_bool
lib_nothing_happens_object(sc_gameref_t game,const sc_char * verb_general,const sc_char * verb_third_person)10181 lib_nothing_happens_object (sc_gameref_t game,
10182                             const sc_char *verb_general,
10183                             const sc_char *verb_third_person)
10184 {
10185   return lib_nothing_happens_common (game,
10186                                      verb_general, verb_third_person, TRUE);
10187 }
10188 
10189 static sc_bool
lib_nothing_happens_other(sc_gameref_t game,const sc_char * verb_general,const sc_char * verb_third_person)10190 lib_nothing_happens_other (sc_gameref_t game,
10191                            const sc_char *verb_general,
10192                            const sc_char *verb_third_person)
10193 {
10194   return lib_nothing_happens_common (game,
10195                                      verb_general, verb_third_person, FALSE);
10196 }
10197 
10198 
10199 /*
10200  * lib_cmd_*()
10201  *
10202  * Shake, rattle and roll, and assorted nothing-happens handlers.
10203  */
10204 sc_bool
lib_cmd_hit_object(sc_gameref_t game)10205 lib_cmd_hit_object (sc_gameref_t game)
10206 {
10207   return lib_nothing_happens_object (game, "hit", "hits");
10208 }
10209 
10210 sc_bool
lib_cmd_kick_object(sc_gameref_t game)10211 lib_cmd_kick_object (sc_gameref_t game)
10212 {
10213   return lib_nothing_happens_object (game, "kick", "kicks");
10214 }
10215 
10216 sc_bool
lib_cmd_press_object(sc_gameref_t game)10217 lib_cmd_press_object (sc_gameref_t game)
10218 {
10219   return lib_nothing_happens_object (game, "press", "presses");
10220 }
10221 
10222 sc_bool
lib_cmd_push_object(sc_gameref_t game)10223 lib_cmd_push_object (sc_gameref_t game)
10224 {
10225   return lib_nothing_happens_object (game, "push", "pushes");
10226 }
10227 
10228 sc_bool
lib_cmd_pull_object(sc_gameref_t game)10229 lib_cmd_pull_object (sc_gameref_t game)
10230 {
10231   return lib_nothing_happens_object (game, "pull", "pulls");
10232 }
10233 
10234 sc_bool
lib_cmd_shake_object(sc_gameref_t game)10235 lib_cmd_shake_object (sc_gameref_t game)
10236 {
10237   return lib_nothing_happens_object (game, "shake", "shakes");
10238 }
10239 
10240 sc_bool
lib_cmd_hit_other(sc_gameref_t game)10241 lib_cmd_hit_other (sc_gameref_t game)
10242 {
10243   return lib_nothing_happens_other (game, "hit", "hits");
10244 }
10245 
10246 sc_bool
lib_cmd_kick_other(sc_gameref_t game)10247 lib_cmd_kick_other (sc_gameref_t game)
10248 {
10249   return lib_nothing_happens_other (game, "kick", "kicks");
10250 }
10251 
10252 sc_bool
lib_cmd_press_other(sc_gameref_t game)10253 lib_cmd_press_other (sc_gameref_t game)
10254 {
10255   return lib_nothing_happens_other (game, "press", "presses");
10256 }
10257 
10258 sc_bool
lib_cmd_push_other(sc_gameref_t game)10259 lib_cmd_push_other (sc_gameref_t game)
10260 {
10261   return lib_nothing_happens_other (game, "push", "pushes");
10262 }
10263 
10264 sc_bool
lib_cmd_pull_other(sc_gameref_t game)10265 lib_cmd_pull_other (sc_gameref_t game)
10266 {
10267   return lib_nothing_happens_other (game, "pull", "pulls");
10268 }
10269 
10270 sc_bool
lib_cmd_shake_other(sc_gameref_t game)10271 lib_cmd_shake_other (sc_gameref_t game)
10272 {
10273   return lib_nothing_happens_other (game, "shake", "shakes");
10274 }
10275 
10276 
10277 /*
10278  * lib_cant_do_common()
10279  * lib_cant_do_object()
10280  * lib_cant_do_other()
10281  *
10282  * Central handler for a range of can't-do messages.  Yet more uninterest-
10283  * ing responses.
10284  */
10285 static sc_bool
lib_cant_do_common(sc_gameref_t game,const sc_char * verb,sc_bool is_object)10286 lib_cant_do_common (sc_gameref_t game,
10287                     const sc_char *verb, sc_bool is_object)
10288 {
10289   const sc_filterref_t filter = gs_get_filter (game);
10290   sc_int object;
10291   sc_bool is_ambiguous;
10292 
10293   /* If the target is not an object, end it here. */
10294   if (!is_object)
10295     {
10296       pf_buffer_string (filter,
10297                         lib_select_response (game,
10298                                              "You can't ",
10299                                              "I can't ", "%player% can't "));
10300       pf_buffer_string (filter, verb);
10301       pf_buffer_string (filter, " that.\n");
10302       return TRUE;
10303     }
10304 
10305   /* Get the referenced object.  If none, return immediately. */
10306   object = lib_disambiguate_object (game, verb, &is_ambiguous);
10307   if (object == -1)
10308     return is_ambiguous;
10309 
10310   /* Whatever it is, don't do it. */
10311   pf_buffer_string (filter,
10312                     lib_select_response (game,
10313                                          "You can't ",
10314                                          "I can't ", "%player% can't "));
10315   pf_buffer_string (filter, verb);
10316   pf_buffer_character (filter, ' ');
10317   lib_print_object_np (game, object);
10318   pf_buffer_string (filter, ".\n");
10319   return TRUE;
10320 }
10321 
10322 static sc_bool
lib_cant_do_object(sc_gameref_t game,const sc_char * verb)10323 lib_cant_do_object (sc_gameref_t game, const sc_char *verb)
10324 {
10325   return lib_cant_do_common (game, verb, TRUE);
10326 }
10327 
10328 static sc_bool
lib_cant_do_other(sc_gameref_t game,const sc_char * verb)10329 lib_cant_do_other (sc_gameref_t game, const sc_char *verb)
10330 {
10331   return lib_cant_do_common (game, verb, FALSE);
10332 }
10333 
10334 
10335 /*
10336  * lib_cmd_*()
10337  *
10338  * Assorted can't-do messages.
10339  */
10340 sc_bool
lib_cmd_block_object(sc_gameref_t game)10341 lib_cmd_block_object (sc_gameref_t game)
10342 {
10343   return lib_cant_do_object (game, "block");
10344 }
10345 
10346 sc_bool
lib_cmd_climb_object(sc_gameref_t game)10347 lib_cmd_climb_object (sc_gameref_t game)
10348 {
10349   return lib_cant_do_object (game, "climb");
10350 }
10351 
10352 sc_bool
lib_cmd_clean_object(sc_gameref_t game)10353 lib_cmd_clean_object (sc_gameref_t game)
10354 {
10355   return lib_cant_do_object (game, "clean");
10356 }
10357 
10358 sc_bool
lib_cmd_cut_object(sc_gameref_t game)10359 lib_cmd_cut_object (sc_gameref_t game)
10360 {
10361   return lib_cant_do_object (game, "cut");
10362 }
10363 
10364 sc_bool
lib_cmd_drink_object(sc_gameref_t game)10365 lib_cmd_drink_object (sc_gameref_t game)
10366 {
10367   return lib_cant_do_object (game, "drink");
10368 }
10369 
10370 sc_bool
lib_cmd_light_object(sc_gameref_t game)10371 lib_cmd_light_object (sc_gameref_t game)
10372 {
10373   return lib_cant_do_object (game, "light");
10374 }
10375 
10376 sc_bool
lib_cmd_lift_object(sc_gameref_t game)10377 lib_cmd_lift_object (sc_gameref_t game)
10378 {
10379   return lib_cant_do_object (game, "lift");
10380 }
10381 
10382 sc_bool
lib_cmd_move_object(sc_gameref_t game)10383 lib_cmd_move_object (sc_gameref_t game)
10384 {
10385   return lib_cant_do_object (game, "move");
10386 }
10387 
10388 sc_bool
lib_cmd_rub_object(sc_gameref_t game)10389 lib_cmd_rub_object (sc_gameref_t game)
10390 {
10391   return lib_cant_do_object (game, "rub");
10392 }
10393 
10394 sc_bool
lib_cmd_stop_object(sc_gameref_t game)10395 lib_cmd_stop_object (sc_gameref_t game)
10396 {
10397   return lib_cant_do_object (game, "stop");
10398 }
10399 
10400 sc_bool
lib_cmd_suck_object(sc_gameref_t game)10401 lib_cmd_suck_object (sc_gameref_t game)
10402 {
10403   return lib_cant_do_object (game, "suck");
10404 }
10405 
10406 sc_bool
lib_cmd_touch_object(sc_gameref_t game)10407 lib_cmd_touch_object (sc_gameref_t game)
10408 {
10409   return lib_cant_do_object (game, "touch");
10410 }
10411 
10412 sc_bool
lib_cmd_turn_object(sc_gameref_t game)10413 lib_cmd_turn_object (sc_gameref_t game)
10414 {
10415   return lib_cant_do_object (game, "turn");
10416 }
10417 
10418 sc_bool
lib_cmd_unblock_object(sc_gameref_t game)10419 lib_cmd_unblock_object (sc_gameref_t game)
10420 {
10421   return lib_cant_do_object (game, "unblock");
10422 }
10423 
10424 sc_bool
lib_cmd_wash_object(sc_gameref_t game)10425 lib_cmd_wash_object (sc_gameref_t game)
10426 {
10427   return lib_cant_do_object (game, "wash");
10428 }
10429 
10430 sc_bool
lib_cmd_block_other(sc_gameref_t game)10431 lib_cmd_block_other (sc_gameref_t game)
10432 {
10433   return lib_cant_do_other (game, "block");
10434 }
10435 
10436 sc_bool
lib_cmd_climb_other(sc_gameref_t game)10437 lib_cmd_climb_other (sc_gameref_t game)
10438 {
10439   return lib_cant_do_other (game, "climb");
10440 }
10441 
10442 sc_bool
lib_cmd_clean_other(sc_gameref_t game)10443 lib_cmd_clean_other (sc_gameref_t game)
10444 {
10445   return lib_cant_do_other (game, "clean");
10446 }
10447 
10448 sc_bool
lib_cmd_close_other(sc_gameref_t game)10449 lib_cmd_close_other (sc_gameref_t game)
10450 {
10451   return lib_cant_do_other (game, "close");
10452 }
10453 
10454 sc_bool
lib_cmd_lock_other(sc_gameref_t game)10455 lib_cmd_lock_other (sc_gameref_t game)
10456 {
10457   return lib_cant_do_other (game, "lock");
10458 }
10459 
10460 sc_bool
lib_cmd_unlock_other(sc_gameref_t game)10461 lib_cmd_unlock_other (sc_gameref_t game)
10462 {
10463   return lib_cant_do_other (game, "unlock");
10464 }
10465 
10466 sc_bool
lib_cmd_stand_other(sc_gameref_t game)10467 lib_cmd_stand_other (sc_gameref_t game)
10468 {
10469   return lib_cant_do_other (game, "stand on");
10470 }
10471 
10472 sc_bool
lib_cmd_sit_other(sc_gameref_t game)10473 lib_cmd_sit_other (sc_gameref_t game)
10474 {
10475   return lib_cant_do_other (game, "sit on");
10476 }
10477 
10478 sc_bool
lib_cmd_lie_other(sc_gameref_t game)10479 lib_cmd_lie_other (sc_gameref_t game)
10480 {
10481   return lib_cant_do_other (game, "lie on");
10482 }
10483 
10484 sc_bool
lib_cmd_cut_other(sc_gameref_t game)10485 lib_cmd_cut_other (sc_gameref_t game)
10486 {
10487   return lib_cant_do_other (game, "cut");
10488 }
10489 
10490 sc_bool
lib_cmd_drink_other(sc_gameref_t game)10491 lib_cmd_drink_other (sc_gameref_t game)
10492 {
10493   return lib_cant_do_other (game, "drink");
10494 }
10495 
10496 sc_bool
lib_cmd_lift_other(sc_gameref_t game)10497 lib_cmd_lift_other (sc_gameref_t game)
10498 {
10499   return lib_cant_do_other (game, "lift");
10500 }
10501 
10502 sc_bool
lib_cmd_light_other(sc_gameref_t game)10503 lib_cmd_light_other (sc_gameref_t game)
10504 {
10505   return lib_cant_do_other (game, "light");
10506 }
10507 
10508 sc_bool
lib_cmd_move_other(sc_gameref_t game)10509 lib_cmd_move_other (sc_gameref_t game)
10510 {
10511   return lib_cant_do_other (game, "move");
10512 }
10513 
10514 sc_bool
lib_cmd_stop_other(sc_gameref_t game)10515 lib_cmd_stop_other (sc_gameref_t game)
10516 {
10517   return lib_cant_do_other (game, "stop");
10518 }
10519 
10520 sc_bool
lib_cmd_rub_other(sc_gameref_t game)10521 lib_cmd_rub_other (sc_gameref_t game)
10522 {
10523   return lib_cant_do_other (game, "rub");
10524 }
10525 
10526 sc_bool
lib_cmd_suck_other(sc_gameref_t game)10527 lib_cmd_suck_other (sc_gameref_t game)
10528 {
10529   return lib_cant_do_other (game, "suck");
10530 }
10531 
10532 sc_bool
lib_cmd_turn_other(sc_gameref_t game)10533 lib_cmd_turn_other (sc_gameref_t game)
10534 {
10535   return lib_cant_do_other (game, "turn");
10536 }
10537 
10538 sc_bool
lib_cmd_touch_other(sc_gameref_t game)10539 lib_cmd_touch_other (sc_gameref_t game)
10540 {
10541   return lib_cant_do_other (game, "touch");
10542 }
10543 
10544 sc_bool
lib_cmd_unblock_other(sc_gameref_t game)10545 lib_cmd_unblock_other (sc_gameref_t game)
10546 {
10547   return lib_cant_do_other (game, "unblock");
10548 }
10549 
10550 sc_bool
lib_cmd_wash_other(sc_gameref_t game)10551 lib_cmd_wash_other (sc_gameref_t game)
10552 {
10553   return lib_cant_do_other (game, "wash");
10554 }
10555 
10556 
10557 /*
10558  * lib_dont_think_common()
10559  * lib_dont_think_object()
10560  * lib_dont_think_other()
10561  *
10562  * Central handler for a range of don't_think messages.  Still more
10563  * uninteresting responses.
10564  */
10565 static sc_bool
lib_dont_think_common(sc_gameref_t game,const sc_char * verb,sc_bool is_object)10566 lib_dont_think_common (sc_gameref_t game,
10567                        const sc_char *verb, sc_bool is_object)
10568 {
10569   const sc_filterref_t filter = gs_get_filter (game);
10570   sc_int object;
10571   sc_bool is_ambiguous;
10572 
10573   /* If the target is not an object, end it here. */
10574   if (!is_object)
10575     {
10576       pf_buffer_string (filter,
10577                         lib_select_response (game,
10578                                              "I don't think you can ",
10579                                              "I don't think I can ",
10580                                              "I don't think %player% can "));
10581       pf_buffer_string (filter, verb);
10582       pf_buffer_string (filter, " that.\n");
10583       return TRUE;
10584     }
10585 
10586   /* Get the referenced object.  If none, return immediately. */
10587   object = lib_disambiguate_object (game, verb, &is_ambiguous);
10588   if (object == -1)
10589     return is_ambiguous;
10590 
10591   /* Whatever it is, don't do it. */
10592   pf_buffer_string (filter, "I don't think you can ");
10593   pf_buffer_string (filter, verb);
10594   pf_buffer_character (filter, ' ');
10595   lib_print_object_np (game, object);
10596   pf_buffer_string (filter, ".\n");
10597   return TRUE;
10598 }
10599 
10600 static sc_bool
lib_dont_think_object(sc_gameref_t game,const sc_char * verb)10601 lib_dont_think_object (sc_gameref_t game, const sc_char *verb)
10602 {
10603   return lib_dont_think_common (game, verb, TRUE);
10604 }
10605 
10606 static sc_bool
lib_dont_think_other(sc_gameref_t game,const sc_char * verb)10607 lib_dont_think_other (sc_gameref_t game, const sc_char *verb)
10608 {
10609   return lib_dont_think_common (game, verb, FALSE);
10610 }
10611 
10612 
10613 /*
10614  * lib_cmd_*()
10615  *
10616  * Assorted don't-think messages.
10617  */
10618 sc_bool
lib_cmd_fix_object(sc_gameref_t game)10619 lib_cmd_fix_object (sc_gameref_t game)
10620 {
10621   return lib_dont_think_object (game, "fix");
10622 }
10623 
10624 sc_bool
lib_cmd_mend_object(sc_gameref_t game)10625 lib_cmd_mend_object (sc_gameref_t game)
10626 {
10627   return lib_dont_think_object (game, "mend");
10628 }
10629 
10630 sc_bool
lib_cmd_repair_object(sc_gameref_t game)10631 lib_cmd_repair_object (sc_gameref_t game)
10632 {
10633   return lib_dont_think_object (game, "repair");
10634 }
10635 
10636 sc_bool
lib_cmd_fix_other(sc_gameref_t game)10637 lib_cmd_fix_other (sc_gameref_t game)
10638 {
10639   return lib_dont_think_other (game, "fix");
10640 }
10641 
10642 sc_bool
lib_cmd_mend_other(sc_gameref_t game)10643 lib_cmd_mend_other (sc_gameref_t game)
10644 {
10645   return lib_dont_think_other (game, "mend");
10646 }
10647 
10648 sc_bool
lib_cmd_repair_other(sc_gameref_t game)10649 lib_cmd_repair_other (sc_gameref_t game)
10650 {
10651   return lib_dont_think_other (game, "repair");
10652 }
10653 
10654 
10655 /*
10656  * lib_what()
10657  *
10658  * Central handler for doing something, but unsure to what.
10659  */
10660 static sc_bool
lib_what(sc_gameref_t game,const sc_char * verb)10661 lib_what (sc_gameref_t game, const sc_char *verb)
10662 {
10663   const sc_filterref_t filter = gs_get_filter (game);
10664 
10665   pf_buffer_string (filter, verb);
10666   pf_buffer_string (filter, " what?\n");
10667   return TRUE;
10668 }
10669 
10670 
10671 /*
10672  * lib_cmd_*()
10673  *
10674  * Assorted "what?" messages.
10675  */
10676 sc_bool
lib_cmd_block_what(sc_gameref_t game)10677 lib_cmd_block_what (sc_gameref_t game)
10678 {
10679   return lib_what (game, "Block");
10680 }
10681 
10682 sc_bool
lib_cmd_break_what(sc_gameref_t game)10683 lib_cmd_break_what (sc_gameref_t game)
10684 {
10685   return lib_what (game, "Break");
10686 }
10687 
10688 sc_bool
lib_cmd_destroy_what(sc_gameref_t game)10689 lib_cmd_destroy_what (sc_gameref_t game)
10690 {
10691   return lib_what (game, "Destroy");
10692 }
10693 
10694 sc_bool
lib_cmd_smash_what(sc_gameref_t game)10695 lib_cmd_smash_what (sc_gameref_t game)
10696 {
10697   return lib_what (game, "Smash");
10698 }
10699 
10700 sc_bool
lib_cmd_buy_what(sc_gameref_t game)10701 lib_cmd_buy_what (sc_gameref_t game)
10702 {
10703   return lib_what (game, "Buy");
10704 }
10705 
10706 sc_bool
lib_cmd_clean_what(sc_gameref_t game)10707 lib_cmd_clean_what (sc_gameref_t game)
10708 {
10709   return lib_what (game, "Clean");
10710 }
10711 
10712 sc_bool
lib_cmd_climb_what(sc_gameref_t game)10713 lib_cmd_climb_what (sc_gameref_t game)
10714 {
10715   return lib_what (game, "Climb");
10716 }
10717 
10718 sc_bool
lib_cmd_cut_what(sc_gameref_t game)10719 lib_cmd_cut_what (sc_gameref_t game)
10720 {
10721   return lib_what (game, "Cut");
10722 }
10723 
10724 sc_bool
lib_cmd_drink_what(sc_gameref_t game)10725 lib_cmd_drink_what (sc_gameref_t game)
10726 {
10727   return lib_what (game, "Drink");
10728 }
10729 
10730 sc_bool
lib_cmd_fix_what(sc_gameref_t game)10731 lib_cmd_fix_what (sc_gameref_t game)
10732 {
10733   return lib_what (game, "Fix");
10734 }
10735 
10736 sc_bool
lib_cmd_hit_what(sc_gameref_t game)10737 lib_cmd_hit_what (sc_gameref_t game)
10738 {
10739   return lib_what (game, "Hit");
10740 }
10741 
10742 sc_bool
lib_cmd_kick_what(sc_gameref_t game)10743 lib_cmd_kick_what (sc_gameref_t game)
10744 {
10745   return lib_what (game, "Kick");
10746 }
10747 
10748 sc_bool
lib_cmd_light_what(sc_gameref_t game)10749 lib_cmd_light_what (sc_gameref_t game)
10750 {
10751   return lib_what (game, "Light");
10752 }
10753 
10754 sc_bool
lib_cmd_lift_what(sc_gameref_t game)10755 lib_cmd_lift_what (sc_gameref_t game)
10756 {
10757   return lib_what (game, "Lift");
10758 }
10759 
10760 sc_bool
lib_cmd_mend_what(sc_gameref_t game)10761 lib_cmd_mend_what (sc_gameref_t game)
10762 {
10763   return lib_what (game, "Mend");
10764 }
10765 
10766 sc_bool
lib_cmd_move_what(sc_gameref_t game)10767 lib_cmd_move_what (sc_gameref_t game)
10768 {
10769   return lib_what (game, "Move");
10770 }
10771 
10772 sc_bool
lib_cmd_press_what(sc_gameref_t game)10773 lib_cmd_press_what (sc_gameref_t game)
10774 {
10775   return lib_what (game, "Press");
10776 }
10777 
10778 sc_bool
lib_cmd_pull_what(sc_gameref_t game)10779 lib_cmd_pull_what (sc_gameref_t game)
10780 {
10781   return lib_what (game, "Pull");
10782 }
10783 
10784 sc_bool
lib_cmd_push_what(sc_gameref_t game)10785 lib_cmd_push_what (sc_gameref_t game)
10786 {
10787   return lib_what (game, "Push");
10788 }
10789 
10790 sc_bool
lib_cmd_repair_what(sc_gameref_t game)10791 lib_cmd_repair_what (sc_gameref_t game)
10792 {
10793   return lib_what (game, "Repair");
10794 }
10795 
10796 sc_bool
lib_cmd_sell_what(sc_gameref_t game)10797 lib_cmd_sell_what (sc_gameref_t game)
10798 {
10799   return lib_what (game, "Sell");
10800 }
10801 
10802 sc_bool
lib_cmd_shake_what(sc_gameref_t game)10803 lib_cmd_shake_what (sc_gameref_t game)
10804 {
10805   return lib_what (game, "Shake");
10806 }
10807 
10808 sc_bool
lib_cmd_rub_what(sc_gameref_t game)10809 lib_cmd_rub_what (sc_gameref_t game)
10810 {
10811   return lib_what (game, "Rub");
10812 }
10813 
10814 sc_bool
lib_cmd_stop_what(sc_gameref_t game)10815 lib_cmd_stop_what (sc_gameref_t game)
10816 {
10817   return lib_what (game, "Stop");
10818 }
10819 
10820 sc_bool
lib_cmd_suck_what(sc_gameref_t game)10821 lib_cmd_suck_what (sc_gameref_t game)
10822 {
10823   return lib_what (game, "Suck");
10824 }
10825 
10826 sc_bool
lib_cmd_touch_what(sc_gameref_t game)10827 lib_cmd_touch_what (sc_gameref_t game)
10828 {
10829   return lib_what (game, "Touch");
10830 }
10831 
10832 sc_bool
lib_cmd_turn_what(sc_gameref_t game)10833 lib_cmd_turn_what (sc_gameref_t game)
10834 {
10835   return lib_what (game, "Turn");
10836 }
10837 
10838 sc_bool
lib_cmd_unblock_what(sc_gameref_t game)10839 lib_cmd_unblock_what (sc_gameref_t game)
10840 {
10841   return lib_what (game, "Unblock");
10842 }
10843 
10844 sc_bool
lib_cmd_wash_what(sc_gameref_t game)10845 lib_cmd_wash_what (sc_gameref_t game)
10846 {
10847   return lib_what (game, "Wash");
10848 }
10849 
10850 sc_bool
lib_cmd_drop_what(sc_gameref_t game)10851 lib_cmd_drop_what (sc_gameref_t game)
10852 {
10853   return lib_what (game, "Drop");
10854 }
10855 
10856 sc_bool
lib_cmd_get_what(sc_gameref_t game)10857 lib_cmd_get_what (sc_gameref_t game)
10858 {
10859   return lib_what (game, "Take");
10860 }
10861 
10862 sc_bool
lib_cmd_give_what(sc_gameref_t game)10863 lib_cmd_give_what (sc_gameref_t game)
10864 {
10865   return lib_what (game, "Give");
10866 }
10867 
10868 sc_bool
lib_cmd_open_what(sc_gameref_t game)10869 lib_cmd_open_what (sc_gameref_t game)
10870 {
10871   return lib_what (game, "Open");
10872 }
10873 
10874 sc_bool
lib_cmd_remove_what(sc_gameref_t game)10875 lib_cmd_remove_what (sc_gameref_t game)
10876 {
10877   return lib_what (game, "Remove");
10878 }
10879 
10880 sc_bool
lib_cmd_wear_what(sc_gameref_t game)10881 lib_cmd_wear_what (sc_gameref_t game)
10882 {
10883   return lib_what (game, "Wear");
10884 }
10885 
10886 sc_bool
lib_cmd_lock_what(sc_gameref_t game)10887 lib_cmd_lock_what (sc_gameref_t game)
10888 {
10889   return lib_what (game, "Lock");
10890 }
10891 
10892 sc_bool
lib_cmd_unlock_what(sc_gameref_t game)10893 lib_cmd_unlock_what (sc_gameref_t game)
10894 {
10895   return lib_what (game, "Unlock");
10896 }
10897 
10898 
10899 /*
10900  * lib_cmd_verb_object()
10901  * lib_cmd_verb_character()
10902  *
10903  * Handlers for unrecognized verbs with known object/NPC.
10904  */
10905 sc_bool
lib_cmd_verb_object(sc_gameref_t game)10906 lib_cmd_verb_object (sc_gameref_t game)
10907 {
10908   const sc_filterref_t filter = gs_get_filter (game);
10909   const sc_var_setref_t vars = gs_get_vars (game);
10910   sc_int count, object, index_;
10911 
10912   /* Ensure the reference is unambiguous. */
10913   count = 0;
10914   object = -1;
10915   for (index_ = 0; index_ < gs_object_count (game); index_++)
10916     {
10917       if (game->object_references[index_]
10918           && gs_object_seen (game, index_)
10919           && obj_indirectly_in_room (game, index_, gs_playerroom (game)))
10920         {
10921           count++;
10922           object = index_;
10923         }
10924     }
10925   if (count != 1)
10926     return FALSE;
10927 
10928   /* Save in variables. */
10929   var_set_ref_object (vars, object);
10930 
10931   /* Print don't understand message. */
10932   pf_buffer_string (filter, "I don't understand what you want me to do with ");
10933   lib_print_object_np (game, object);
10934   pf_buffer_string (filter, ".\n");
10935   return TRUE;
10936 }
10937 
10938 sc_bool
lib_cmd_verb_npc(sc_gameref_t game)10939 lib_cmd_verb_npc (sc_gameref_t game)
10940 {
10941   const sc_filterref_t filter = gs_get_filter (game);
10942   const sc_var_setref_t vars = gs_get_vars (game);
10943   sc_int count, npc, index_;
10944 
10945   /* Ensure the reference is unambiguous. */
10946   count = 0;
10947   npc = -1;
10948   for (index_ = 0; index_ < gs_npc_count (game); index_++)
10949     {
10950       if (game->npc_references[index_]
10951           && gs_npc_seen (game, index_)
10952           && npc_in_room (game, index_, gs_playerroom (game)))
10953         {
10954           count++;
10955           npc = index_;
10956         }
10957     }
10958   if (count != 1)
10959     return FALSE;
10960 
10961   /* Save in variables. */
10962   var_set_ref_character (vars, npc);
10963 
10964   /* Print don't understand message; unlike objects, there's no "me" here. */
10965   pf_buffer_string (filter, "I don't understand what you want to do with ");
10966   lib_print_npc_np (game, npc);
10967   pf_buffer_string (filter, ".\n");
10968   return TRUE;
10969 }
10970 
10971 
10972 /*
10973  * lib_debug_trace()
10974  *
10975  * Set library tracing on/off.
10976  */
10977 void
lib_debug_trace(sc_bool flag)10978 lib_debug_trace (sc_bool flag)
10979 {
10980   lib_trace = flag;
10981 }
10982