9655 lines
282 KiB
C++
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, ×tamp, &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, ×tamp, &turns);
|
|
|
|
/*
|
|
* If this is the "!!" special case, match everything. Otherwise,
|
|
* either search the command for the target, or match if the command
|
|
* begins with the target.
|
|
*/
|
|
if (is_do_last)
|
|
is_matched = TRUE;
|
|
else if (is_contains) {
|
|
sc_int index_;
|
|
|
|
/* Search this command for an occurrence of target anywhere. */
|
|
is_matched = FALSE;
|
|
for (index_ = strlen(command) - length; index_ >= 0; index_--) {
|
|
if (sc_strncasecmp(command + index_, target, length) == 0) {
|
|
is_matched = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} else
|
|
is_matched = (sc_strncasecmp(command, target, length) == 0);
|
|
|
|
/* If the command matched the target criteria, note it and continue. */
|
|
if (is_matched)
|
|
matched_sequence = sequence;
|
|
}
|
|
|
|
/* If we found a match, set the redo values accordingly. */
|
|
if (matched_sequence > 0) {
|
|
game->do_again = TRUE;
|
|
game->redo_sequence = matched_sequence;
|
|
} else {
|
|
if_print_string("No matching entry found in the command history.\n");
|
|
|
|
/* As with failed numeric redo above, special-case this return. */
|
|
game->do_again = FALSE;
|
|
game->redo_sequence = INTEGER_MAX;
|
|
}
|
|
|
|
game->is_admin = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
sc_bool lib_cmd_redo_text(sc_gameref_t game) {
|
|
const sc_var_setref_t vars = gs_get_vars(game);
|
|
|
|
/* Call the common redo with the referenced text from %text%. */
|
|
return lib_cmd_redo_text_last_common(game, var_get_ref_text(vars));
|
|
}
|
|
|
|
sc_bool lib_cmd_redo_last(sc_gameref_t game) {
|
|
/* Call the common redo with, literally, "!", forming "!!" . */
|
|
return lib_cmd_redo_text_last_common(game, "!");
|
|
}
|
|
|
|
|
|
/*
|
|
* lib_cmd_hints()
|
|
*
|
|
* Called on "hints". Requests the interface to display any available hints.
|
|
*/
|
|
sc_bool lib_cmd_hints(sc_gameref_t game) {
|
|
const sc_filterref_t filter = gs_get_filter(game);
|
|
sc_int task;
|
|
sc_bool game_has_hints;
|
|
|
|
/*
|
|
* Check for the presence of any game hints at all, no matter whether the
|
|
* task is runnable or not.
|
|
*/
|
|
game_has_hints = FALSE;
|
|
for (task = 0; task < gs_task_count(game); task++) {
|
|
if (task_has_hints(game, task)) {
|
|
game_has_hints = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the game has hints, display any relevant ones. */
|
|
if (game_has_hints) {
|
|
if (run_hint_iterate(game, 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
|