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

1686 lines
47 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/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/scgamest.h"
#include "glk/glk.h"
#include "glk/events.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Gender enumerations are 0/1/2, but 1/2/3 in jAsea. The 0/1/2 values
* seem to be right. Is jAsea off by one?
*
* o jAsea tries to read Globals.CompileDate. It's just CompileDate.
*
* o State_ and obstate are implemented, but not fully tested due to a lack
* of games that use them.
*/
/* Assorted definitions and constants. */
static const sc_uint VARS_MAGIC = 0xabcc7a71;
static const sc_char NUL = '\0';
/* Variables trace flag. */
static sc_bool var_trace = FALSE;
/* Table of numbers zero to twenty spelled out. */
enum { VAR_NUMBERS_SIZE = 21 };
static const sc_char *const VAR_NUMBERS[VAR_NUMBERS_SIZE] = {
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
"sixteen", "seventeen", "eighteen", "nineteen", "twenty"
};
/* Variable entry, held on a list hashed by variable name. */
struct sc_var_s {
struct sc_var_s *next;
const sc_char *name;
sc_int type;
sc_vartype_t value;
};
typedef sc_var_s sc_var_t;
typedef sc_var_t *sc_varref_t;
/*
* Variables set structure. A self-contained set of variables on which
* variables functions operate. 211 is prime, making it a reasonable hash
* divisor. There's no rehashing here; few games, if any, are likely to
* exceed a fill factor of two (~422 variables).
*/
enum { VAR_HASH_TABLE_SIZE = 211 };
struct sc_var_set_s {
sc_uint magic;
sc_prop_setref_t bundle;
sc_int referenced_character;
sc_int referenced_object;
sc_int referenced_number;
sc_bool is_number_referenced;
sc_char *referenced_text;
sc_char *temporary;
uint32 timestamp;
sc_uint time_offset;
sc_gameref_t game;
sc_varref_t variable[VAR_HASH_TABLE_SIZE];
};
typedef sc_var_set_s sc_var_set_t;
/*
* var_is_valid()
*
* Return TRUE if pointer is a valid variables set, FALSE otherwise.
*/
static sc_bool var_is_valid(sc_var_setref_t vars) {
return vars && vars->magic == VARS_MAGIC;
}
/*
* var_hash_name()
*
* Hash a variable name, modulo'ed to the number of buckets.
*/
static sc_uint var_hash_name(const sc_char *name) {
return sc_hash(name) % VAR_HASH_TABLE_SIZE;
}
/*
* var_create_empty()
*
* Create and return a new empty set of variables.
*/
static sc_var_setref_t var_create_empty(void) {
sc_var_setref_t vars;
sc_int index_;
/* Create a clean set of variables. */
vars = (sc_var_setref_t)sc_malloc(sizeof(*vars));
vars->magic = VARS_MAGIC;
vars->bundle = nullptr;
vars->referenced_character = -1;
vars->referenced_object = -1;
vars->referenced_number = 0;
vars->is_number_referenced = FALSE;
vars->referenced_text = nullptr;
vars->temporary = nullptr;
vars->timestamp = g_vm->_events->getTotalPlayTicks() / 1000;
vars->time_offset = 0;
vars->game = nullptr;
/* Clear all variable hash lists. */
for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++)
vars->variable[index_] = nullptr;
return vars;
}
/*
* var_destroy()
*
* Destroy a variable set, and free its heap memory.
*/
void var_destroy(sc_var_setref_t vars) {
sc_int index_;
assert(var_is_valid(vars));
/*
* Free the content of each string variable, and variable entry. String
* variable content needs to use mutable string instead of const string.
*/
for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++) {
sc_varref_t var, next;
for (var = vars->variable[index_]; var; var = next) {
next = var->next;
if (var->type == VAR_STRING)
sc_free(var->value.mutable_string);
sc_free(var);
}
}
/* Free any temporary and reference text storage area. */
sc_free(vars->temporary);
sc_free(vars->referenced_text);
/* Poison and free the variable set itself. */
memset(vars, 0xaa, sizeof(*vars));
sc_free(vars);
}
/*
* var_find()
* var_add()
*
* Find and return a pointer to a named variable structure, or nullptr if no such
* variable exists, and add a new variable structure to the lists.
*/
static sc_varref_t var_find(sc_var_setref_t vars, const sc_char *name) {
sc_uint hash;
sc_varref_t var;
/* Hash name, search list and return if name match found. */
hash = var_hash_name(name);
for (var = vars->variable[hash]; var; var = var->next) {
if (strcmp(name, var->name) == 0)
break;
}
/* Return variable, or nullptr if no such variable. */
return var;
}
static sc_varref_t var_add(sc_var_setref_t vars, const sc_char *name, sc_int type) {
sc_varref_t var;
sc_uint hash;
/* Create a new variable entry. */
var = (sc_varref_t)sc_malloc(sizeof(*var));
var->name = name;
var->type = type;
var->value.voidp = nullptr;
/* Hash its name, and insert it at start of the relevant list. */
hash = var_hash_name(name);
var->next = vars->variable[hash];
vars->variable[hash] = var;
return var;
}
/*
* var_get_scare_version()
*
* Return the value of %scare_version%. Used to generate the system version
* of this variable, and to re-initialize user versions initialized to zero.
*/
static sc_int var_get_scare_version(void) {
sc_int major, minor, point, version;
if (sscanf(SCARE_VERSION, "%ld.%ld.%ld", &major, &minor, &point) != 3) {
sc_error("var_get_scare_version: unable to generate scare_version\n");
return 0;
}
version = major * 10000 + minor * 100 + point;
return version;
}
/*
* var_put()
*
* Store a variable type in a named variable. If not present, the variable
* is created. Type is one of 'I' or 'S' for integer or string.
*/
void var_put(sc_var_setref_t vars, const sc_char *name, sc_int type, sc_vartype_t vt_value) {
sc_varref_t var;
sc_bool is_modification;
assert(var_is_valid(vars));
assert(name);
/* Check type is either integer or string. */
switch (type) {
case VAR_INTEGER:
case VAR_STRING:
break;
default:
sc_fatal("var_put: invalid variable type, %ld\n", type);
}
/* See if the user variable already exists. */
var = var_find(vars, name);
if (var) {
/* Verify that nothing is trying to change the variable's type. */
if (var->type != type)
sc_fatal("var_put: variable type changed, %s\n", name);
/*
* Special case %scare_version%. If a game changes its value, it may
* compromise version checking, so warn here, but continue.
*/
if (strcmp(name, "scare_version") == 0) {
if (var->value.integer != vt_value.integer)
sc_error("var_put: warning: %%%s%% value changed\n", name);
}
is_modification = TRUE;
} else {
/*
* Special case %scare_version%. If a game defines this and initializes
* it to zero, re-initialize it to SCARE's version number. Games that
* define %scare_version%, initially zero, can use this to test if
* running under SCARE or Runner.
*/
if (strcmp(name, "scare_version") == 0 && vt_value.integer == 0) {
vt_value.integer = var_get_scare_version();
if (var_trace)
sc_trace("Variable: %%%s%% [new] caught and mapped\n", name);
}
/*
* Create a new and empty variable entry. The mutable string needs to
* be set to nullptr here so that realloc works correctly on assigning
* the value below.
*/
var = var_add(vars, name, type);
var->value.mutable_string = nullptr;
is_modification = FALSE;
}
/* Update the existing variable, or populate the new one fully. */
switch (var->type) {
case VAR_INTEGER:
var->value.integer = vt_value.integer;
break;
case VAR_STRING: {
size_t ln = strlen(vt_value.string) + 1;
/* Use mutable string instead of const string. */
var->value.mutable_string = (sc_char *)sc_realloc(var->value.mutable_string, ln);
Common::strcpy_s(var->value.mutable_string, ln, vt_value.string);
break;
}
default:
sc_fatal("var_put: invalid variable type, %ld\n", var->type);
}
if (var_trace) {
sc_trace("Variable: %%%s%%%s = ",
name, is_modification ? "" : " [new]");
switch (var->type) {
case VAR_INTEGER:
sc_trace("%ld", var->value.integer);
break;
case VAR_STRING:
sc_trace("\"%s\"", var->value.string);
break;
default:
sc_trace("[invalid variable type, %ld]", var->type);
break;
}
sc_trace("\n");
}
}
/*
* var_append_temp()
*
* Helper for object listers. Extends temporary, and appends the given text
* to the string.
*/
static void var_append_temp(sc_var_setref_t vars, const sc_char *string) {
sc_bool new_sentence;
sc_int noted;
if (!vars->temporary) {
/* Create a new temporary area and copy string. */
new_sentence = TRUE;
noted = 0;
size_t ln = strlen(string) + 1;
vars->temporary = (sc_char *)sc_malloc(ln);
Common::strcpy_s(vars->temporary, ln, string);
} else {
/* Append string to existing temporary. */
new_sentence = (vars->temporary[0] == NUL);
noted = strlen(vars->temporary);
size_t ln = strlen(vars->temporary) + strlen(string) + 1;
vars->temporary = (sc_char *)sc_realloc(vars->temporary, ln);
Common::strcat_s(vars->temporary, ln, string);
}
if (new_sentence)
vars->temporary[noted] = sc_toupper(vars->temporary[noted]);
}
/*
* var_print_object_np
* var_print_object
*
* Convenience functions to append an object's name, with and without any
* prefix, to variables temporary.
*/
static void var_print_object_np(sc_gameref_t game, sc_int object) {
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[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);
/*
* Try the same shenanigans as done by the equivalent function in the
* library.
*/
normalized = prefix;
if (sc_compare_word(prefix, "a", 1)) {
normalized = prefix + 1;
var_append_temp(vars, "the");
} else if (sc_compare_word(prefix, "an", 2)) {
normalized = prefix + 2;
var_append_temp(vars, "the");
} else if (sc_compare_word(prefix, "the", 3)) {
normalized = prefix + 3;
var_append_temp(vars, "the");
} else if (sc_compare_word(prefix, "some", 4)) {
normalized = prefix + 4;
var_append_temp(vars, "the");
} else if (sc_strempty(prefix))
var_append_temp(vars, "the ");
/* As with the library, handle the remaining prefix. */
if (!sc_strempty(normalized)) {
var_append_temp(vars, normalized);
var_append_temp(vars, " ");
} else if (normalized > prefix)
var_append_temp(vars, " ");
/*
* Print the object's name, again, as with the library, stripping any
* leading article
*/
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;
var_append_temp(vars, name);
}
static void var_print_object(sc_gameref_t game, sc_int object) {
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[3];
const sc_char *prefix, *name;
/*
* Get the object's prefix. As with the library, if the prefix is empty,
* put in an "a ".
*/
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)) {
var_append_temp(vars, prefix);
var_append_temp(vars, " ");
} else
var_append_temp(vars, "a ");
/* Print the object's name. */
vt_key[2].string = "Short";
name = prop_get_string(bundle, "S<-sis", vt_key);
var_append_temp(vars, name);
}
/*
* var_select_plurality()
*
* Convenience function for listers. Selects one of two responses depending
* on whether an object appears singular or plural.
*/
static const sc_char *var_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;
}
/*
* var_list_in_object()
*
* List the objects in a given container object.
*/
static void var_list_in_object(sc_gameref_t game, sc_int container) {
const sc_var_setref_t vars = gs_get_vars(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)
var_append_temp(vars, ", ");
/* Print out the current list object. */
var_print_object(game, trail);
}
trail = object;
count++;
}
}
if (count >= 1) {
/* Print out final listed object. */
if (count == 1) {
var_print_object(game, trail);
var_append_temp(vars,
var_select_plurality(game, trail,
" is inside ",
" are inside "));
} else {
var_append_temp(vars, " and ");
var_print_object(game, trail);
var_append_temp(vars, " are inside ");
}
/* Print out the container. */
var_print_object_np(game, container);
var_append_temp(vars, ".");
}
}
/*
* var_list_on_object()
*
* List the objects on a given surface object.
*/
static void var_list_on_object(sc_gameref_t game, sc_int supporter) {
const sc_var_setref_t vars = gs_get_vars(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)
var_append_temp(vars, ", ");
/* Print out the current list object. */
var_print_object(game, trail);
}
trail = object;
count++;
}
}
if (count >= 1) {
/* Print out final listed object. */
if (count == 1) {
var_print_object(game, trail);
var_append_temp(vars,
var_select_plurality(game, trail,
" is on ", " are on "));
} else {
var_append_temp(vars, " and ");
var_print_object(game, trail);
var_append_temp(vars, " are on ");
}
/* Print out the surface. */
var_print_object_np(game, supporter);
var_append_temp(vars, ".");
}
}
/*
* var_list_onin_object()
*
* List the objects on and in a given associate object.
*/
static void var_list_onin_object(sc_gameref_t game, sc_int associate) {
const sc_var_setref_t vars = gs_get_vars(game);
sc_int object, count, trail;
sc_bool supporting;
/* List out the objects standing on this object. */
count = 0;
trail = -1;
supporting = FALSE;
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) == associate) {
if (count > 0) {
if (count > 1)
var_append_temp(vars, ", ");
/* Print out the current list object. */
var_print_object(game, trail);
}
trail = object;
count++;
}
}
if (count >= 1) {
/* Print out final listed object. */
if (count == 1) {
var_print_object(game, trail);
var_append_temp(vars,
var_select_plurality(game, trail,
" is on ", " are on "));
} else {
var_append_temp(vars, " and ");
var_print_object(game, trail);
var_append_temp(vars, " are on ");
}
/* Print out the surface. */
var_print_object_np(game, associate);
supporting = TRUE;
}
/* 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) == associate) {
if (count > 0) {
if (count == 1) {
if (supporting)
var_append_temp(vars, ", and ");
} else
var_append_temp(vars, ", ");
/* Print out the current list object. */
var_print_object(game, trail);
}
trail = object;
count++;
}
}
if (count >= 1) {
/* Print out final listed object. */
if (count == 1) {
if (supporting)
var_append_temp(vars, ", and ");
var_print_object(game, trail);
var_append_temp(vars,
var_select_plurality(game, trail,
" is inside ",
" are inside "));
} else {
var_append_temp(vars, " and ");
var_print_object(game, trail);
var_append_temp(vars, " are inside");
}
/* Print out the container. */
if (!supporting) {
var_append_temp(vars, " ");
var_print_object_np(game, associate);
}
var_append_temp(vars, ".");
} else {
if (supporting)
var_append_temp(vars, ".");
}
}
/*
* var_return_integer()
* var_return_string()
*
* Convenience helpers for var_get_system(). Provide convenience and some
* mild syntactic sugar for making returning a value as a system variable
* a bit easier. Set appropriate values for return type and the relevant
* return value field, and always return TRUE. A macro was tempting here...
*/
static sc_bool var_return_integer(sc_int value, sc_int *type, sc_vartype_t *vt_rvalue) {
*type = VAR_INTEGER;
vt_rvalue->integer = value;
return TRUE;
}
static sc_bool var_return_string(const sc_char *value, sc_int *type, sc_vartype_t *vt_rvalue) {
*type = VAR_STRING;
vt_rvalue->string = value;
return TRUE;
}
/*
* var_get_system()
*
* Construct a system variable, and return its type and value, or FALSE
* if invalid name passed in. Uses var_return_*() to reduce code untidiness.
*/
static sc_bool var_get_system(sc_var_setref_t vars, const sc_char *name,
sc_int *type, sc_vartype_t *vt_rvalue) {
const sc_prop_setref_t bundle = vars->bundle;
const sc_gameref_t game = vars->game;
/* Check name for known system variables. */
if (strcmp(name, "author") == 0) {
sc_vartype_t vt_key[2];
const sc_char *author;
/* Get and return the global gameauthor string. */
vt_key[0].string = "Globals";
vt_key[1].string = "GameAuthor";
author = prop_get_string(bundle, "S<-ss", vt_key);
if (sc_strempty(author))
author = "[Author unknown]";
return var_return_string(author, type, vt_rvalue);
}
else if (strcmp(name, "character") == 0) {
/* See if there is a referenced character. */
if (vars->referenced_character != -1) {
sc_vartype_t vt_key[3];
const sc_char *npc_name;
/* Return the character name string. */
vt_key[0].string = "NPCs";
vt_key[1].integer = vars->referenced_character;
vt_key[2].string = "Name";
npc_name = prop_get_string(bundle, "S<-sis", vt_key);
if (sc_strempty(npc_name))
npc_name = "[Character unknown]";
return var_return_string(npc_name, type, vt_rvalue);
} else {
sc_error("var_get_system: no referenced character yet\n");
return var_return_string("[Character unknown]", type, vt_rvalue);
}
}
else if (strcmp(name, "heshe") == 0 || strcmp(name, "himher") == 0) {
/* See if there is a referenced character. */
if (vars->referenced_character != -1) {
sc_vartype_t vt_key[3];
sc_int gender;
const sc_char *retval;
/* Return the appropriate character gender string. */
vt_key[0].string = "NPCs";
vt_key[1].integer = vars->referenced_character;
vt_key[2].string = "Gender";
gender = prop_get_integer(bundle, "I<-sis", vt_key);
switch (gender) {
case NPC_MALE:
retval = (strcmp(name, "heshe") == 0) ? "he" : "him";
break;
case NPC_FEMALE:
retval = (strcmp(name, "heshe") == 0) ? "she" : "her";
break;
case NPC_NEUTER:
retval = "it";
break;
default:
sc_error("var_get_system: unknown gender, %ld\n", gender);
retval = "[Gender unknown]";
break;
}
return var_return_string(retval, type, vt_rvalue);
} else {
sc_error("var_get_system: no referenced character yet\n");
return var_return_string("[Gender unknown]", type, vt_rvalue);
}
}
else if (strncmp(name, "in_", 3) == 0) {
sc_int saved_ref_object = vars->referenced_object;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for in_\n");
return var_return_string("[In_ unavailable]", type, vt_rvalue);
}
if (!uip_match("%object%", name + 3, game)) {
sc_error("var_get_system: invalid object for in_\n");
return var_return_string("[In_ unavailable]", type, vt_rvalue);
}
/* Clear any current temporary for appends. */
vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
vars->temporary[0] = '\0';
/* Write what's in the object into temporary. */
var_list_in_object(game, vars->referenced_object);
/* Restore saved referenced object and return. */
vars->referenced_object = saved_ref_object;
return var_return_string(vars->temporary, type, vt_rvalue);
}
else if (strcmp(name, "maxscore") == 0) {
sc_vartype_t vt_key[2];
sc_int maxscore;
/* Return the maximum score. */
vt_key[0].string = "Globals";
vt_key[1].string = "MaxScore";
maxscore = prop_get_integer(bundle, "I<-ss", vt_key);
return var_return_integer(maxscore, type, vt_rvalue);
}
else if (strcmp(name, "modified") == 0) {
sc_vartype_t vt_key;
const sc_char *compiledate;
/* Return the game compilation date. */
vt_key.string = "CompileDate";
compiledate = prop_get_string(bundle, "S<-s", &vt_key);
if (sc_strempty(compiledate))
compiledate = "[Modified unknown]";
return var_return_string(compiledate, type, vt_rvalue);
}
else if (strcmp(name, "number") == 0) {
/* Return the referenced number, or 0 if none yet. */
if (!vars->is_number_referenced)
sc_error("var_get_system: no referenced number yet\n");
return var_return_integer(vars->referenced_number, type, vt_rvalue);
}
else if (strcmp(name, "object") == 0) {
/* See if we have a referenced object yet. */
if (vars->referenced_object != -1) {
/* Return object name with its prefix. */
sc_vartype_t vt_key[3];
const sc_char *prefix, *objname;
vt_key[0].string = "Objects";
vt_key[1].integer = vars->referenced_object;
vt_key[2].string = "Prefix";
prefix = prop_get_string(bundle, "S<-sis", vt_key);
size_t ln = strlen(prefix) + 1;
vars->temporary = (sc_char *)sc_realloc(vars->temporary, ln);
Common::strcpy_s(vars->temporary, ln, prefix);
vt_key[2].string = "Short";
objname = prop_get_string(bundle, "S<-sis", vt_key);
ln = strlen(vars->temporary) + strlen(objname) + 2;
vars->temporary = (sc_char *)sc_realloc(vars->temporary, ln);
Common::strcat_s(vars->temporary, ln, " ");
Common::strcat_s(vars->temporary, ln, objname);
return var_return_string(vars->temporary, type, vt_rvalue);
} else {
sc_error("var_get_system: no referenced object yet\n");
return var_return_string("[Object unknown]", type, vt_rvalue);
}
}
else if (strcmp(name, "obstate") == 0) {
sc_vartype_t vt_key[3];
sc_bool is_statussed;
sc_char *state;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for obstate\n");
return var_return_string("[Obstate unavailable]", type, vt_rvalue);
}
if (vars->referenced_object == -1) {
sc_error("var_get_system: no object for obstate\n");
return var_return_string("[Obstate unavailable]", type, vt_rvalue);
}
/*
* If not a stateful object, Runner 4.0.45 crashes; we'll do something
* different here.
*/
vt_key[0].string = "Objects";
vt_key[1].integer = vars->referenced_object;
vt_key[2].string = "CurrentState";
is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
if (!is_statussed)
return var_return_string("stateless", type, vt_rvalue);
/* Get state, and copy to temporary. */
state = obj_state_name(game, vars->referenced_object);
if (!state) {
sc_error("var_get_system: invalid state for obstate\n");
return var_return_string("[Obstate unknown]", type, vt_rvalue);
}
size_t ln = strlen(state) + 1;
vars->temporary = (sc_char *)sc_realloc(vars->temporary, ln);
Common::strcpy_s(vars->temporary, ln, state);
sc_free(state);
/* Return temporary. */
return var_return_string(vars->temporary, type, vt_rvalue);
}
else if (strcmp(name, "obstatus") == 0) {
sc_vartype_t vt_key[3];
sc_bool is_openable;
sc_int openness;
const sc_char *retval;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for obstatus\n");
return var_return_string("[Obstatus unavailable]", type, vt_rvalue);
}
if (vars->referenced_object == -1) {
sc_error("var_get_system: no object for obstatus\n");
return var_return_string("[Obstatus unavailable]", type, vt_rvalue);
}
/* If not an openable object, return unopenable to match Adrift. */
vt_key[0].string = "Objects";
vt_key[1].integer = vars->referenced_object;
vt_key[2].string = "Openable";
is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
if (!is_openable)
return var_return_string("unopenable", type, vt_rvalue);
/* Return one of open, closed, or locked. */
openness = gs_object_openness(game, vars->referenced_object);
switch (openness) {
case OBJ_OPEN:
retval = "open";
break;
case OBJ_CLOSED:
retval = "closed";
break;
case OBJ_LOCKED:
retval = "locked";
break;
default:
retval = "[Obstatus unknown]";
break;
}
return var_return_string(retval, type, vt_rvalue);
}
else if (strncmp(name, "on_", 3) == 0) {
sc_int saved_ref_object = vars->referenced_object;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for on_\n");
return var_return_string("[On_ unavailable]", type, vt_rvalue);
}
if (!uip_match("%object%", name + 3, game)) {
sc_error("var_get_system: invalid object for on_\n");
return var_return_string("[On_ unavailable]", type, vt_rvalue);
}
/* Clear any current temporary for appends. */
vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
vars->temporary[0] = '\0';
/* Write what's on the object into temporary. */
var_list_on_object(game, vars->referenced_object);
/* Restore saved referenced object and return. */
vars->referenced_object = saved_ref_object;
return var_return_string(vars->temporary, type, vt_rvalue);
}
else if (strncmp(name, "onin_", 5) == 0) {
sc_int saved_ref_object = vars->referenced_object;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for onin_\n");
return var_return_string("[Onin_ unavailable]", type, vt_rvalue);
}
if (!uip_match("%object%", name + 5, game)) {
sc_error("var_get_system: invalid object for onin_\n");
return var_return_string("[Onin_ unavailable]", type, vt_rvalue);
}
/* Clear any current temporary for appends. */
vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
vars->temporary[0] = '\0';
/* Write what's on/in the object into temporary. */
var_list_onin_object(game, vars->referenced_object);
/* Restore saved referenced object and return. */
vars->referenced_object = saved_ref_object;
return var_return_string(vars->temporary, type, vt_rvalue);
}
else if (strcmp(name, "player") == 0) {
sc_vartype_t vt_key[2];
const sc_char *playername;
/*
* Return player's name from properties, or just "Player" if not set
* in the properties.
*/
vt_key[0].string = "Globals";
vt_key[1].string = "PlayerName";
playername = prop_get_string(bundle, "S<-ss", vt_key);
if (sc_strempty(playername))
playername = "Player";
return var_return_string(playername, type, vt_rvalue);
}
else if (strcmp(name, "room") == 0) {
const sc_char *roomname;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for room\n");
return var_return_string("[Room unavailable]", type, vt_rvalue);
}
/* Return the current player room. */
roomname = lib_get_room_name(game, gs_playerroom(game));
return var_return_string(roomname, type, vt_rvalue);
}
else if (strcmp(name, "score") == 0) {
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for score\n");
return var_return_integer(0, type, vt_rvalue);
}
/* Return the current game score. */
return var_return_integer(game->score, type, vt_rvalue);
}
else if (strncmp(name, "state_", 6) == 0) {
sc_int saved_ref_object = vars->referenced_object;
sc_vartype_t vt_key[3];
sc_bool is_statussed;
sc_char *state;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for state_\n");
return var_return_string("[State_ unavailable]", type, vt_rvalue);
}
if (!uip_match("%object%", name + 6, game)) {
sc_error("var_get_system: invalid object for state_\n");
return var_return_string("[State_ unavailable]", type, vt_rvalue);
}
/* Verify this is a stateful object. */
vt_key[0].string = "Objects";
vt_key[1].integer = vars->referenced_object;
vt_key[2].string = "CurrentState";
is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
if (!is_statussed) {
vars->referenced_object = saved_ref_object;
sc_error("var_get_system: stateless object for state_\n");
return var_return_string("[State_ unavailable]", type, vt_rvalue);
}
/* Get state, and copy to temporary. */
state = obj_state_name(game, vars->referenced_object);
if (!state) {
vars->referenced_object = saved_ref_object;
sc_error("var_get_system: invalid state for state_\n");
return var_return_string("[State_ unknown]", type, vt_rvalue);
}
size_t ln = strlen(state) + 1;
vars->temporary = (sc_char *)sc_realloc(vars->temporary, ln);
Common::strcpy_s(vars->temporary, ln, state);
sc_free(state);
/* Restore saved referenced object and return. */
vars->referenced_object = saved_ref_object;
return var_return_string(vars->temporary, type, vt_rvalue);
}
else if (strncmp(name, "status_", 7) == 0) {
sc_int saved_ref_object = vars->referenced_object;
sc_vartype_t vt_key[3];
sc_bool is_openable;
sc_int openness;
const sc_char *retval;
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for status_\n");
return var_return_string("[Status_ unavailable]", type, vt_rvalue);
}
if (!uip_match("%object%", name + 7, game)) {
sc_error("var_get_system: invalid object for status_\n");
return var_return_string("[Status_ unavailable]", type, vt_rvalue);
}
/* Verify this is an openable object. */
vt_key[0].string = "Objects";
vt_key[1].integer = vars->referenced_object;
vt_key[2].string = "Openable";
is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
if (!is_openable) {
vars->referenced_object = saved_ref_object;
sc_error("var_get_system: stateless object for status_\n");
return var_return_string("[Status_ unavailable]", type, vt_rvalue);
}
/* Return one of open, closed, or locked. */
openness = gs_object_openness(game, vars->referenced_object);
switch (openness) {
case OBJ_OPEN:
retval = "open";
break;
case OBJ_CLOSED:
retval = "closed";
break;
case OBJ_LOCKED:
retval = "locked";
break;
default:
retval = "[Status_ unknown]";
break;
}
/* Restore saved referenced object and return. */
vars->referenced_object = saved_ref_object;
return var_return_string(retval, type, vt_rvalue);
}
else if (strcmp(name, "t_number") == 0) {
/* See if we have a referenced number yet. */
if (vars->is_number_referenced) {
sc_int number;
const sc_char *retval;
/* Return the referenced number as a string. */
number = vars->referenced_number;
if (number >= 0 && number < VAR_NUMBERS_SIZE)
retval = VAR_NUMBERS[number];
else {
vars->temporary = (sc_char *)sc_realloc(vars->temporary, 32);
Common::sprintf_s(vars->temporary, 32, "%ld", number);
retval = vars->temporary;
}
return var_return_string(retval, type, vt_rvalue);
} else {
sc_error("var_get_system: no referenced number yet\n");
return var_return_string("[Number unknown]", type, vt_rvalue);
}
}
else if (strncmp(name, "t_", 2) == 0) {
sc_varref_t var;
/* Find the variable; must be a user, not a system, one. */
var = var_find(vars, name + 2);
if (!var) {
sc_error("var_get_system:"
" no such variable, %s\n", name + 2);
return var_return_string("[Unknown variable]", type, vt_rvalue);
} else if (var->type != VAR_INTEGER) {
sc_error("var_get_system:"
" not an integer variable, %s\n", name + 2);
return var_return_string(var->value.string, type, vt_rvalue);
} else {
sc_int number;
const sc_char *retval;
/* Return the variable value as a string. */
number = var->value.integer;
if (number >= 0 && number < VAR_NUMBERS_SIZE)
retval = VAR_NUMBERS[number];
else {
vars->temporary = (sc_char *)sc_realloc(vars->temporary, 32);
Common::sprintf_s(vars->temporary, 32, "%ld", number);
retval = vars->temporary;
}
return var_return_string(retval, type, vt_rvalue);
}
}
else if (strcmp(name, "text") == 0) {
const sc_char *retval;
/* Return any referenced text, otherwise a neutral string. */
if (vars->referenced_text)
retval = vars->referenced_text;
else {
sc_error("var_get_system: no text yet to reference\n");
retval = "[Text unknown]";
}
return var_return_string(retval, type, vt_rvalue);
}
else if (strcmp(name, "theobject") == 0) {
/* See if we have a referenced object yet. */
if (vars->referenced_object != -1) {
/* Return object name prefixed with "the"... */
sc_vartype_t vt_key[3];
const sc_char *prefix, *normalized, *objname;
vt_key[0].string = "Objects";
vt_key[1].integer = vars->referenced_object;
vt_key[2].string = "Prefix";
prefix = prop_get_string(bundle, "S<-sis", vt_key);
size_t temporary_ln = strlen(prefix) + 5;
vars->temporary = (sc_char *)sc_realloc(vars->temporary, temporary_ln);
vars->temporary[0] = '\0';
normalized = prefix;
if (sc_compare_word(prefix, "a", 1)) {
Common::strcat_s(vars->temporary, temporary_ln, "the");
normalized = prefix + 1;
} else if (sc_compare_word(prefix, "an", 2)) {
Common::strcat_s(vars->temporary, temporary_ln, "the");
normalized = prefix + 2;
} else if (sc_compare_word(prefix, "the", 3)) {
Common::strcat_s(vars->temporary, temporary_ln, "the");
normalized = prefix + 3;
} else if (sc_compare_word(prefix, "some", 4)) {
Common::strcat_s(vars->temporary, temporary_ln, "the");
normalized = prefix + 4;
} else if (sc_strempty(prefix))
Common::strcat_s(vars->temporary, temporary_ln, "the ");
if (!sc_strempty(normalized)) {
Common::strcat_s(vars->temporary, temporary_ln, normalized);
Common::strcat_s(vars->temporary, temporary_ln, " ");
} else if (normalized > prefix)
Common::strcat_s(vars->temporary, temporary_ln, " ");
vt_key[2].string = "Short";
objname = prop_get_string(bundle, "S<-sis", vt_key);
if (sc_compare_word(objname, "a", 1))
objname += 1;
else if (sc_compare_word(objname, "an", 2))
objname += 2;
else if (sc_compare_word(objname, "the", 3))
objname += 3;
else if (sc_compare_word(objname, "some", 4))
objname += 4;
temporary_ln = strlen(vars->temporary) + strlen(objname) + 1;
vars->temporary = (sc_char *)sc_realloc(vars->temporary, temporary_ln);
Common::strcat_s(vars->temporary, temporary_ln, objname);
return var_return_string(vars->temporary, type, vt_rvalue);
} else {
sc_error("var_get_system: no referenced object yet\n");
return var_return_string("[Object unknown]", type, vt_rvalue);
}
}
else if (strcmp(name, "time") == 0) {
double delta;
sc_int retval;
/* Return the elapsed game time in seconds. */
delta = vars->timestamp - (g_vm->_events->getTotalPlayTicks() / 1000);
retval = (sc_int) delta + vars->time_offset;
return var_return_integer(retval, type, vt_rvalue);
}
else if (strcmp(name, "title") == 0) {
sc_vartype_t vt_key[2];
const sc_char *gamename;
/* Return the game's title. */
vt_key[0].string = "Globals";
vt_key[1].string = "GameName";
gamename = prop_get_string(bundle, "S<-ss", vt_key);
if (sc_strempty(gamename))
gamename = "[Title unknown]";
return var_return_string(gamename, type, vt_rvalue);
}
else if (strcmp(name, "turns") == 0) {
/* Check there's enough information to return a value. */
if (!game) {
sc_error("var_get_system: no game for turns\n");
return var_return_integer(0, type, vt_rvalue);
}
/* Return the count of game turns. */
return var_return_integer(game->turns, type, vt_rvalue);
}
else if (strcmp(name, "version") == 0) {
/* Return the Adrift emulation level of SCARE. */
return var_return_integer(SCARE_EMULATION, type, vt_rvalue);
}
else if (strcmp(name, "scare_version") == 0) {
/* Private system variable, return SCARE's version number. */
return var_return_integer(var_get_scare_version(), type, vt_rvalue);
}
return FALSE;
}
/*
* var_get_user()
*
* Retrieve a user variable, and return its type and value, or FALSE if the
* name passed in is not a defined user variable.
*/
static sc_bool var_get_user(sc_var_setref_t vars, const sc_char *name,
sc_int *type, sc_vartype_t *vt_rvalue) {
sc_varref_t var;
/* Check user variables for a reference to the named variable. */
var = var_find(vars, name);
if (var) {
/* Copy out variable details. */
*type = var->type;
switch (var->type) {
case VAR_INTEGER:
vt_rvalue->integer = var->value.integer;
break;
case VAR_STRING:
vt_rvalue->string = var->value.string;
break;
default:
sc_fatal("var_get_user: invalid variable type, %ld\n", var->type);
}
/* Return success. */
return TRUE;
}
return FALSE;
}
/*
* var_get()
*
* Retrieve a variable, and return its value and type. Returns FALSE if the
* named variable does not exist.
*/
sc_bool var_get(sc_var_setref_t vars, const sc_char *name, sc_int *type, sc_vartype_t *vt_rvalue) {
sc_bool status;
assert(var_is_valid(vars));
assert(name && type && vt_rvalue);
/*
* Check user and system variables for a reference to the name. User
* variables take precedence over system ones; that is, they may override
* them in a game.
*/
status = var_get_user(vars, name, type, vt_rvalue);
if (!status)
status = var_get_system(vars, name, type, vt_rvalue);
if (var_trace) {
if (status) {
sc_trace("Variable: %%%s%% retrieved, ", name);
switch (*type) {
case VAR_INTEGER:
sc_trace("%ld", vt_rvalue->integer);
break;
case VAR_STRING:
sc_trace("\"%s\"", vt_rvalue->string);
break;
default:
sc_trace("Variable: invalid variable type, %ld\n", *type);
break;
}
sc_trace("\n");
} else
sc_trace("Variable: \"%s\", no such variable\n", name);
}
return status;
}
/*
* var_put_integer()
* var_get_integer()
*
* Convenience functions to store and retrieve an integer variable. It is
* an error for the variable not to exist or to have the wrong type.
*/
void var_put_integer(sc_var_setref_t vars, const sc_char *name, sc_int value) {
sc_vartype_t vt_value;
assert(var_is_valid(vars));
vt_value.integer = value;
var_put(vars, name, VAR_INTEGER, vt_value);
}
sc_int var_get_integer(sc_var_setref_t vars, const sc_char *name) {
sc_vartype_t vt_rvalue;
sc_int type;
assert(var_is_valid(vars));
if (!var_get(vars, name, &type, &vt_rvalue))
sc_fatal("var_get_integer: no such variable, %s\n", name);
else if (type != VAR_INTEGER)
sc_fatal("var_get_integer: not an integer, %s\n", name);
return vt_rvalue.integer;
}
/*
* var_put_string()
* var_get_string()
*
* Convenience functions to store and retrieve a string variable. It is
* an error for the variable not to exist or to have the wrong type.
*/
void var_put_string(sc_var_setref_t vars, const sc_char *name, const sc_char *string) {
sc_vartype_t vt_value;
assert(var_is_valid(vars));
vt_value.string = string;
var_put(vars, name, VAR_STRING, vt_value);
}
const sc_char *var_get_string(sc_var_setref_t vars, const sc_char *name) {
sc_vartype_t vt_rvalue;
sc_int type;
assert(var_is_valid(vars));
if (!var_get(vars, name, &type, &vt_rvalue))
sc_fatal("var_get_string: no such variable, %s\n", name);
else if (type != VAR_STRING)
sc_fatal("var_get_string: not a string, %s\n", name);
return vt_rvalue.string;
}
/*
* var_create()
*
* Create and return a new set of variables. Variables are created from the
* properties bundle passed in.
*/
sc_var_setref_t var_create(sc_prop_setref_t bundle) {
sc_var_setref_t vars;
sc_int var_count, index_;
sc_vartype_t vt_key[3];
assert(bundle);
/* Create a clean set of variables to fill from the bundle. */
vars = var_create_empty();
vars->bundle = bundle;
/* Retrieve the count of variables. */
vt_key[0].string = "Variables";
var_count = prop_get_child_count(bundle, "I<-s", vt_key);
/* Create a variable for each variable property held. */
for (index_ = 0; index_ < var_count; index_++) {
const sc_char *name;
sc_int var_type;
const sc_char *value;
/* Retrieve variable name, type, and string initial value. */
vt_key[1].integer = index_;
vt_key[2].string = "Name";
name = prop_get_string(bundle, "S<-sis", vt_key);
vt_key[2].string = "Type";
var_type = prop_get_integer(bundle, "I<-sis", vt_key);
vt_key[2].string = "Value";
value = prop_get_string(bundle, "S<-sis", vt_key);
/* Handle numerics and strings differently. */
switch (var_type) {
case TAFVAR_NUMERIC: {
sc_int integer_value;
if (sscanf(value, "%ld", &integer_value) != 1) {
sc_error("var_create:"
" invalid numeric variable %s, %s\n", name, value);
integer_value = 0;
}
var_put_integer(vars, name, integer_value);
break;
}
case TAFVAR_STRING:
var_put_string(vars, name, value);
break;
default:
sc_fatal("var_create: invalid variable type, %ld\n", var_type);
}
}
return vars;
}
/*
* var_register_game()
*
* Register the game, used by variables to satisfy requests for selected
* system variables. To ensure integrity, the game being registered must
* reference this variable set.
*/
void var_register_game(sc_var_setref_t vars, sc_gameref_t game) {
assert(var_is_valid(vars));
assert(gs_is_game_valid(game));
if (vars != gs_get_vars(game))
sc_fatal("var_register_game: game binding error\n");
vars->game = game;
}
/*
* var_set_ref_character()
* var_set_ref_object()
* var_set_ref_number()
* var_set_ref_text()
*
* Set the "referenced" character, object, number, and text.
*/
void var_set_ref_character(sc_var_setref_t vars, sc_int character) {
assert(var_is_valid(vars));
vars->referenced_character = character;
}
void var_set_ref_object(sc_var_setref_t vars, sc_int object) {
assert(var_is_valid(vars));
vars->referenced_object = object;
}
void var_set_ref_number(sc_var_setref_t vars, sc_int number) {
assert(var_is_valid(vars));
vars->referenced_number = number;
vars->is_number_referenced = TRUE;
}
void var_set_ref_text(sc_var_setref_t vars, const sc_char *text) {
assert(var_is_valid(vars));
/* Take a copy of the string, and retain it. */
size_t ln = strlen(text) + 1;
vars->referenced_text = (sc_char *)sc_realloc(vars->referenced_text, ln);
Common::strcpy_s(vars->referenced_text, ln, text);
}
/*
* var_get_ref_character()
* var_get_ref_object()
* var_get_ref_number()
* var_get_ref_text()
*
* Get the "referenced" character, object, number, and text.
*/
sc_int var_get_ref_character(sc_var_setref_t vars) {
assert(var_is_valid(vars));
return vars->referenced_character;
}
sc_int var_get_ref_object(sc_var_setref_t vars) {
assert(var_is_valid(vars));
return vars->referenced_object;
}
sc_int var_get_ref_number(sc_var_setref_t vars) {
assert(var_is_valid(vars));
return vars->referenced_number;
}
const sc_char *var_get_ref_text(sc_var_setref_t vars) {
assert(var_is_valid(vars));
/*
* If currently nullptr, return "". A game may check restrictions involving
* referenced text before any value has been set; returning "" here for
* this case prevents problems later (strcmp (nullptr, ...), for example).
*/
return vars->referenced_text ? vars->referenced_text : "";
}
/*
* var_get_elapsed_seconds()
* var_set_elapsed_seconds()
*
* Get a count of seconds elapsed since the variables were created (start
* of game), and set the count to a given value (game restore).
*/
sc_uint var_get_elapsed_seconds(sc_var_setref_t vars) {
double delta;
assert(var_is_valid(vars));
delta = vars->timestamp - g_vm->_events->getTotalPlayTicks();
return (sc_uint) delta + vars->time_offset;
}
void var_set_elapsed_seconds(sc_var_setref_t vars, sc_uint seconds) {
assert(var_is_valid(vars));
/*
* Reset the timestamp to now, and store seconds in offset. This is sort-of
* forced by the fact that ANSI offers difftime but no 'settime' -- here,
* we'd really want to set the timestamp to now less seconds.
*/
vars->timestamp = g_vm->_events->getTotalPlayTicks() / 1000;
vars->time_offset = seconds;
}
/*
* var_debug_trace()
*
* Set variable tracing on/off.
*/
void var_debug_trace(sc_bool flag) {
var_trace = flag;
}
/*
* var_debug_dump()
*
* Print out a complete variables set.
*/
void var_debug_dump(sc_var_setref_t vars) {
sc_int index_;
sc_varref_t var;
assert(var_is_valid(vars));
/* Dump complete structure. */
sc_trace("Variable: debug dump follows...\n");
sc_trace("vars->bundle = %p\n", (void *) vars->bundle);
sc_trace("vars->referenced_character = %ld\n", vars->referenced_character);
sc_trace("vars->referenced_object = %ld\n", vars->referenced_object);
sc_trace("vars->referenced_number = %ld\n", vars->referenced_number);
sc_trace("vars->is_number_referenced = %s\n",
vars->is_number_referenced ? "true" : "false");
sc_trace("vars->referenced_text = ");
if (vars->referenced_text)
sc_trace("\"%s\"\n", vars->referenced_text);
else
sc_trace("(nil)\n");
sc_trace("vars->temporary = %p\n", (void *) vars->temporary);
sc_trace("vars->timestamp = %lu\n", (sc_uint) vars->timestamp);
sc_trace("vars->game = %p\n", (void *) vars->game);
sc_trace("vars->variables =\n");
for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++) {
for (var = vars->variable[index_]; var; var = var->next) {
if (var == vars->variable[index_])
sc_trace("%3ld : ", index_);
else
sc_trace(" : ");
switch (var->type) {
case VAR_STRING:
sc_trace("[String ] %s = \"%s\"", var->name, var->value.string);
break;
case VAR_INTEGER:
sc_trace("[Integer] %s = %ld", var->name, var->value.integer);
break;
default:
sc_trace("[Invalid] %s = %p", var->name, var->value.voidp);
break;
}
sc_trace("\n");
}
}
}
} // End of namespace Adrift
} // End of namespace Glk