Files
scummvm-cursorfix/engines/glk/adrift/sclibrar.cpp
2026-02-02 04:50:13 +01:00

9655 lines
282 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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",
nullptr
};
static const sc_char *const DIRNAMES_8[] = {
"north", "east", "south", "west", "up", "down", "in", "out",
"northeast", "southeast", "southwest", "northwest",
nullptr
};
/*
* 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, &timestamp, &turns);
if (count >= first) {
sc_int hr, min, sec;
sc_char buffer[64];
/* Write the history entry sequence. */
Common::sprintf_s(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)
Common::sprintf_s(buffer, "%ldh %02ldm %02lds", hr, min, sec);
else
Common::sprintf_s(buffer, "%ldm %02lds", min, sec);
if_print_string(buffer);
/* Follow up with the turns count, and the command string itself. */
Common::sprintf_s(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, &timestamp, &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, nullptr)) {
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 <object>, [Get <object>],"
" [Drop <object>], [...it], [...all]\n [Where is <object>]\n"
" [Give <object> to <character>], [Open...], [Close...],"
" [Ask <character> about <subject>]\n"
" [Wear <object>], [Remove <object>], [I]nventory\n"
" [Put <object> into <object>], [Put <object> onto <object>]\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, nullptr, nullptr,
&score, nullptr, &room, &status, nullptr, nullptr, nullptr, nullptr);
/* 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: ");
Common::sprintf_s(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;
Common::sprintf_s(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, "(");
Common::sprintf_s(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 ");
Common::sprintf_s(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)
Common::sprintf_s(buffer, "%ldh %02ldm %02lds", hr, min, sec);
else
Common::sprintf_s(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 "<some_tag>" 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 = nullptr;
/* 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, nullptr, -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.
*/
Common::sprintf_s(command, required, "%s %s %s %s %s %s", verb,
prefix, name, preposition, associate_prefix, associate_name);
status = run_game_task_commands(game, command);
if (!status) {
Common::sprintf_s(command, required, "%s %s %s %s %s",
verb, prefix, name, preposition, associate_name);
status = run_game_task_commands(game, command);
}
if (!status) {
Common::sprintf_s(command, required, "%s %s %s %s %s",
verb, name, preposition, associate_prefix, associate_name);
status = run_game_task_commands(game, command);
}
if (!status) {
Common::sprintf_s(command, required, "%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. */
Common::sprintf_s(command, required, "%s %s %s", verb, prefix, name);
status = run_game_task_commands(game, command);
if (!status) {
Common::sprintf_s(command, required, "%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, nullptr, -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 ");
Common::sprintf_s(buffer, "%ld", size);
pf_buffer_string(filter, buffer);
pf_buffer_string(filter, ". The most you can hold is ");
Common::sprintf_s(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 ");
Common::sprintf_s(buffer, "%ld", weight);
pf_buffer_string(filter, buffer);
pf_buffer_string(filter, ". The most you can hold is ");
Common::sprintf_s(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 <something>" 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,
nullptr);
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,
nullptr);
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 <object>" 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,
nullptr);
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,
nullptr);
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", nullptr);
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,
nullptr);
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,
nullptr);
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, nullptr)
|| lib_object_too_large(game, object, nullptr))
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", nullptr);
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", nullptr);
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, nullptr);
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, nullptr);
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", nullptr);
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 = nullptr;
cant_do_that = nullptr;
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 ");
Common::sprintf_s(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 "));
Common::sprintf_s(buffer, "%ld", game->score);
pf_buffer_string(filter, buffer);
pf_buffer_string(filter, " out of a maximum of ");
Common::sprintf_s(buffer, "%ld", max_score);
pf_buffer_string(filter, buffer);
pf_buffer_string(filter, ". (");
Common::sprintf_s(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 = nullptr;
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 = nullptr;
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