/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "glk/adrift/scare.h" #include "glk/adrift/scprotos.h" #include "glk/adrift/scgamest.h" namespace Glk { namespace Adrift { /* Assorted definitions and constants. */ static const sc_char NUL = '\0'; /* Trace flag, set before running. */ static sc_bool obj_trace = FALSE; /* * obj_is_static() * obj_is_surface() * obj_is_container() * * Convenience functions to return TRUE for given object attributes. */ sc_bool obj_is_static(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_bool bstatic; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Static"; bstatic = prop_get_boolean(bundle, "B<-sis", vt_key); return bstatic; } sc_bool obj_is_container(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_bool is_container; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Container"; is_container = prop_get_boolean(bundle, "B<-sis", vt_key); return is_container; } sc_bool obj_is_surface(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_bool is_surface; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Surface"; is_surface = prop_get_boolean(bundle, "B<-sis", vt_key); return is_surface; } /* * obj_container_object() * * Return the index of the n'th container object found. */ sc_int obj_container_object(sc_gameref_t game, sc_int n) { sc_int object, count; /* Progress through objects until n containers found. */ count = n; for (object = 0; object < gs_object_count(game) && count >= 0; object++) { if (obj_is_container(game, object)) count--; } return object - 1; } /* * obj_container_index() * * Return index such that obj_container_object(index) == objnum. */ sc_int obj_container_index(sc_gameref_t game, sc_int objnum) { sc_int object, count; /* Progress through objects up to objnum. */ count = 0; for (object = 0; object < objnum; object++) { if (obj_is_container(game, object)) count++; } return count; } /* * obj_surface_object() * * Return the index of the n'th surface object found. */ sc_int obj_surface_object(sc_gameref_t game, sc_int n) { sc_int object, count; /* Progress through objects until n surfaces found. */ count = n; for (object = 0; object < gs_object_count(game) && count >= 0; object++) { if (obj_is_surface(game, object)) count--; } return object - 1; } /* * obj_surface_index() * * Return index such that obj_surface_object(index) == objnum. */ sc_int obj_surface_index(sc_gameref_t game, sc_int objnum) { sc_int object, count; /* Progress through objects up to objnum. */ count = 0; for (object = 0; object < objnum; object++) { if (obj_is_surface(game, object)) count++; } return count; } /* * obj_stateful_object() * * Return the index of the n'th openable or statussed object found. */ sc_int obj_stateful_object(sc_gameref_t game, sc_int n) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, count; /* Progress through objects until n matches found. */ count = n; for (object = 0; object < gs_object_count(game) && count >= 0; object++) { sc_vartype_t vt_key[3]; sc_bool is_openable, is_statussed; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Openable"; is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0; vt_key[2].string = "CurrentState"; is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0; if (is_openable || is_statussed) count--; } return object - 1; } /* * obj_stateful_index() * * Return index such that obj_stateful_object(index) == objnum. */ sc_int obj_stateful_index(sc_gameref_t game, sc_int objnum) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, count; /* Progress through objects up to objnum. */ count = 0; for (object = 0; object < objnum; object++) { sc_vartype_t vt_key[3]; sc_bool is_openable, is_statussed; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Openable"; is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0; vt_key[2].string = "CurrentState"; is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0; if (is_openable || is_statussed) count++; } return count; } /* * obj_state_name() * * Return the string name of the state of a given stateful object. The * string is malloc'ed, and needs to be freed by the caller. Returns NULL * if no valid state string found. */ sc_char *obj_state_name(sc_gameref_t game, sc_int objnum) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; const sc_char *states; sc_int length, state, count, first, last; sc_char *string; /* Get the list of state strings for the object. */ vt_key[0].string = "Objects"; vt_key[1].integer = objnum; vt_key[2].string = "States"; states = prop_get_string(bundle, "S<-sis", vt_key); /* Find the start of the element for the current state. */ state = gs_object_state(game, objnum); length = strlen(states); for (first = 0, count = state; first < length && count > 1; first++) { if (states[first] == '|') count--; } if (count != 1) return NULL; /* Find the end of the state string. */ for (last = first; last < length; last++) { if (states[last] == '|') break; } /* Allocate and take a copy of the state string. */ string = (sc_char *)sc_malloc(last - first + 1); memcpy(string, states + first, last - first); string[last - first] = NUL; return string; } /* * obj_dynamic_object() * * Return the index of the n'th non-static object found. */ sc_int obj_dynamic_object(sc_gameref_t game, sc_int n) { sc_int object, count; /* Progress through objects until n matches found. */ count = n; for (object = 0; object < gs_object_count(game) && count >= 0; object++) { if (!obj_is_static(game, object)) count--; } return object - 1; } /* * obj_wearable_object() * * Return the index of the n'th wearable object found. */ sc_int obj_wearable_object(sc_gameref_t game, sc_int n) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, count; /* Progress through objects until n matches found. */ count = n; for (object = 0; object < gs_object_count(game) && count >= 0; object++) { if (!obj_is_static(game, object)) { sc_vartype_t vt_key[3]; vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Wearable"; if (prop_get_boolean(bundle, "B<-sis", vt_key)) count--; } } return object - 1; } /* * Size is held in the ten's digit of SizeWeight, and weight in the units. * Size and weight are multipliers -- the relative size and weight of objects * rises by a factor of three for each incremental multiplier. These factors * are also used for the maximum size of object that can fit in a container, * and the number of these that fit. */ enum { OBJ_DIMENSION_DIVISOR = 10, OBJ_DIMENSION_MULTIPLE = 3 }; /* * obj_get_size() * obj_get_weight() * * Return the relative size and weight of an object. For containers, the * weight includes the weight of each contained object. * * TODO It's possible to have static objects in the player inventory, moved * by events -- how should these be handled, as they have no SizeWeight? */ sc_int obj_get_size(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int size, count; /* TODO For now, give static objects no size. */ if (obj_is_static(game, object)) return 0; /* Size is the 'tens' component of SizeWeight. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "SizeWeight"; count = prop_get_integer(bundle, "I<-sis", vt_key) / OBJ_DIMENSION_DIVISOR; /* * Calculate base object size. Unlike weights below, we take this as simply * being the maximum size; that is, when a container carries other objects * its weight increases by the sum of objects carried, but its size remains * constant. */ size = 1; for (; count > 0; count--) size *= OBJ_DIMENSION_MULTIPLE; if (obj_trace) sc_trace("Object: object %ld is size %ld\n", object, size); /* Return total size. */ return size; } sc_int obj_get_weight(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int weight, count; /* TODO For now, give static objects no weight. */ if (obj_is_static(game, object)) return 0; /* Weight is the 'units' component of SizeWeight. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "SizeWeight"; count = prop_get_integer(bundle, "I<-sis", vt_key) % OBJ_DIMENSION_DIVISOR; /* Calculate base object weight. */ weight = 1; for (; count > 0; count--) weight *= OBJ_DIMENSION_MULTIPLE; /* If this is a container or a surface, add weights of parented objects. */ if (obj_is_container(game, object) || obj_is_surface(game, object)) { sc_int other; /* Find and add contained or surface objects. */ for (other = 0; other < gs_object_count(game); other++) { if ((gs_object_position(game, other) == OBJ_IN_OBJECT || gs_object_position(game, other) == OBJ_ON_OBJECT) && gs_object_parent(game, other) == object) { weight += obj_get_weight(game, other); } } } if (obj_trace) sc_trace("Object: object %ld is weight %ld\n", object, weight); /* Return total weight. */ return weight; } /* * obj_convert_player_limit() * obj_get_player_size_limit() * obj_get_player_weight_limit() * * Return the limits set on the sizes and weights a player can handle. Not * really object-related except that they deal with sizing multiples. */ static sc_int obj_convert_player_limit(sc_int value) { sc_int retval, index_; /* 'Tens' of value multiplied by 3 to the power 'units' of value. */ retval = value / OBJ_DIMENSION_DIVISOR; for (index_ = 0; index_ < value % OBJ_DIMENSION_DIVISOR; index_++) retval *= OBJ_DIMENSION_MULTIPLE; return retval; } sc_int obj_get_player_size_limit(sc_gameref_t game) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; sc_int max_size; vt_key[0].string = "Globals"; vt_key[1].string = "MaxSize"; max_size = prop_get_integer(bundle, "I<-ss", vt_key); return obj_convert_player_limit(max_size); } sc_int obj_get_player_weight_limit(sc_gameref_t game) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[2]; sc_int max_weight; vt_key[0].string = "Globals"; vt_key[1].string = "MaxWt"; max_weight = prop_get_integer(bundle, "I<-ss", vt_key); return obj_convert_player_limit(max_weight); } /* * obj_get_container_maxsize() * obj_get_container_capacity() * * Return the maximum size of an object that can be placed in a container, * and the number that will fit. */ sc_int obj_get_container_maxsize(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int maxsize, count; /* Maxsize is found from the 'units' component of Capacity. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Capacity"; count = prop_get_integer(bundle, "I<-sis", vt_key) % OBJ_DIMENSION_DIVISOR; /* Calculate and return maximum size. */ maxsize = 1; for (; count > 0; count--) maxsize *= OBJ_DIMENSION_MULTIPLE; if (obj_trace) sc_trace("Object: object %ld has max size %ld\n", object, maxsize); return maxsize; } sc_int obj_get_container_capacity(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int capacity; /* The count of objects is in the 'tens' component of Capacity. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Capacity"; capacity = prop_get_integer(bundle, "I<-sis", vt_key) / OBJ_DIMENSION_DIVISOR; if (obj_trace) sc_trace("Object: object %ld has capacity %ld\n", object, capacity); return capacity; } /* Sit/lie bit mask enumerations. */ enum { OBJ_STANDABLE_MASK = 1 << 0, OBJ_LIEABLE_MASK = 1 << 1 }; /* * obj_standable_object() * * Return the index of the n'th standable object found. */ sc_int obj_standable_object(sc_gameref_t game, sc_int n) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, count; /* Progress through objects until n standable found. */ count = n; for (object = 0; object < gs_object_count(game) && count >= 0; object++) { sc_vartype_t vt_key[3]; sc_int sit_lie_flags; 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 & OBJ_STANDABLE_MASK) count--; } return object - 1; } /* * obj_lieable_object() * * Return the index of the n'th lieable object found. */ sc_int obj_lieable_object(sc_gameref_t game, sc_int n) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_int object, count; /* Progress through objects until n lieable found. */ count = n; for (object = 0; object < gs_object_count(game) && count >= 0; object++) { sc_vartype_t vt_key[3]; sc_int sit_lie_flags; 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 & OBJ_LIEABLE_MASK) count--; } return object - 1; } /* * obj_appears_plural() * * Return TRUE if the object appears to be plural. Adrift makes a guess at * this to produce "... is on.." or "... are on...". It's not clear how it * does it, but it looks something like: singular if prefix is "a" or "an" * or ""; plural if prefix is "the" or "some" and short name ends with 's' * that is not preceded by 'u'. */ sc_bool obj_appears_plural(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; const sc_char *prefix, *name; /* Check prefix for "a", "an", or empty. */ 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) || sc_compare_word(prefix, "a", 1) || sc_compare_word(prefix, "an", 2))) { sc_int length; /* Check name for ending in 's', but not 'us'. */ vt_key[2].string = "Short"; name = prop_get_string(bundle, "S<-sis", vt_key); length = strlen(name); if (!sc_strempty(name) && sc_tolower(name[length - 1]) == 's' && (length < 2 || sc_tolower(name[length - 2]) != 'u')) return TRUE; } /* Doesn't look plural. */ return FALSE; } /* * obj_directly_in_room_internal() * obj_directly_in_room() * * Return TRUE if a given object is currently on the floor of a given room. */ static sc_bool obj_directly_in_room_internal(sc_gameref_t game, sc_int object, sc_int room) { const sc_prop_setref_t bundle = gs_get_bundle(game); /* See if the object is static or dynamic. */ if (obj_is_static(game, object)) { sc_vartype_t vt_key[5]; sc_int type; /* Static object moved to player or room by event? */ if (!gs_object_static_unmoved(game, object)) { if (gs_object_position(game, object) == OBJ_HELD_PLAYER) return FALSE; else return gs_object_position(game, object) - 1 == room; } /* Check and return the room list for the object. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Where"; vt_key[3].string = "Type"; type = prop_get_integer(bundle, "I<-siss", vt_key); switch (type) { case ROOMLIST_ALL_ROOMS: return TRUE; case ROOMLIST_NO_ROOMS: case ROOMLIST_NPC_PART: return FALSE; case ROOMLIST_ONE_ROOM: vt_key[3].string = "Room"; return prop_get_integer(bundle, "I<-siss", vt_key) == room + 1; case ROOMLIST_SOME_ROOMS: vt_key[3].string = "Rooms"; vt_key[4].integer = room + 1; return prop_get_boolean(bundle, "B<-sissi", vt_key); default: sc_fatal("obj_directly_in_room_internal:" " invalid type, %ld\n", type); return FALSE; } } else return gs_object_position(game, object) == room + 1; } sc_bool obj_directly_in_room(sc_gameref_t game, sc_int object, sc_int room) { sc_bool result; /* Check, trace result, and return. */ result = obj_directly_in_room_internal(game, object, room); if (obj_trace) { sc_trace("Object: checking for object %ld directly in room %ld, %s\n", object, room, result ? "true" : "false"); } return result; } /* * obj_indirectly_in_room_internal() * obj_indirectly_in_room() * * Return TRUE if a given object is currently in a given room, either * directly, on an object indirectly, in an open object indirectly, or * carried by an NPC in the room. */ static sc_bool obj_indirectly_in_room_internal(sc_gameref_t game, sc_int object, sc_int room) { const sc_prop_setref_t bundle = gs_get_bundle(game); /* See if the object is static or dynamic. */ if (obj_is_static(game, object)) { sc_vartype_t vt_key[5]; sc_int type; /* Static object moved to player or room by event? */ if (!gs_object_static_unmoved(game, object)) { if (gs_object_position(game, object) == OBJ_HELD_PLAYER) return gs_player_in_room(game, room); else return gs_object_position(game, object) - 1 == room; } /* Check and return the room list for the object. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "Where"; vt_key[3].string = "Type"; type = prop_get_integer(bundle, "I<-siss", vt_key); switch (type) { case ROOMLIST_ALL_ROOMS: return TRUE; case ROOMLIST_NO_ROOMS: return FALSE; case ROOMLIST_ONE_ROOM: vt_key[3].string = "Room"; return prop_get_integer(bundle, "I<-siss", vt_key) == room + 1; case ROOMLIST_SOME_ROOMS: vt_key[3].string = "Rooms"; vt_key[4].integer = room + 1; return prop_get_boolean(bundle, "B<-sissi", vt_key); case ROOMLIST_NPC_PART: { sc_int npc; vt_key[2].string = "Parent"; npc = prop_get_integer(bundle, "I<-sis", vt_key); if (npc == 0) return gs_player_in_room(game, room); else return npc_in_room(game, npc - 1, room); } default: sc_fatal("obj_indirectly_in_room_internal:" " invalid type, %ld\n", type); return FALSE; } } else { sc_int parent, position; /* Get dynamic object's parent and position. */ parent = gs_object_parent(game, object); position = gs_object_position(game, object); /* Decide depending on positioning. */ switch (position) { case OBJ_HIDDEN: /* Hidden. */ return FALSE; case OBJ_HELD_PLAYER: /* Held by player. */ case OBJ_WORN_PLAYER: /* Worn by player. */ return gs_player_in_room(game, room); case OBJ_HELD_NPC: /* Held by NPC. */ case OBJ_WORN_NPC: /* Worn by NPC. */ return npc_in_room(game, parent, room); case OBJ_IN_OBJECT: { /* In another object. */ sc_int openness; openness = gs_object_openness(game, parent); switch (openness) { case OBJ_WONTCLOSE: case OBJ_OPEN: return obj_indirectly_in_room(game, parent, room); default: return FALSE; } } case OBJ_ON_OBJECT: /* On another object. */ return obj_indirectly_in_room(game, parent, room); default: /* Within a room. */ if (position > gs_room_count(game) + 1) { sc_error("sc_object_indirectly_in_room:" " position out of bounds, %ld\n", position); } return position - 1 == room; } } } sc_bool obj_indirectly_in_room(sc_gameref_t game, sc_int object, sc_int room) { sc_bool result; /* Check, trace result, and return. */ result = obj_indirectly_in_room_internal(game, object, room); if (obj_trace) { sc_trace("Object: checking for object %ld indirectly in room %ld, %s\n", object, room, result ? "true" : "false"); } return result; } /* * obj_indirectly_held_by_player_internal() * obj_indirectly_held_by_player() * * Return TRUE if a given object is currently held by the player, either * directly, on an object indirectly, or in an open object indirectly. */ static sc_bool obj_indirectly_held_by_player_internal(sc_gameref_t game, sc_int object) { /* See if the object is static or dynamic. */ if (obj_is_static(game, object)) { /* Static object moved to player or room by event? */ if (!gs_object_static_unmoved(game, object)) { if (gs_object_position(game, object) == OBJ_HELD_PLAYER) return TRUE; else return FALSE; } /* An unmoved static object is not held by the player. */ return FALSE; } else { sc_int parent, position; /* Get dynamic object's parent and position. */ parent = gs_object_parent(game, object); position = gs_object_position(game, object); /* Decide depending on positioning. */ switch (position) { case OBJ_HIDDEN: /* Hidden. */ return FALSE; case OBJ_HELD_PLAYER: /* Held by player. */ case OBJ_WORN_PLAYER: /* Worn by player. */ return TRUE; case OBJ_HELD_NPC: /* Held by NPC. */ case OBJ_WORN_NPC: /* Worn by NPC. */ return FALSE; case OBJ_IN_OBJECT: { /* In another object. */ sc_int openness; openness = gs_object_openness(game, parent); switch (openness) { case OBJ_WONTCLOSE: case OBJ_OPEN: return obj_indirectly_held_by_player(game, parent); default: return FALSE; } } case OBJ_ON_OBJECT: /* On another object. */ return obj_indirectly_held_by_player(game, parent); default: /* Within a room. */ return FALSE; } } } sc_bool obj_indirectly_held_by_player(sc_gameref_t game, sc_int object) { sc_bool result; /* Check, trace result, and return. */ result = obj_indirectly_held_by_player_internal(game, object); if (obj_trace) { sc_trace("Object: checking for object %ld indirectly" " held by player, %s\n", object, result ? "true" : "false"); } return result; } /* * sc_obj_shows_initial_description() * * Return TRUE if this object should be listed as room content. */ sc_bool obj_shows_initial_description(sc_gameref_t game, sc_int object) { const sc_prop_setref_t bundle = gs_get_bundle(game); sc_vartype_t vt_key[3]; sc_int onlywhennotmoved; /* Get only when moved property. */ vt_key[0].string = "Objects"; vt_key[1].integer = object; vt_key[2].string = "OnlyWhenNotMoved"; onlywhennotmoved = prop_get_integer(bundle, "I<-sis", vt_key); /* Combine this with game in mysterious ways. */ switch (onlywhennotmoved) { case 0: return TRUE; case 1: return gs_object_unmoved(game, object); case 2: { sc_int initialposition; if (gs_object_unmoved(game, object)) return TRUE; vt_key[2].string = "InitialPosition"; initialposition = prop_get_integer(bundle, "I<-sis", vt_key) - 3; return gs_object_position(game, object) == initialposition; } } /* What you talkin' 'bout, Willis? */ return FALSE; } /* * obj_turn_update() * obj_setup_initial() * * Set initial values for object states, and update after a turn. */ void obj_turn_update(sc_gameref_t game) { sc_int index_; /* Update object seen flag to current state. */ for (index_ = 0; index_ < gs_object_count(game); index_++) { if (!gs_object_seen(game, index_) && obj_indirectly_in_room(game, index_, gs_playerroom(game))) gs_set_object_seen(game, index_, TRUE); } } void obj_setup_initial(sc_gameref_t game) { /* Set initial seen states for objects. */ obj_turn_update(game); } /* * obj_debug_trace() * * Set object tracing on/off. */ void obj_debug_trace(sc_bool flag) { obj_trace = flag; } } // End of namespace Adrift } // End of namespace Glk