/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "glk/adrift/adrift.h" #include "glk/adrift/scprotos.h" #include "glk/adrift/scgamest.h" #include "glk/adrift/serialization.h" namespace Glk { namespace Adrift { /* * Module notes: * * o Ensure module messages precisely match the real Runner ones. This * matters for ALRs. * * o Capacity checks on the player and on containers are implemented, but * may not be right. */ /* Assorted definitions and constants. */ static const sc_char NUL = '\0'; static const sc_char COMMA = ','; enum { SECS_PER_MINUTE = 60, MINS_PER_HOUR = 60, SECS_PER_HOUR = 3600 }; enum { LIB_ALLOCATION_AVOIDANCE_SIZE = 128 }; /* Trace flag, set before running. */ static sc_bool lib_trace = FALSE; /* * lib_warn_battle_system() * * Display a warning when the battle system is detected in a game. Print * directly rather than using the printfilter to avoid possible clashes * with ALRs. */ void lib_warn_battle_system(void) { if_print_tag(SC_TAG_FONT, "size=16"); if_print_string("SCARE WARNING"); if_print_tag(SC_TAG_ENDFONT, ""); if_print_string( "\n\nThe game uses Adrift's Battle System, something not fully supported" " by this release of SCARE.\n\n"); if_print_string( "SCARE will still run the game, but it will not create character" " battles where they would normally occur. For some games, this may" " be perfectly okay, as the Battle System is sometimes turned on" " by accident in a game, but never actually used. For others, though," " the omission of this feature may be more serious.\n\n"); if_print_string("Please press a key to continue...\n\n"); if_print_tag(SC_TAG_WAITKEY, ""); } /* * lib_random_roomgroup_member() * * Return a random member of a roomgroup. */ sc_int lib_random_roomgroup_member(sc_gameref_t game, sc_int roomgroup) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[4]; sc_int count, room; /* Get the count of rooms in the group. */ vt_key[0].string = "RoomGroups"; vt_key[1].integer = roomgroup; vt_key[2].string = "List2"; count = prop_get_child_count(bundle, "I<-sis", vt_key); if (count == 0) { sc_fatal("lib_random_roomgroup_member:" " no rooms in group %ld\n", roomgroup); } /* Pick a room at random and return it. */ vt_key[3].integer = sc_randomint(0, count - 1); room = prop_get_integer(bundle, "I<-sisi", vt_key); if (lib_trace) { sc_trace("Library: random room for group %ld is %ld\n", roomgroup, room); } return room; } /* * lib_use_room_alt() * * Return TRUE if a particular alternate room description should be used. */ static sc_bool lib_use_room_alt(sc_gameref_t game, sc_int room, sc_int alt) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_int type; sc_bool retval; /* Get alternate type. */ vt_key[0].string = "Rooms"; vt_key[1].integer = room; vt_key[2].string = "Alts"; vt_key[3].integer = alt; vt_key[4].string = "Type"; type = prop_get_integer(bundle, "I<-sisis", vt_key); /* Select based on type. */ retval = FALSE; switch (type) { case 0: { /* Task. */ sc_int var2, var3; vt_key[4].string = "Var2"; var2 = prop_get_integer(bundle, "I<-sisis", vt_key); if (var2 == 0) /* No task. */ retval = TRUE; else { vt_key[4].string = "Var3"; var3 = prop_get_integer(bundle, "I<-sisis", vt_key); retval = gs_task_done(game, var2 - 1) == !(var3 != 0); } break; } case 1: { /* Stateful object. */ sc_int var2, var3, object; vt_key[4].string = "Var2"; var2 = prop_get_integer(bundle, "I<-sisis", vt_key); if (var2 == 0) /* No object. */ retval = TRUE; else { vt_key[4].string = "Var3"; var3 = prop_get_integer(bundle, "I<-sisis", vt_key); object = obj_stateful_index(game, var2 - 1); retval = restr_pass_task_object_state(game, object + 1, var3 - 1); } break; } case 2: { /* Player condition. */ sc_int var2, var3, object; vt_key[4].string = "Var2"; var2 = prop_get_integer(bundle, "I<-sisis", vt_key); vt_key[4].string = "Var3"; var3 = prop_get_integer(bundle, "I<-sisis", vt_key); if (var3 == 0) { switch (var2) { case 0: case 2: case 5: retval = TRUE; break; case 1: case 3: case 4: retval = FALSE; break; default: sc_fatal("lib_use_room_alt:" " invalid player condition, %ld\n", var2); } break; } if (var2 == 2 || var2 == 3) object = obj_wearable_object(game, var3 - 1); else object = obj_dynamic_object(game, var3 - 1); switch (var2) { case 0: /* Isn't holding (or wearing). */ retval = gs_object_position(game, object) != OBJ_HELD_PLAYER && gs_object_position(game, object) != OBJ_WORN_PLAYER; break; case 1: /* Is holding (or wearing). */ retval = gs_object_position(game, object) == OBJ_HELD_PLAYER || gs_object_position(game, object) == OBJ_WORN_PLAYER; break; case 2: /* Isn't wearing. */ retval = gs_object_position(game, object) != OBJ_WORN_PLAYER; break; case 3: /* Is wearing. */ retval = gs_object_position(game, object) == OBJ_WORN_PLAYER; break; case 4: /* Isn't in the same room as. */ retval = !obj_indirectly_in_room(game, object, gs_playerroom(game)); break; case 5: /* Is in the same room as. */ retval = obj_indirectly_in_room(game, object, gs_playerroom(game)); break; default: sc_fatal("lib_use_room_alt:" " invalid player condition, %ld\n", var2); } break; } default: sc_fatal("lib_use_room_alt: invalid type, %ld\n", type); } return retval; } /* * lib_find_starting_alt() * * Return the alt index for the alt at which we need to start running down * the alts list when generating room names or descriptions. Returns -1 if * no alt overrides the default room long description. */ static sc_int lib_find_starting_alt(sc_gameref_t game, sc_int room) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_int alt_count, alt, retval; /* Get count of room alternates. */ vt_key[0].string = "Rooms"; vt_key[1].integer = room; vt_key[2].string = "Alts"; alt_count = prop_get_child_count(bundle, "I<-sis", vt_key); /* Search backwards for a method-0 or method-1 overriding description. */ retval = -1; for (alt = alt_count - 1; alt >= 0; alt--) { sc_int method; vt_key[3].integer = alt; vt_key[4].string = "DisplayRoom"; method = prop_get_integer(bundle, "I<-sisis", vt_key); if (!(method == 0 || method == 1)) continue; if (lib_use_room_alt(game, room, alt)) { const sc_char *m1; vt_key[3].integer = alt; vt_key[4].string = "M1"; m1 = prop_get_string(bundle, "S<-sisis", vt_key); if (!sc_strempty(m1)) { retval = alt; break; } } else { const sc_char *m2; vt_key[3].integer = alt; vt_key[4].string = "M2"; m2 = prop_get_string(bundle, "S<-sisis", vt_key); if (!sc_strempty(m2)) { retval = alt; break; } } } /* Return the index of the base alt, or -1 if none found. */ return retval; } /* * lib_get_room_name() * lib_print_room_name() * * Get/print out the name for a given room. */ const sc_char *lib_get_room_name(sc_gameref_t game, sc_int room) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_int alt_count, alt, start; const sc_char *name; /* Get the basic room name, and the count of room alternates. */ vt_key[0].string = "Rooms"; vt_key[1].integer = room; vt_key[2].string = "Short"; name = prop_get_string(bundle, "S<-sis", vt_key); vt_key[2].string = "Alts"; alt_count = prop_get_child_count(bundle, "I<-sis", vt_key); /* Get our starting point in the alts list. */ start = lib_find_starting_alt(game, room); /* * Run forwards through all alts lower than our starting point, or all alts * if no starting point found. */ for (alt = (start != -1) ? start : 0; alt < alt_count; alt++) { /* Ignore all non-method-2 alts except for the starter. */ if (alt != start) { sc_int method; vt_key[3].integer = alt; vt_key[4].string = "DisplayRoom"; method = prop_get_integer(bundle, "I<-sisis", vt_key); if (method != 2) continue; } /* If this alt offers a name change, note it and continue. */ if (lib_use_room_alt(game, room, alt)) { const sc_char *changed; vt_key[3].integer = alt; vt_key[4].string = "Changed"; changed = prop_get_string(bundle, "S<-sisis", vt_key); if (!sc_strempty(changed)) name = changed; } } /* Return the final selected name. */ return name; } void lib_print_room_name(sc_gameref_t game, sc_int room) { const sc_filterref_t filter = gs_get_filter(game); const sc_char *name; /* Print the room name, possibly in bold. */ name = lib_get_room_name(game, room); if (game->bold_room_names) { pf_buffer_tag(filter, SC_TAG_BOLD); pf_buffer_string(filter, name); pf_buffer_tag(filter, SC_TAG_ENDBOLD); } else pf_buffer_string(filter, name); pf_buffer_character(filter, '\n'); } /* * lib_print_object_np * lib_print_object * * Convenience functions to print out an object's name, with a "normalized" * prefix -- any "a"/"an"/"some" is replaced by "the" -- and with the full * prefix. */ static void lib_print_object_np(sc_gameref_t game, sc_int object) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; const sc_char *prefix, *normalized, *name; /* Get the object's prefix. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Prefix"; prefix = prop_get_string(bundle, "S<-sis", vt_key); /* * Normalize by skipping any leading "a"/"an"/"some", replacing it instead * with "the", and skipping any odd "the" already present. If no prefix at * all, add a "the " anyway. * * TODO This is empirical, based on observed Adrift Runner behavior, and * what it's _really_ supposed to do is a mystery. This routine has been a * real PITA. */ normalized = prefix; if (sc_compare_word(prefix, "a", 1)) { normalized = prefix + 1; pf_buffer_string(filter, "the"); } else if (sc_compare_word(prefix, "an", 2)) { normalized = prefix + 2; pf_buffer_string(filter, "the"); } else if (sc_compare_word(prefix, "the", 3)) { normalized = prefix + 3; pf_buffer_string(filter, "the"); } else if (sc_compare_word(prefix, "some", 4)) { normalized = prefix + 4; pf_buffer_string(filter, "the"); } else if (sc_strempty(prefix)) pf_buffer_string(filter, "the "); /* * If the remaining normalized prefix isn't empty, print it, and a space. * If it is, then consider adding a space to any "the" printed above, except * for the one done for empty prefixes, that is. */ if (!sc_strempty(normalized)) { pf_buffer_string(filter, normalized); pf_buffer_character(filter, ' '); } else if (normalized > prefix) pf_buffer_character(filter, ' '); /* * Print the object's name; here we also look for a leading article and * strip if found -- some games may avoid prefix and do this instead. */ vt_key[2].string = "Short"; name = prop_get_string(bundle, "S<-sis", vt_key); if (sc_compare_word(name, "a", 1)) name += 1; else if (sc_compare_word(name, "an", 2)) name += 2; else if (sc_compare_word(name, "the", 3)) name += 3; else if (sc_compare_word(name, "some", 4)) name += 4; pf_buffer_string(filter, name); } static void lib_print_object(sc_gameref_t game, sc_int object) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; const sc_char *prefix, *name; /* * Get the object's prefix, and print if not empty, otherwise default to an * "a " prefix, as that's what Adrift seems to do. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Prefix"; prefix = prop_get_string(bundle, "S<-sis", vt_key); if (!sc_strempty(prefix)) { pf_buffer_string(filter, prefix); pf_buffer_character(filter, ' '); } else pf_buffer_string(filter, "a "); /* Print object name. */ vt_key[2].string = "Short"; name = prop_get_string(bundle, "S<-sis", vt_key); pf_buffer_string(filter, name); } /* * lib_print_npc_np * lib_print_npc * * Convenience functions to print out an NPC's name, with and without * any prefix. */ static void lib_print_npc_np(sc_gameref_t game, sc_int npc) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; const sc_char *name; /* Get the NPC's short description, and print it. */ vt_key[0].string = "NPCs"; vt_key[1].integer = npc; vt_key[2].string = "Name"; name = prop_get_string(bundle, "S<-sis", vt_key); pf_buffer_string(filter, name); } #if 0 static void lib_print_npc(sc_gameref_t game, sc_int npc) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; const sc_char *prefix; /* Get the NPC's prefix. */ vt_key[0].string = "NPCs"; vt_key[1].integer = npc; vt_key[2].string = "Prefix"; prefix = prop_get_string(bundle, "S<-sis", vt_key); /* If the prefix isn't empty, print it, then print NPC name. */ if (!sc_strempty(prefix)) { pf_buffer_string(filter, prefix); pf_buffer_character(filter, ' '); } lib_print_npc_np(game, npc); } #endif /* * lib_select_response() * lib_select_plurality() * * Convenience functions for multiple handlers. Returns the appropriate * response string for a game, based on perspective or object plurality. */ 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) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; sc_int perspective; const sc_char *response; /* Return the response appropriate for Perspective. */ vt_key[0].string = "Globals"; vt_key[1].string = "Perspective"; perspective = prop_get_integer(bundle, "I<-ss", vt_key); switch (perspective) { case LIB_FIRST_PERSON: response = first_person; break; case LIB_SECOND_PERSON: response = second_person; break; case LIB_THIRD_PERSON: response = third_person; break; default: sc_error("lib_select_response:" " unknown perspective, %ld\n", perspective); response = second_person; break; } return response; } static const sc_char *lib_select_plurality(sc_gameref_t game, sc_int object, const sc_char *singular, const sc_char *plural) { return obj_appears_plural(game, object) ? plural : singular; } /* * lib_get_npc_inroom_text() * * Returns the inroom description to be use for an NPC; if the NPC has * gone walkabout and offers a changed description, return that; otherwise * return the standard inroom text. */ static const sc_char *lib_get_npc_inroom_text(sc_gameref_t game, sc_int npc) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_int walk_count, walk; const sc_char *inroomtext; /* Get the count of NPC walks. */ vt_key[0].string = "NPCs"; vt_key[1].integer = npc; vt_key[2].string = "Walks"; walk_count = prop_get_child_count(bundle, "I<-sis", vt_key); /* Check for any active walk with a description, return if found. */ for (walk = walk_count - 1; walk >= 0; walk--) { if (gs_npc_walkstep(game, npc, walk) > 0) { const sc_char *changeddesc; /* Get and check any walk active description. */ vt_key[3].integer = walk; vt_key[4].string = "ChangedDesc"; changeddesc = prop_get_string(bundle, "S<-sisis", vt_key); if (!sc_strempty(changeddesc)) return changeddesc; } } /* Return the standard inroom text. */ vt_key[2].string = "InRoomText"; inroomtext = prop_get_string(bundle, "S<-sis", vt_key); return inroomtext; } /* * lib_print_room_contents() * * Print a list of the contents of a room. */ static void lib_print_room_contents(sc_gameref_t game, sc_int room) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[4]; sc_int object, npc, count, trail; /* List all objects that show their initial description. */ count = 0; for (object = 0; object < gs_object_count(game); object++) { if (obj_directly_in_room(game, object, room) && obj_shows_initial_description(game, object)) { const sc_char *inroomdesc; /* Find and print in room description. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "InRoomDesc"; inroomdesc = prop_get_string(bundle, "S<-sis", vt_key); if (!sc_strempty(inroomdesc)) { if (count == 0) pf_buffer_character(filter, '\n'); else pf_buffer_string(filter, " "); pf_buffer_string(filter, inroomdesc); count++; } } } if (count > 0) pf_buffer_character(filter, '\n'); /* * List dynamic objects directly located in the room, and not already listed * above since they lack, or suppress, an in room description. * * If an object sets ListFlag, then if dynamic it's suppressed from the list * where it would normally be included, but if static it's included where it * would normally be excluded. */ count = 0; trail = -1; for (object = 0; object < gs_object_count(game); object++) { if (obj_directly_in_room(game, object, room)) { const sc_char *inroomdesc; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "InRoomDesc"; inroomdesc = prop_get_string(bundle, "S<-sis", vt_key); if (!obj_shows_initial_description(game, object) || sc_strempty(inroomdesc)) { sc_bool listflag; vt_key[2].string = "ListFlag"; listflag = prop_get_boolean(bundle, "B<-sis", vt_key); if (listflag == obj_is_static(game, object)) { if (count > 0) { if (count == 1) pf_buffer_string(filter, lib_select_plurality(game, trail, "\nAlso here is ", "\nAlso here are ")); else pf_buffer_string(filter, ", "); lib_print_object(game, trail); } trail = object; count++; } } } } if (count >= 1) { if (count == 1) pf_buffer_string(filter, lib_select_plurality(game, trail, "\nAlso here is ", "\nAlso here are ")); else pf_buffer_string(filter, " and "); lib_print_object(game, trail); pf_buffer_string(filter, ".\n"); } /* List NPCs directly in the room that have an in room description. */ count = 0; for (npc = 0; npc < gs_npc_count(game); npc++) { if (npc_in_room(game, npc, room)) { const sc_char *description; /* Print any non='#' in-room description. */ description = lib_get_npc_inroom_text(game, npc); if (!sc_strempty(description) && sc_strcasecmp(description, "#")) { if (count == 0) pf_buffer_character(filter, '\n'); else pf_buffer_string(filter, " "); pf_buffer_string(filter, description); count++; } } } if (count > 0) pf_buffer_character(filter, '\n'); /* * List NPCs in the room that don't have an in room description and that * request a default "...is here" with "#". * * TODO Is this right? */ count = 0; trail = -1; for (npc = 0; npc < gs_npc_count(game); npc++) { if (npc_in_room(game, npc, room)) { const sc_char *description; /* Print name for descriptions marked '#'. */ description = lib_get_npc_inroom_text(game, npc); if (!sc_strempty(description) && !sc_strcasecmp(description, "#")) { if (count > 0) { if (count > 1) pf_buffer_string(filter, ", "); else { pf_buffer_character(filter, '\n'); pf_new_sentence(filter); } lib_print_npc_np(game, trail); } trail = npc; count++; } } } if (count >= 1) { if (count == 1) { pf_buffer_character(filter, '\n'); pf_new_sentence(filter); lib_print_npc_np(game, trail); pf_buffer_string(filter, " is here"); } else { pf_buffer_string(filter, " and "); lib_print_npc_np(game, trail); pf_buffer_string(filter, " are here"); } pf_buffer_string(filter, ".\n"); } } /* * lib_print_room_description() * * Print out the long description for a given room. */ void lib_print_room_description(sc_gameref_t game, sc_int room) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_bool showobjects, is_described, is_suppressed; sc_int alt_count, alt, start, event; /* Get count of room alternates. */ vt_key[0].string = "Rooms"; vt_key[1].integer = room; vt_key[2].string = "Alts"; alt_count = prop_get_child_count(bundle, "I<-sis", vt_key); /* Start with no description, and get our starting point in the alts list. */ is_described = FALSE; start = lib_find_starting_alt(game, room); /* Print the standard description unless a start alt indicates not. */ if (start == -1) is_suppressed = FALSE; else { sc_int method; vt_key[3].integer = start; vt_key[4].string = "DisplayRoom"; method = prop_get_integer(bundle, "I<-sisis", vt_key); is_suppressed = (method == 0); } if (!is_suppressed) { const sc_char *description; vt_key[0].string = "Rooms"; vt_key[1].integer = room; vt_key[2].string = "Long"; description = prop_get_string(bundle, "S<-sis", vt_key); if (!sc_strempty(description)) { pf_buffer_string(filter, description); is_described = TRUE; } vt_key[2].string = "Res"; res_handle_resource(game, "sis", vt_key); } /* Ensure that we're back to handling room alts. */ vt_key[0].string = "Rooms"; vt_key[1].integer = room; vt_key[2].string = "Alts"; /* * Run forwards through all alts lower than our starting point, or all alts * if no starting point overrider found. */ showobjects = TRUE; for (alt = (start != -1) ? start : 0; alt < alt_count; alt++) { /* Ignore all non-method-2 alts except for the starter. */ if (alt != start) { sc_int method; vt_key[3].integer = alt; vt_key[4].string = "DisplayRoom"; method = prop_get_integer(bundle, "I<-sisis", vt_key); if (method != 2) continue; } if (lib_use_room_alt(game, room, alt)) { const sc_char *m1; sc_int hideobjects; vt_key[3].integer = alt; vt_key[4].string = "M1"; m1 = prop_get_string(bundle, "S<-sisis", vt_key); if (!sc_strempty(m1)) { if (is_described) pf_buffer_string(filter, " "); pf_buffer_string(filter, m1); is_described = TRUE; } vt_key[4].string = "Res1"; res_handle_resource(game, "sisis", vt_key); vt_key[4].string = "HideObjects"; hideobjects = prop_get_integer(bundle, "I<-sisis", vt_key); if (hideobjects == 1) showobjects = FALSE; } else { const sc_char *m2; vt_key[3].integer = alt; vt_key[4].string = "M2"; m2 = prop_get_string(bundle, "S<-sisis", vt_key); if (!sc_strempty(m2)) { if (is_described) pf_buffer_string(filter, " "); pf_buffer_string(filter, m2); is_described = TRUE; } vt_key[4].string = "Res2"; res_handle_resource(game, "sisis", vt_key); } } /* Print out any relevant event look text. */ for (event = 0; event < gs_event_count(game); event++) { if (gs_event_state(game, event) == ES_RUNNING && evt_can_see_event(game, event)) { const sc_char *looktext; vt_key[0].string = "Events"; vt_key[1].integer = event; vt_key[2].string = "LookText"; looktext = prop_get_string(bundle, "S<-sis", vt_key); if (is_described) pf_buffer_string(filter, " "); pf_buffer_string(filter, looktext); is_described = TRUE; vt_key[2].string = "Res"; vt_key[3].integer = 1; res_handle_resource(game, "sisi", vt_key); } } if (is_described) pf_buffer_character(filter, '\n'); /* Finally, print room contents. */ if (showobjects) lib_print_room_contents(game, room); } /* * lib_can_go() * * Return TRUE if the player can move in the given direction. */ static sc_bool lib_can_go(sc_gameref_t game, sc_int room, sc_int direction) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_int restriction; sc_bool is_restricted = FALSE; /* Set up invariant parts of key. */ vt_key[0].string = "Rooms"; vt_key[1].integer = room; vt_key[2].string = "Exits"; vt_key[3].integer = direction; /* Check for any movement restrictions. */ vt_key[4].string = "Var1"; restriction = prop_get_integer(bundle, "I<-sisis", vt_key) - 1; if (restriction >= 0) { sc_int type; if (lib_trace) sc_trace("Library: hit move restriction\n"); /* Get restriction type. */ vt_key[4].string = "Var3"; type = prop_get_integer(bundle, "I<-sisis", vt_key); switch (type) { case 0: { /* Task type restriction */ sc_int check; /* Get the expected completion state. */ vt_key[4].string = "Var2"; check = prop_get_integer(bundle, "I<-sisis", vt_key); if (lib_trace) { sc_trace("Library: task %ld, check %ld\n", restriction, check); } /* Restrict if task isn't done/not done as expected. */ if ((check != 0) == gs_task_done(game, restriction)) is_restricted = TRUE; break; } case 1: { /* Object state restriction */ sc_int object, check, openable; /* Get the target object. */ object = obj_stateful_object(game, restriction); /* Get the expected object state. */ vt_key[4].string = "Var2"; check = prop_get_integer(bundle, "I<-sisis", vt_key); if (lib_trace) sc_trace("Library: object %ld, check %ld\n", object, check); /* Check openable and lockable objects. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Openable"; openable = prop_get_integer(bundle, "I<-sis", vt_key); if (openable > 0) { sc_int lockable; /* See if lockable. */ vt_key[2].string = "Key"; lockable = prop_get_integer(bundle, "I<-sis", vt_key); if (lockable >= 0) { /* Lockable. */ if (check <= 2) { if (gs_object_openness(game, object) != check + 5) is_restricted = TRUE; } else { if (gs_object_state(game, object) != check - 2) is_restricted = TRUE; } } else { /* Not lockable, though openable. */ if (check <= 1) { if (gs_object_openness(game, object) != check + 5) is_restricted = TRUE; } else { if (gs_object_state(game, object) != check - 1) is_restricted = TRUE; } } } else { /* Not openable. */ if (gs_object_state(game, object) != check + 1) is_restricted = TRUE; } break; } default: break; } } /* Return TRUE if not restricted. */ return !is_restricted; } /* List of direction names, for printing and counting exits. */ static const sc_char *const DIRNAMES_4[] = { "north", "east", "south", "west", "up", "down", "in", "out", NULL }; static const sc_char *const DIRNAMES_8[] = { "north", "east", "south", "west", "up", "down", "in", "out", "northeast", "southeast", "southwest", "northwest", NULL }; /* * lib_cmd_print_room_exits() * * Print a list of exits from the player room. */ sc_bool lib_cmd_print_room_exits(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[4]; sc_bool eightpointcompass; const sc_char *const *dirnames; sc_int count, index_, trail; /* Decide on four or eight point compass names list. */ vt_key[0].string = "Globals"; vt_key[1].string = "EightPointCompass"; eightpointcompass = prop_get_boolean(bundle, "B<-ss", vt_key); dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4; /* Poll for an exit for each valid direction name. */ count = 0; trail = -1; for (index_ = 0; dirnames[index_]; index_++) { sc_vartype_t vt_rvalue; vt_key[0].string = "Rooms"; vt_key[1].integer = gs_playerroom(game); vt_key[2].string = "Exits"; vt_key[3].integer = index_; if (prop_get(bundle, "I<-sisi", &vt_rvalue, vt_key) && lib_can_go(game, gs_playerroom(game), index_)) { if (count > 0) { if (count == 1) { /* Vary text slightly for DispFirstRoom. */ if (game->turns == 0) pf_buffer_string(filter, "There are exits "); else pf_buffer_string(filter, lib_select_response(game, "You can move ", "I can move ", "%player% can move ")); } else pf_buffer_string(filter, ", "); pf_buffer_string(filter, dirnames[trail]); } trail = index_; count++; } } if (count >= 1) { if (count == 1) { /* Vary text slightly for DispFirstRoom. */ if (game->turns == 0) pf_buffer_string(filter, "There is an exit "); else pf_buffer_string(filter, lib_select_response(game, "You can only move ", "I can only move ", "%player% can only move ")); } else pf_buffer_string(filter, " and "); pf_buffer_string(filter, dirnames[trail]); pf_buffer_string(filter, ".\n"); } else { pf_buffer_string(filter, lib_select_response(game, "You can't go in any direction!\n", "I can't go in any direction!\n", "%player% can't go in any direction!\n")); } return TRUE; } /* * lib_describe_player_room() * * Print out details of the player room, in brief if verbose not set and the * room has already been visited. */ static void lib_describe_player_room(sc_gameref_t game, sc_bool force_verbose) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; /* Print the room name. */ lib_print_room_name(game, gs_playerroom(game)); /* Print other room details if applicable. */ if (force_verbose || game->verbose || !gs_room_seen(game, gs_playerroom(game))) { sc_bool showexits; /* Print room description, and objects and NPCs. */ lib_print_room_description(game, gs_playerroom(game)); /* Print exits if the ShowExits global requests it. */ vt_key[0].string = "Globals"; vt_key[1].string = "ShowExits"; showexits = prop_get_boolean(bundle, "B<-ss", vt_key); if (showexits) { pf_buffer_character(filter, '\n'); lib_cmd_print_room_exits(game); } } } /* * lib_cmd_look() * * Command handler for "look" command. */ sc_bool lib_cmd_look(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_character(filter, '\n'); lib_describe_player_room(game, TRUE); return TRUE; } /* * lib_cmd_quit() * * Called on "quit". Exits from the game main loop. */ sc_bool lib_cmd_quit(sc_gameref_t game) { if (if_confirm(SC_CONF_QUIT)) game->is_running = FALSE; game->is_admin = TRUE; return TRUE; } /* * lib_cmd_restart() * * Called on "restart". Exits from the game main loop with restart * request set. */ sc_bool lib_cmd_restart(sc_gameref_t game) { if (if_confirm(SC_CONF_RESTART)) { game->is_running = FALSE; game->do_restart = TRUE; } game->is_admin = TRUE; return TRUE; } /* * lib_cmd_undo() * * Called on "undo". Restores any undo game or memo to the main game. */ sc_bool lib_cmd_undo(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_memo_setref_t memento = gs_get_memento(game); /* If an undo buffer is available, restore it. */ if (game->undo_available) { gs_copy(game, game->undo); game->undo_available = FALSE; lib_print_room_name(game, gs_playerroom(game)); pf_buffer_string(filter, "[The previous turn has been undone.]\n"); /* Undo can't properly unravel layered sounds... */ game->stop_sound = TRUE; } /* * If there is no undo buffer, try to restore one saved previously in a * memo. If that works, treat as for restore from file, since that's * effectively what it is. */ else if (memo_load_game(memento, game)) { lib_print_room_name(game, gs_playerroom(game)); pf_buffer_string(filter, "[The previous turn has been undone.]\n"); game->is_running = FALSE; game->do_restore = TRUE; } /* If no undo buffer and memo restore failed, there's no undo available. */ else if (game->turns == 0) pf_buffer_string(filter, "You can't undo what hasn't been done.\n"); else pf_buffer_string(filter, "Sorry, no more undo is available.\n"); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_history_common() * lib_cmd_history_number() * lib_cmd_history() * * Prints a history of saved commands for the game. Print directly rather * than using the printfilter to avoid possible clashes with ALRs. */ static sc_bool lib_cmd_history_common(sc_gameref_t game, sc_int limit) { const sc_var_setref_t vars = gs_get_vars(game); const sc_memo_setref_t memento = gs_get_memento(game); sc_int first, count, timestamp; /* * The runner main loop will add an entry for the "history" command that * got us here, but it hasn't done so yet. To keep the history list * accurate for recalling commands, we add a surrogate "history" command * to the history here, and remove it when we've done listing. This matches * the c-shell, which always shows 'history' listed last. */ timestamp = var_get_elapsed_seconds(vars); memo_save_command(memento, "[history]", timestamp, game->turns); /* Decide on the first history to display; all if limit is 0 or less. */ if (limit > 0) { /* * Get a count of the history length recorded. Because of the surrogate * "history" above, this is always at least one. From this, choose a * start point for the display; all if not enough history. */ count = memo_get_command_count(memento); first = (count > limit) ? count - limit : 0; } else first = 0; if_print_string("These are your most recent game commands:\n\n"); /* Display history starting at the first entry determined above. */ memo_first_command(memento); for (count = 0; memo_more_commands(memento); count++) { const sc_char *command; sc_int sequence, turns; /* Obtain the history entry, and write if included. */ memo_next_command(memento, &command, &sequence, ×tamp, &turns); if (count >= first) { sc_int hr, min, sec; sc_char buffer[64]; /* Write the history entry sequence. */ sprintf(buffer, "%4ld -- Time ", sequence); if_print_string(buffer); /* Separate the timestamp out into components. */ hr = timestamp / SECS_PER_HOUR; min = (timestamp % SECS_PER_HOUR) / MINS_PER_HOUR; sec = timestamp % SECS_PER_MINUTE; /* Print playing time as "[HHh ][M]Mm SSs". */ if (hr > 0) sprintf(buffer, "%ldh %02ldm %02lds", hr, min, sec); else sprintf(buffer, "%ldm %02lds", min, sec); if_print_string(buffer); /* Follow up with the turns count, and the command string itself. */ sprintf(buffer, ", turn %ld : ", turns); if_print_string(buffer); if_print_string(command); if_print_character('\n'); } } /* Remove the surrogate "history"; the main loop will add the real one. */ memo_unsave_command(memento); game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_history_number(sc_gameref_t game) { const sc_var_setref_t vars = gs_get_vars(game); sc_int limit; /* Get requested length of history list, and complain if not valid. */ limit = var_get_ref_number(vars); if (limit < 1) { if_print_string("That's not a valid history length.\n"); game->is_admin = TRUE; return TRUE; } return lib_cmd_history_common(game, limit); } sc_bool lib_cmd_history(sc_gameref_t game) { return lib_cmd_history_common(game, 0); } /* * lib_cmd_again() * lib_cmd_redo_number() * lib_cmd_redo_text_last_common() * lib_cmd_redo_text() * lib_cmd_redo_last() * * The first function is called on "again", and simply sets the game do_again * flag. The others allow the user to select a command from the history list * to re-run. */ sc_bool lib_cmd_again(sc_gameref_t game) { game->do_again = TRUE; game->redo_sequence = 0; game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_redo_number(sc_gameref_t game) { const sc_var_setref_t vars = gs_get_vars(game); const sc_memo_setref_t memento = gs_get_memento(game); sc_int sequence; /* * Get the history sequence entry requested and validate it. The sequence * may be positive (absolute) or negative (relative to history end), but * not zero. */ sequence = var_get_ref_number(vars); if (sequence != 0 && memo_find_command(memento, sequence)) { game->do_again = TRUE; game->redo_sequence = sequence; } else { if_print_string("No matching entry found in the command history.\n"); /* * This is a failed redo, but returning FALSE will cause the game's * unknown command message to come up. However, returning TRUE will * cause the runner main loop to add this to its history, and at some * point a "redo 7" could cause problems (say, when it's at sequence 7, * where it'll cause an infinite loop). To work round this, here we'll * return a redo_sequence _without_ do_again, and have the runner catch * that as an indication not to save the command in its history. Sorry * for the ugliness. */ game->do_again = FALSE; game->redo_sequence = INTEGER_MAX; } game->is_admin = TRUE; return TRUE; } static sc_bool lib_cmd_redo_text_last_common(sc_gameref_t game, const sc_char *target) { const sc_memo_setref_t memento = gs_get_memento(game); sc_bool is_do_last, is_contains; sc_int length, matched_sequence; /* Make a special case of "!!", rerun the final command in the history. */ is_do_last = (strcmp(target, "!") == 0); /* * Differentiate starts-with and contains searches, setting is_contains and * advancing by one if the target begins '?' (word search). Note target * string length. */ is_contains = (target[0] == '?'); target += is_contains ? 1 : 0; length = strlen(target); /* If there's no text left to search for, reject this call now. */ if (length == 0) { if_print_string("No matching entry found in the command history.\n"); /* As with failed numeric redo above, special-case this return. */ game->do_again = FALSE; game->redo_sequence = INTEGER_MAX; game->is_admin = TRUE; return TRUE; } /* * Search saved commands for one that matches the target string in the * required way. We want to return the most recently saved match, so ideally * we'd search backwards, but the iterator is only forwards, so we do it the * hard way. */ matched_sequence = 0; memo_first_command(memento); while (memo_more_commands(memento)) { const sc_char *command; sc_int sequence, timestamp, turns; sc_bool is_matched; /* Get the command; only command and sequence are relevant. */ memo_next_command(memento, &command, &sequence, ×tamp, &turns); /* * If this is the "!!" special case, match everything. Otherwise, * either search the command for the target, or match if the command * begins with the target. */ if (is_do_last) is_matched = TRUE; else if (is_contains) { sc_int index_; /* Search this command for an occurrence of target anywhere. */ is_matched = FALSE; for (index_ = strlen(command) - length; index_ >= 0; index_--) { if (sc_strncasecmp(command + index_, target, length) == 0) { is_matched = TRUE; break; } } } else is_matched = (sc_strncasecmp(command, target, length) == 0); /* If the command matched the target criteria, note it and continue. */ if (is_matched) matched_sequence = sequence; } /* If we found a match, set the redo values accordingly. */ if (matched_sequence > 0) { game->do_again = TRUE; game->redo_sequence = matched_sequence; } else { if_print_string("No matching entry found in the command history.\n"); /* As with failed numeric redo above, special-case this return. */ game->do_again = FALSE; game->redo_sequence = INTEGER_MAX; } game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_redo_text(sc_gameref_t game) { const sc_var_setref_t vars = gs_get_vars(game); /* Call the common redo with the referenced text from %text%. */ return lib_cmd_redo_text_last_common(game, var_get_ref_text(vars)); } sc_bool lib_cmd_redo_last(sc_gameref_t game) { /* Call the common redo with, literally, "!", forming "!!" . */ return lib_cmd_redo_text_last_common(game, "!"); } /* * lib_cmd_hints() * * Called on "hints". Requests the interface to display any available hints. */ sc_bool lib_cmd_hints(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int task; sc_bool game_has_hints; /* * Check for the presence of any game hints at all, no matter whether the * task is runnable or not. */ game_has_hints = FALSE; for (task = 0; task < gs_task_count(game); task++) { if (task_has_hints(game, task)) { game_has_hints = TRUE; break; } } /* If the game has hints, display any relevant ones. */ if (game_has_hints) { if (run_hint_iterate(game, NULL)) { if (if_confirm(SC_CONF_VIEW_HINTS)) if_display_hints(game); } else pf_buffer_string(filter, "There are currently no hints available.\n"); } else { pf_buffer_string(filter, "There are no hints available for this adventure.\n"); pf_buffer_string(filter, "You're just going to have to work it out for" " yourself...\n"); } game->is_admin = TRUE; return TRUE; } /* * lib_print_string_bold() * lib_print_string_italics() * * Convenience helpers for printing licensing and game information. */ static void lib_print_string_bold(const sc_char *string) { if_print_tag(SC_TAG_BOLD, ""); if_print_string(string); if_print_tag(SC_TAG_ENDBOLD, ""); } static void lib_print_string_italics(const sc_char *string) { if_print_tag(SC_TAG_ITALICS, ""); if_print_string(string); if_print_tag(SC_TAG_ENDITALICS, ""); } /* * lib_cmd_help() * lib_cmd_license() * * A form of standard help output for games that don't define it themselves, * and the GPL licensing. Print directly rather than using the printfilter * to avoid possible clashes with ALRs. */ sc_bool lib_cmd_help(sc_gameref_t game) { if_print_string( "These are some of the typical commands used in this adventure:\n\n"); if_print_string( " [N]orth, [E]ast, [S]outh, [W]est, [U]p, [D]own, [In], [O]ut," " [L]ook, [Exits]\n E[x]amine , [Get ]," " [Drop ], [...it], [...all]\n [Where is ]\n" " [Give to ], [Open...], [Close...]," " [Ask about ]\n" " [Wear ], [Remove ], [I]nventory\n" " [Put into ], [Put onto ]\n"); if_print_string("\nUse the "); lib_print_string_italics("Save"); if_print_string(", "); lib_print_string_italics("Restore"); if_print_string(", "); lib_print_string_italics("Undo"); if_print_string(", and "); lib_print_string_italics("Quit"); if_print_string( " commands to save and restore games, undo a move, and leave the " " game. Use "); lib_print_string_italics("History"); if_print_string(" and "); lib_print_string_italics("Redo"); if_print_string( " to view and repeat recent game commands.\n"); if_print_string("\nThe "); lib_print_string_italics("Hint"); if_print_string(" command displays any game hints, "); lib_print_string_italics("Notify"); if_print_string(" provides score change notification, and "); lib_print_string_italics("Verbose"); if_print_string(" and "); lib_print_string_italics("Brief"); if_print_string(" control room descriptions.\n"); if_print_string("\nUse "); lib_print_string_italics("License"); if_print_string( " to view SCARE's licensing terms and conditions, and "); lib_print_string_italics("Version"); if_print_string( " to print both SCARE's and the game's version number.\n"); game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_license(sc_gameref_t game) { lib_print_string_bold("SCARE"); if_print_string(" is "); lib_print_string_italics( "Copyright (C) 2003-2008 Simon Baldwin and Mark J. Tilford"); if_print_string(".\n\n"); if_print_string( "This program is free software; you can redistribute it and/or modify" " it under the terms of version 2 of the GNU General Public License" " as published by the Free Software Foundation.\n\n"); if_print_string( "This program is distributed in the hope that it will be useful, but "); lib_print_string_bold("WITHOUT ANY WARRANTY"); if_print_string("; without even the implied warranty of "); lib_print_string_bold("MERCHANTABILITY"); if_print_string(" or "); lib_print_string_bold("FITNESS FOR A PARTICULAR PURPOSE"); if_print_string( ". See the GNU General Public License for more details.\n\n"); if_print_string( "You should have received a copy of the GNU General Public License" " along with this program; if not, write to the Free Software" " Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301" " USA\n\n"); if_print_string("Please report any bugs, omissions, or misfeatures to "); lib_print_string_italics("simon_baldwin@yahoo.com"); if_print_string(".\n"); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_information() * * Display a few small pieces of game information, done by a dialog GUI * in real Adrift. Prints directly rather than using the printfilter to * avoid possible clashes with ALRs. */ sc_bool lib_cmd_information(sc_gameref_t game) { const sc_prop_setref_t bundle = gs_get_bundle(game); const sc_var_setref_t vars = gs_get_vars(game); sc_vartype_t vt_key[2]; const sc_char *gamename, *compile_date, *gameauthor; sc_char *filtered; vt_key[0].string = "Globals"; vt_key[1].string = "GameName"; gamename = prop_get_string(bundle, "S<-ss", vt_key); filtered = pf_filter_for_info(gamename, vars); pf_strip_tags(filtered); if_print_string("\""); if_print_string(!sc_strempty(filtered) ? filtered : "Untitled"); if_print_string("\""); sc_free(filtered); vt_key[0].string = "CompileDate"; compile_date = prop_get_string(bundle, "S<-s", vt_key); if (!sc_strempty(compile_date)) { if_print_string(", "); if_print_string(compile_date); } vt_key[0].string = "Globals"; vt_key[1].string = "GameAuthor"; gameauthor = prop_get_string(bundle, "S<-ss", vt_key); filtered = pf_filter_for_info(gameauthor, vars); pf_strip_tags(filtered); if_print_string(", "); if_print_string(!sc_strempty(filtered) ? filtered : "Anonymous"); if_print_string(".\n"); sc_free(filtered); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_clear() * * Clear the main game window (almost). */ sc_bool lib_cmd_clear(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_tag(filter, SC_TAG_CLS); pf_buffer_string(filter, "Screen cleared.\n"); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_statusline() * * Display the status line as would be shown by the Runner. Useful for * interpreter builds that can't offer a true status line. Prints directly * rather than using the printfilter to avoid possible clashes with ALRs. */ sc_bool lib_cmd_statusline(sc_gameref_t game) { const sc_char *name, *author, *room, *status; sc_int score; /* * Retrieve the game's name and author, the description of the current * game room, and any formatted game status line. */ run_get_attributes(game, &name, &author, NULL, NULL, &score, NULL, &room, &status, NULL, NULL, NULL, NULL); /* If nothing is yet determined, print the game name and author. */ if (!room || sc_strempty(room)) { if_print_string(name); if_print_string(" | "); if_print_string(author); } else { /* Print the player location, and a separator. */ if_print_string(room); if_print_string(" | "); /* If the game offers a status line, print it, otherwise the score. */ if (status && !sc_strempty(status)) if_print_string(status); else { sc_char buffer[32]; if_print_string("Score: "); sprintf(buffer, "%ld", score); if_print_string(buffer); } } if_print_character('\n'); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_version() * * Display the "Runner version". Prints directly rather than using the * printfilter to avoid possible clashes with ALRs. */ sc_bool lib_cmd_version(sc_gameref_t game) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key; sc_char buffer[64]; sc_int major, minor, point; const sc_char *version; if_print_string("SCARE version "); if_print_string(SCARE_VERSION SCARE_PATCH_LEVEL); if_print_string(" [Adrift "); major = SCARE_EMULATION / 1000; minor = (SCARE_EMULATION % 1000) / 100; point = SCARE_EMULATION % 100; sprintf(buffer, "%ld.%02ld.%02ld", major, minor, point); if_print_string(buffer); if_print_string(" compatible], "); vt_key.string = "VersionString"; version = prop_get_string(bundle, "S<-s", &vt_key); if_print_string("Generator version "); if_print_string(version); if_print_string(".\n"); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_wait() * lib_cmd_wait_number() * * Set game waitcounter to a count of turns for which the main loop will run * without taking input. Many Adrift Runners ignore any WaitTurns setting in * the game, and use always use one; this might make a game misbehave, so to * try to cover this case we supply 'wait N' as a player control to override * the game's setting. The latter prints directly rather than using the * printfilter to avoid possible clashes with ALRs. */ sc_bool lib_cmd_wait(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; sc_int waitturns; /* Note if wait turns is different from the game's setting. */ vt_key[0].string = "Globals"; vt_key[1].string = "WaitTurns"; waitturns = prop_get_integer(bundle, "I<-ss", vt_key); if (waitturns != game->waitturns) { sc_char buffer[32]; pf_buffer_string(filter, "("); sprintf(buffer, "%ld", game->waitturns); pf_buffer_string(filter, buffer); pf_buffer_string(filter, game->waitturns == 1 ? " turn)\n" : " turns)\n"); } /* Reset the wait counter to the current waitturns setting. */ game->waitcounter = game->waitturns; pf_buffer_string(filter, "Time passes...\n"); return TRUE; } sc_bool lib_cmd_wait_number(sc_gameref_t game) { const sc_var_setref_t vars = gs_get_vars(game); sc_int waitturns; sc_char buffer[32]; /* Get and validate the waitturns setting. */ waitturns = var_get_ref_number(vars); if (waitturns < 1 || waitturns > 20) { if_print_string("You can only wait between 1 and 20 turns.\n"); game->is_admin = TRUE; return TRUE; } /* Update the game setting, and confirm for the player. */ game->waitturns = waitturns; if_print_string("The game will now wait "); sprintf(buffer, "%ld", waitturns); if_print_string(buffer); if_print_string(waitturns == 1 ? " turn" : " turns"); if_print_string(" for each 'wait' command you enter.\n"); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_verbose() * lib_cmd_brief() * * Set/clear game verbose flag. Print directly rather than using the * printfilter to avoid possible clashes with ALRs. */ sc_bool lib_cmd_verbose(sc_gameref_t game) { /* Set game verbose flag and return. */ game->verbose = TRUE; if_print_string("The game is now in its "); if_print_tag(SC_TAG_ITALICS, ""); if_print_string("verbose"); if_print_tag(SC_TAG_ENDITALICS, ""); if_print_string(" mode, which always gives long descriptions of locations" " (even if you've been there before).\n"); game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_brief(sc_gameref_t game) { /* Clear game verbose flag and return. */ game->verbose = FALSE; if_print_string("The game is now in its "); if_print_tag(SC_TAG_ITALICS, ""); if_print_string("brief"); if_print_tag(SC_TAG_ENDITALICS, ""); if_print_string(" mode, which gives long descriptions of places never" " before visited and short descriptions otherwise.\n"); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_notify_on_off() * lib_cmd_notify() * * Set/clear/query game score change notification flag. Print directly * rather than using the printfilter to avoid possible clashes with ALRs. */ sc_bool lib_cmd_notify_on_off(sc_gameref_t game) { const sc_var_setref_t vars = gs_get_vars(game); const sc_char *control; /* Get the text following the notify command, and check for "on"/"off". */ control = var_get_ref_text(vars); if (sc_strcasecmp(control, "on") == 0) { /* Set score change notification. */ game->notify_score_change = TRUE; if_print_string("Game score change notification is now "); if_print_tag(SC_TAG_ITALICS, ""); if_print_string("on"); if_print_tag(SC_TAG_ENDITALICS, ""); if_print_string(", and the game will tell you of any changes in the" " score.\n"); } else if (sc_strcasecmp(control, "off") == 0) { /* Clear score change notification. */ game->notify_score_change = FALSE; if_print_string("Game score change notification is now "); if_print_tag(SC_TAG_ITALICS, ""); if_print_string("off"); if_print_tag(SC_TAG_ENDITALICS, ""); if_print_string(", and the game will be silent on changes in the" " score.\n"); } else { if_print_string("Use 'notify on' or 'notify off' to control game" " score notification.\n"); } game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_notify(sc_gameref_t game) { /* Report the current state of notification. */ if_print_string("Game score change notification is "); if_print_tag(SC_TAG_ITALICS, ""); if_print_string(game->notify_score_change ? "on" : "off"); if_print_tag(SC_TAG_ENDITALICS, ""); if (game->notify_score_change) { if_print_string(", and the game will tell you of any changes in the" " score.\n"); } else { if_print_string(", and the game will be silent on changes in the" " score.\n"); } game->is_admin = TRUE; return TRUE; } /* * lib_cmd_time() * lib_cmd_date() * * Print elapsed game time, and smart-alec "date" response. The Adrift * Runner responds here with the system time and date, but we'll do something * different. */ sc_bool lib_cmd_time(sc_gameref_t game) { const sc_var_setref_t vars = gs_get_vars(game); sc_uint timestamp; sc_int hr, min, sec; sc_char buffer[64]; /* Get elapsed game time and convert to hour, minutes, and seconds. */ timestamp = var_get_elapsed_seconds(vars); hr = timestamp / SECS_PER_HOUR; min = (timestamp % SECS_PER_HOUR) / MINS_PER_HOUR; sec = timestamp % SECS_PER_MINUTE; if (hr > 0) sprintf(buffer, "%ldh %02ldm %02lds", hr, min, sec); else sprintf(buffer, "%ldm %02lds", min, sec); /* Print the game's elapsed time. */ if_print_string("You have been running the game for "); if_print_string(buffer); if_print_string(".\n"); game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_date(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "Maybe we should just be good friends.\n"); return TRUE; } /* * Direction enumeration. Used by movement commands, to multiplex them all * into a single function. The values are explicit to ensure they match * enumerations in the game data. */ enum { DIR_NORTH = 0, DIR_EAST = 1, DIR_SOUTH = 2, DIR_WEST = 3, DIR_UP = 4, DIR_DOWN = 5, DIR_IN = 6, DIR_OUT = 7, DIR_NORTHEAST = 8, DIR_SOUTHEAST = 9, DIR_SOUTHWEST = 10, DIR_NORTHWEST = 11 }; /* * lib_go() * * Central movement command, called by all movement handlers. */ static sc_bool lib_go(sc_gameref_t game, sc_int direction) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5], vt_rvalue; sc_bool eightpointcompass, is_trapped, is_exitable[12]; sc_int destination, index_; const sc_char *const *dirnames; /* Decide on four or eight point compass names list. */ vt_key[0].string = "Globals"; vt_key[1].string = "EightPointCompass"; eightpointcompass = prop_get_boolean(bundle, "B<-ss", vt_key); dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4; /* Start by seeing if there are any exits at all available. */ is_trapped = TRUE; for (index_ = 0; dirnames[index_]; index_++) { vt_key[0].string = "Rooms"; vt_key[1].integer = gs_playerroom(game); vt_key[2].string = "Exits"; vt_key[3].integer = index_; if (prop_get(bundle, "I<-sisi", &vt_rvalue, vt_key) && lib_can_go(game, gs_playerroom(game), index_)) { is_exitable[index_] = TRUE; is_trapped = FALSE; } else is_exitable[index_] = FALSE; } if (is_trapped) { pf_buffer_string(filter, lib_select_response(game, "You can't go in any direction!\n", "I can't go in any direction!\n", "%player% can't go in any direction!\n")); return TRUE; } /* * Check for the exit, and if it doesn't exist, refuse, and list the possible * options. */ vt_key[0].string = "Rooms"; vt_key[1].integer = gs_playerroom(game); vt_key[2].string = "Exits"; vt_key[3].integer = direction; vt_key[4].string = "Dest"; if (prop_get(bundle, "I<-sisis", &vt_rvalue, vt_key)) destination = vt_rvalue.integer - 1; else { sc_int count, trail; pf_buffer_string(filter, lib_select_response(game, "You can't go in that direction, but you can move ", "I can't go in that direction, but I can move ", "%player% can't go in that direction, but can move ")); /* List available exits, found in exit test loop earlier. */ count = 0; trail = -1; for (index_ = 0; dirnames[index_]; index_++) { if (is_exitable[index_]) { if (count > 0) { if (count > 1) pf_buffer_string(filter, ", "); pf_buffer_string(filter, dirnames[trail]); } trail = index_; count++; } } if (count >= 1) { if (count > 1) pf_buffer_string(filter, " and "); pf_buffer_string(filter, dirnames[trail]); } pf_buffer_string(filter, ".\n"); return TRUE; } /* Check for any movement restrictions. */ if (!lib_can_go(game, gs_playerroom(game), direction)) { pf_buffer_string(filter, lib_select_response(game, "You can't go in that direction (at present).\n", "I can't go in that direction (at present).\n", "%player% can't go in that direction (at present).\n")); return TRUE; } if (lib_trace) { sc_trace("Library: moving player from %ld to %ld\n", gs_playerroom(game), destination); } /* Indicate if getting off something or standing up first. */ if (gs_playerparent(game) != -1) { pf_buffer_string(filter, "(Getting off "); lib_print_object_np(game, gs_playerparent(game)); pf_buffer_string(filter, " first)\n"); } else if (gs_playerposition(game) != 0) pf_buffer_string(filter, "(Standing up first)\n"); /* Confirm and then make move. */ pf_buffer_string(filter, lib_select_response(game, "You move ", "I move ", "%player% moves ")); pf_buffer_string(filter, dirnames[direction]); pf_buffer_string(filter, ".\n"); gs_move_player_to_room(game, destination); /* Describe the new room and return. */ lib_describe_player_room(game, FALSE); return TRUE; } /* * lib_cmd_go_*() * * Direction-specific movement commands. */ sc_bool lib_cmd_go_north(sc_gameref_t game) { return lib_go(game, DIR_NORTH); } sc_bool lib_cmd_go_east(sc_gameref_t game) { return lib_go(game, DIR_EAST); } sc_bool lib_cmd_go_south(sc_gameref_t game) { return lib_go(game, DIR_SOUTH); } sc_bool lib_cmd_go_west(sc_gameref_t game) { return lib_go(game, DIR_WEST); } sc_bool lib_cmd_go_up(sc_gameref_t game) { return lib_go(game, DIR_UP); } sc_bool lib_cmd_go_down(sc_gameref_t game) { return lib_go(game, DIR_DOWN); } sc_bool lib_cmd_go_in(sc_gameref_t game) { return lib_go(game, DIR_IN); } sc_bool lib_cmd_go_out(sc_gameref_t game) { return lib_go(game, DIR_OUT); } sc_bool lib_cmd_go_northeast(sc_gameref_t game) { return lib_go(game, DIR_NORTHEAST); } sc_bool lib_cmd_go_southeast(sc_gameref_t game) { return lib_go(game, DIR_SOUTHEAST); } sc_bool lib_cmd_go_northwest(sc_gameref_t game) { return lib_go(game, DIR_NORTHWEST); } sc_bool lib_cmd_go_southwest(sc_gameref_t game) { return lib_go(game, DIR_SOUTHWEST); } /* * lib_compare_rooms() * * Helper for lib_cmd_go_room(). Compare the name of the passed in room * with the string passed in, and return TRUE if they match. The routine * requires that string is filtered, stripped, trimmed and normalized. */ static sc_bool lib_compare_rooms(sc_gameref_t game, sc_int room, const sc_char *string) { const sc_var_setref_t vars = gs_get_vars(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_char *name, *compare_name; sc_bool status; /* Get the name of the room, and filter it down to a plain string. */ name = pf_filter(lib_get_room_name(game, room), vars, bundle); pf_strip_tags(name); sc_normalize_string(sc_trim_string(name)); /* Bypass any prefix on the room name. */ if (sc_compare_word(name, "a", 1)) compare_name = name + 1; else if (sc_compare_word(name, "an", 2)) compare_name = name + 2; else if (sc_compare_word(name, "the", 3)) compare_name = name + 3; else compare_name = name; sc_trim_string(compare_name); /* Compare strings, then free the allocated name. */ status = sc_strcasecmp(compare_name, string) == 0; sc_free(name); return status; } /* * lib_cmd_go_room() * * A weak replica of the Runner's claimed ability to go to a named room via * rooms that have already been visited using a shortest-path search. This * version scans adjacent rooms for accessibility, and then generates the * required directional move for any unique match. * * Note that rooms can have the same name after they've been cleaned up for * text comparisons, for example, two "Manor Grounds" at the start of Humbug, * differentiated within the game with trailing "" components. */ sc_bool lib_cmd_go_room(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5], vt_rvalue; sc_bool eightpointcompass, is_trapped, is_ambiguous; sc_int direction, destination, index_; const sc_char *const *dirnames; sc_char *name, *compare_name; /* Determine the requested room, and filter it down to a plain string. */ name = pf_filter(var_get_ref_text(vars), vars, bundle); pf_strip_tags(name); sc_normalize_string(sc_trim_string(name)); /* Bypass any prefix on the request room name. */ if (sc_compare_word(name, "a", 1)) compare_name = name + 1; else if (sc_compare_word(name, "an", 2)) compare_name = name + 2; else if (sc_compare_word(name, "the", 3)) compare_name = name + 3; else compare_name = name; sc_trim_string(compare_name); /* See if the named room is the current player room. */ if (lib_compare_rooms(game, gs_playerroom(game), compare_name)) { pf_buffer_string(filter, "You are already there!\n"); sc_free(name); return TRUE; } /* Decide on four or eight point compass names list. */ vt_key[0].string = "Globals"; vt_key[1].string = "EightPointCompass"; eightpointcompass = prop_get_boolean(bundle, "B<-ss", vt_key); dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4; /* Search adjacent and available rooms for a name match. */ is_trapped = TRUE; is_ambiguous = FALSE; direction = -1; destination = -1; for (index_ = 0; dirnames[index_]; index_++) { vt_key[0].string = "Rooms"; vt_key[1].integer = gs_playerroom(game); vt_key[2].string = "Exits"; vt_key[3].integer = index_; if (prop_get(bundle, "I<-sisi", &vt_rvalue, vt_key) && lib_can_go(game, gs_playerroom(game), index_)) { is_trapped = FALSE; /* * Room is available. Compare its name with that requested provided * that it's a location we've not already accepted (that is, some * rooms are reachable by multiple directions, such as both "south" * and "out"). */ vt_key[4].string = "Dest"; if (prop_get(bundle, "I<-sisis", &vt_rvalue, vt_key)) { sc_int location; location = vt_rvalue.integer - 1; if (location != destination && lib_compare_rooms(game, location, compare_name)) { if (direction != -1) is_ambiguous = TRUE; direction = index_; destination = location; } } } } sc_free(name); /* If trapped or it's unclear where to go, handle these cases. */ if (is_trapped) { pf_buffer_string(filter, lib_select_response(game, "You can't go in any direction!\n", "I can't go in any direction!\n", "%player% can't go in any direction!\n")); return TRUE; } else if (is_ambiguous) { pf_buffer_string(filter, "I'm not clear about where you want to go." " Please try using just a direction.\n"); pf_buffer_character(filter, '\n'); lib_cmd_print_room_exits(game); return TRUE; } /* If no match, note it, otherwise handle as standard directional move. */ if (direction == -1) { pf_buffer_string(filter, "I don't know how to get there from here.\n"); pf_buffer_character(filter, '\n'); lib_cmd_print_room_exits(game); return TRUE; } return lib_go(game, direction); } /* * lib_cmd_examine_self() * * Show the long description of a player. */ sc_bool lib_cmd_examine_self(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; sc_int task, object, count, trail; const sc_char *description, *position = NULL; /* Get selection task. */ vt_key[0].string = "Globals"; vt_key[1].string = "Task"; task = prop_get_integer(bundle, "I<-ss", vt_key) - 1; /* Select either the main or the alternate description. */ if (task >= 0 && gs_task_done(game, task)) vt_key[1].string = "AltDesc"; else vt_key[1].string = "PlayerDesc"; /* Print the description, or default response. */ description = prop_get_string(bundle, "S<-ss", vt_key); if (!sc_strempty(description)) pf_buffer_string(filter, description); else { pf_buffer_string(filter, lib_select_response(game, "You are as well as can be expected," " considering the circumstances.", "I am as well as can be expected," " considering the circumstances.", "%player% is as well as can be expected," " considering the circumstances.")); } /* If not just standing on the floor, say more. */ switch (gs_playerposition(game)) { case 0: position = lib_select_response(game, "You are standing", "I am standing", "%player% is standing"); break; case 1: position = lib_select_response(game, "You are sitting down", "I am sitting down", "%player% is sitting down"); break; case 2: position = lib_select_response(game, "You are lying down", "I am lying down", "%player% is lying down"); break; default: break; } if (position && !(gs_playerposition(game) == 0 && gs_playerparent(game) == -1)) { pf_buffer_string(filter, " "); pf_buffer_string(filter, position); if (gs_playerparent(game) != -1) { pf_buffer_string(filter, " on "); lib_print_object_np(game, gs_playerparent(game)); } pf_buffer_character(filter, '.'); } /* Find and list each object worn by the player. */ count = 0; trail = -1; for (object = 0; object < gs_object_count(game); object++) { if (gs_object_position(game, object) == OBJ_WORN_PLAYER) { if (count > 0) { if (count == 1) { pf_buffer_string(filter, lib_select_response(game, " You are wearing ", " I am wearing ", " %player% is wearing ")); } else pf_buffer_string(filter, ", "); lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { pf_buffer_string(filter, lib_select_response(game, " You are wearing ", " I am wearing ", " %player% is wearing ")); } else pf_buffer_string(filter, " and "); lib_print_object(game, trail); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_disambiguate_npc() * * Filter, then search the set of NPC matches. If only one matched, note * and return it. If multiple matched, print a disambiguation message and * the list, and return -1 with *is_ambiguous TRUE. If none matched, return * -1 with *is_ambiguous FALSE if requested, otherwise print a message then * return -1. */ static sc_int lib_disambiguate_npc(sc_gameref_t game, const sc_char *verb, sc_bool *is_ambiguous) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); sc_int count, index_, npc, listed; /* * Filter out all referenced NPCs not actually visible or seen. Count the * number of NPCs remaining as referenced by the last command, and note the * last referenced NPC, for where count is 1. */ count = 0; npc = -1; for (index_ = 0; index_ < gs_npc_count(game); index_++) { if (game->npc_references[index_] && gs_npc_seen(game, index_) && npc_in_room(game, index_, gs_playerroom(game))) { count++; npc = index_; } else game->npc_references[index_] = FALSE; } /* If the reference is unambiguous, set in variables and return it. */ if (count == 1) { /* Set this NPC as the referenced character. */ var_set_ref_character(vars, npc); /* Return, setting no ambiguity. */ if (is_ambiguous) *is_ambiguous = FALSE; return npc; } /* If nothing referenced, return no NPC. */ if (count == 0) { if (is_ambiguous) *is_ambiguous = FALSE; else { pf_buffer_string(filter, "Please be more clear, who do you want to "); pf_buffer_string(filter, verb); pf_buffer_string(filter, "?\n"); } return -1; } /* The NPC reference is ambiguous, so list the choices. */ pf_buffer_string(filter, "Please be more clear, who do you want to "); pf_buffer_string(filter, verb); pf_buffer_string(filter, "? "); pf_new_sentence(filter); listed = 0; for (index_ = 0; index_ < gs_npc_count(game); index_++) { if (game->npc_references[index_]) { lib_print_npc_np(game, index_); listed++; if (listed < count) pf_buffer_string(filter, (listed < count - 1) ? ", " : " or "); } } pf_buffer_string(filter, "?\n"); /* Return no NPC for an ambiguous reference. */ if (is_ambiguous) *is_ambiguous = TRUE; return -1; } /* * lib_disambiguate_object_common() * lib_disambiguate_object() * lib_disambiguate_object_extended() * * Filter, then search the set of object matches. If only one matched, note * and return it. If multiple matched, print a disambiguation message and * the list, and return -1 with *is_ambiguous TRUE. If none matched, return * -1 with *is_ambiguous FALSE if requested, otherwise print a message then * return -1. * * Extended disambiguation operates as normal disambiguation, except that if * normal disambiguation returns more than one object, the resolver function, * if supplied, is used to see if the multiple objects can be resolved into * just one object. The resolver function can normally be the same as the * function used to filter objects for multiple references. */ 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) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); sc_int count, index_, object, listed; /* * Filter out all referenced objects not actually visible or seen. Count * the number of objects remaining as referenced by the last command, and * note the last referenced object, for where count is 1. */ count = 0; object = -1; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (game->object_references[index_] && gs_object_seen(game, index_) && obj_indirectly_in_room(game, index_, gs_playerroom(game))) { count++; object = index_; } else game->object_references[index_] = FALSE; } /* * If this reference is ambiguous and a resolver was supplied, try to * resolve it unambiguously by calling the resolver filter on the remaining * set references. */ if (resolver && count > 1) { sc_int retry_count; /* * Search for objects accepted by the resolver filter, but don't filter * references just yet. Again, note the last referenced. */ retry_count = 0; object = -1; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (game->object_references[index_] && resolver(game, index_, resolver_arg)) { retry_count++; object = index_; } } /* See if we narrowed the field without eliminating every object. */ if (retry_count > 0 && retry_count < count) { /* * If we got down to a single object, the ambiguity is resolved. * In this case, set count to 1 so that 'object' is returned. */ if (retry_count == 1) count = retry_count; else { /* * We got down to fewer objects; reduce references so that the * disambiguation message is clearer. Note that here we still * leave with count greater than 1. */ count = 0; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (game->object_references[index_] && resolver(game, index_, resolver_arg)) count++; else game->object_references[index_] = FALSE; } } } } /* If the reference is unambiguous, set in variables and return it. */ if (count == 1) { /* Set this object as referenced. */ var_set_ref_object(vars, object); /* Return, setting no ambiguity. */ if (is_ambiguous) *is_ambiguous = FALSE; return object; } /* If nothing referenced, return no object. */ if (count == 0) { if (is_ambiguous) *is_ambiguous = FALSE; else { pf_buffer_string(filter, "Please be more clear, what do you want to "); pf_buffer_string(filter, verb); pf_buffer_string(filter, "?\n"); } return -1; } /* The object reference is ambiguous, so list the choices. */ pf_buffer_string(filter, "Please be more clear, what do you want to "); pf_buffer_string(filter, verb); pf_buffer_string(filter, "? "); pf_new_sentence(filter); listed = 0; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (game->object_references[index_]) { lib_print_object_np(game, index_); listed++; if (listed < count) pf_buffer_string(filter, (listed < count - 1) ? ", " : " or "); } } pf_buffer_string(filter, "?\n"); /* Return no object for an ambiguous reference. */ if (is_ambiguous) *is_ambiguous = TRUE; return -1; } static sc_int lib_disambiguate_object(sc_gameref_t game, const sc_char *verb, sc_bool *is_ambiguous) { return lib_disambiguate_object_common(game, verb, NULL, -1, is_ambiguous); } 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) { return lib_disambiguate_object_common(game, verb, resolver, resolver_arg, is_ambiguous); } /* * lib_list_npc_inventory() * * List objects carried and worn by an NPC. */ static sc_bool lib_list_npc_inventory(sc_gameref_t game, sc_int npc, sc_bool is_described) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, count, trail; sc_bool wearing; /* Find and list each object worn by the NPC. */ count = 0; trail = -1; wearing = FALSE; for (object = 0; object < gs_object_count(game); object++) { if (gs_object_position(game, object) == OBJ_WORN_NPC && gs_object_parent(game, object) == npc) { if (count > 0) { if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, npc); pf_buffer_string(filter, " is wearing "); } else pf_buffer_string(filter, ", "); lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, npc); pf_buffer_string(filter, " is wearing "); } else pf_buffer_string(filter, " and "); lib_print_object(game, trail); wearing = TRUE; } /* Find and list each object owned by the NPC. */ count = 0; trail = -1; for (object = 0; object < gs_object_count(game); object++) { if (gs_object_position(game, object) == OBJ_HELD_NPC && gs_object_parent(game, object) == npc) { if (count > 0) { if (count == 1) { if (!wearing) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, npc); } else pf_buffer_string(filter, ", and"); pf_buffer_string(filter, " is carrying "); } else pf_buffer_string(filter, ", "); lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { if (!wearing) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, npc); } else pf_buffer_string(filter, ", and"); pf_buffer_string(filter, " is carrying "); } else pf_buffer_string(filter, " and "); lib_print_object(game, trail); pf_buffer_character(filter, '.'); } else { if (wearing) pf_buffer_character(filter, '.'); } /* Return TRUE if anything worn or carried. */ return wearing || count > 0; } /* * lib_cmd_examine_npc() * * Show the long description of the most recently referenced NPC, and a * list of what they're wearing and carrying. */ sc_bool lib_cmd_examine_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[4]; sc_int npc, task, resource; sc_bool is_ambiguous; const sc_char *description; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "examine", &is_ambiguous); if (npc == -1) return is_ambiguous; /* Get selection task. */ vt_key[0].string = "NPCs"; vt_key[1].integer = npc; vt_key[2].string = "Task"; task = prop_get_integer(bundle, "I<-sis", vt_key) - 1; /* Select either the main or the alternate description. */ if (task >= 0 && gs_task_done(game, task)) { vt_key[2].string = "AltText"; resource = 1; } else { vt_key[2].string = "Descr"; resource = 0; } /* Print the description, or a default message if none. */ description = prop_get_string(bundle, "S<-sis", vt_key); if (!sc_strempty(description)) pf_buffer_string(filter, description); else { pf_buffer_string(filter, "There's nothing special about "); lib_print_npc_np(game, npc); pf_buffer_character(filter, '.'); } /* Handle any associated resource. */ vt_key[2].string = "Res"; vt_key[3].integer = resource; res_handle_resource(game, "sisi", vt_key); /* Print what the NPC is wearing and carrying. */ lib_list_npc_inventory(game, npc, TRUE); pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_list_in_object_normal() * * List the objects in a given container object, normal format listing. */ static sc_bool lib_list_in_object_normal(sc_gameref_t game, sc_int container, sc_bool is_described) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, count, trail; /* List out the containers contained in this container. */ count = 0; trail = -1; for (object = 0; object < gs_object_count(game); object++) { /* Contained? */ if (gs_object_position(game, object) == OBJ_IN_OBJECT && gs_object_parent(game, object) == container) { if (count > 0) { if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_buffer_string(filter, "Inside "); lib_print_object_np(game, container); pf_buffer_string(filter, lib_select_plurality(game, trail, " is ", " are ")); } else pf_buffer_string(filter, ", "); /* Print out the current list object. */ lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_buffer_string(filter, "Inside "); lib_print_object_np(game, container); pf_buffer_string(filter, lib_select_plurality(game, trail, " is ", " are ")); } else pf_buffer_string(filter, " and "); /* Print out the final object. */ lib_print_object(game, trail); pf_buffer_character(filter, '.'); } /* Return TRUE if anything listed. */ return count > 0; } /* * lib_list_in_object_alternate() * * List the objects in a given container object, alternate format listing. */ static sc_bool lib_list_in_object_alternate(sc_gameref_t game,sc_int container, sc_bool is_described) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, count, trail; /* List out the objects contained in this object. */ count = 0; trail = -1; for (object = 0; object < gs_object_count(game); object++) { /* Contained? */ if (gs_object_position(game, object) == OBJ_IN_OBJECT && gs_object_parent(game, object) == container) { if (count > 0) { if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); } else pf_buffer_string(filter, ", "); /* Print out the current list object. */ lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object(game, trail); pf_buffer_string(filter, lib_select_plurality(game, trail, " is inside ", " are inside ")); } else { pf_buffer_string(filter, " and "); lib_print_object(game, trail); pf_buffer_string(filter, " are inside "); } /* Print out the container. */ lib_print_object_np(game, container); pf_buffer_character(filter, '.'); } /* Return TRUE if anything listed. */ return count > 0; } /* * lib_list_in_object() * * List the objects in a given container object. * * TODO The Adrift Runner has two distinct styles it uses for listing objects * within a container, but which it picks at any one point is, frankly, a * mystery. The selection below seems to work with the few games checked for * this, and in particular works with the ALR magic in "To Hell in a Hamper", * but it's almost certainly wrong. Or, at minimum, incomplete. */ static sc_bool lib_list_in_object(sc_gameref_t game, sc_int container, sc_bool is_described) { sc_bool use_alternate_format = FALSE; /* * Switch if the object is static and part of an NPC or the player, or if * the count of contained objects in a dynamic container is exactly one. */ if (obj_is_static(game, container)) { sc_int object_position; object_position = gs_object_position(game, container); if (object_position == OBJ_PART_NPC || object_position == OBJ_PART_PLAYER) use_alternate_format = TRUE; } else { sc_int object, count; count = 0; for (object = 0; object < gs_object_count(game); object++) { if (gs_object_position(game, object) == OBJ_IN_OBJECT && gs_object_parent(game, object) == container) count++; if (count > 1) break; } if (count == 1) use_alternate_format = TRUE; } /* List contained objects using the selected handler. */ return use_alternate_format ? lib_list_in_object_alternate(game, container, is_described) : lib_list_in_object_normal(game, container, is_described); } /* * lib_list_on_object() * * List the objects on a given surface object. */ static sc_bool lib_list_on_object(sc_gameref_t game, sc_int supporter, sc_bool is_described) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, count, trail; /* List out the objects standing on this object. */ count = 0; trail = -1; for (object = 0; object < gs_object_count(game); object++) { /* Standing on? */ if (gs_object_position(game, object) == OBJ_ON_OBJECT && gs_object_parent(game, object) == supporter) { if (count > 0) { if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); } else pf_buffer_string(filter, ", "); /* Print out the current list object. */ lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object(game, trail); pf_buffer_string(filter, lib_select_plurality(game, trail, " is on ", " are on ")); } else { pf_buffer_string(filter, " and "); lib_print_object(game, trail); pf_buffer_string(filter, " are on "); } /* Print out the surface. */ lib_print_object_np(game, supporter); pf_buffer_character(filter, '.'); } /* Return TRUE if anything listed. */ return count > 0; } /* * lib_list_object_state() * * Describe the state of a stateful object. */ static sc_bool lib_list_object_state(sc_gameref_t game, sc_int object, sc_bool is_described) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_bool is_statussed; sc_char *state; /* Get object statefulness. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "CurrentState"; is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0; /* Ensure this is a stateful object. */ if (is_statussed) { if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is ", " are ")); /* Add object state string. */ state = obj_state_name(game, object); if (state) { pf_buffer_string(filter, state); sc_free(state); pf_buffer_string(filter, "."); } else { sc_error("lib_list_object_state: invalid object state\n"); pf_buffer_string(filter, "[invalid state]."); } } /* Return TRUE if a state was printed. */ return is_statussed; } /* * lib_cmd_examine_object() * * Show the long description of the most recently referenced object. */ sc_bool lib_cmd_examine_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int object, task, openness; sc_bool is_described, is_statussed, is_mentioned, is_ambiguous, should_be; const sc_char *description, *resource; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "examine", &is_ambiguous); if (object == -1) return is_ambiguous; /* Begin assuming no description printed. */ is_described = FALSE; /* * Get selection task and expected state; for the expected task state, FALSE * indicates task completed, TRUE not completed. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Task"; task = prop_get_integer(bundle, "I<-sis", vt_key) - 1; vt_key[2].string = "TaskNotDone"; should_be = !prop_get_boolean(bundle, "B<-sis", vt_key); /* Select either the main or the alternate description. */ if (task >= 0 && gs_task_done(game, task) == should_be) { vt_key[2].string = "AltDesc"; resource = "Res2"; } else { vt_key[2].string = "Description"; resource = "Res1"; } /* Print the description, or a default response. */ description = prop_get_string(bundle, "S<-sis", vt_key); if (!sc_strempty(description)) { pf_buffer_string(filter, description); is_described |= TRUE; } /* Handle any associated resource. */ vt_key[2].string = resource; res_handle_resource(game, "sis", vt_key); /* If the object is openable, print its openness state. */ openness = gs_object_openness(game, object); switch (openness) { case OBJ_OPEN: if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is open.", " are open.")); is_described |= TRUE; break; case OBJ_CLOSED: if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is closed.", " are closed.")); is_described |= TRUE; break; case OBJ_LOCKED: if (is_described) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is locked.", " are locked.")); is_described |= TRUE; break; default: break; } /* Add any extra details for stateful objects. */ vt_key[1].integer = object; vt_key[2].string = "CurrentState"; is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0; if (is_statussed) { vt_key[2].string = "StateListed"; is_mentioned = prop_get_boolean(bundle, "B<-sis", vt_key); if (is_mentioned) is_described |= lib_list_object_state(game, object, is_described); } /* For open container objects, list out what's in them. */ if (obj_is_container(game, object) && openness <= OBJ_OPEN) is_described |= lib_list_in_object(game, object, is_described); /* For surface objects, list out what's on them. */ if (obj_is_surface(game, object)) is_described |= lib_list_on_object(game, object, is_described); /* If nothing yet said, print a default response. */ if (!is_described) { pf_buffer_string(filter, lib_select_response(game, "You see nothing special about ", "I see nothing special about ", "%player% sees nothing special about ")); lib_print_object_np(game, object); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_save_game_references() * lib_restore_game_references() * * Helpers for trying game commands. Save and restore game references * so that parsing game commands doesn't interfere with backend loops that * are working through game references set by prior commands. Saving * references uses the buffer passed in if possible, otherwise allocates * its own buffer; testing the return value shows which happened. */ static sc_bool *lib_save_object_references(sc_gameref_t game, sc_bool buffer[], sc_int length) { sc_int required, available; sc_bool *references; /* * Calculate the required bytes for references, and then either allocate or * use the buffer supplied. */ required = gs_object_count(game) * sizeof(*references); available = length * sizeof(buffer[0]); references = required > available ? (sc_bool *)sc_malloc(required) : buffer; /* Copy over references from the game, and return the saved copy. */ memcpy(references, game->object_references, required); return references; } static void lib_restore_object_references(sc_gameref_t game, const sc_bool references[]) { sc_int bytes; /* Calculate the bytes in the references array, and copy back to the game. */ bytes = gs_object_count(game) * sizeof(references[0]); memcpy(game->object_references, references, bytes); } /* * lib_try_game_command_common() * lib_try_game_command_short() * lib_try_game_command_with_object() * lib_try_game_command_with_npc() * * Try a game command with a standard verb. Used by get and drop handlers * to retry game commands using standard "get " and "drop " commands. This * makes "take/pick up/put down" work with a game's overridden get/drop. */ 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) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_char buffer[LIB_ALLOCATION_AVOIDANCE_SIZE]; sc_bool references_buffer[LIB_ALLOCATION_AVOIDANCE_SIZE]; const sc_char *prefix, *name; sc_char *command; sc_bool *references, status; assert(!is_associate_object || !is_associate_npc); /* Save the game's references, for restore later on. */ references = lib_save_object_references(game, references_buffer, LIB_ALLOCATION_AVOIDANCE_SIZE); /* Get the addressed object's prefix and main name. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Prefix"; prefix = prop_get_string(bundle, "S<-sis", vt_key); vt_key[2].string = "Short"; name = prop_get_string(bundle, "S<-sis", vt_key); /* Construct and try for game commands with a standard verb. */ if (is_associate_object || is_associate_npc) { const sc_char *associate_prefix, *associate_name; sc_int required; /* Get the associate's prefix and main name. */ if (is_associate_object) { vt_key[0].string = "Objects"; vt_key[1].integer = associate; vt_key[2].string = "Prefix"; associate_prefix = prop_get_string(bundle, "S<-sis", vt_key); vt_key[2].string = "Short"; associate_name = prop_get_string(bundle, "S<-sis", vt_key); } else { assert(is_associate_npc); vt_key[0].string = "NPCs"; vt_key[1].integer = associate; vt_key[2].string = "Prefix"; associate_prefix = prop_get_string(bundle, "S<-sis", vt_key); vt_key[2].string = "Name"; associate_name = prop_get_string(bundle, "S<-sis", vt_key); } assert(preposition); required = strlen(verb) + strlen(prefix) + strlen(name) + strlen(preposition) + strlen(associate_prefix) + strlen(associate_name) + 6; command = required > (sc_int) sizeof(buffer) ? (sc_char *)sc_malloc(required) : buffer; /* * Try the command with and without prefixes on both the target object * and the associate. */ sprintf(command, "%s %s %s %s %s %s", verb, prefix, name, preposition, associate_prefix, associate_name); status = run_game_task_commands(game, command); if (!status) { sprintf(command, "%s %s %s %s %s", verb, prefix, name, preposition, associate_name); status = run_game_task_commands(game, command); } if (!status) { sprintf(command, "%s %s %s %s %s", verb, name, preposition, associate_prefix, associate_name); status = run_game_task_commands(game, command); } if (!status) { sprintf(command, "%s %s %s %s", verb, name, preposition, associate_name); status = run_game_task_commands(game, command); } } else { sc_int required; required = strlen(verb) + strlen(prefix) + strlen(name) + 3; command = required > (sc_int) sizeof(buffer) ? (sc_char *)sc_malloc(required) : buffer; /* Try the command with and without prefixes on the addressed object. */ sprintf(command, "%s %s %s", verb, prefix, name); status = run_game_task_commands(game, command); if (!status) { sprintf(command, "%s %s", verb, name); status = run_game_task_commands(game, command); } } /* Restore the game object references back to their state on entry. */ lib_restore_object_references(game, references); /* Free any allocations, and return the game command status. */ if (command != buffer) sc_free(command); if (references != references_buffer) sc_free(references); return status; } static sc_bool lib_try_game_command_short(sc_gameref_t game, const sc_char *verb, sc_int object) { return lib_try_game_command_common(game, verb, object, NULL, -1, FALSE, FALSE); } 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) { return lib_try_game_command_common(game, verb, object, preposition, other_object, TRUE, FALSE); } 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) { return lib_try_game_command_common(game, verb, object, preposition, npc, FALSE, TRUE); } /* * lib_parse_next_object() * * Helper for lib_parse_multiple_objects(). Extracts the next object, if any, * from referenced text, and returns it. Disambiguates any ambiguous objects * using the verb supplied, and sets are_more_objects if we found an object * but there appear to be more following it. */ 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) { const sc_var_setref_t vars = gs_get_vars(game); const sc_char *list; sc_bool is_matched; /* Look for "object" or "object and ...", and set match and more flags. */ list = var_get_ref_text(vars); if (uip_match("%object%", list, game)) { *are_more_objects = FALSE; is_matched = TRUE; } else if (uip_match("%object% and %text%", list, game)) { *are_more_objects = TRUE; is_matched = TRUE; } else is_matched = FALSE; /* If we extracted an object from referenced text, disambiguate. */ if (is_matched) *object = lib_disambiguate_object_extended(game, verb, resolver, resolver_arg, is_ambiguous); else *is_ambiguous = FALSE; /* Return TRUE if we matched anything. */ return is_matched; } /* * lib_parse_multiple_objects() * * Parser for commands that take multiple object targets from a %text% match. * Parses object lists such as "object" and "object and object" and returns * the multiple objects in the game's multiple_references. */ 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) { const sc_filterref_t filter = gs_get_filter(game); sc_int count_, object; sc_bool are_more_objects, is_ambiguous; /* Initialize variables to avoid gcc warnings. */ object = -1; are_more_objects = FALSE; /* Clear all current multiple object references, and the count. */ gs_clear_multiple_references(game); count_ = 0; /* * Parse the first object from the list. If we get nothing here, return * FALSE if it didn't look like a multiple object list, TRUE if ambiguous. * Beyond here, we always return TRUE, since after this point _something_ * looked believable... */ if (!lib_parse_next_object(game, verb, resolver, resolver_arg, &object, &are_more_objects, &is_ambiguous)) return FALSE; else if (object == -1) { if (is_ambiguous) { /* * Return TRUE, with zero count, to cause caller to return. We get * here if the first parsed object was ambiguous. In this case, * the disambiguation has printed a message, so we want our caller * to simply return TRUE to indicate that the command was handled, * albeit not fully successfully. */ *count = count_; return TRUE; } else { /* * No object matched after disambiguation, so return FALSE to have * our caller ignore the command. */ return FALSE; } } /* Mark this first object as referenced in the return array. */ game->multiple_references[object] = TRUE; count_++; /* Now parse each additional object from the list. */ while (are_more_objects) { sc_int last_object; /* * If no next object, leave the loop. If no disambiguation message * then it was probably garble, so print a message for that case. We * also catch repeated objects here. */ last_object = object; if (!lib_parse_next_object(game, verb, resolver, resolver_arg, &object, &are_more_objects, &is_ambiguous) || object == -1 || game->multiple_references[object]) { if (!is_ambiguous) { pf_buffer_string(filter, "I only understood you as far as wanting to "); pf_buffer_string(filter, verb); pf_buffer_character(filter, ' '); lib_print_object_np(game, last_object); pf_buffer_string(filter, ".\n"); } /* Zero count to indicate an error somewhere in the list. */ count_ = 0; break; } /* Mark the object as referenced in the return array. */ game->multiple_references[object] = TRUE; count_++; } /* We found at least enough of an object list to say we matched. */ *count = count_; return TRUE; } /* * lib_apply_multiple_filter() * lib_apply_except_filter() * * Apply filters for multiple object frontends. Transfer multiple object * references into standard object references, using the supplied filter. * The first is inclusive, the second exclusive. */ 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) { sc_int count, object, references_; /* Clear all object references initially. */ gs_clear_object_references(game); /* * Find objects included by the filter, and transfer the reference of each * from the multiple references into standard references. */ count = 0; references_ = references ? *references : 0; for (object = 0; object < gs_object_count(game); object++) { if (filter(game, object, filter_arg)) { /* Transfer the reference. */ if (game->multiple_references[object]) { game->object_references[object] = TRUE; count++; game->multiple_references[object] = FALSE; references_--; } } } /* Copy back the updated reference count, return count. */ if (references) *references = references_; return count; } 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) { sc_int count, object, references_; /* Clear all object references initially. */ gs_clear_object_references(game); /* * Find objects included by the filter, and transfer the reference of each * from the multiple references into standard references. */ count = 0; references_ = references ? *references : 0; for (object = 0; object < gs_object_count(game); object++) { if (filter(game, object, filter_arg)) { /* If excepted, remove from exceptions, else add to references. */ if (game->multiple_references[object]) { game->multiple_references[object] = FALSE; references_--; } else { game->object_references[object] = TRUE; count++; } } } /* Copy back the updated reference count, return count. */ if (references) *references = references_; return count; } /* * lib_cmd_count() * * Display player weight and size limits and amounts currently carried. */ sc_bool lib_cmd_count(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int index_, size, weight; sc_char buffer[32]; /* Sum sizes for objects currently held or worn by player. */ size = 0; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (gs_object_position(game, index_) == OBJ_HELD_PLAYER || gs_object_position(game, index_) == OBJ_WORN_PLAYER) size += obj_get_size(game, index_); } /* Sum weights for objects currently held or worn by player. */ weight = 0; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (gs_object_position(game, index_) == OBJ_HELD_PLAYER || gs_object_position(game, index_) == OBJ_WORN_PLAYER) weight += obj_get_weight(game, index_); } /* Print the player limits and amounts used. */ pf_buffer_string(filter, "Size: You have "); sprintf(buffer, "%ld", size); pf_buffer_string(filter, buffer); pf_buffer_string(filter, ". The most you can hold is "); sprintf(buffer, "%ld", obj_get_player_size_limit(game)); pf_buffer_string(filter, buffer); pf_buffer_string(filter, ".\n"); pf_buffer_string(filter, "Weight: You have "); sprintf(buffer, "%ld", weight); pf_buffer_string(filter, buffer); pf_buffer_string(filter, ". The most you can hold is "); sprintf(buffer, "%ld", obj_get_player_weight_limit(game)); pf_buffer_string(filter, buffer); pf_buffer_string(filter, ".\n"); game->is_admin = TRUE; return TRUE; } /* * lib_object_too_heavy() * * Return TRUE if the given object is too heavy for the player to carry. */ static sc_bool lib_object_too_heavy(sc_gameref_t game, sc_int object, sc_bool *is_portable) { sc_int player_limit, index_, weight, object_weight; /* Get the player limit and the given object weight. */ player_limit = obj_get_player_weight_limit(game); object_weight = obj_get_weight(game, object); /* Sum weights for objects currently held or worn by player. */ weight = 0; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (gs_object_position(game, index_) == OBJ_HELD_PLAYER || gs_object_position(game, index_) == OBJ_WORN_PLAYER) weight += obj_get_weight(game, index_); } /* If requested, return object portability. */ if (is_portable) *is_portable = !(object_weight > player_limit); /* Return TRUE if the new object exceeds limit. */ return weight + object_weight > player_limit; } /* * lib_object_too_large() * * Return TRUE if the given object is too large for the player to carry. */ static sc_bool lib_object_too_large(sc_gameref_t game, sc_int object, sc_bool *is_portable) { sc_int player_limit, index_, size, object_size; /* Get the player limit and the given object size. */ player_limit = obj_get_player_size_limit(game); object_size = obj_get_size(game, object); /* Sum sizes for objects currently held or worn by player. */ size = 0; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (gs_object_position(game, index_) == OBJ_HELD_PLAYER || gs_object_position(game, index_) == OBJ_WORN_PLAYER) size += obj_get_size(game, index_); } /* If requested, return object portability. */ if (is_portable) *is_portable = !(object_size > player_limit); /* Return TRUE if the new object exceeds limit. */ return size + object_size > player_limit; } /* * lib_cmd_take_npc() * * Reject attempts to take an npc. */ sc_bool lib_cmd_take_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int npc; sc_bool is_ambiguous; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "take", &is_ambiguous); if (npc == -1) return is_ambiguous; /* Reject this attempt. */ pf_buffer_string(filter, "I don't think "); lib_print_npc_np(game, npc); pf_buffer_string(filter, " would appreciate being handled.\n"); return TRUE; } /* * lib_take_backend_common() * * Common backend handler for taking objects. Takes all objects currently * referenced in the game, trying game commands first, and then moving other * unhandled objects to the player inventory. * * Objects to action are flagged in object_references; objects requested but * deemed not actionable are flagged in multiple_references. */ static void lib_take_backend_common(sc_gameref_t game, sc_int associate, sc_bool is_associate_object, sc_bool is_associate_npc) { const sc_filterref_t filter = gs_get_filter(game); sc_int object_count, object, count, trail, total, npc; sc_int too_heavy, too_large; sc_bool too_heavy_portable, too_large_portable, has_printed; assert(!is_associate_object || !is_associate_npc); /* Initialize our notions of anything exceeding player capacity. */ too_heavy_portable = too_large_portable = FALSE; too_large = too_heavy = -1; /* * Try game commands for all referenced objects first. If any succeed, * remove that reference from the list. At the same time, filter out and * flag any object that takes us over the player's capacity. We report * only the first. */ has_printed = FALSE; object_count = gs_object_count(game); for (object = 0; object < object_count; object++) { sc_bool status; if (!game->object_references[object]) continue; /* * If the object is inside or on something already held by the player, * capacity checks are meaningless. */ if (!((gs_object_position(game, object) == OBJ_IN_OBJECT || gs_object_position(game, object) == OBJ_ON_OBJECT) && obj_indirectly_held_by_player(game, gs_object_parent(game, object)))) { sc_bool is_portable; /* * See if the object takes us beyond capacity. If it does and it's * the first of its kind, note it and continue. */ if (lib_object_too_heavy(game, object, &is_portable)) { if (too_heavy == -1) { too_heavy = object; too_heavy_portable = is_portable; } game->object_references[object] = FALSE; continue; } if (lib_object_too_large(game, object, &is_portable)) { if (too_large == -1) { too_large = object; too_large_portable = is_portable; } game->object_references[object] = FALSE; continue; } } /* Now try for a game command, using the associate if supplied. */ if (is_associate_object) status = lib_try_game_command_with_object(game, "get", object, "from", associate); else if (is_associate_npc) status = lib_try_game_command_with_npc(game, "get", object, "from", associate); else status = lib_try_game_command_short(game, "get", object); if (status) { game->object_references[object] = FALSE; has_printed = TRUE; } } /* * We attempt acquisition of get-able objects here only for cases where * there is either no associate, or where the associate is an object. If * the associate is an NPC, we're going to refuse all acquisitions later * on, by forcing object references. */ total = 0; if (!is_associate_npc) { sc_int parent, start, limit; /* * Attempt to acquire each remaining get-able object in turn, looping * on each possible parent object in turn, with an initial parent of * -1 for objects not contained or supported. * * If we're dealing with only objects from a known container or * supporter, eliminate all but one iteration of the parent search. */ start = is_associate_object ? associate : -1; limit = is_associate_object ? associate : object_count - 1; for (parent = start; parent <= limit; parent++) { count = 0; trail = -1; for (object = 0; object < object_count; object++) { sc_bool is_portable; if (!game->object_references[object]) continue; /* * If parent is -1, ignore contained objects, otherwise ignore * objects not contained, or if contained, not contained by the * current parent. */ if (parent == -1) { if (gs_object_position(game, object) == OBJ_IN_OBJECT || gs_object_position(game, object) == OBJ_ON_OBJECT) continue; } else { if (!(gs_object_position(game, object) == OBJ_IN_OBJECT || gs_object_position(game, object) == OBJ_ON_OBJECT)) continue; if (gs_object_parent(game, object) != parent) continue; } /* * Here we have to repeat capacity checks. As objects are * acquired more and more of the player's capacity gets used up. * This means a check directly before each acquisition. */ if (parent == -1 || !obj_indirectly_held_by_player(game, parent)) { if (lib_object_too_heavy(game, object, &is_portable)) { if (too_heavy == -1) { too_heavy = object; too_heavy_portable = is_portable; } continue; } if (lib_object_too_large(game, object, &is_portable)) { if (too_large == -1) { too_large = object; too_large_portable = is_portable; } continue; } } if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, total == 0 ? "\n" : " "); if (parent == -1) pf_buffer_string(filter, lib_select_response(game, "You pick up ", "I pick up ", "%player% picks up ")); else pf_buffer_string(filter, lib_select_response(game, "You take ", "I take ", "%player% takes ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; gs_object_player_get(game, object); } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, total == 0 ? "\n" : " "); if (parent == -1) pf_buffer_string(filter, lib_select_response(game, "You pick up ", "I pick up ", "%player% picks up ")); else pf_buffer_string(filter, lib_select_response(game, "You take ", "I take ", "%player% takes ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); if (parent != -1) { pf_buffer_string(filter, " from "); lib_print_object_np(game, parent); } pf_buffer_character(filter, '.'); } total += count; has_printed |= count > 0; } } /* * If we ran out of capacity, either in weight or in size, print the * details. Note that we currently only report the first object of any * type to go over capacity. */ if (too_heavy != -1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, too_heavy); pf_buffer_string(filter, lib_select_plurality(game, too_heavy, " is", " are")); pf_buffer_string(filter, lib_select_response(game, " too heavy for you to carry", " too heavy for me to carry", " too heavy for %player% to carry")); if (too_heavy_portable) pf_buffer_string(filter, " at the moment"); pf_buffer_character(filter, '.'); has_printed |= TRUE; } else if (too_large != -1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "Your hands are full", "My hands are full", "%player%'s hands are full")); if (too_large_portable) pf_buffer_string(filter, " at the moment"); pf_buffer_character(filter, '.'); has_printed |= TRUE; } /* * Note any remaining multiple references left out of the take operation. * This is some workload... * * First, deal with the case where we have an associated object. */ if (is_associate_object) { count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (gs_object_position(game, object) == OBJ_HELD_PLAYER || gs_object_position(game, object) == OBJ_WORN_PLAYER) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, trail); } else pf_buffer_string(filter, ", "); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, trail); pf_buffer_string(filter, lib_select_plurality(game, trail, " is not ", " are not ")); } else { pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_string(filter, " are not "); } if (obj_is_container(game, associate)) { pf_buffer_string(filter, "in "); if (obj_is_surface(game, associate)) pf_buffer_string(filter, "or on "); } else pf_buffer_string(filter, "on "); lib_print_object_np(game, associate); pf_buffer_character(filter, '.'); } has_printed |= count > 0; } /* * Now, deal with the case where we have an associated NPC. Once this * case is handled, we can force the object references so that the code * that follows on from here will report errors taking all objects. * * Note that this means that we can never successfully take an object * from an NPC; that'll have to happen via a game's own commands. */ if (is_associate_npc) { count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (gs_object_position(game, object) == OBJ_PART_NPC) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, associate); pf_buffer_string(filter, " is not carrying "); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, associate); pf_buffer_string(filter, " is not carrying "); lib_print_object_np(game, trail); } else { pf_buffer_string(filter, " or "); lib_print_object_np(game, trail); } pf_buffer_character(filter, '!'); } has_printed |= count > 0; /* * Merge any remaining object references into multiple references, * so that succeeding code complains about the inability to acquire * these objects. */ for (object = 0; object < object_count; object++) { game->multiple_references[object] |= game->object_references[object]; game->object_references[object] = FALSE; } } /* * The remainder of this routine is common error reporting for both object * and NPC associates (and also for no associates). */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (gs_object_position(game, object) != OBJ_HELD_PLAYER) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You've already got ", "I've already got ", "%player% already has ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You've already got ", "I've already got ", "%player% already has ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '!'); } has_printed |= count > 0; count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (gs_object_position(game, object) != OBJ_WORN_PLAYER) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You're already wearing ", "I'm already wearing ", "%player% is already wearing ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You're already wearing ", "I'm already wearing ", "%player% is already wearing ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '!'); } has_printed |= count > 0; for (npc = 0; npc < gs_npc_count(game); npc++) { count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (gs_object_position(game, object) != OBJ_HELD_NPC && gs_object_position(game, object) != OBJ_WORN_NPC) continue; if (gs_object_parent(game, object) != npc) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, gs_object_parent(game, trail)); pf_buffer_string(filter, lib_select_response(game, " refuses to give you ", " refuses to give me ", " refuses to give %player% ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_npc_np(game, gs_object_parent(game, trail)); pf_buffer_string(filter, lib_select_response(game, " refuses to give you ", " refuses to give me ", " refuses to give %player% ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '!'); } has_printed |= count > 0; } count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You can't take ", "I can't take ", "%player% can't take ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You can't take ", "I can't take ", "%player% can't take ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '!'); } } /* * lib_take_backend() * lib_take_from_object_backend() * lib_take_from_npc_backend() * * Facets of lib_take_backend_common(). Provide backend handling for either * the plain "take" handlers, or the "take from " handlers. */ static void lib_take_backend(sc_gameref_t game) { lib_take_backend_common(game, -1, FALSE, FALSE); } static void lib_take_from_object_backend(sc_gameref_t game, sc_int associate) { lib_take_backend_common(game, associate, TRUE, FALSE); } static void lib_take_from_npc_backend(sc_gameref_t game, sc_int associate) { lib_take_backend_common(game, associate, FALSE, TRUE); } /* * lib_take_filter() * lib_take_not_associated_filter() * * Helper functions for deciding if an object may be acquired in this context. * Returns TRUE if an object may be acquired, FALSE otherwise. */ static sc_bool lib_take_filter(sc_gameref_t game, sc_int object, sc_int unused) { assert(unused == -1); /* * To be take-able, an object must be visible in the room, not static, * and not already held or worn by the player or an NPC. */ return obj_indirectly_in_room(game, object, gs_playerroom(game)) && !obj_is_static(game, object) && !(gs_object_position(game, object) == OBJ_HELD_PLAYER || gs_object_position(game, object) == OBJ_WORN_PLAYER) && !(gs_object_position(game, object) == OBJ_HELD_NPC || gs_object_position(game, object) == OBJ_WORN_NPC); } static sc_bool lib_take_not_associated_filter(sc_gameref_t game, sc_int object, sc_int unused) { assert(unused == -1); /* In addition to other checks, the object may not be in or on an object. */ return lib_take_filter(game, object, -1) && !(gs_object_position(game, object) == OBJ_ON_OBJECT || gs_object_position(game, object) == OBJ_IN_OBJECT); } /* * lib_cmd_take_all() * * Attempt to take all objects currently visible to the player. */ sc_bool lib_cmd_take_all(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_take_not_associated_filter, -1, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_take_backend(game); else pf_buffer_string(filter, "There is nothing to pick up here."); pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_take_except_multiple() * * Take all objects currently available to the player, excepting those listed * in %text%. */ sc_bool lib_cmd_take_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find leave target objects. */ if (!lib_parse_multiple_objects(game, "leave", lib_take_not_associated_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_take_not_associated_filter, -1, &references); if (objects > 0 || references > 0) lib_take_backend(game); else { if (objects == 0) pf_buffer_string(filter, "There is nothing else to pick up here."); else pf_buffer_string(filter, "There is nothing to pick up here."); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_take_multiple() * * Take all objects currently available to the player and listed in %text%. */ sc_bool lib_cmd_take_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find take target objects. */ if (!lib_parse_multiple_objects(game, "take", lib_take_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_take_filter, -1, &references); if (objects > 0 || references > 0) lib_take_backend(game); else pf_buffer_string(filter, "There is nothing to pick up here."); pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_take_from_filter() * * Helper function for deciding if an object may be acquired in this context. * Returns TRUE if an object may be acquired, FALSE otherwise. */ static sc_bool lib_take_from_filter(sc_gameref_t game, sc_int object, sc_int associate) { /* * To be take-able, an object must be either inside or on the specified * object. */ return (gs_object_position(game, object) == OBJ_IN_OBJECT || gs_object_position(game, object) == OBJ_ON_OBJECT) && !obj_is_static(game, object) && gs_object_parent(game, object) == associate; } /* * lib_take_from_empty() * * Common error handling for when nothing is taken from a container or * supporter object. */ static void lib_take_from_empty(sc_gameref_t game, sc_int associate, sc_bool is_except) { const sc_filterref_t filter = gs_get_filter(game); if (obj_is_container(game, associate) && obj_is_surface(game, associate)) { if (gs_object_openness(game, associate) <= OBJ_OPEN) { if (is_except) pf_buffer_string(filter, "There is nothing else in or on "); else pf_buffer_string(filter, "There is nothing in or on "); lib_print_object_np(game, associate); pf_buffer_character(filter, '.'); } else { if (is_except) pf_buffer_string(filter, "There is nothing else on "); else pf_buffer_string(filter, "There is nothing on "); lib_print_object_np(game, associate); if (gs_object_openness(game, associate) == OBJ_LOCKED) pf_buffer_string(filter, " and it is locked."); else pf_buffer_string(filter, " and it is closed."); } } else { if (obj_is_container(game, associate)) { if (gs_object_openness(game, associate) <= OBJ_OPEN) { if (is_except) pf_buffer_string(filter, "There is nothing else inside "); else pf_buffer_string(filter, "There is nothing inside "); lib_print_object_np(game, associate); pf_buffer_character(filter, '.'); } else { pf_new_sentence(filter); lib_print_object_np(game, associate); pf_buffer_string(filter, lib_select_plurality(game, associate, " is ", " are ")); if (gs_object_openness(game, associate) == OBJ_LOCKED) pf_buffer_string(filter, "locked."); else pf_buffer_string(filter, "closed."); } } else { if (is_except) pf_buffer_string(filter, "There is nothing else on "); else pf_buffer_string(filter, "There is nothing on "); lib_print_object_np(game, associate); pf_buffer_character(filter, '.'); } } } /* * lib_take_from_is_valid() * * Validate the supporter requested in "take from" commands. */ static sc_bool lib_take_from_is_valid(sc_gameref_t game, sc_int associate) { const sc_filterref_t filter = gs_get_filter(game); /* Disallow emptying non-container/non-surface objects. */ if (!(obj_is_container(game, associate) || obj_is_surface(game, associate))) { pf_buffer_string(filter, lib_select_response(game, "You can't take anything from ", "I can't take anything from ", "%player% can't take anything from ")); lib_print_object_np(game, associate); pf_buffer_string(filter, ".\n"); return FALSE; } /* If object is a container, and is closed, reject now. */ if (obj_is_container(game, associate) && gs_object_openness(game, associate) > OBJ_OPEN) { pf_new_sentence(filter); lib_print_object_np(game, associate); pf_buffer_string(filter, lib_select_plurality(game, associate, " is closed.\n", " are closed.\n")); return FALSE; } /* Associate is a valid target for "take from". */ return TRUE; } /* * lib_cmd_take_all_from() * * Attempt to take all objects contained in or supported by a given object. */ sc_bool lib_cmd_take_all_from(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int associate, objects; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ associate = lib_disambiguate_object(game, "take from", &is_ambiguous); if (associate == -1) return is_ambiguous; /* Validate the associate object to take from. */ if (!lib_take_from_is_valid(game, associate)) return TRUE; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_take_from_filter, associate, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_take_from_object_backend(game, associate); else lib_take_from_empty(game, associate, FALSE); pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_take_from_except_multiple() * * Take all objects contained in or supported by a given object, excepting * those listed in %text%. */ sc_bool lib_cmd_take_from_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int associate, objects, references; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ associate = lib_disambiguate_object(game, "take from", &is_ambiguous); if (associate == -1) return is_ambiguous; /* Parse the multiple objects list to find leave target objects. */ if (!lib_parse_multiple_objects(game, "leave", lib_take_from_filter, associate, &references)) return FALSE; else if (references == 0) return TRUE; /* Validate the associate object to take from. */ if (!lib_take_from_is_valid(game, associate)) return TRUE; /* As a special case, complain about requests to retain the associate. */ if (game->multiple_references[associate]) { pf_buffer_string(filter, "I only understood you as far as wanting to leave "); lib_print_object_np(game, associate); pf_buffer_string(filter, ".\n"); return TRUE; } /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_take_from_filter, associate, &references); if (objects > 0 || references > 0) lib_take_from_object_backend(game, associate); else lib_take_from_empty(game, associate, TRUE); pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_take_from_multiple() * * Take the objects currently inside an object and listed in %text%. This * function isn't mandatory -- plain "take " works fine with contain- * ers and surfaces, but it's a standard in Adrift so here it is. */ sc_bool lib_cmd_take_from_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int associate, objects, references; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ associate = lib_disambiguate_object(game, "take from", &is_ambiguous); if (associate == -1) return is_ambiguous; /* Parse the multiple objects list to find take target objects. */ if (!lib_parse_multiple_objects(game, "take", lib_take_from_filter, associate, &references)) return FALSE; else if (references == 0) return TRUE; /* Validate the associate object to take from. */ if (!lib_take_from_is_valid(game, associate)) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_take_from_filter, associate, &references); if (objects > 0 || references > 0) lib_take_from_object_backend(game, associate); else lib_take_from_empty(game, associate, FALSE); pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_take_from_npc_filter() * * Helper function for deciding if an object may be acquired in this context. * Returns TRUE if an object may be acquired, FALSE otherwise. */ static sc_bool lib_take_from_npc_filter(sc_gameref_t game, sc_int object, sc_int associate) { /* * To be take-able, an object must be either held or worn by the specified * NPC. */ return (gs_object_position(game, object) == OBJ_HELD_NPC || gs_object_position(game, object) == OBJ_WORN_NPC) && !obj_is_static(game, object) && gs_object_parent(game, object) == associate; } /* * lib_cmd_take_all_from_npc() * * Attempt to take all objects held or worn by a given NPC. */ sc_bool lib_cmd_take_all_from_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int associate, objects; sc_bool is_ambiguous; /* Get the referenced NPC, and if none, consider complete. */ associate = lib_disambiguate_npc(game, "take from", &is_ambiguous); if (associate == -1) return is_ambiguous; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_take_from_npc_filter, associate, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_take_from_npc_backend(game, associate); else { pf_new_sentence(filter); lib_print_npc_np(game, associate); pf_buffer_string(filter, " is not carrying anything!"); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_take_from_npc_except_multiple() * * Attempt to take all objects held or worn by a given NPC, excepting those * listed in %text%. */ sc_bool lib_cmd_take_from_npc_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int associate, objects, references; sc_bool is_ambiguous; /* Get the referenced NPC, and if none, consider complete. */ associate = lib_disambiguate_npc(game, "take from", &is_ambiguous); if (associate == -1) return is_ambiguous; /* Parse the multiple objects list to find leave target objects. */ if (!lib_parse_multiple_objects(game, "leave", lib_take_from_npc_filter, associate, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_take_from_npc_filter, associate, &references); if (objects > 0 || references > 0) lib_take_from_npc_backend(game, associate); else { pf_new_sentence(filter); lib_print_npc_np(game, associate); pf_buffer_string(filter, " is not carrying anything else!"); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_take_from_npc_multiple() * * Attempt to take the objects currently held or worn by an NPC and listed * in %text%. */ sc_bool lib_cmd_take_from_npc_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int associate, objects, references; sc_bool is_ambiguous; /* Get the referenced NPC, and if none, consider complete. */ associate = lib_disambiguate_npc(game, "take from", &is_ambiguous); if (associate == -1) return is_ambiguous; /* Parse the multiple objects list to find take target objects. */ if (!lib_parse_multiple_objects(game, "take", lib_take_from_npc_filter, associate, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_take_from_npc_filter, associate, &references); if (objects > 0 || references > 0) lib_take_from_npc_backend(game, associate); else { pf_new_sentence(filter); lib_print_npc_np(game, associate); pf_buffer_string(filter, " is not carrying anything!"); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_drop_backend() * * Common backend handler for dropping objects. Drops all objects currently * referenced in the game, trying game commands first, and then moving other * unhandled objects to the player room floor. * * Objects to action are flagged in object_references; objects requested but * deemed not actionable are flagged in multiple_references. */ static void lib_drop_backend(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object_count, object, count, trail; sc_bool has_printed; /* * Try game commands for all referenced objects first. If any succeed, * remove that reference from the list. */ has_printed = FALSE; object_count = gs_object_count(game); for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (lib_try_game_command_short(game, "drop", object)) { game->object_references[object] = FALSE; has_printed = TRUE; } } /* Drop every object that remains referenced. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You drop ", "I drop ", "%player% drops ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; gs_object_to_room(game, object, gs_playerroom(game)); } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You drop ", "I drop ", "%player% drops ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } has_printed |= count > 0; /* Note any remaining multiple references left out of the drop operation. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, " or "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } } /* * lib_drop_filter() * * Helper function for deciding if an object may be dropped in this context. * Returns TRUE if an object may be dropped, FALSE otherwise. */ static sc_bool lib_drop_filter(sc_gameref_t game, sc_int object, sc_int unused) { assert(unused == -1); return !obj_is_static(game, object) && gs_object_position(game, object) == OBJ_HELD_PLAYER; } /* * lib_cmd_drop_all() * * Drop all objects currently held by the player. */ sc_bool lib_cmd_drop_all(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_drop_filter, -1, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_drop_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You're not carrying anything.", "I'm not carrying anything.", "%player%'s not carrying anything.")); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_drop_except_multiple() * * Drop all objects currently held by the player, excepting those listed in * %text%. */ sc_bool lib_cmd_drop_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find retain target objects. */ if (!lib_parse_multiple_objects(game, "retain", lib_drop_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_drop_filter, -1, &references); if (objects > 0 || references > 0) lib_drop_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything", "I am not holding anything", "%player% is not holding anything")); if (objects == 0) pf_buffer_string(filter, " else"); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_drop_multiple() * * Drop all objects currently held by the player and listed in %text%. */ sc_bool lib_cmd_drop_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find drop target objects. */ if (!lib_parse_multiple_objects(game, "drop", lib_drop_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_drop_filter, -1, &references); if (objects > 0 || references > 0) lib_drop_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything.", "I am not holding anything.", "%player% is not holding anything.")); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_give_object_npc() * lib_cmd_give_object() * * Attempt to give an object to an NPC. */ sc_bool lib_cmd_give_object_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, npc; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "give", &is_ambiguous); if (object == -1) return is_ambiguous; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "give to", NULL); if (npc == -1) return TRUE; /* Reject if not holding the object offered. */ if (gs_object_position(game, object) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You don't have ", "I don't have ", "%player% doesn't have ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; } /* After all that, the npc is disinterested. */ pf_new_sentence(filter); lib_print_npc_np(game, npc); pf_buffer_string(filter, " doesn't seem interested in "); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } sc_bool lib_cmd_give_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "give", &is_ambiguous); if (object == -1) return is_ambiguous; /* Reject if not holding the object offered. */ if (gs_object_position(game, object) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You don't have ", "I don't have ", "%player% doesn't have ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; } /* After all that, we have to ask (and shouldn't this be "to whom?"). */ pf_buffer_string(filter, "Give "); lib_print_object_np(game, object); pf_buffer_string(filter, " to who?\n"); return TRUE; } /* * lib_wear_backend() * * Common backend handler for wearing objects. Puts on all objects currently * referenced in the game, moving objects to worn by player. * * Objects to action are flagged in object_references; objects requested but * deemed not actionable are flagged in multiple_references. */ static void lib_wear_backend(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object_count, object, count, trail; sc_bool has_printed; /* * Try game commands for all referenced objects first. If any succeed, * remove that reference from the list. */ has_printed = FALSE; object_count = gs_object_count(game); for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (lib_try_game_command_short(game, "wear", object)) { game->object_references[object] = FALSE; has_printed = TRUE; } } /* Wear every object referenced. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You put on ", "I put on ", "%player% puts on ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; gs_object_player_wear(game, object); } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You put on ", "I put on ", "%player% puts on ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } has_printed |= count > 0; /* Note any remaining multiple references left out of the wear operation. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (gs_object_position(game, object) != OBJ_WORN_PLAYER) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are already wearing ", "I am already wearing ", "%player% is already wearing ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are already wearing ", "I am already wearing ", "%player% is already wearing ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } has_printed |= count > 0; count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (gs_object_position(game, object) == OBJ_HELD_PLAYER) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, " or "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } has_printed |= count > 0; count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You can't wear ", "I can't wear ", "%player% can't wear ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You can't wear ", "I can't wear ", "%player% can't wear ")); } else pf_buffer_string(filter, " or "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } } /* * lib_wear_filter() * * Helper function for deciding if an object may be worn in this context. * Returns TRUE if an object may be worn, FALSE otherwise. */ static sc_bool lib_wear_filter(sc_gameref_t game, sc_int object, sc_int unused) { const sc_prop_setref_t bundle = gs_get_bundle(game); assert(unused == -1); /* * The object is wearable if the player is holding it, and it's not static * (static moved to player inventory by event), and if it's marked wearable * in properties. */ if (gs_object_position(game, object) == OBJ_HELD_PLAYER && !obj_is_static(game, object)) { sc_vartype_t vt_key[3]; /* Return wearability from the object properties. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Wearable"; return prop_get_boolean(bundle, "B<-sis", vt_key); } return FALSE; } /* * lib_cmd_wear_all() * * Wear all wearable objects currently held by the player. */ sc_bool lib_cmd_wear_all(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_wear_filter, -1, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_wear_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You're not carrying anything", "I'm not carrying anything", "%player%'s not carrying anything")); pf_buffer_string(filter, " that can be worn."); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_wear_except_multiple() * * Wear all wearable objects currently held by the player, excepting those * listed in %text%. */ sc_bool lib_cmd_wear_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find retain target objects. */ if (!lib_parse_multiple_objects(game, "retain", lib_wear_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_wear_filter, -1, &references); if (objects > 0 || references > 0) lib_wear_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything", "I am not holding anything", "%player% is not holding anything")); if (objects == 0) pf_buffer_string(filter, " else"); pf_buffer_string(filter, " that can be worn."); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_wear_multiple() * * Wear all objects currently held by the player, wearable, and listed * in %text%. */ sc_bool lib_cmd_wear_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find wear target objects. */ if (!lib_parse_multiple_objects(game, "wear", lib_wear_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_wear_filter, -1, &references); if (objects > 0 || references > 0) lib_wear_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything", "I am not holding anything", "%player% is not holding anything")); pf_buffer_string(filter, " that can be worn."); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_remove_backend() * * Common backend handler for removing objects. Takes off on all objects * currently referenced in the game, moving objects to held by player. * * Objects to action are flagged in object_references; objects requested but * deemed not actionable are flagged in multiple_references. */ static void lib_remove_backend(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object_count, object, count, trail; sc_bool has_printed; /* * Try game commands for all referenced objects first. If any succeed, * remove that reference from the list. */ has_printed = FALSE; object_count = gs_object_count(game); for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (lib_try_game_command_short(game, "remove", object)) { game->object_references[object] = FALSE; has_printed = TRUE; } } /* Remove every object referenced. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You remove ", "I remove ", "%player% removes ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; gs_object_player_get(game, object); } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You remove ", "I remove ", "%player% removes ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } has_printed |= count > 0; /* Note any remaining multiple references left out of the remove operation. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not wearing ", "I am not wearing ", "%player% is not wearing ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not wearing ", "I am not wearing ", "%player% is not wearing ")); } else pf_buffer_string(filter, " or "); lib_print_object_np(game, trail); pf_buffer_character(filter, '!'); } } /* * lib_remove_filter() * * Helper function for deciding if an object may be removed in this context. * Returns TRUE if an object is currently being worn, FALSE otherwise. */ static sc_bool lib_remove_filter(sc_gameref_t game, sc_int object, sc_int unused) { assert(unused == -1); return !obj_is_static(game, object) && gs_object_position(game, object) == OBJ_WORN_PLAYER; } /* * lib_cmd_remove_all() * * Remove all objects currently held by the player. */ sc_bool lib_cmd_remove_all(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_remove_filter, -1, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_remove_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You're not wearing anything", "I'm not wearing anything", "%player%'s not wearing anything")); pf_buffer_string(filter, " that can be removed."); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_remove_except_multiple() * * Remove all objects currently worn by the player, excepting those listed * in %text%. */ sc_bool lib_cmd_remove_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find retain target objects. */ if (!lib_parse_multiple_objects(game, "retain", lib_remove_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_remove_filter, -1, &references); if (objects > 0 || references > 0) lib_remove_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You are not wearing anything", "I am not wearing anything", "%player% is not wearing anything")); if (objects == 0) pf_buffer_string(filter, " else"); pf_buffer_string(filter, " that can be removed."); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_remove_multiple() * * Remove all objects currently worn by the player, and listed in %text%. */ sc_bool lib_cmd_remove_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int objects, references; /* Parse the multiple objects list to find remove target objects. */ if (!lib_parse_multiple_objects(game, "remove", lib_remove_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_remove_filter, -1, &references); if (objects > 0 || references > 0) lib_remove_backend(game); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything", "I am not holding anything", "%player% is not holding anything")); pf_buffer_string(filter, " that can be removed."); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_inventory() * * List objects carried and worn by the player. */ sc_bool lib_cmd_inventory(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, count, trail; sc_bool wearing; /* Find and list each object worn by the player. */ count = 0; trail = -1; wearing = FALSE; for (object = 0; object < gs_object_count(game); object++) { if (gs_object_position(game, object) == OBJ_WORN_PLAYER) { if (count > 0) { if (count == 1) { pf_buffer_string(filter, lib_select_response(game, "You are wearing ", "I am wearing ", "%player% is wearing ")); } else pf_buffer_string(filter, ", "); lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { pf_buffer_string(filter, lib_select_response(game, "You are wearing ", "I am wearing ", "%player% is wearing ")); } else pf_buffer_string(filter, " and "); lib_print_object(game, trail); wearing = TRUE; } /* Find and list each object owned by the player. */ count = 0; for (object = 0; object < gs_object_count(game); object++) { if (gs_object_position(game, object) == OBJ_HELD_PLAYER) { if (count > 0) { if (count == 1) { if (wearing) { pf_buffer_string(filter, lib_select_response(game, ", and you are carrying ", ", and I am carrying ", ", and %player% is carrying ")); } else { pf_buffer_string(filter, lib_select_response(game, "You are carrying ", "I am carrying ", "%player% is carrying ")); } } else pf_buffer_string(filter, ", "); lib_print_object(game, trail); } trail = object; count++; } } if (count >= 1) { /* Print out final listed object. */ if (count == 1) { if (wearing) { pf_buffer_string(filter, lib_select_response(game, ", and you are carrying ", ", and I am carrying ", ", and %player% is carrying ")); } else { pf_buffer_string(filter, lib_select_response(game, "You are carrying ", "I am carrying ", "%player% is carrying ")); } } else pf_buffer_string(filter, " and "); lib_print_object(game, trail); pf_buffer_character(filter, '.'); /* Print contents of every container and surface carried. */ for (object = 0; object < gs_object_count(game); object++) { if (gs_object_position(game, object) == OBJ_HELD_PLAYER) { if (obj_is_container(game, object) && gs_object_openness(game, object) <= OBJ_OPEN) lib_list_in_object(game, object, TRUE); if (obj_is_surface(game, object)) lib_list_on_object(game, object, TRUE); } } pf_buffer_character(filter, '\n'); } else { if (wearing) { pf_buffer_string(filter, ", and "); pf_buffer_string(filter, lib_select_response(game, "you are carrying nothing.\n", "I am carrying nothing.\n", "%player% is carrying nothing.\n")); } else { pf_buffer_string(filter, lib_select_response(game, "You are carrying nothing.\n", "I am carrying nothing.\n", "%player% is carrying nothing.\n")); } } /* Successful command. */ return TRUE; } /* * lib_cmd_open_object() * * Attempt to open the referenced object. */ sc_bool lib_cmd_open_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, openness; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "open", &is_ambiguous); if (object == -1) return is_ambiguous; /* Get the current object openness. */ openness = gs_object_openness(game, object); /* React to the request based on openness state. */ switch (openness) { case OBJ_OPEN: pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is already open!\n", " are already open!\n")); return TRUE; case OBJ_CLOSED: pf_buffer_string(filter, lib_select_response(game, "You open ", "I open ", "%player% opens ")); lib_print_object_np(game, object); pf_buffer_character(filter, '.'); /* Set open state, and list contents. */ gs_set_object_openness(game, object, OBJ_OPEN); lib_list_in_object(game, object, TRUE); pf_buffer_character(filter, '\n'); return TRUE; case OBJ_LOCKED: pf_buffer_string(filter, lib_select_response(game, "You can't open ", "I can't open ", "%player% can't open ")); lib_print_object_np(game, object); pf_buffer_string(filter, " as it is locked!\n"); return TRUE; default: break; } /* The object isn't openable. */ pf_buffer_string(filter, lib_select_response(game, "You can't open ", "I can't open ", "%player% can't open ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; } /* * lib_cmd_close_object() * * Attempt to close the referenced object. */ sc_bool lib_cmd_close_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object, openness; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "close", &is_ambiguous); if (object == -1) return is_ambiguous; /* Get the current object openness. */ openness = gs_object_openness(game, object); /* React to the request based on openness state. */ switch (openness) { case OBJ_OPEN: pf_buffer_string(filter, lib_select_response(game, "You close ", "I close ", "%player% closes ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); /* Set closed state. */ gs_set_object_openness(game, object, OBJ_CLOSED); return TRUE; case OBJ_CLOSED: case OBJ_LOCKED: pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is already closed!\n", " are already closed!\n")); return TRUE; default: break; } /* The object isn't closeable. */ pf_buffer_string(filter, lib_select_response(game, "You can't close ", "I can't close ", "%player% can't close ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; } /* * lib_attempt_key_acquisition() * * Automatically get an object being used as a key, if possible. */ static void lib_attempt_key_acquisition(sc_gameref_t game, sc_int object) { const sc_filterref_t filter = gs_get_filter(game); /* Disallow getting static objects. */ if (obj_is_static(game, object)) return; /* If the object is not seen or available, reject the attempt. */ if (!(gs_object_seen(game, object) && obj_indirectly_in_room(game, object, gs_playerroom(game)))) return; /* * Check if we already have it, or are wearing it, or if a NPC has or is * wearing it. */ if (gs_object_position(game, object) == OBJ_HELD_PLAYER || gs_object_position(game, object) == OBJ_WORN_PLAYER || gs_object_position(game, object) == OBJ_HELD_NPC || gs_object_position(game, object) == OBJ_WORN_NPC) return; /* * If the object is contained in or on something we're already holding, * capacity checks are meaningless. */ if (!obj_indirectly_held_by_player(game, object)) { if (lib_object_too_heavy(game, object, NULL) || lib_object_too_large(game, object, NULL)) return; } /* Retry game commands for the object with a standard "get". */ if (lib_try_game_command_short(game, "get", object)) return; /* Note what we're doing. */ if (gs_object_position(game, object) == OBJ_IN_OBJECT || gs_object_position(game, object) == OBJ_ON_OBJECT) { pf_buffer_string(filter, "(Taking "); lib_print_object_np(game, object); pf_buffer_string(filter, " from "); lib_print_object_np(game, gs_object_parent(game, object)); pf_buffer_string(filter, " first)\n"); } else { pf_buffer_string(filter, "(Picking up "); lib_print_object_np(game, object); pf_buffer_string(filter, " first)\n"); } /* Take possession of the object. */ gs_object_player_get(game, object); } /* * lib_cmd_unlock_object_with() * * Attempt to unlock the referenced object. */ sc_bool lib_cmd_unlock_object_with(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, key, openness; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "unlock", &is_ambiguous); if (object == -1) return is_ambiguous; /* * Now try to get the key from referenced text, and disambiguate as usual. */ if (!uip_match("%object%", var_get_ref_text(vars), game)) { pf_buffer_string(filter, "What do you want to unlock that with?\n"); return TRUE; } key = lib_disambiguate_object(game, "unlock that with", NULL); if (key == -1) return TRUE; /* React to the request based on openness state. */ openness = gs_object_openness(game, object); switch (openness) { case OBJ_OPEN: case OBJ_CLOSED: pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is not locked!\n", " are not locked!\n")); return TRUE; case OBJ_LOCKED: { sc_vartype_t vt_key[3]; sc_int key_index, the_key; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Key"; key_index = prop_get_integer(bundle, "I<-sis", vt_key); if (key_index == -1) break; the_key = obj_dynamic_object(game, key_index); if (the_key != key) { pf_buffer_string(filter, lib_select_response(game, "You can't unlock ", "I can't unlock ", "%player% can't unlock ")); lib_print_object_np(game, object); pf_buffer_string(filter, " with "); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } if (gs_object_position(game, key) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } gs_set_object_openness(game, object, OBJ_CLOSED); pf_buffer_string(filter, lib_select_response(game, "You unlock ", "I unlock ", "%player% unlocks ")); lib_print_object_np(game, object); pf_buffer_string(filter, " with "); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } default: break; } /* The object isn't lockable. */ pf_buffer_string(filter, lib_select_response(game, "You can't unlock ", "I can't unlock ", "%player% can't unlock ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* * lib_cmd_unlock_object() * * Attempt to unlock the referenced object, automatically selecting key. */ sc_bool lib_cmd_unlock_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, openness; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "unlock", &is_ambiguous); if (object == -1) return is_ambiguous; /* React to the request based on openness state. */ openness = gs_object_openness(game, object); switch (openness) { case OBJ_OPEN: case OBJ_CLOSED: pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is not locked!\n", " are not locked!\n")); return TRUE; case OBJ_LOCKED: { sc_vartype_t vt_key[3]; sc_int key_index, key; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Key"; key_index = prop_get_integer(bundle, "I<-sis", vt_key); if (key_index == -1) break; key = obj_dynamic_object(game, key_index); lib_attempt_key_acquisition(game, key); if (gs_object_position(game, key) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You don't have", "I don't have", "%player% doesn't have")); pf_buffer_string(filter, " anything to unlock "); lib_print_object_np(game, object); pf_buffer_string(filter, " with!\n"); return TRUE; } gs_set_object_openness(game, object, OBJ_CLOSED); pf_buffer_string(filter, lib_select_response(game, "You unlock ", "I unlock ", "%player% unlocks ")); lib_print_object_np(game, object); pf_buffer_string(filter, " with "); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } default: break; } /* The object isn't lockable. */ pf_buffer_string(filter, lib_select_response(game, "You can't unlock ", "I can't unlock ", "%player% can't unlock ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* * lib_cmd_lock_object_with() * * Attempt to lock the referenced object. */ sc_bool lib_cmd_lock_object_with(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, key, openness; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "lock", &is_ambiguous); if (object == -1) return is_ambiguous; /* * Now try to get the key from referenced text, and disambiguate as usual. */ if (!uip_match("%object%", var_get_ref_text(vars), game)) { pf_buffer_string(filter, "What do you want to lock that with?\n"); return TRUE; } key = lib_disambiguate_object(game, "lock that with", NULL); if (key == -1) return TRUE; /* React to the request based on openness state. */ openness = gs_object_openness(game, object); switch (openness) { case OBJ_OPEN: pf_buffer_string(filter, lib_select_response(game, "You can't lock ", "I can't lock ", "%player% can't lock ")); lib_print_object_np(game, object); pf_buffer_string(filter, " as it is open.\n"); return TRUE; case OBJ_CLOSED: { sc_vartype_t vt_key[3]; sc_int key_index, the_key; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Key"; key_index = prop_get_integer(bundle, "I<-sis", vt_key); if (key_index == -1) break; the_key = obj_dynamic_object(game, key_index); if (the_key != key) { pf_buffer_string(filter, lib_select_response(game, "You can't lock ", "I can't lock ", "%player% can't lock ")); lib_print_object_np(game, object); pf_buffer_string(filter, " with "); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } if (gs_object_position(game, key) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } gs_set_object_openness(game, object, OBJ_LOCKED); pf_buffer_string(filter, lib_select_response(game, "You lock ", "I lock ", "%player% locks ")); lib_print_object_np(game, object); pf_buffer_string(filter, " with "); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } case OBJ_LOCKED: pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is already locked!\n", " are already locked!\n")); return TRUE; default: break; } /* The object isn't lockable. */ pf_buffer_string(filter, lib_select_response(game, "You can't lock ", "I can't lock ", "%player% can't lock ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* * lib_cmd_lock_object() * * Attempt to lock the referenced object, automatically selecting key. */ sc_bool lib_cmd_lock_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, openness; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "lock", &is_ambiguous); if (object == -1) return is_ambiguous; /* React to the request based on openness state. */ openness = gs_object_openness(game, object); switch (openness) { case OBJ_OPEN: pf_buffer_string(filter, lib_select_response(game, "You can't lock ", "I can't lock ", "%player% can't lock ")); lib_print_object_np(game, object); pf_buffer_string(filter, " as it is open.\n"); return TRUE; case OBJ_CLOSED: { sc_vartype_t vt_key[3]; sc_int key_index, key; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Key"; key_index = prop_get_integer(bundle, "I<-sis", vt_key); if (key_index == -1) break; key = obj_dynamic_object(game, key_index); lib_attempt_key_acquisition(game, key); if (gs_object_position(game, key) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You don't have", "I don't have", "%player% doesn't have")); pf_buffer_string(filter, " anything to lock "); lib_print_object_np(game, object); pf_buffer_string(filter, " with!\n"); return TRUE; } gs_set_object_openness(game, object, OBJ_LOCKED); pf_buffer_string(filter, lib_select_response(game, "You lock ", "I lock ", "%player% locks ")); lib_print_object_np(game, object); pf_buffer_string(filter, " with "); lib_print_object_np(game, key); pf_buffer_string(filter, ".\n"); return TRUE; } case OBJ_LOCKED: pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is already locked!\n", " are already locked!\n")); return TRUE; default: break; } /* The object isn't lockable. */ pf_buffer_string(filter, lib_select_response(game, "You can't lock ", "I can't lock ", "%player% can't lock ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* * lib_compare_subject() * * Compare a subject, comma or NUL terminated. Helper for ask. */ static sc_bool lib_compare_subject(const sc_char *subject, sc_int posn, const sc_char *string) { sc_int word_posn, string_posn; /* Skip any leading subject spaces. */ for (word_posn = posn; subject[word_posn] != NUL && sc_isspace(subject[word_posn]);) word_posn++; for (string_posn = 0; string[string_posn] != NUL && sc_isspace(string[string_posn]);) string_posn++; /* Match characters from words with the string at position. */ while (TRUE) { /* Any character mismatch means no match. */ if (sc_tolower(subject[word_posn]) != sc_tolower(string[string_posn])) return FALSE; /* Move to next character in each. */ word_posn++; string_posn++; /* * If at space, advance over whitespace in subjects list. Stop when we * hit the end of the element or list. */ while (sc_isspace(subject[word_posn]) && subject[word_posn] != COMMA && subject[word_posn] != NUL) subject++; /* Advance over whitespace in the current string too. */ while (sc_isspace(string[string_posn]) && string[string_posn] != NUL) string_posn++; /* * If we found the end of the subject, and the end of the current string, * we've matched. If not at the end of the current string, though, only * a partial match. */ if (subject[word_posn] == NUL || subject[word_posn] == COMMA) { if (string[string_posn] == NUL) break; else return FALSE; } } /* Matched in the loop; return TRUE. */ return TRUE; } /* * lib_npc_reply_to() * * Reply for an NPC on a given topic. Helper for ask. */ static sc_bool lib_npc_reply_to(sc_gameref_t game, sc_int npc, sc_int topic) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_int task; const sc_char *response; /* Find any associated task to control response. */ vt_key[0].string = "NPCs"; vt_key[1].integer = npc; vt_key[2].string = "Topics"; vt_key[3].integer = topic; vt_key[4].string = "Task"; task = prop_get_integer(bundle, "I<-sisis", vt_key); /* Get the response, and print if anything there. */ if (task > 0 && gs_task_done(game, task - 1)) vt_key[4].string = "AltReply"; else vt_key[4].string = "Reply"; response = prop_get_string(bundle, "S<-sisis", vt_key); if (!sc_strempty(response)) { pf_buffer_string(filter, response); pf_buffer_character(filter, '\n'); return TRUE; } /* No response to this combination. */ return FALSE; } /* * lib_cmd_ask_npc_about() * * Converse with NPC. */ sc_bool lib_cmd_ask_npc_about(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[5]; sc_int npc, topic_count, topic, topic_match, default_topic; sc_bool found, default_found, is_ambiguous; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "ask", &is_ambiguous); if (npc == -1) return is_ambiguous; if (lib_trace) sc_trace("Library: asking NPC %ld\n", npc); /* Get the topics the NPC converses about. */ vt_key[0].string = "NPCs"; vt_key[1].integer = npc; vt_key[2].string = "Topics"; topic_count = prop_get_child_count(bundle, "I<-sis", vt_key); topic_match = default_topic = -1; found = default_found = FALSE; for (topic = 0; topic < topic_count; topic++) { const sc_char *subjects; sc_int posn; /* Get subject list for this topic. */ vt_key[3].integer = topic; vt_key[4].string = "Subject"; subjects = prop_get_string(bundle, "S<-sisis", vt_key); /* If this is the special "*" topic, note and continue. */ if (!sc_strcasecmp(subjects, "*")) { if (lib_trace) sc_trace("Library: \"*\" is %ld\n", topic); default_topic = topic; default_found = TRUE; continue; } /* Split into subjects by comma delimiter. */ for (posn = 0; subjects[posn] != NUL;) { if (lib_trace) sc_trace("Library: subject %s[%ld]\n", subjects, posn); /* See if this subject matches. */ if (lib_compare_subject(subjects, posn, var_get_ref_text(vars))) { if (lib_trace) sc_trace("Library: matched\n"); topic_match = topic; found = TRUE; break; } /* Move to next subject, or end of list. */ while (subjects[posn] != COMMA && subjects[posn] != NUL) posn++; if (subjects[posn] == COMMA) posn++; } } /* Handle any matched subject first, and "*" second. */ if (found && lib_npc_reply_to(game, npc, topic_match)) return TRUE; else if (default_found && lib_npc_reply_to(game, npc, default_topic)) return TRUE; /* NPC has no response. */ pf_new_sentence(filter); lib_print_npc_np(game, npc); pf_buffer_string(filter, lib_select_response(game, " does not respond to your question.\n", " does not respond to my question.\n", " does not respond to %player%'s question.\n")); return TRUE; } /* * lib_check_put_in_recursion() * * Checks for infinite recursion when placing an object in an object. Returns * TRUE if no recursion detected. */ static sc_bool lib_check_put_in_recursion(sc_gameref_t game, sc_int object, sc_int container, sc_bool report) { const sc_filterref_t filter = gs_get_filter(game); sc_int check; /* Avoid the obvious possibility of infinite recursion. */ if (container == object) { if (report) { pf_buffer_string(filter, lib_select_response(game, "You can't put an object inside itself!", "I can't put an object inside itself!", "%player% can't put an object inside itself!")); } return FALSE; } /* Avoid the subtle possibility of infinite recursion. */ check = container; while (gs_object_position(game, check) == OBJ_ON_OBJECT || gs_object_position(game, check) == OBJ_IN_OBJECT) { check = gs_object_parent(game, check); if (check == object) { if (report) { pf_buffer_string(filter, lib_select_response(game, "You can't put an object inside one", "I can't put an object inside one", "%player% can't put an object inside one")); pf_buffer_string(filter, " it's on or in!"); } return FALSE; } } /* No infinite recursion detected. */ return TRUE; } /* * lib_put_in_backend() * * Common backend handler for placing objects in containers. Places all * objects currently referenced in the game into a container, trying game * commands first, and then moving other unhandled objects into the container. * * Objects to action are flagged in object_references; objects requested but * deemed not actionable are flagged in multiple_references. */ static void lib_put_in_backend(sc_gameref_t game, sc_int container) { const sc_filterref_t filter = gs_get_filter(game); sc_int object_count, object, count, trail, capacity, maxsize; sc_bool has_printed; /* * Try game commands for all referenced objects first. If any succeed, * remove that reference from the list. At the same time, check for and * weed out any moves that result in infinite recursion. */ has_printed = FALSE; object_count = gs_object_count(game); for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; /* Reject and remove attempts to place objects in themselves. */ if (!lib_check_put_in_recursion(game, object, container, !has_printed)) { game->object_references[object] = FALSE; has_printed = TRUE; continue; } if (lib_try_game_command_with_object(game, "put", object, "in", container)) { game->object_references[object] = FALSE; has_printed = TRUE; } } /* Retrieve the container's limits. */ maxsize = obj_get_container_maxsize(game, container); capacity = obj_get_container_capacity(game, container); /* Put in every object that remains referenced. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; /* If too big, or exceeds container limits, ignore for now. */ if (obj_get_size(game, object) > maxsize) continue; else { sc_int other, contains; contains = 0; for (other = 0; other < gs_object_count(game); other++) { if (gs_object_position(game, other) == OBJ_IN_OBJECT && gs_object_parent(game, other) == container) contains++; } if (contains >= capacity) continue; } if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You put ", "I put ", "%player% puts ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; gs_object_move_into(game, object, container); game->object_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You put ", "I put ", "%player% puts ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_string(filter, " inside "); lib_print_object_np(game, container); pf_buffer_character(filter, '.'); } has_printed |= count > 0; /* * Report objects not put in because of their size. These objects remain in * standard references, as do objects rejected because of capacity limits. * By removing too large objects in this loop, we're left later on with just * the objects rejected by capacity limits. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (!(obj_get_size(game, object) > maxsize)) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, trail); } else pf_buffer_string(filter, ", "); } trail = object; count++; game->object_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, trail); pf_buffer_string(filter, lib_select_plurality(game, trail, " is too big", " are too big")); } else { pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_string(filter, " are too big"); } pf_buffer_string(filter, " to fit inside "); lib_print_object_np(game, container); pf_buffer_character(filter, '.'); } has_printed |= count > 0; /* * Report objects not put in because the container is too full. This should * be all remaining objects in standard references. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->object_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_new_sentence(filter); lib_print_object_np(game, trail); } else { pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); } pf_buffer_string(filter, " can't fit inside "); lib_print_object_np(game, container); pf_buffer_string(filter, " at the moment."); } has_printed |= count > 0; /* Note any remaining multiple references left out of the operation. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, " or "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } } /* * lib_put_in_filter() * lib_put_in_not_container_filter() * * Helper functions for deciding if an object may be put in another this * context. Returns TRUE if an object may be manipulated, FALSE otherwise. */ static sc_bool lib_put_in_filter(sc_gameref_t game, sc_int object, sc_int unused) { assert(unused == -1); return !obj_is_static(game, object) && gs_object_position(game, object) == OBJ_HELD_PLAYER; } static sc_bool lib_put_in_not_container_filter(sc_gameref_t game, sc_int object, sc_int container) { return lib_put_in_filter(game, object, -1) && object != container; } /* * lib_put_in_is_valid() * * Validate the container requested in "put in" commands. */ static sc_bool lib_put_in_is_valid(sc_gameref_t game, sc_int container) { const sc_filterref_t filter = gs_get_filter(game); /* Verify that the container object is a container. */ if (!obj_is_container(game, container)) { pf_buffer_string(filter, lib_select_response(game, "You can't put anything inside ", "I can't put anything inside ", "%player% can't put anything inside ")); lib_print_object_np(game, container); pf_buffer_string(filter, "!\n"); return FALSE; } /* If the container is closed, reject now. */ if (gs_object_openness(game, container) > OBJ_OPEN) { pf_new_sentence(filter); lib_print_object_np(game, container); pf_buffer_string(filter, lib_select_plurality(game, container, " is", " are")); if (gs_object_openness(game, container) == OBJ_LOCKED) pf_buffer_string(filter, " locked!\n"); else pf_buffer_string(filter, " closed!\n"); return FALSE; } /* Container is a valid target for "put in". */ return TRUE; } /* * lib_cmd_put_all_in() * * Put all objects currently held by the player into a container. */ sc_bool lib_cmd_put_all_in(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int container, objects; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ container = lib_disambiguate_object(game, "put that into", &is_ambiguous); if (container == -1) return is_ambiguous; /* Validate the container object to take from. */ if (!lib_put_in_is_valid(game, container)) return TRUE; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_put_in_not_container_filter, container, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_put_in_backend(game, container); else { pf_buffer_string(filter, lib_select_response(game, "You're not carrying anything", "I'm not carrying anything", "%player%'s not carrying anything")); if (obj_indirectly_held_by_player(game, container)) pf_buffer_string(filter, " else"); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_put_in_except_multiple() * * Put all objects currently held by the player into an object, excepting * those listed in %text%. */ sc_bool lib_cmd_put_in_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int container, objects, references; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ container = lib_disambiguate_object(game, "put that into", &is_ambiguous); if (container == -1) return is_ambiguous; /* Parse the multiple objects list to find retain target objects. */ if (!lib_parse_multiple_objects(game, "retain", lib_put_in_not_container_filter, container, &references)) return FALSE; else if (references == 0) return TRUE; /* Validate the container object to put into. */ if (!lib_put_in_is_valid(game, container)) return TRUE; /* As a special case, complain about requests to retain the container. */ if (game->multiple_references[container]) { pf_buffer_string(filter, "I only understood you as far as wanting to retain "); lib_print_object_np(game, container); pf_buffer_string(filter, ".\n"); return TRUE; } /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_put_in_not_container_filter, container, &references); if (objects > 0 || references > 0) lib_put_in_backend(game, container); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything", "I am not holding anything", "%player% is not holding anything")); if (objects == 0) pf_buffer_string(filter, " else"); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_put_in_multiple() * * Put all objects currently held by the player and listed in %text% into an * object. */ sc_bool lib_cmd_put_in_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int container, objects, references; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ container = lib_disambiguate_object(game, "put that into", &is_ambiguous); if (container == -1) return is_ambiguous; /* Parse the multiple objects list to find retain target objects. */ if (!lib_parse_multiple_objects(game, "move", lib_put_in_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Validate the container object to put into. */ if (!lib_put_in_is_valid(game, container)) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_put_in_filter, -1, &references); if (objects > 0 || references > 0) lib_put_in_backend(game, container); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything.", "I am not holding anything.", "%player% is not holding anything.")); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_check_put_on_recursion() * * Checks for infinite recursion when placing an object on an object. Returns * TRUE if no recursion detected. */ static sc_bool lib_check_put_on_recursion(sc_gameref_t game, sc_int object, sc_int supporter, sc_bool report) { const sc_filterref_t filter = gs_get_filter(game); sc_int check; /* Avoid the obvious possibility of infinite recursion. */ if (supporter == object) { if (report) { pf_buffer_string(filter, lib_select_response(game, "You can't put an object onto itself!", "I can't put an object onto itself!", "%player% can't put an object onto itself!")); } return FALSE; } /* Avoid the subtle possibility of infinite recursion. */ check = supporter; while (gs_object_position(game, check) == OBJ_ON_OBJECT || gs_object_position(game, check) == OBJ_IN_OBJECT) { check = gs_object_parent(game, check); if (check == object) { if (report) { pf_buffer_string(filter, lib_select_response(game, "You can't put an object onto one", "I can't put an object onto one", "%player% can't put an object onto one")); pf_buffer_string(filter, " it's on or in!"); } return FALSE; } } /* No infinite recursion detected. */ return TRUE; } /* * lib_put_on_backend() * * Common backend handler for placing objects on supporters. Places all * objects currently referenced in the game onto a supporter, trying game * commands first, and then moving other unhandled objects onto the supporter. * * Objects to action are flagged in object_references; objects requested but * deemed not actionable are flagged in multiple_references. */ static void lib_put_on_backend(sc_gameref_t game, sc_int supporter) { const sc_filterref_t filter = gs_get_filter(game); sc_int object_count, object, count, trail; sc_bool has_printed; /* * Try game commands for all referenced objects first. If any succeed, * remove that reference from the list. At the same time, check for and * weed out any moves that result in infinite recursion. */ has_printed = FALSE; object_count = gs_object_count(game); for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; /* Reject and remove attempts to place objects on themselves. */ if (!lib_check_put_on_recursion(game, object, supporter, !has_printed)) { game->object_references[object] = FALSE; has_printed = TRUE; continue; } if (lib_try_game_command_with_object(game, "put", object, "on", supporter)) { game->object_references[object] = FALSE; has_printed = TRUE; } } /* Put on every object that remains referenced. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->object_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You put ", "I put ", "%player% puts ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; gs_object_move_onto(game, object, supporter); } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You put ", "I put ", "%player% puts ")); } else pf_buffer_string(filter, " and "); lib_print_object_np(game, trail); pf_buffer_string(filter, " onto "); lib_print_object_np(game, supporter); pf_buffer_character(filter, '.'); } has_printed |= count > 0; /* Note any remaining multiple references left out of the operation. */ count = 0; trail = -1; for (object = 0; object < object_count; object++) { if (!game->multiple_references[object]) continue; if (count > 0) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, ", "); lib_print_object_np(game, trail); } trail = object; count++; game->multiple_references[object] = FALSE; } if (count >= 1) { if (count == 1) { if (has_printed) pf_buffer_string(filter, " "); pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); } else pf_buffer_string(filter, " or "); lib_print_object_np(game, trail); pf_buffer_character(filter, '.'); } } /* * lib_put_on_filter() * lib_put_on_not_supporter_filter() * * Helper functions for deciding if an object may be put on another this * context. Returns TRUE if an object may be manipulated, FALSE otherwise. */ static sc_bool lib_put_on_filter(sc_gameref_t game, sc_int object, sc_int unused) { assert(unused == -1); return !obj_is_static(game, object) && gs_object_position(game, object) == OBJ_HELD_PLAYER; } static sc_bool lib_put_on_not_supporter_filter(sc_gameref_t game, sc_int object, sc_int supporter) { return lib_put_on_filter(game, object, -1) && object != supporter; } /* * lib_put_on_is_valid() * * Validate the supporter requested in "put on" commands. */ static sc_bool lib_put_on_is_valid(sc_gameref_t game, sc_int supporter) { const sc_filterref_t filter = gs_get_filter(game); /* Verify that the supporter object is a supporter. */ if (!obj_is_surface(game, supporter)) { pf_buffer_string(filter, lib_select_response(game, "You can't put anything on ", "I can't put anything on ", "%player% can't put anything on ")); lib_print_object_np(game, supporter); pf_buffer_string(filter, "!\n"); return FALSE; } /* Surface is a valid target for "put on". */ return TRUE; } /* * lib_cmd_put_all_on() * * Put all objects currently held by the player onto a supporter. */ sc_bool lib_cmd_put_all_on(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int supporter, objects; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ supporter = lib_disambiguate_object(game, "put that onto", &is_ambiguous); if (supporter == -1) return is_ambiguous; /* Validate the supporter object to take from. */ if (!lib_put_on_is_valid(game, supporter)) return TRUE; /* Filter objects into references, then handle with the backend. */ gs_set_multiple_references(game); objects = lib_apply_multiple_filter(game, lib_put_on_not_supporter_filter, supporter, NULL); gs_clear_multiple_references(game); if (objects > 0) lib_put_on_backend(game, supporter); else { pf_buffer_string(filter, lib_select_response(game, "You're not carrying anything", "I'm not carrying anything", "%player%'s not carrying anything")); if (obj_indirectly_held_by_player(game, supporter)) pf_buffer_string(filter, " else"); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_put_on_except_multiple() * * Put all objects currently held by the player onto an object, excepting * those listed in %text%. */ sc_bool lib_cmd_put_on_except_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int supporter, objects, references; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ supporter = lib_disambiguate_object(game, "put that onto", &is_ambiguous); if (supporter == -1) return is_ambiguous; /* Parse the multiple objects list to find retain target objects. */ if (!lib_parse_multiple_objects(game, "retain", lib_put_on_not_supporter_filter, supporter, &references)) return FALSE; else if (references == 0) return TRUE; /* Validate the supporter object to put into. */ if (!lib_put_on_is_valid(game, supporter)) return TRUE; /* As a special case, complain about requests to retain the supporter. */ if (game->multiple_references[supporter]) { pf_buffer_string(filter, "I only understood you as far as wanting to retain "); lib_print_object_np(game, supporter); pf_buffer_string(filter, ".\n"); return TRUE; } /* Filter objects into references, then handle with the backend. */ objects = lib_apply_except_filter(game, lib_put_on_not_supporter_filter, supporter, &references); if (objects > 0 || references > 0) lib_put_on_backend(game, supporter); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything", "I am not holding anything", "%player% is not holding anything")); if (objects == 0) pf_buffer_string(filter, " else"); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_put_on_multiple() * * Put all objects currently held by the player and listed in %text% onto an * object. */ sc_bool lib_cmd_put_on_multiple(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int supporter, objects, references; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ supporter = lib_disambiguate_object(game, "put that onto", &is_ambiguous); if (supporter == -1) return is_ambiguous; /* Parse the multiple objects list to find retain target objects. */ if (!lib_parse_multiple_objects(game, "move", lib_put_on_filter, -1, &references)) return FALSE; else if (references == 0) return TRUE; /* Validate the supporter object to put into. */ if (!lib_put_on_is_valid(game, supporter)) return TRUE; /* Filter objects into references, then handle with the backend. */ objects = lib_apply_multiple_filter(game, lib_put_on_filter, -1, &references); if (objects > 0 || references > 0) lib_put_on_backend(game, supporter); else { pf_buffer_string(filter, lib_select_response(game, "You are not holding anything.", "I am not holding anything.", "%player% is not holding anything.")); } pf_buffer_character(filter, '\n'); return TRUE; } /* * lib_cmd_read_object() * lib_cmd_read_other() * * Attempt to read the referenced object, or something else. */ sc_bool lib_cmd_read_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int object, task; sc_bool is_readable, is_ambiguous; const sc_char *readtext, *description; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "read", &is_ambiguous); if (object == -1) return is_ambiguous; /* Verify that the object is readable. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Readable"; is_readable = prop_get_boolean(bundle, "B<-sis", vt_key); if (!is_readable) { pf_buffer_string(filter, lib_select_response(game, "You can't read ", "I can't read ", "%player% can't read ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; } /* Get and print the object's read text, if any. */ vt_key[2].string = "ReadText"; readtext = prop_get_string(bundle, "S<-sis", vt_key); if (!sc_strempty(readtext)) { pf_buffer_string(filter, readtext); pf_buffer_character(filter, '\n'); return TRUE; } /* Degrade to a shortened object examine. */ vt_key[2].string = "Task"; task = prop_get_integer(bundle, "I<-sis", vt_key) - 1; /* Select either the main or the alternate description. */ if (task >= 0 && gs_task_done(game, task)) vt_key[2].string = "AltDesc"; else vt_key[2].string = "Description"; /* Print the description, or a "nothing special" default. */ description = prop_get_string(bundle, "S<-sis", vt_key); if (!sc_strempty(description)) pf_buffer_string(filter, description); else { pf_buffer_string(filter, "There is nothing special about "); lib_print_object_np(game, object); pf_buffer_character(filter, '.'); } pf_buffer_character(filter, '\n'); return TRUE; } sc_bool lib_cmd_read_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); /* Reject the attempt. */ pf_buffer_string(filter, lib_select_response(game, "You see no such thing.\n", "I see no such thing.\n", "%player% sees no such thing.\n")); return TRUE; } /* * lib_cmd_attack_npc() * lib_cmd_attack_npc_with() * * Attempt to attack an NPC, with and without weaponry. */ sc_bool lib_cmd_attack_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int npc; sc_bool is_ambiguous; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "attack", &is_ambiguous); if (npc == -1) return is_ambiguous; /* Print a standard response. */ pf_new_sentence(filter); lib_print_npc_np(game, npc); pf_buffer_string(filter, lib_select_response(game, " avoids your feeble attempts.\n", " avoids my feeble attempts.\n", " avoids %player%'s feeble attempts.\n")); return TRUE; } sc_bool lib_cmd_attack_npc_with(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, npc; sc_vartype_t vt_key[3]; sc_bool weapon, is_ambiguous; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "attack", &is_ambiguous); if (npc == -1) return is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "attack with", NULL); if (object == -1) return TRUE; /* Ensure the referenced object is held. */ if (gs_object_position(game, object) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* Check for static object moved to player by event. */ if (obj_is_static(game, object)) { pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is", " are")); pf_buffer_string(filter, " not a weapon.\n"); return TRUE; } /* Print standard response depending on if the object is a weapon. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Weapon"; weapon = prop_get_boolean(bundle, "B<-sis", vt_key); if (weapon) { pf_buffer_string(filter, lib_select_response(game, "You swing at ", "I swing at ", "%player% swings at ")); lib_print_npc_np(game, npc); pf_buffer_string(filter, " with "); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_response(game, " but you miss.\n", " but I miss.\n", " but misses.\n")); } else { /* * TODO Adrift uses "affective" [sic] here. Should SCARE be right, or * bug-compatible? */ pf_buffer_string(filter, "I don't think "); lib_print_object_np(game, object); pf_buffer_string(filter, " would be a very effective weapon.\n"); } return TRUE; } /* * lib_cmd_kiss_npc() * lib_cmd_kiss_object() * lib_cmd_kiss_other() * * Reject romantic advances in all cases. */ sc_bool lib_cmd_kiss_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int npc, gender; sc_bool is_ambiguous; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "kiss", &is_ambiguous); if (npc == -1) return is_ambiguous; /* Reject this attempt. */ vt_key[0].string = "NPCs"; vt_key[1].integer = npc; vt_key[2].string = "Gender"; gender = prop_get_integer(bundle, "I<-sis", vt_key); switch (gender) { case NPC_MALE: pf_buffer_string(filter, "I'm not sure he would appreciate that!\n"); break; case NPC_FEMALE: pf_buffer_string(filter, "I'm not sure she would appreciate that!\n"); break; case NPC_NEUTER: pf_buffer_string(filter, "I'm not sure it would appreciate that!\n"); break; default: sc_error("lib_cmd_kiss_npc: unknown gender, %ld\n", gender); } return TRUE; } sc_bool lib_cmd_kiss_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "kiss", &is_ambiguous); if (object == -1) return is_ambiguous; /* Reject this attempt. */ pf_buffer_string(filter, "I'm not sure "); lib_print_object_np(game, object); pf_buffer_string(filter, " would appreciate that.\n"); return TRUE; } sc_bool lib_cmd_kiss_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); /* Reject this attempt. */ pf_buffer_string(filter, "I'm not sure it would appreciate that.\n"); return TRUE; } /* * lib_cmd_buy_object() * lib_cmd_buy_other() * * Standard responses to attempts to buy something. */ sc_bool lib_cmd_buy_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "buy", &is_ambiguous); if (object == -1) return is_ambiguous; /* Reject this attempt. */ pf_buffer_string(filter, "I don't think "); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is", " are")); pf_buffer_string(filter, " for sale.\n"); return TRUE; } sc_bool lib_cmd_buy_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); /* Reject this attempt. */ pf_buffer_string(filter, "I don't think that is for sale.\n"); return TRUE; } /* * lib_cmd_break_object() * lib_cmd_break_other() * * Standard responses to attempts to break something. */ sc_bool lib_cmd_break_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "break", &is_ambiguous); if (object == -1) return is_ambiguous; /* Reject this attempt. */ pf_buffer_string(filter, lib_select_response(game, "You might need ", "I might need ", "%player% might need ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } sc_bool lib_cmd_break_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); /* Reject this attempt. */ pf_buffer_string(filter, lib_select_response(game, "You might need that.\n", "I might need that.\n", "%player% might need that.\n")); return TRUE; } /* * lib_cmd_smell_object() * lib_cmd_smell_other() * * Standard responses to attempts to smell something. */ sc_bool lib_cmd_smell_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "smell", &is_ambiguous); if (object == -1) return is_ambiguous; /* Reject this attempt. */ pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, " smells normal.\n"); return TRUE; } sc_bool lib_cmd_smell_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); /* Reject this attempt. */ pf_buffer_string(filter, "That smells normal.\n"); return TRUE; } /* * lib_cmd_sell_object() * lib_cmd_sell_other() * * Standard responses to attempts to sell something. */ sc_bool lib_cmd_sell_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "sell", &is_ambiguous); if (object == -1) return is_ambiguous; /* Reject this attempt. */ pf_buffer_string(filter, "No-one is interested in buying "); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } sc_bool lib_cmd_sell_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "No-one is interested in buying that.\n"); return TRUE; } /* * lib_cmd_eat_object() * * Consume edible objects. */ sc_bool lib_cmd_eat_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int object; sc_bool edible, is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "eat", &is_ambiguous); if (object == -1) return is_ambiguous; /* Check that we have the object to eat. */ if (gs_object_position(game, object) != OBJ_HELD_PLAYER) { pf_buffer_string(filter, lib_select_response(game, "You are not holding ", "I am not holding ", "%player% is not holding ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* Check for static object moved to player by event. */ if (obj_is_static(game, object)) { pf_buffer_string(filter, lib_select_response(game, "You can't eat ", "I can't eat ", "%player% can't eat ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* Is this object inedible? */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Edible"; edible = prop_get_boolean(bundle, "B<-sis", vt_key); if (!edible) { pf_buffer_string(filter, lib_select_response(game, "You can't eat ", "I can't eat ", "%player% can't eat ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } /* Confirm, and hide the object. */ pf_buffer_string(filter, lib_select_response(game, "You eat ", "I eat ", "%player% eats ")); lib_print_object_np(game, object); pf_buffer_string(filter, ". Not bad, but it could do with a pinch of salt!\n"); gs_object_make_hidden(game, object); return TRUE; } /* Enumerated sit/stand/lie types. */ enum { OBJ_STANDABLE_MASK = 1 << 0, OBJ_LIEABLE_MASK = 1 << 1 }; enum { MOVE_SIT, MOVE_SIT_FLOOR, MOVE_STAND, MOVE_STAND_FLOOR, MOVE_LIE, MOVE_LIE_FLOOR }; /* * lib_stand_sit_lie() * * Central handler for stand, sit, and lie commands. */ static sc_bool lib_stand_sit_lie(sc_gameref_t game, sc_int movement) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, position; const sc_char *already_doing_that, *success_message; /* Initialize variables to avoid gcc warnings. */ object = -1; already_doing_that = FALSE; success_message = FALSE; position = 0; /* Get a target object for movement, -1 if floor. */ switch (movement) { case MOVE_STAND: case MOVE_SIT: case MOVE_LIE: { const sc_char *disambiguate, *cant_do_that; sc_int sit_lie_flags, movement_mask; sc_vartype_t vt_key[3]; sc_bool is_ambiguous; /* Initialize variables to avoid gcc warnings. */ disambiguate = NULL; cant_do_that = NULL; movement_mask = 0; /* Set disambiguation and not amenable messages. */ switch (movement) { case MOVE_STAND: disambiguate = "stand on"; cant_do_that = lib_select_response(game, "You can't stand on ", "I can't stand on ", "%player% can't stand on "); movement_mask = OBJ_STANDABLE_MASK; break; case MOVE_SIT: disambiguate = "sit on"; cant_do_that = lib_select_response(game, "You can't sit on ", "I can't sit on ", "%player% can't sit on "); movement_mask = OBJ_STANDABLE_MASK; break; case MOVE_LIE: disambiguate = "lie on"; cant_do_that = lib_select_response(game, "You can't lie on ", "I can't lie on ", "%player% can't lie on "); movement_mask = OBJ_LIEABLE_MASK; break; default: sc_fatal("lib_sit_stand_lie: movement error, %ld\n", movement); } /* Get the referenced object; if none, consider complete. */ object = lib_disambiguate_object(game, disambiguate, &is_ambiguous); if (object == -1) return is_ambiguous; /* Verify the referenced object is amenable. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "SitLie"; sit_lie_flags = prop_get_integer(bundle, "I<-sis", vt_key); if (!(sit_lie_flags & movement_mask)) { pf_buffer_string(filter, cant_do_that); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } break; } case MOVE_STAND_FLOOR: case MOVE_SIT_FLOOR: case MOVE_LIE_FLOOR: object = -1; break; default: sc_fatal("lib_sit_stand_lie: movement error, %ld\n", movement); } /* Set up confirmation messages and position. */ switch (movement) { case MOVE_STAND: already_doing_that = lib_select_response(game, "You are already standing on ", "I am already standing on ", "%player% is already standing on "); success_message = lib_select_response(game, "You stand on ", "I stand on ", "%player% stands on "); position = 0; break; case MOVE_STAND_FLOOR: already_doing_that = lib_select_response(game, "You are already standing!\n", "I am already standing!\n", "%player% is already standing!\n"); success_message = lib_select_response(game, "You stand up", "I stand up", "%player% stands up"); position = 0; break; case MOVE_SIT: already_doing_that = lib_select_response(game, "You are already sitting on ", "I am already sitting on ", "%player% is already sitting on "); if (gs_playerposition(game) == 2) success_message = lib_select_response(game, "You sit up on ", "I sit up on ", "%player% sits up on "); else success_message = lib_select_response(game, "You sit down on ", "I sit down on ", "%player% sits down on "); position = 1; break; case MOVE_SIT_FLOOR: already_doing_that = lib_select_response(game, "You are already sitting down.\n", "I am already sitting down.\n", "%player% is already sitting down.\n"); if (gs_playerposition(game) == 2) success_message = lib_select_response(game, "You sit up on the ground.\n", "I sit up on the ground.\n", "%player% sits up on the ground.\n"); else success_message = lib_select_response(game, "You sit down on the ground.\n", "I sit down on the ground.\n", "%player% sits down on the ground.\n"); position = 1; break; case MOVE_LIE: already_doing_that = lib_select_response(game, "You are already lying on ", "I am already lying on ", "%player% is already lying on "); success_message = lib_select_response(game, "You lie down on ", "I lie down on ", "%player% lies down on "); position = 2; break; case MOVE_LIE_FLOOR: already_doing_that = lib_select_response(game, "You are already lying down.\n", "I am already lying down.\n", "%player% is already lying down.\n"); success_message = lib_select_response(game, "You lie down on the ground.\n", "I lie down on the ground.\n", "%player% lies down on the ground.\n"); position = 2; break; default: sc_fatal("lib_sit_stand_lie: movement error, %ld\n", movement); } /* See if already doing this. */ if (gs_playerposition(game) == position && gs_playerparent(game) == object) { pf_buffer_string(filter, already_doing_that); if (object != -1) { lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); } return TRUE; } /* Confirm movement, with special case for getting off an object. */ pf_buffer_string(filter, success_message); if (movement == MOVE_STAND_FLOOR) { if (gs_playerparent(game) != -1) { pf_buffer_string(filter, " from "); lib_print_object_np(game, gs_playerparent(game)); } pf_buffer_string(filter, ".\n"); } else if (object != -1) { lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); } /* Adjust player position and parent. */ gs_set_playerposition(game, position); gs_set_playerparent(game, object); return TRUE; } /* * lib_cmd_stand_* * lib_cmd_sit_* * lib_cmd_lie_* * * Stand, sit, or lie on an object, or on the floor. */ sc_bool lib_cmd_stand_on_object(sc_gameref_t game) { return lib_stand_sit_lie(game, MOVE_STAND); } sc_bool lib_cmd_stand_on_floor(sc_gameref_t game) { return lib_stand_sit_lie(game, MOVE_STAND_FLOOR); } sc_bool lib_cmd_sit_on_object(sc_gameref_t game) { return lib_stand_sit_lie(game, MOVE_SIT); } sc_bool lib_cmd_sit_on_floor(sc_gameref_t game) { return lib_stand_sit_lie(game, MOVE_SIT_FLOOR); } sc_bool lib_cmd_lie_on_object(sc_gameref_t game) { return lib_stand_sit_lie(game, MOVE_LIE); } sc_bool lib_cmd_lie_on_floor(sc_gameref_t game) { return lib_stand_sit_lie(game, MOVE_LIE_FLOOR); } /* * lib_cmd_get_off_object() * lib_cmd_get_off() * * Get off whatever supporter the player rests on. */ sc_bool lib_cmd_get_off_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object; if none, consider complete. */ object = lib_disambiguate_object(game, "get off", &is_ambiguous); if (object == -1) return is_ambiguous; /* Reject the attempt if the player is not on the given object. */ if (gs_playerparent(game) != object) { pf_buffer_string(filter, lib_select_response(game, "You are not on ", "I am not on ", "%player% is not on ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; } /* Confirm movement. */ pf_buffer_string(filter, lib_select_response(game, "You get off ", "I get off ", "%player% gets off ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); /* Adjust player position and parent. */ gs_set_playerposition(game, 0); gs_set_playerparent(game, -1); return TRUE; } sc_bool lib_cmd_get_off(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); /* Reject the attempt if the player is not on anything. */ if (gs_playerparent(game) == -1) { pf_buffer_string(filter, lib_select_response(game, "You are not on anything!\n", "I am not on anything!\n", "%player% is not on anything!\n")); return TRUE; } /* Confirm movement. */ pf_buffer_string(filter, lib_select_response(game, "You get off ", "I get off ", "%player% gets off ")); lib_print_object_np(game, gs_playerparent(game)); pf_buffer_string(filter, ".\n"); /* Adjust player position and parent. */ gs_set_playerposition(game, 0); gs_set_playerparent(game, -1); return TRUE; } /* * lib_cmd_save() * lib_cmd_restore() * * Save/restore a game. */ sc_bool lib_cmd_save(sc_gameref_t game) { if (if_confirm(SC_CONF_SAVE)) { if (g_vm->saveGame().getCode() == Common::kNoError) if_print_string("Ok.\n"); else if_print_string("Save failed.\n"); } game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_restore(sc_gameref_t game) { if (if_confirm(SC_CONF_RESTORE)) { if (g_vm->loadGame().getCode() == Common::kNoError) { if_print_string("Ok.\n"); game->is_running = FALSE; game->do_restore = TRUE; } else { if_print_string("Restore failed.\n"); } } game->is_admin = TRUE; return TRUE; } /* * lib_cmd_locate_object() * lib_cmd_locate_npc() * * Display the location of a selected object, and selected NPC. */ sc_bool lib_cmd_locate_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); sc_int index_, count, object, room, position, parent; game->is_admin = TRUE; /* * Filter to remove unseen object references. Note that this is different * from NPCs, who we acknowledge even when unseen. */ for (index_ = 0; index_ < gs_object_count(game); index_++) { if (!gs_object_seen(game, index_)) game->object_references[index_] = FALSE; } /* Count the number of objects referenced by the last command. */ count = 0; object = -1; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (game->object_references[index_]) { count++; object = index_; } } /* * If no objects identified, be coy about revealing anything; if more than * one, be vague. */ if (count == 0) { pf_buffer_string(filter, "I don't know where that is.\n"); return TRUE; } else if (count > 1) { pf_buffer_string(filter, "Please be more clear about what you want to" " locate.\n"); return TRUE; } /* * The reference is unambiguous, so we're responsible for noting it in * variables. Disambiguation would normally do this for us, but we just * bypassed it. */ var_set_ref_object(vars, object); /* See if we can print a message based on position and parent. */ position = gs_object_position(game, object); parent = gs_object_parent(game, object); switch (position) { case OBJ_HIDDEN: if (!obj_is_static(game, object)) { pf_buffer_string(filter, "I don't know where that is.\n"); return TRUE; } break; case OBJ_HELD_PLAYER: pf_new_sentence(filter); pf_buffer_string(filter, lib_select_response(game, "You are carrying ", "I am carrying ", "%player% is carrying ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; case OBJ_WORN_PLAYER: pf_new_sentence(filter); pf_buffer_string(filter, lib_select_response(game, "You are wearing ", "I am wearing ", "%player% is wearing ")); lib_print_object_np(game, object); pf_buffer_string(filter, "!\n"); return TRUE; case OBJ_HELD_NPC: case OBJ_WORN_NPC: if (gs_npc_seen(game, parent)) { pf_new_sentence(filter); lib_print_npc_np(game, parent); pf_buffer_string(filter, (position == OBJ_HELD_NPC) ? " is holding " : " is wearing "); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); } else pf_buffer_string(filter, "I don't know where that is.\n"); return TRUE; case OBJ_PART_NPC: if (parent == -1) { pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is", " are")); pf_buffer_string(filter, lib_select_response(game, " a part of you!\n", " a part of me!\n", " a part of %player%!\n")); } else { if (gs_npc_seen(game, parent)) { pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is", " are")); pf_buffer_string(filter, " a part of "); lib_print_npc_np(game, parent); pf_buffer_string(filter, ".\n"); } else pf_buffer_string(filter, "I don't know where that is.\n"); } return TRUE; case OBJ_ON_OBJECT: case OBJ_IN_OBJECT: if (gs_object_seen(game, parent)) { pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is", " are")); pf_buffer_string(filter, (position == OBJ_ON_OBJECT) ? " on " : " inside "); lib_print_object_np(game, parent); pf_buffer_string(filter, ".\n"); } else pf_buffer_string(filter, "I don't know where that is.\n"); return TRUE; default: break; } /* * Object is either static unmoved, or dynamic and on the floor of a room. * Check each room for the object, stopping on first found. */ for (room = 0; room < gs_room_count(game); room++) { if (obj_indirectly_in_room(game, object, room)) break; } if (room == gs_room_count(game)) { pf_buffer_string(filter, "I don't know where that is.\n"); return TRUE; } /* Check that this room's been visited by the player. */ if (!gs_room_seen(game, room)) { pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, lib_select_plurality(game, object, " is", " are")); pf_buffer_string(filter, lib_select_response(game, " somewhere that you haven't been yet.\n", " somewhere that I haven't been yet.\n", " somewhere that %player% hasn't been yet.\n")); return TRUE; } /* Print the details of the object's room. */ pf_new_sentence(filter); lib_print_object_np(game, object); pf_buffer_string(filter, " -- "); pf_buffer_string(filter, lib_get_room_name(game, room)); pf_buffer_string(filter, ".\n"); return TRUE; } sc_bool lib_cmd_locate_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); sc_int index_, count, npc, room; game->is_admin = TRUE; /* Count the number of NPCs referenced by the last command. */ count = 0; npc = -1; for (index_ = 0; index_ < gs_npc_count(game); index_++) { if (game->npc_references[index_]) { count++; npc = index_; } } /* * If no NPCs identified, be coy about revealing anything; if more than one, * be vague. The "... where that is..." is the correct message even for * NPCs -- it's the same response as for lib_locate_other(). */ if (count == 0) { pf_buffer_string(filter, "I don't know where that is.\n"); return TRUE; } else if (count > 1) { pf_buffer_string(filter, "Please be more clear about who you want to locate.\n"); return TRUE; } /* * The reference is unambiguous, so we're responsible for noting it in * variables. Disambiguation would normally do this for us, but we just * bypassed it. */ var_set_ref_character(vars, npc); /* See if this NPC has been seen yet. */ if (!gs_npc_seen(game, npc)) { pf_buffer_string(filter, lib_select_response(game, "You haven't seen ", "I haven't seen ", "%player% hasn't seen ")); lib_print_npc_np(game, npc); pf_buffer_string(filter, " yet!\n"); return TRUE; } /* Check each room for the NPC, stopping on first found. */ for (room = 0; room < gs_room_count(game); room++) { if (npc_in_room(game, npc, room)) break; } if (room == gs_room_count(game)) { pf_buffer_string(filter, "I don't know where "); lib_print_npc_np(game, npc); pf_buffer_string(filter, " is.\n"); return TRUE; } /* Check that this room's been visited by the player. */ if (!gs_room_seen(game, room)) { lib_print_npc_np(game, npc); pf_buffer_string(filter, lib_select_response(game, " is somewhere that you haven't been yet.\n", " is somewhere that I haven't been yet.\n", " is somewhere that %player% hasn't been yet.\n")); return TRUE; } /* Print the location, and smart-alec response. */ pf_new_sentence(filter); lib_print_npc_np(game, npc); pf_buffer_string(filter, " -- "); pf_buffer_string(filter, lib_get_room_name(game, room)); #if 0 if (room == gs_playerroom(game)) { pf_buffer_string(filter, lib_select_response(game, " (Right next to you, silly!)", " (Right next to me, silly!)", " (Right next to %player%, silly!)")); } #endif pf_buffer_string(filter, ".\n"); return TRUE; } /* * lib_cmd_turns() * lib_cmd_score() * * Display turns taken and score so far. */ sc_bool lib_cmd_turns(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_char buffer[32]; pf_buffer_string(filter, "You have taken "); sprintf(buffer, "%ld", game->turns); pf_buffer_string(filter, buffer); if (game->turns == 1) pf_buffer_string(filter, " turn so far.\n"); else pf_buffer_string(filter, " turns so far.\n"); game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_score(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; sc_int max_score, percent; sc_char buffer[32]; /* Get max score, and calculate score as a percentage. */ vt_key[0].string = "Globals"; vt_key[1].string = "MaxScore"; max_score = prop_get_integer(bundle, "I<-ss", vt_key); if (game->score > 0 && max_score > 0) percent = (game->score * 100) / max_score; else percent = 0; /* Output carefully formatted response. */ pf_buffer_string(filter, lib_select_response(game, "Your score is ", "My score is ", "%player%'s score is ")); sprintf(buffer, "%ld", game->score); pf_buffer_string(filter, buffer); pf_buffer_string(filter, " out of a maximum of "); sprintf(buffer, "%ld", max_score); pf_buffer_string(filter, buffer); pf_buffer_string(filter, ". ("); sprintf(buffer, "%ld", percent); pf_buffer_string(filter, buffer); pf_buffer_string(filter, "%)\n"); game->is_admin = TRUE; return TRUE; } /* * lib_cmd_*() * * Standard response commands. These are uninteresting catch-all cases, * but it's good to make then right as game ALRs may look for them. */ sc_bool lib_cmd_profanity(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "I really don't think there's any need for language like" " that!\n"); return TRUE; } sc_bool lib_cmd_examine_all(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "Please examine one object at a time.\n"); return TRUE; } sc_bool lib_cmd_examine_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You see no such thing.\n", "I see no such thing.\n", "%player% sees no such thing.\n")); return TRUE; } sc_bool lib_cmd_locate_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "I don't know where that is!\n"); game->is_admin = TRUE; return TRUE; } sc_bool lib_cmd_unix_like(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "This isn't Unix you know!\n"); return TRUE; } sc_bool lib_cmd_dos_like(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "This isn't Dos you know!\n"); return TRUE; } sc_bool lib_cmd_cry(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "There's no need for that!\n"); return TRUE; } sc_bool lib_cmd_dance(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You do a little dance.\n", "I do a little dance.\n", "%player% does a little dance.\n")); return TRUE; } sc_bool lib_cmd_eat_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "I don't understand what you are trying to eat.\n"); return TRUE; } sc_bool lib_cmd_fight(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "There is nothing worth fighting here.\n"); return TRUE; } sc_bool lib_cmd_feed(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "There is nothing worth feeding here.\n"); return TRUE; } sc_bool lib_cmd_feel(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You feel nothing out of the ordinary.\n", "I feel nothing out of the ordinary.\n", "%player% feels nothing out of the ordinary.\n")); return TRUE; } sc_bool lib_cmd_fly(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You can't fly.\n", "I can't fly.\n", "%player% can't fly.\n")); return TRUE; } sc_bool lib_cmd_hint(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "You're just going to have to work it out for" " yourself...\n"); return TRUE; } sc_bool lib_cmd_hum(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You hum a little tune.\n", "I hum a little tune.\n", "%player% hums a little tune.\n")); return TRUE; } sc_bool lib_cmd_jump(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "Wheee-boinng.\n"); return TRUE; } sc_bool lib_cmd_listen(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You hear nothing out of the ordinary.\n", "I hear nothing out of the ordinary.\n", "%player% hears nothing out of the ordinary.\n")); return TRUE; } sc_bool lib_cmd_please(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "Your kindness gets you nowhere.\n", "My kindness gets me nowhere.\n", "%player%'s kindness gets nowhere.\n")); return TRUE; } sc_bool lib_cmd_punch(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "Who do you think you are, Mike Tyson?\n"); return TRUE; } sc_bool lib_cmd_run(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "Why would you want to run?\n", "Why would I want to run?\n", "Why would %player% want to run?\n")); return TRUE; } sc_bool lib_cmd_shout(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "Aaarrrrgggghhhhhh!\n"); return TRUE; } sc_bool lib_cmd_say(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_char *string = NULL; switch (sc_randomint(1, 5)) { case 1: string = "Gosh, that was very impressive.\n"; break; case 2: string = lib_select_response(game, "Not surprisingly, no-one takes any notice" " of you.\n", "Not surprisingly, no-one takes any notice" " of me.\n", "Not surprisingly, no-one takes any notice" " of %player%.\n"); break; case 3: string = "Wow! That achieved a lot.\n"; break; case 4: string = "Uh huh, yes, very interesting.\n"; break; default: string = "That's the most interesting thing I've ever heard!\n"; break; } pf_buffer_string(filter, string); return TRUE; } sc_bool lib_cmd_sing(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You sing a little song.\n", "I sing a little song.\n", "%player% sings a little song.\n")); return TRUE; } sc_bool lib_cmd_sleep(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "Zzzzz. Bored are you?\n"); return TRUE; } sc_bool lib_cmd_talk(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "No-one listens to your rabblings.\n", "No-one listens to my rabblings.\n", "No-one listens to %player%'s rabblings.\n")); return TRUE; } sc_bool lib_cmd_thank(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "You're welcome.\n"); return TRUE; } sc_bool lib_cmd_whistle(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, lib_select_response(game, "You whistle a little tune.\n", "I whistle a little tune.\n", "%player% whistles a little tune.\n")); return TRUE; } sc_bool lib_cmd_interrogation(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_char *string = NULL; switch (sc_randomint(1, 17)) { case 1: string = "Why do you want to know?\n"; break; case 2: string = "Interesting question.\n"; break; case 3: string = "Let me think about that one...\n"; break; case 4: string = "I haven't a clue!\n"; break; case 5: string = "All these questions are hurting my head.\n"; break; case 6: string = "I'm not going to tell you.\n"; break; case 7: string = "Someday I'll know the answer to that one.\n"; break; case 8: string = "I could tell you, but then I'd have to kill you.\n"; break; case 9: string = "Ha, as if I'd tell you!\n"; break; case 10: string = "Ask me again later.\n"; break; case 11: string = "I don't know - could you ask anyone else?\n"; break; case 12: string = "Err, yes?!?\n"; break; case 13: string = "Let me just check my memory banks...\n"; break; case 14: string = "Because that's just the way it is.\n"; break; case 15: string = "Do I ask you all sorts of awkward questions?\n"; break; case 16: string = "Questions, questions...\n"; break; default: string = "Who cares.\n"; break; } pf_buffer_string(filter, string); return TRUE; } sc_bool lib_cmd_xyzzy(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "I'm sorry, but XYZZY doesn't do anything special in" " this game!\n"); return TRUE; } sc_bool lib_cmd_egotistic(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); #if 0 pf_buffer_string(filter, "Campbell wrote this Adrift Runner. It's pretty" " good huh!\n"); #else pf_buffer_string(filter, "No comment.\n"); #endif return TRUE; } sc_bool lib_cmd_yes_or_no(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "That's interesting, but it doesn't mean much.\n"); return TRUE; } /* * lib_cmd_ask_npc() * lib_cmd_ask_object() * lib_cmd_ask_other() * * Malformed and rhetorical question responses. */ sc_bool lib_cmd_ask_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int npc; sc_bool is_ambiguous; /* Get the referenced npc, and if none, consider complete. */ npc = lib_disambiguate_npc(game, "ask", &is_ambiguous); if (npc == -1) return is_ambiguous; /* Incomplete ask command, so offer help and return. */ pf_buffer_string(filter, "Use the format \"ask "); lib_print_npc_np(game, npc); pf_buffer_string(filter, " about [subject]\".\n"); return TRUE; } sc_bool lib_cmd_ask_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* Get the referenced object, and if none, consider complete. */ object = lib_disambiguate_object(game, "ask", &is_ambiguous); if (object == -1) return is_ambiguous; /* No reply. */ pf_buffer_string(filter, lib_select_response(game, "You get no reply from ", "I get no reply from ", "%player% gets no reply from ")); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } sc_bool lib_cmd_ask_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); /* Incomplete ask command, so offer help and return. */ pf_buffer_string(filter, "Use the format \"ask [character] about [subject]\".\n"); return TRUE; } /* * lib_cmd_kill_other() * * Uninteresting kill message when no weaponry is involved. */ sc_bool lib_cmd_kill_other(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, "Now that isn't very nice.\n"); return TRUE; } /* * lib_nothing_happens_common() * lib_nothing_happens_object() * lib_nothing_happens_other() * * Central handler for a range of nothing-happens messages. More * uninteresting responses. */ 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) { const sc_filterref_t filter = gs_get_filter(game); const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; sc_int perspective, object; const sc_char *person, *verb; sc_bool is_ambiguous; /* Use person and verb tense according to perspective. */ vt_key[0].string = "Globals"; vt_key[1].string = "Perspective"; perspective = prop_get_integer(bundle, "I<-ss", vt_key); switch (perspective) { case LIB_FIRST_PERSON: person = "I "; verb = verb_general; break; case LIB_SECOND_PERSON: person = "You "; verb = verb_general; break; case LIB_THIRD_PERSON: person = "%player% "; verb = verb_third_person; break; default: sc_error("lib_nothing_happens: unknown perspective, %ld\n", perspective); person = "You "; verb = verb_general; break; } /* If the command target was not an object, end it here. */ if (!is_object) { pf_buffer_string(filter, person); pf_buffer_string(filter, verb); pf_buffer_string(filter, ", but nothing happens.\n"); return TRUE; } /* Get the referenced object. If none, return immediately. */ object = lib_disambiguate_object(game, verb_general, &is_ambiguous); if (object == -1) return is_ambiguous; /* Nothing happens. */ pf_buffer_string(filter, person); pf_buffer_string(filter, verb); pf_buffer_character(filter, ' '); lib_print_object_np(game, object); pf_buffer_string(filter, ", but nothing happens.\n"); return TRUE; } static sc_bool lib_nothing_happens_object(sc_gameref_t game, const sc_char *verb_general, const sc_char *verb_third_person) { return lib_nothing_happens_common(game, verb_general, verb_third_person, TRUE); } static sc_bool lib_nothing_happens_other(sc_gameref_t game, const sc_char *verb_general, const sc_char *verb_third_person) { return lib_nothing_happens_common(game, verb_general, verb_third_person, FALSE); } /* * lib_cmd_*() * * Shake, rattle and roll, and assorted nothing-happens handlers. */ sc_bool lib_cmd_hit_object(sc_gameref_t game) { return lib_nothing_happens_object(game, "hit", "hits"); } sc_bool lib_cmd_kick_object(sc_gameref_t game) { return lib_nothing_happens_object(game, "kick", "kicks"); } sc_bool lib_cmd_press_object(sc_gameref_t game) { return lib_nothing_happens_object(game, "press", "presses"); } sc_bool lib_cmd_push_object(sc_gameref_t game) { return lib_nothing_happens_object(game, "push", "pushes"); } sc_bool lib_cmd_pull_object(sc_gameref_t game) { return lib_nothing_happens_object(game, "pull", "pulls"); } sc_bool lib_cmd_shake_object(sc_gameref_t game) { return lib_nothing_happens_object(game, "shake", "shakes"); } sc_bool lib_cmd_hit_other(sc_gameref_t game) { return lib_nothing_happens_other(game, "hit", "hits"); } sc_bool lib_cmd_kick_other(sc_gameref_t game) { return lib_nothing_happens_other(game, "kick", "kicks"); } sc_bool lib_cmd_press_other(sc_gameref_t game) { return lib_nothing_happens_other(game, "press", "presses"); } sc_bool lib_cmd_push_other(sc_gameref_t game) { return lib_nothing_happens_other(game, "push", "pushes"); } sc_bool lib_cmd_pull_other(sc_gameref_t game) { return lib_nothing_happens_other(game, "pull", "pulls"); } sc_bool lib_cmd_shake_other(sc_gameref_t game) { return lib_nothing_happens_other(game, "shake", "shakes"); } /* * lib_cant_do_common() * lib_cant_do_object() * lib_cant_do_other() * * Central handler for a range of can't-do messages. Yet more uninterest- * ing responses. */ static sc_bool lib_cant_do_common(sc_gameref_t game, const sc_char *verb, sc_bool is_object) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* If the target is not an object, end it here. */ if (!is_object) { pf_buffer_string(filter, lib_select_response(game, "You can't ", "I can't ", "%player% can't ")); pf_buffer_string(filter, verb); pf_buffer_string(filter, " that.\n"); return TRUE; } /* Get the referenced object. If none, return immediately. */ object = lib_disambiguate_object(game, verb, &is_ambiguous); if (object == -1) return is_ambiguous; /* Whatever it is, don't do it. */ pf_buffer_string(filter, lib_select_response(game, "You can't ", "I can't ", "%player% can't ")); pf_buffer_string(filter, verb); pf_buffer_character(filter, ' '); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } static sc_bool lib_cant_do_object(sc_gameref_t game, const sc_char *verb) { return lib_cant_do_common(game, verb, TRUE); } static sc_bool lib_cant_do_other(sc_gameref_t game, const sc_char *verb) { return lib_cant_do_common(game, verb, FALSE); } /* * lib_cmd_*() * * Assorted can't-do messages. */ sc_bool lib_cmd_block_object(sc_gameref_t game) { return lib_cant_do_object(game, "block"); } sc_bool lib_cmd_climb_object(sc_gameref_t game) { return lib_cant_do_object(game, "climb"); } sc_bool lib_cmd_clean_object(sc_gameref_t game) { return lib_cant_do_object(game, "clean"); } sc_bool lib_cmd_cut_object(sc_gameref_t game) { return lib_cant_do_object(game, "cut"); } sc_bool lib_cmd_drink_object(sc_gameref_t game) { return lib_cant_do_object(game, "drink"); } sc_bool lib_cmd_light_object(sc_gameref_t game) { return lib_cant_do_object(game, "light"); } sc_bool lib_cmd_lift_object(sc_gameref_t game) { return lib_cant_do_object(game, "lift"); } sc_bool lib_cmd_move_object(sc_gameref_t game) { return lib_cant_do_object(game, "move"); } sc_bool lib_cmd_rub_object(sc_gameref_t game) { return lib_cant_do_object(game, "rub"); } sc_bool lib_cmd_stop_object(sc_gameref_t game) { return lib_cant_do_object(game, "stop"); } sc_bool lib_cmd_suck_object(sc_gameref_t game) { return lib_cant_do_object(game, "suck"); } sc_bool lib_cmd_touch_object(sc_gameref_t game) { return lib_cant_do_object(game, "touch"); } sc_bool lib_cmd_turn_object(sc_gameref_t game) { return lib_cant_do_object(game, "turn"); } sc_bool lib_cmd_unblock_object(sc_gameref_t game) { return lib_cant_do_object(game, "unblock"); } sc_bool lib_cmd_wash_object(sc_gameref_t game) { return lib_cant_do_object(game, "wash"); } sc_bool lib_cmd_block_other(sc_gameref_t game) { return lib_cant_do_other(game, "block"); } sc_bool lib_cmd_climb_other(sc_gameref_t game) { return lib_cant_do_other(game, "climb"); } sc_bool lib_cmd_clean_other(sc_gameref_t game) { return lib_cant_do_other(game, "clean"); } sc_bool lib_cmd_close_other(sc_gameref_t game) { return lib_cant_do_other(game, "close"); } sc_bool lib_cmd_lock_other(sc_gameref_t game) { return lib_cant_do_other(game, "lock"); } sc_bool lib_cmd_unlock_other(sc_gameref_t game) { return lib_cant_do_other(game, "unlock"); } sc_bool lib_cmd_stand_other(sc_gameref_t game) { return lib_cant_do_other(game, "stand on"); } sc_bool lib_cmd_sit_other(sc_gameref_t game) { return lib_cant_do_other(game, "sit on"); } sc_bool lib_cmd_lie_other(sc_gameref_t game) { return lib_cant_do_other(game, "lie on"); } sc_bool lib_cmd_cut_other(sc_gameref_t game) { return lib_cant_do_other(game, "cut"); } sc_bool lib_cmd_drink_other(sc_gameref_t game) { return lib_cant_do_other(game, "drink"); } sc_bool lib_cmd_lift_other(sc_gameref_t game) { return lib_cant_do_other(game, "lift"); } sc_bool lib_cmd_light_other(sc_gameref_t game) { return lib_cant_do_other(game, "light"); } sc_bool lib_cmd_move_other(sc_gameref_t game) { return lib_cant_do_other(game, "move"); } sc_bool lib_cmd_stop_other(sc_gameref_t game) { return lib_cant_do_other(game, "stop"); } sc_bool lib_cmd_rub_other(sc_gameref_t game) { return lib_cant_do_other(game, "rub"); } sc_bool lib_cmd_suck_other(sc_gameref_t game) { return lib_cant_do_other(game, "suck"); } sc_bool lib_cmd_turn_other(sc_gameref_t game) { return lib_cant_do_other(game, "turn"); } sc_bool lib_cmd_touch_other(sc_gameref_t game) { return lib_cant_do_other(game, "touch"); } sc_bool lib_cmd_unblock_other(sc_gameref_t game) { return lib_cant_do_other(game, "unblock"); } sc_bool lib_cmd_wash_other(sc_gameref_t game) { return lib_cant_do_other(game, "wash"); } /* * lib_dont_think_common() * lib_dont_think_object() * lib_dont_think_other() * * Central handler for a range of don't_think messages. Still more * uninteresting responses. */ static sc_bool lib_dont_think_common(sc_gameref_t game, const sc_char *verb, sc_bool is_object) { const sc_filterref_t filter = gs_get_filter(game); sc_int object; sc_bool is_ambiguous; /* If the target is not an object, end it here. */ if (!is_object) { pf_buffer_string(filter, lib_select_response(game, "I don't think you can ", "I don't think I can ", "I don't think %player% can ")); pf_buffer_string(filter, verb); pf_buffer_string(filter, " that.\n"); return TRUE; } /* Get the referenced object. If none, return immediately. */ object = lib_disambiguate_object(game, verb, &is_ambiguous); if (object == -1) return is_ambiguous; /* Whatever it is, don't do it. */ pf_buffer_string(filter, "I don't think you can "); pf_buffer_string(filter, verb); pf_buffer_character(filter, ' '); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } static sc_bool lib_dont_think_object(sc_gameref_t game, const sc_char *verb) { return lib_dont_think_common(game, verb, TRUE); } static sc_bool lib_dont_think_other(sc_gameref_t game, const sc_char *verb) { return lib_dont_think_common(game, verb, FALSE); } /* * lib_cmd_*() * * Assorted don't-think messages. */ sc_bool lib_cmd_fix_object(sc_gameref_t game) { return lib_dont_think_object(game, "fix"); } sc_bool lib_cmd_mend_object(sc_gameref_t game) { return lib_dont_think_object(game, "mend"); } sc_bool lib_cmd_repair_object(sc_gameref_t game) { return lib_dont_think_object(game, "repair"); } sc_bool lib_cmd_fix_other(sc_gameref_t game) { return lib_dont_think_other(game, "fix"); } sc_bool lib_cmd_mend_other(sc_gameref_t game) { return lib_dont_think_other(game, "mend"); } sc_bool lib_cmd_repair_other(sc_gameref_t game) { return lib_dont_think_other(game, "repair"); } /* * lib_what() * * Central handler for doing something, but unsure to what. */ static sc_bool lib_what(sc_gameref_t game, const sc_char *verb) { const sc_filterref_t filter = gs_get_filter(game); pf_buffer_string(filter, verb); pf_buffer_string(filter, " what?\n"); return TRUE; } /* * lib_cmd_*() * * Assorted "what?" messages. */ sc_bool lib_cmd_block_what(sc_gameref_t game) { return lib_what(game, "Block"); } sc_bool lib_cmd_break_what(sc_gameref_t game) { return lib_what(game, "Break"); } sc_bool lib_cmd_destroy_what(sc_gameref_t game) { return lib_what(game, "Destroy"); } sc_bool lib_cmd_smash_what(sc_gameref_t game) { return lib_what(game, "Smash"); } sc_bool lib_cmd_buy_what(sc_gameref_t game) { return lib_what(game, "Buy"); } sc_bool lib_cmd_clean_what(sc_gameref_t game) { return lib_what(game, "Clean"); } sc_bool lib_cmd_climb_what(sc_gameref_t game) { return lib_what(game, "Climb"); } sc_bool lib_cmd_cut_what(sc_gameref_t game) { return lib_what(game, "Cut"); } sc_bool lib_cmd_drink_what(sc_gameref_t game) { return lib_what(game, "Drink"); } sc_bool lib_cmd_fix_what(sc_gameref_t game) { return lib_what(game, "Fix"); } sc_bool lib_cmd_hit_what(sc_gameref_t game) { return lib_what(game, "Hit"); } sc_bool lib_cmd_kick_what(sc_gameref_t game) { return lib_what(game, "Kick"); } sc_bool lib_cmd_light_what(sc_gameref_t game) { return lib_what(game, "Light"); } sc_bool lib_cmd_lift_what(sc_gameref_t game) { return lib_what(game, "Lift"); } sc_bool lib_cmd_mend_what(sc_gameref_t game) { return lib_what(game, "Mend"); } sc_bool lib_cmd_move_what(sc_gameref_t game) { return lib_what(game, "Move"); } sc_bool lib_cmd_press_what(sc_gameref_t game) { return lib_what(game, "Press"); } sc_bool lib_cmd_pull_what(sc_gameref_t game) { return lib_what(game, "Pull"); } sc_bool lib_cmd_push_what(sc_gameref_t game) { return lib_what(game, "Push"); } sc_bool lib_cmd_repair_what(sc_gameref_t game) { return lib_what(game, "Repair"); } sc_bool lib_cmd_sell_what(sc_gameref_t game) { return lib_what(game, "Sell"); } sc_bool lib_cmd_shake_what(sc_gameref_t game) { return lib_what(game, "Shake"); } sc_bool lib_cmd_rub_what(sc_gameref_t game) { return lib_what(game, "Rub"); } sc_bool lib_cmd_stop_what(sc_gameref_t game) { return lib_what(game, "Stop"); } sc_bool lib_cmd_suck_what(sc_gameref_t game) { return lib_what(game, "Suck"); } sc_bool lib_cmd_touch_what(sc_gameref_t game) { return lib_what(game, "Touch"); } sc_bool lib_cmd_turn_what(sc_gameref_t game) { return lib_what(game, "Turn"); } sc_bool lib_cmd_unblock_what(sc_gameref_t game) { return lib_what(game, "Unblock"); } sc_bool lib_cmd_wash_what(sc_gameref_t game) { return lib_what(game, "Wash"); } sc_bool lib_cmd_drop_what(sc_gameref_t game) { return lib_what(game, "Drop"); } sc_bool lib_cmd_get_what(sc_gameref_t game) { return lib_what(game, "Take"); } sc_bool lib_cmd_give_what(sc_gameref_t game) { return lib_what(game, "Give"); } sc_bool lib_cmd_open_what(sc_gameref_t game) { return lib_what(game, "Open"); } sc_bool lib_cmd_remove_what(sc_gameref_t game) { return lib_what(game, "Remove"); } sc_bool lib_cmd_wear_what(sc_gameref_t game) { return lib_what(game, "Wear"); } sc_bool lib_cmd_lock_what(sc_gameref_t game) { return lib_what(game, "Lock"); } sc_bool lib_cmd_unlock_what(sc_gameref_t game) { return lib_what(game, "Unlock"); } /* * lib_cmd_verb_object() * lib_cmd_verb_character() * * Handlers for unrecognized verbs with known object/NPC. */ sc_bool lib_cmd_verb_object(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); sc_int count, object, index_; /* Ensure the reference is unambiguous. */ count = 0; object = -1; for (index_ = 0; index_ < gs_object_count(game); index_++) { if (game->object_references[index_] && gs_object_seen(game, index_) && obj_indirectly_in_room(game, index_, gs_playerroom(game))) { count++; object = index_; } } if (count != 1) return FALSE; /* Save in variables. */ var_set_ref_object(vars, object); /* Print don't understand message. */ pf_buffer_string(filter, "I don't understand what you want me to do with "); lib_print_object_np(game, object); pf_buffer_string(filter, ".\n"); return TRUE; } sc_bool lib_cmd_verb_npc(sc_gameref_t game) { const sc_filterref_t filter = gs_get_filter(game); const sc_var_setref_t vars = gs_get_vars(game); sc_int count, npc, index_; /* Ensure the reference is unambiguous. */ count = 0; npc = -1; for (index_ = 0; index_ < gs_npc_count(game); index_++) { if (game->npc_references[index_] && gs_npc_seen(game, index_) && npc_in_room(game, index_, gs_playerroom(game))) { count++; npc = index_; } } if (count != 1) return FALSE; /* Save in variables. */ var_set_ref_character(vars, npc); /* Print don't understand message; unlike objects, there's no "me" here. */ pf_buffer_string(filter, "I don't understand what you want to do with "); lib_print_npc_np(game, npc); pf_buffer_string(filter, ".\n"); return TRUE; } /* * lib_debug_trace() * * Set library tracing on/off. */ void lib_debug_trace(sc_bool flag) { lib_trace = flag; } } // End of namespace Adrift } // End of namespace Glk