/* vi: set ts=2 shiftwidth=2 expandtab: * * Copyright (C) 2003-2007 Simon Baldwin and Mark J. Tilford * * 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. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ /* * 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 lack * of games that use them. */ #include #include #include #include #include #include #include "scare.h" #include "scprotos.h" #include "scgamest.h" /* 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; /* Enumerated variable types. */ enum { VAR_INTEGER = 'I', VAR_STRING = 'S' }; /* TAF file enumerated values. */ enum { TAFVAR_NUMERIC = 0, TAFVAR_STRING = 1 }; enum { NPC_MALE = 0, NPC_FEMALE = 1, NPC_NEUTER = 2 }; enum { OBJ_OPEN = 5, OBJ_CLOSED = 6, OBJ_LOCKED = 7 }; /* 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. */ typedef struct sc_var_s { struct sc_var_s *next; const sc_char *name; sc_int type; sc_vartype_t value; } 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 }; typedef struct sc_var_set_s { sc_uint magic; sc_prop_setref_t bundle; sc_int ref_character; sc_int ref_object; sc_int ref_number; sc_bool number_refd; sc_char *ref_text; sc_char *temporary; time_t timestamp; sc_uint time_offset; sc_gameref_t game; sc_varref_t variable[VAR_HASH_TABLE_SIZE]; } 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 set of variables. Variables are created from * the properties bundle passed in. */ 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_malloc (sizeof (*vars)); vars->magic = VARS_MAGIC; vars->bundle = NULL; vars->ref_character = -1; vars->ref_object = -1; vars->ref_number = 0; vars->number_refd = FALSE; vars->ref_text = NULL; vars->temporary = NULL; vars->timestamp = time (NULL); vars->time_offset = 0; vars->game = NULL; /* Clear all variable hash lists. */ for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++) vars->variable[index_] = NULL; /* Return new variables. */ 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->ref_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 NULL 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) return var; } /* No such variable. */ return NULL; } static void var_add (sc_var_setref_t vars, sc_varref_t var) { sc_uint hash; /* Hash name, and insert at start of the relevant list. */ hash = var_hash_name (var->name); var->next = vars->variable[hash]; vars->variable[hash] = var; } /* * 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; 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 type, %ld\n", type); } /* See if the user variable already exists. */ var = var_find (vars, name); if (var) { /* It exists, so modify its value to that passed in. */ if (var->type != type) { sc_fatal ("var_put: variable type changed, %s\n", name); } switch (var->type) { case VAR_INTEGER: var->value.integer = vt_value.integer; break; case VAR_STRING: /* Use mutable string instead of const string. */ var->value.mutable_string = sc_realloc (var->value.mutable_string, strlen (vt_value.string) + 1); strcpy (var->value.mutable_string, vt_value.string); break; default: sc_fatal ("var_put: invalid type, %ld\n", var->type); } /* Trace variable modification. */ if (var_trace) { sc_trace ("Variable: %%%s%% = ", name); switch (var->type) { case VAR_INTEGER: sc_trace ("%ld", var->value.integer); break; case VAR_STRING: sc_trace ("\"%s\"", var->value.string); break; } sc_trace ("\n"); } return; } /* Variable not found, so create a new variable and populate it. */ var = sc_malloc (sizeof (*var)); var->name = name; var->type = type; switch (var->type) { case VAR_INTEGER: var->value.integer = vt_value.integer; break; case VAR_STRING: /* Use mutable string instead of const string. */ var->value.mutable_string = sc_malloc (strlen (vt_value.string) + 1); strcpy (var->value.mutable_string, vt_value.string); break; default: sc_fatal ("var_put: invalid type, %ld\n", var->type); } var->next = NULL; /* Add the new variable to the set. */ var_add (vars, var); /* Trace variable creation. */ if (var_trace) { sc_trace ("Variable: %%%s%% [new] = ", name); switch (var->type) { case VAR_INTEGER: sc_trace ("%ld", var->value.integer); break; case VAR_STRING: sc_trace ("\"%s\"", var->value.string); 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; vars->temporary = sc_malloc (strlen (string) + 1); strcpy (vars->temporary, string); } else { /* Append string to existing temporary. */ new_sentence = (vars->temporary[0] == NUL); noted = strlen (vars->temporary); vars->temporary = sc_realloc (vars->temporary, strlen (vars->temporary) + strlen (string) + 1); strcat (vars->temporary, string); } if (new_sentence) vars->temporary[noted] = 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")) { normalized = prefix + 1; var_append_temp (vars, "the"); } else if (sc_compare_word (prefix, "an")) { normalized = prefix + 2; var_append_temp (vars, "the"); } else if (sc_compare_word (prefix, "the")) { normalized = prefix + 3; var_append_temp (vars, "the"); } else if (sc_compare_word (prefix, "some")) { 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")) name += 1; else if (sc_compare_word (name, "an")) name += 2; else if (sc_compare_word (name, "the")) name += 3; else if (sc_compare_word (name, "some")) 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, but * I resisted... */ 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->ref_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->ref_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->ref_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->ref_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->ref_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_realloc (vars->temporary, 1); strcpy (vars->temporary, ""); /* Write what's in the object into temporary. */ var_list_in_object (game, vars->ref_object); /* Restore saved referenced object and return. */ vars->ref_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->number_refd) sc_error ("var_get_system: no referenced number yet\n"); return var_return_integer (vars->ref_number, type, vt_rvalue); } else if (strcmp (name, "object") == 0) { /* See if we have a referenced object yet. */ if (vars->ref_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->ref_object; vt_key[2].string = "Prefix"; prefix = prop_get_string (bundle, "S<-sis", vt_key); vars->temporary = sc_realloc (vars->temporary, strlen (prefix) + 1); strcpy (vars->temporary, prefix); vt_key[2].string = "Short"; objname = prop_get_string (bundle, "S<-sis", vt_key); vars->temporary = sc_realloc (vars->temporary, strlen (vars->temporary) + strlen (objname) + 2); strcat (vars->temporary, " "); strcat (vars->temporary, 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->ref_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->ref_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->ref_object); if (!state) { sc_error ("var_get_system: invalid state for obstate\n"); return var_return_string ("[Obstate unknown]", type, vt_rvalue); } vars->temporary = sc_realloc (vars->temporary, strlen (state) + 1); strcpy (vars->temporary, 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->ref_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->ref_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->ref_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->ref_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_realloc (vars->temporary, 1); strcpy (vars->temporary, ""); /* Write what's on the object into temporary. */ var_list_on_object (game, vars->ref_object); /* Restore saved referenced object and return. */ vars->ref_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->ref_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_realloc (vars->temporary, 1); strcpy (vars->temporary, ""); /* Write what's on/in the object into temporary. */ var_list_onin_object (game, vars->ref_object); /* Restore saved referenced object and return. */ vars->ref_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_string ("[Score unavailable]", 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->ref_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->ref_object; vt_key[2].string = "CurrentState"; is_statussed = prop_get_integer (bundle, "I<-sis", vt_key) != 0; if (!is_statussed) { vars->ref_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->ref_object); if (!state) { vars->ref_object = saved_ref_object; sc_error ("var_get_system: invalid state for state_\n"); return var_return_string ("[State_ unknown]", type, vt_rvalue); } vars->temporary = sc_realloc (vars->temporary, strlen (state) + 1); strcpy (vars->temporary, state); sc_free (state); /* Restore saved referenced object and return. */ vars->ref_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->ref_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->ref_object; vt_key[2].string = "Openable"; is_openable = prop_get_integer (bundle, "I<-sis", vt_key) != 0; if (!is_openable) { vars->ref_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->ref_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->ref_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->number_refd) { sc_int number; const sc_char *retval; /* Return the referenced number as a string. */ number = vars->ref_number; if (number >= 0 && number < VAR_NUMBERS_SIZE) retval = VAR_NUMBERS[number]; else { vars->temporary = sc_realloc (vars->temporary, 32); sprintf (vars->temporary, "%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_realloc (vars->temporary, 32); sprintf (vars->temporary, "%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->ref_text) retval = vars->ref_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->ref_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->ref_object; vt_key[2].string = "Prefix"; prefix = prop_get_string (bundle, "S<-sis", vt_key); vars->temporary = sc_realloc (vars->temporary, strlen (prefix) + 5); strcpy (vars->temporary, ""); normalized = prefix; if (sc_compare_word (prefix, "a")) { strcat (vars->temporary, "the"); normalized = prefix + 1; } else if (sc_compare_word (prefix, "an")) { strcat (vars->temporary, "the"); normalized = prefix + 2; } else if (sc_compare_word (prefix, "the")) { strcat (vars->temporary, "the"); normalized = prefix + 3; } else if (sc_compare_word (prefix, "some")) { strcat (vars->temporary, "the"); normalized = prefix + 4; } else if (sc_strempty (prefix)) strcat (vars->temporary, "the "); if (!sc_strempty (normalized)) { strcat (vars->temporary, normalized); strcat (vars->temporary, " "); } else if (normalized > prefix) strcat (vars->temporary, " "); vt_key[2].string = "Short"; objname = prop_get_string (bundle, "S<-sis", vt_key); if (sc_compare_word (objname, "a")) objname += 1; else if (sc_compare_word (objname, "an")) objname += 2; else if (sc_compare_word (objname, "the")) objname += 3; else if (sc_compare_word (objname, "some")) objname += 4; vars->temporary = sc_realloc (vars->temporary, strlen (vars->temporary) + strlen (objname) + 1); strcat (vars->temporary, 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 = difftime (time (NULL), vars->timestamp); 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_string ("[Turns unavailable]", 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); } 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_varref_t var; assert (var_is_valid (vars)); assert (name && type && vt_rvalue); /* 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: invalid type, %ld\n", var->type); } /* Trace user variable reads. */ if (var_trace) { sc_trace ("Variable: %%%s%% [user] retrieved, ", name); switch (var->type) { case VAR_INTEGER: sc_trace ("%ld", var->value.integer); break; case VAR_STRING: sc_trace ("\"%s\"", var->value.string); break; } sc_trace ("\n"); } /* Return success. */ return TRUE; } /* Try for a system variable matching this name. */ if (var_get_system (vars, name, type, vt_rvalue)) { if (var_trace) { sc_trace ("Variable: %%%s%% [system] retrieved, ", name); switch (*type) { case VAR_INTEGER: sc_trace ("%ld", vt_rvalue->integer); break; case VAR_STRING: sc_trace ("\"%s\"", vt_rvalue->string); break; } sc_trace ("\n"); } /* Return success. */ return TRUE; } if (var_trace) sc_trace ("Variable: \"%s\", no such variable\n", name); /* No such variable. */ return FALSE; } /* * 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 on retrieval, or to be of 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); 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 on retrieval, or to be of 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); 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 *initial_string; /* 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"; initial_string = prop_get_string (bundle, "S<-sis", vt_key); /* Handle numerics and strings differently. */ switch (var_type) { case TAFVAR_NUMERIC: { sc_int initial_integer; if (sscanf (initial_string, "%ld", &initial_integer) != 1) { sc_error ("var_create: invalid variable %ld numeric, %s\n", index_, initial_string); initial_integer = 0; } var_put_integer (vars, name, initial_integer); break; } case TAFVAR_STRING: var_put_string (vars, name, initial_string); break; default: sc_fatal ("var_create: unknown 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 for the variable * set. */ void var_set_ref_character (sc_var_setref_t vars, sc_int character) { assert (var_is_valid (vars)); vars->ref_character = character; } void var_set_ref_object (sc_var_setref_t vars, sc_int object) { assert (var_is_valid (vars)); vars->ref_object = object; } void var_set_ref_number (sc_var_setref_t vars, sc_int number) { assert (var_is_valid (vars)); vars->ref_number = number; vars->number_refd = 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. */ vars->ref_text = sc_realloc (vars->ref_text, strlen (text) + 1); strcpy (vars->ref_text, 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 for the variable * set. */ sc_int var_get_ref_character (sc_var_setref_t vars) { assert (var_is_valid (vars)); return vars->ref_character; } sc_int var_get_ref_object (sc_var_setref_t vars) { assert (var_is_valid (vars)); return vars->ref_object; } sc_int var_get_ref_number (sc_var_setref_t vars) { assert (var_is_valid (vars)); return vars->ref_number; } const sc_char * var_get_ref_text (sc_var_setref_t vars) { assert (var_is_valid (vars)); /* * If currently NULL, return "". A game may check restrictions involving * referenced text before any value has been set; returning "" here for * this case prevents problems later (strcmp (NULL, ...), for example). */ return vars->ref_text ? vars->ref_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 = difftime (time (NULL), vars->timestamp); 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 = time (NULL); 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->ref_character = %ld\n", vars->ref_character); sc_trace ("vars->ref_object = %ld\n", vars->ref_object); sc_trace ("vars->ref_number = %ld\n", vars->ref_number); sc_trace ("vars->number_refd = %s\n", vars->number_refd ? "TRUE" : "FALSE"); sc_trace ("vars->ref_text = "); if (vars->ref_text) sc_trace ("\"%s\"\n", vars->ref_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 ("[S] %s = \"%s\"", var->name, var->value.string); break; case VAR_INTEGER: sc_trace ("[I] %s = %ld", var->name, var->value.integer); break; default: sc_fatal ("var_debug_dump: invalid type\n"); } sc_trace ("\n"); } } }