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, ×tamp, &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, ×tamp, &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