1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/adrift/scare.h"
24 #include "glk/adrift/scprotos.h"
25 #include "glk/adrift/scgamest.h"
26 #include "glk/glk.h"
27 #include "glk/events.h"
28 
29 namespace Glk {
30 namespace Adrift {
31 
32 /*
33  * Module notes:
34  *
35  * o Gender enumerations are 0/1/2, but 1/2/3 in jAsea.  The 0/1/2 values
36  *   seem to be right.  Is jAsea off by one?
37  *
38  * o jAsea tries to read Globals.CompileDate.  It's just CompileDate.
39  *
40  * o State_ and obstate are implemented, but not fully tested due to a lack
41  *   of games that use them.
42  */
43 
44 /* Assorted definitions and constants. */
45 static const sc_uint VARS_MAGIC = 0xabcc7a71;
46 static const sc_char NUL = '\0';
47 
48 /* Variables trace flag. */
49 static sc_bool var_trace = FALSE;
50 
51 /* Table of numbers zero to twenty spelled out. */
52 enum { VAR_NUMBERS_SIZE = 21 };
53 static const sc_char *const VAR_NUMBERS[VAR_NUMBERS_SIZE] = {
54 	"zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
55 	"nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
56 	"sixteen", "seventeen", "eighteen", "nineteen", "twenty"
57 };
58 
59 /* Variable entry, held on a list hashed by variable name. */
60 struct sc_var_s {
61 	struct sc_var_s *next;
62 
63 	const sc_char *name;
64 	sc_int type;
65 	sc_vartype_t value;
66 };
67 typedef sc_var_s sc_var_t;
68 typedef sc_var_t *sc_varref_t;
69 
70 /*
71  * Variables set structure.  A self-contained set of variables on which
72  * variables functions operate.  211 is prime, making it a reasonable hash
73  * divisor.  There's no rehashing here; few games, if any, are likely to
74  * exceed a fill factor of two (~422 variables).
75  */
76 enum { VAR_HASH_TABLE_SIZE = 211 };
77 struct sc_var_set_s {
78 	sc_uint magic;
79 	sc_prop_setref_t bundle;
80 	sc_int referenced_character;
81 	sc_int referenced_object;
82 	sc_int referenced_number;
83 	sc_bool is_number_referenced;
84 	sc_char *referenced_text;
85 	sc_char *temporary;
86 	uint32 timestamp;
87 	sc_uint time_offset;
88 	sc_gameref_t game;
89 	sc_varref_t variable[VAR_HASH_TABLE_SIZE];
90 };
91 typedef sc_var_set_s sc_var_set_t;
92 
93 
94 /*
95  * var_is_valid()
96  *
97  * Return TRUE if pointer is a valid variables set, FALSE otherwise.
98  */
var_is_valid(sc_var_setref_t vars)99 static sc_bool var_is_valid(sc_var_setref_t vars) {
100 	return vars && vars->magic == VARS_MAGIC;
101 }
102 
103 
104 /*
105  * var_hash_name()
106  *
107  * Hash a variable name, modulo'ed to the number of buckets.
108  */
var_hash_name(const sc_char * name)109 static sc_uint var_hash_name(const sc_char *name) {
110 	return sc_hash(name) % VAR_HASH_TABLE_SIZE;
111 }
112 
113 
114 /*
115  * var_create_empty()
116  *
117  * Create and return a new empty set of variables.
118  */
var_create_empty(void)119 static sc_var_setref_t var_create_empty(void) {
120 	sc_var_setref_t vars;
121 	sc_int index_;
122 
123 	/* Create a clean set of variables. */
124 	vars = (sc_var_setref_t)sc_malloc(sizeof(*vars));
125 	vars->magic = VARS_MAGIC;
126 	vars->bundle = nullptr;
127 	vars->referenced_character = -1;
128 	vars->referenced_object = -1;
129 	vars->referenced_number = 0;
130 	vars->is_number_referenced = FALSE;
131 	vars->referenced_text = nullptr;
132 	vars->temporary = nullptr;
133 	vars->timestamp = g_vm->_events->getTotalPlayTicks() / 1000;
134 	vars->time_offset = 0;
135 	vars->game = nullptr;
136 
137 	/* Clear all variable hash lists. */
138 	for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++)
139 		vars->variable[index_] = nullptr;
140 
141 	return vars;
142 }
143 
144 
145 /*
146  * var_destroy()
147  *
148  * Destroy a variable set, and free its heap memory.
149  */
var_destroy(sc_var_setref_t vars)150 void var_destroy(sc_var_setref_t vars) {
151 	sc_int index_;
152 	assert(var_is_valid(vars));
153 
154 	/*
155 	 * Free the content of each string variable, and variable entry.  String
156 	 * variable content needs to use mutable string instead of const string.
157 	 */
158 	for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++) {
159 		sc_varref_t var, next;
160 
161 		for (var = vars->variable[index_]; var; var = next) {
162 			next = var->next;
163 			if (var->type == VAR_STRING)
164 				sc_free(var->value.mutable_string);
165 			sc_free(var);
166 		}
167 	}
168 
169 	/* Free any temporary and reference text storage area. */
170 	sc_free(vars->temporary);
171 	sc_free(vars->referenced_text);
172 
173 	/* Poison and free the variable set itself. */
174 	memset(vars, 0xaa, sizeof(*vars));
175 	sc_free(vars);
176 }
177 
178 
179 /*
180  * var_find()
181  * var_add()
182  *
183  * Find and return a pointer to a named variable structure, or nullptr if no such
184  * variable exists, and add a new variable structure to the lists.
185  */
var_find(sc_var_setref_t vars,const sc_char * name)186 static sc_varref_t var_find(sc_var_setref_t vars, const sc_char *name) {
187 	sc_uint hash;
188 	sc_varref_t var;
189 
190 	/* Hash name, search list and return if name match found. */
191 	hash = var_hash_name(name);
192 	for (var = vars->variable[hash]; var; var = var->next) {
193 		if (strcmp(name, var->name) == 0)
194 			break;
195 	}
196 
197 	/* Return variable, or nullptr if no such variable. */
198 	return var;
199 }
200 
var_add(sc_var_setref_t vars,const sc_char * name,sc_int type)201 static sc_varref_t var_add(sc_var_setref_t vars, const sc_char *name, sc_int type) {
202 	sc_varref_t var;
203 	sc_uint hash;
204 
205 	/* Create a new variable entry. */
206 	var = (sc_varref_t)sc_malloc(sizeof(*var));
207 	var->name = name;
208 	var->type = type;
209 	var->value.voidp = nullptr;
210 
211 	/* Hash its name, and insert it at start of the relevant list. */
212 	hash = var_hash_name(name);
213 	var->next = vars->variable[hash];
214 	vars->variable[hash] = var;
215 
216 	return var;
217 }
218 
219 
220 /*
221  * var_get_scare_version()
222  *
223  * Return the value of %scare_version%.  Used to generate the system version
224  * of this variable, and to re-initialize user versions initialized to zero.
225  */
var_get_scare_version(void)226 static sc_int var_get_scare_version(void) {
227 	sc_int major, minor, point, version;
228 
229 	if (sscanf(SCARE_VERSION, "%ld.%ld.%ld", &major, &minor, &point) != 3) {
230 		sc_error("var_get_scare_version: unable to generate scare_version\n");
231 		return 0;
232 	}
233 
234 	version = major * 10000 + minor * 100 + point;
235 	return version;
236 }
237 
238 
239 /*
240  * var_put()
241  *
242  * Store a variable type in a named variable.  If not present, the variable
243  * is created.  Type is one of 'I' or 'S' for integer or string.
244  */
var_put(sc_var_setref_t vars,const sc_char * name,sc_int type,sc_vartype_t vt_value)245 void var_put(sc_var_setref_t vars, const sc_char *name, sc_int type, sc_vartype_t vt_value) {
246 	sc_varref_t var;
247 	sc_bool is_modification;
248 	assert(var_is_valid(vars));
249 	assert(name);
250 
251 	/* Check type is either integer or string. */
252 	switch (type) {
253 	case VAR_INTEGER:
254 	case VAR_STRING:
255 		break;
256 
257 	default:
258 		sc_fatal("var_put: invalid variable type, %ld\n", type);
259 	}
260 
261 	/* See if the user variable already exists. */
262 	var = var_find(vars, name);
263 	if (var) {
264 		/* Verify that nothing is trying to change the variable's type. */
265 		if (var->type != type)
266 			sc_fatal("var_put: variable type changed, %s\n", name);
267 
268 		/*
269 		 * Special case %scare_version%.  If a game changes its value, it may
270 		 * compromise version checking, so warn here, but continue.
271 		 */
272 		if (strcmp(name, "scare_version") == 0) {
273 			if (var->value.integer != vt_value.integer)
274 				sc_error("var_put: warning: %%%s%% value changed\n", name);
275 		}
276 
277 		is_modification = TRUE;
278 	} else {
279 		/*
280 		 * Special case %scare_version%.  If a game defines this and initializes
281 		 * it to zero, re-initialize it to SCARE's version number.  Games that
282 		 * define %scare_version%, initially zero, can use this to test if
283 		 * running under SCARE or Runner.
284 		 */
285 		if (strcmp(name, "scare_version") == 0 && vt_value.integer == 0) {
286 			vt_value.integer = var_get_scare_version();
287 
288 			if (var_trace)
289 				sc_trace("Variable: %%%s%% [new] caught and mapped\n", name);
290 		}
291 
292 		/*
293 		 * Create a new and empty variable entry.  The mutable string needs to
294 		 * be set to nullptr here so that realloc works correctly on assigning
295 		 * the value below.
296 		 */
297 		var = var_add(vars, name, type);
298 		var->value.mutable_string = nullptr;
299 
300 		is_modification = FALSE;
301 	}
302 
303 	/* Update the existing variable, or populate the new one fully. */
304 	switch (var->type) {
305 	case VAR_INTEGER:
306 		var->value.integer = vt_value.integer;
307 		break;
308 
309 	case VAR_STRING:
310 		/* Use mutable string instead of const string. */
311 		var->value.mutable_string = (sc_char *)sc_realloc(var->value.mutable_string,
312 		                            strlen(vt_value.string) + 1);
313 		strcpy(var->value.mutable_string, vt_value.string);
314 		break;
315 
316 	default:
317 		sc_fatal("var_put: invalid variable type, %ld\n", var->type);
318 	}
319 
320 	if (var_trace) {
321 		sc_trace("Variable: %%%s%%%s = ",
322 		         name, is_modification ? "" : " [new]");
323 		switch (var->type) {
324 		case VAR_INTEGER:
325 			sc_trace("%ld", var->value.integer);
326 			break;
327 		case VAR_STRING:
328 			sc_trace("\"%s\"", var->value.string);
329 			break;
330 
331 		default:
332 			sc_trace("[invalid variable type, %ld]", var->type);
333 			break;
334 		}
335 		sc_trace("\n");
336 	}
337 }
338 
339 
340 /*
341  * var_append_temp()
342  *
343  * Helper for object listers.  Extends temporary, and appends the given text
344  * to the string.
345  */
var_append_temp(sc_var_setref_t vars,const sc_char * string)346 static void var_append_temp(sc_var_setref_t vars, const sc_char *string) {
347 	sc_bool new_sentence;
348 	sc_int noted;
349 
350 	if (!vars->temporary) {
351 		/* Create a new temporary area and copy string. */
352 		new_sentence = TRUE;
353 		noted = 0;
354 		vars->temporary = (sc_char *)sc_malloc(strlen(string) + 1);
355 		strcpy(vars->temporary, string);
356 	} else {
357 		/* Append string to existing temporary. */
358 		new_sentence = (vars->temporary[0] == NUL);
359 		noted = strlen(vars->temporary);
360 		vars->temporary = (sc_char *)sc_realloc(vars->temporary,
361 		                                        strlen(vars->temporary) +
362 		                                        strlen(string) + 1);
363 		strcat(vars->temporary, string);
364 	}
365 
366 	if (new_sentence)
367 		vars->temporary[noted] = sc_toupper(vars->temporary[noted]);
368 }
369 
370 
371 /*
372  * var_print_object_np
373  * var_print_object
374  *
375  * Convenience functions to append an object's name, with and without any
376  * prefix, to variables temporary.
377  */
var_print_object_np(sc_gameref_t game,sc_int object)378 static void var_print_object_np(sc_gameref_t game, sc_int object) {
379 	const sc_var_setref_t vars = gs_get_vars(game);
380 	const sc_prop_setref_t bundle = gs_get_bundle(game);
381 	sc_vartype_t vt_key[3];
382 	const sc_char *prefix, *normalized, *name;
383 
384 	/* Get the object's prefix. */
385 	vt_key[0].string = "Objects";
386 	vt_key[1].integer = object;
387 	vt_key[2].string = "Prefix";
388 	prefix = prop_get_string(bundle, "S<-sis", vt_key);
389 
390 	/*
391 	 * Try the same shenanigans as done by the equivalent function in the
392 	 * library.
393 	 */
394 	normalized = prefix;
395 	if (sc_compare_word(prefix, "a", 1)) {
396 		normalized = prefix + 1;
397 		var_append_temp(vars, "the");
398 	} else if (sc_compare_word(prefix, "an", 2)) {
399 		normalized = prefix + 2;
400 		var_append_temp(vars, "the");
401 	} else if (sc_compare_word(prefix, "the", 3)) {
402 		normalized = prefix + 3;
403 		var_append_temp(vars, "the");
404 	} else if (sc_compare_word(prefix, "some", 4)) {
405 		normalized = prefix + 4;
406 		var_append_temp(vars, "the");
407 	} else if (sc_strempty(prefix))
408 		var_append_temp(vars, "the ");
409 
410 	/* As with the library, handle the remaining prefix. */
411 	if (!sc_strempty(normalized)) {
412 		var_append_temp(vars, normalized);
413 		var_append_temp(vars, " ");
414 	} else if (normalized > prefix)
415 		var_append_temp(vars, " ");
416 
417 	/*
418 	 * Print the object's name, again, as with the library, stripping any
419 	 * leading article
420 	 */
421 	vt_key[2].string = "Short";
422 	name = prop_get_string(bundle, "S<-sis", vt_key);
423 	if (sc_compare_word(name, "a", 1))
424 		name += 1;
425 	else if (sc_compare_word(name, "an", 2))
426 		name += 2;
427 	else if (sc_compare_word(name, "the", 3))
428 		name += 3;
429 	else if (sc_compare_word(name, "some", 4))
430 		name += 4;
431 	var_append_temp(vars, name);
432 }
433 
var_print_object(sc_gameref_t game,sc_int object)434 static void var_print_object(sc_gameref_t game, sc_int object) {
435 	const sc_var_setref_t vars = gs_get_vars(game);
436 	const sc_prop_setref_t bundle = gs_get_bundle(game);
437 	sc_vartype_t vt_key[3];
438 	const sc_char *prefix, *name;
439 
440 	/*
441 	 * Get the object's prefix.  As with the library, if the prefix is empty,
442 	 * put in an "a ".
443 	 */
444 	vt_key[0].string = "Objects";
445 	vt_key[1].integer = object;
446 	vt_key[2].string = "Prefix";
447 	prefix = prop_get_string(bundle, "S<-sis", vt_key);
448 	if (!sc_strempty(prefix)) {
449 		var_append_temp(vars, prefix);
450 		var_append_temp(vars, " ");
451 	} else
452 		var_append_temp(vars, "a ");
453 
454 	/* Print the object's name. */
455 	vt_key[2].string = "Short";
456 	name = prop_get_string(bundle, "S<-sis", vt_key);
457 	var_append_temp(vars, name);
458 }
459 
460 
461 /*
462  * var_select_plurality()
463  *
464  * Convenience function for listers.  Selects one of two responses depending
465  * on whether an object appears singular or plural.
466  */
var_select_plurality(sc_gameref_t game,sc_int object,const sc_char * singular,const sc_char * plural)467 static const sc_char *var_select_plurality(sc_gameref_t game, sc_int object,
468 		const sc_char *singular, const sc_char *plural) {
469 	return obj_appears_plural(game, object) ? plural : singular;
470 }
471 
472 
473 /*
474  * var_list_in_object()
475  *
476  * List the objects in a given container object.
477  */
var_list_in_object(sc_gameref_t game,sc_int container)478 static void var_list_in_object(sc_gameref_t game, sc_int container) {
479 	const sc_var_setref_t vars = gs_get_vars(game);
480 	sc_int object, count, trail;
481 
482 	/* List out the objects contained in this object. */
483 	count = 0;
484 	trail = -1;
485 	for (object = 0; object < gs_object_count(game); object++) {
486 		/* Contained? */
487 		if (gs_object_position(game, object) == OBJ_IN_OBJECT
488 		        && gs_object_parent(game, object) == container) {
489 			if (count > 0) {
490 				if (count > 1)
491 					var_append_temp(vars, ", ");
492 
493 				/* Print out the current list object. */
494 				var_print_object(game, trail);
495 			}
496 			trail = object;
497 			count++;
498 		}
499 	}
500 	if (count >= 1) {
501 		/* Print out final listed object. */
502 		if (count == 1) {
503 			var_print_object(game, trail);
504 			var_append_temp(vars,
505 			                var_select_plurality(game, trail,
506 			                                     " is inside ",
507 			                                     " are inside "));
508 		} else {
509 			var_append_temp(vars, " and ");
510 			var_print_object(game, trail);
511 			var_append_temp(vars, " are inside ");
512 		}
513 
514 		/* Print out the container. */
515 		var_print_object_np(game, container);
516 		var_append_temp(vars, ".");
517 	}
518 }
519 
520 
521 /*
522  * var_list_on_object()
523  *
524  * List the objects on a given surface object.
525  */
var_list_on_object(sc_gameref_t game,sc_int supporter)526 static void var_list_on_object(sc_gameref_t game, sc_int supporter) {
527 	const sc_var_setref_t vars = gs_get_vars(game);
528 	sc_int object, count, trail;
529 
530 	/* List out the objects standing on this object. */
531 	count = 0;
532 	trail = -1;
533 	for (object = 0; object < gs_object_count(game); object++) {
534 		/* Standing on? */
535 		if (gs_object_position(game, object) == OBJ_ON_OBJECT
536 		        && gs_object_parent(game, object) == supporter) {
537 			if (count > 0) {
538 				if (count > 1)
539 					var_append_temp(vars, ", ");
540 
541 				/* Print out the current list object. */
542 				var_print_object(game, trail);
543 			}
544 			trail = object;
545 			count++;
546 		}
547 	}
548 	if (count >= 1) {
549 		/* Print out final listed object. */
550 		if (count == 1) {
551 			var_print_object(game, trail);
552 			var_append_temp(vars,
553 			                var_select_plurality(game, trail,
554 			                                     " is on ", " are on "));
555 		} else {
556 			var_append_temp(vars, " and ");
557 			var_print_object(game, trail);
558 			var_append_temp(vars, " are on ");
559 		}
560 
561 		/* Print out the surface. */
562 		var_print_object_np(game, supporter);
563 		var_append_temp(vars, ".");
564 	}
565 }
566 
567 
568 /*
569  * var_list_onin_object()
570  *
571  * List the objects on and in a given associate object.
572  */
var_list_onin_object(sc_gameref_t game,sc_int associate)573 static void var_list_onin_object(sc_gameref_t game, sc_int associate) {
574 	const sc_var_setref_t vars = gs_get_vars(game);
575 	sc_int object, count, trail;
576 	sc_bool supporting;
577 
578 	/* List out the objects standing on this object. */
579 	count = 0;
580 	trail = -1;
581 	supporting = FALSE;
582 	for (object = 0; object < gs_object_count(game); object++) {
583 		/* Standing on? */
584 		if (gs_object_position(game, object) == OBJ_ON_OBJECT
585 		        && gs_object_parent(game, object) == associate) {
586 			if (count > 0) {
587 				if (count > 1)
588 					var_append_temp(vars, ", ");
589 
590 				/* Print out the current list object. */
591 				var_print_object(game, trail);
592 			}
593 			trail = object;
594 			count++;
595 		}
596 	}
597 	if (count >= 1) {
598 		/* Print out final listed object. */
599 		if (count == 1) {
600 			var_print_object(game, trail);
601 			var_append_temp(vars,
602 			                var_select_plurality(game, trail,
603 			                                     " is on ", " are on "));
604 		} else {
605 			var_append_temp(vars, " and ");
606 			var_print_object(game, trail);
607 			var_append_temp(vars, " are on ");
608 		}
609 
610 		/* Print out the surface. */
611 		var_print_object_np(game, associate);
612 		supporting = TRUE;
613 	}
614 
615 	/* List out the objects contained in this object. */
616 	count = 0;
617 	trail = -1;
618 	for (object = 0; object < gs_object_count(game); object++) {
619 		/* Contained? */
620 		if (gs_object_position(game, object) == OBJ_IN_OBJECT
621 		        && gs_object_parent(game, object) == associate) {
622 			if (count > 0) {
623 				if (count == 1) {
624 					if (supporting)
625 						var_append_temp(vars, ", and ");
626 				} else
627 					var_append_temp(vars, ", ");
628 
629 				/* Print out the current list object. */
630 				var_print_object(game, trail);
631 			}
632 			trail = object;
633 			count++;
634 		}
635 	}
636 	if (count >= 1) {
637 		/* Print out final listed object. */
638 		if (count == 1) {
639 			if (supporting)
640 				var_append_temp(vars, ", and ");
641 			var_print_object(game, trail);
642 			var_append_temp(vars,
643 			                var_select_plurality(game, trail,
644 			                                     " is inside ",
645 			                                     " are inside "));
646 		} else {
647 			var_append_temp(vars, " and ");
648 			var_print_object(game, trail);
649 			var_append_temp(vars, " are inside");
650 		}
651 
652 		/* Print out the container. */
653 		if (!supporting) {
654 			var_append_temp(vars, " ");
655 			var_print_object_np(game, associate);
656 		}
657 		var_append_temp(vars, ".");
658 	} else {
659 		if (supporting)
660 			var_append_temp(vars, ".");
661 	}
662 }
663 
664 
665 /*
666  * var_return_integer()
667  * var_return_string()
668  *
669  * Convenience helpers for var_get_system().  Provide convenience and some
670  * mild syntactic sugar for making returning a value as a system variable
671  * a bit easier.  Set appropriate values for return type and the relevant
672  * return value field, and always return TRUE.  A macro was tempting here...
673  */
var_return_integer(sc_int value,sc_int * type,sc_vartype_t * vt_rvalue)674 static sc_bool var_return_integer(sc_int value, sc_int *type, sc_vartype_t *vt_rvalue) {
675 	*type = VAR_INTEGER;
676 	vt_rvalue->integer = value;
677 	return TRUE;
678 }
679 
var_return_string(const sc_char * value,sc_int * type,sc_vartype_t * vt_rvalue)680 static sc_bool var_return_string(const sc_char *value, sc_int *type, sc_vartype_t *vt_rvalue) {
681 	*type = VAR_STRING;
682 	vt_rvalue->string = value;
683 	return TRUE;
684 }
685 
686 
687 /*
688  * var_get_system()
689  *
690  * Construct a system variable, and return its type and value, or FALSE
691  * if invalid name passed in.  Uses var_return_*() to reduce code untidiness.
692  */
var_get_system(sc_var_setref_t vars,const sc_char * name,sc_int * type,sc_vartype_t * vt_rvalue)693 static sc_bool var_get_system(sc_var_setref_t vars, const sc_char *name,
694 		sc_int *type, sc_vartype_t *vt_rvalue) {
695 	const sc_prop_setref_t bundle = vars->bundle;
696 	const sc_gameref_t game = vars->game;
697 
698 	/* Check name for known system variables. */
699 	if (strcmp(name, "author") == 0) {
700 		sc_vartype_t vt_key[2];
701 		const sc_char *author;
702 
703 		/* Get and return the global gameauthor string. */
704 		vt_key[0].string = "Globals";
705 		vt_key[1].string = "GameAuthor";
706 		author = prop_get_string(bundle, "S<-ss", vt_key);
707 		if (sc_strempty(author))
708 			author = "[Author unknown]";
709 
710 		return var_return_string(author, type, vt_rvalue);
711 	}
712 
713 	else if (strcmp(name, "character") == 0) {
714 		/* See if there is a referenced character. */
715 		if (vars->referenced_character != -1) {
716 			sc_vartype_t vt_key[3];
717 			const sc_char *npc_name;
718 
719 			/* Return the character name string. */
720 			vt_key[0].string = "NPCs";
721 			vt_key[1].integer = vars->referenced_character;
722 			vt_key[2].string = "Name";
723 			npc_name = prop_get_string(bundle, "S<-sis", vt_key);
724 			if (sc_strempty(npc_name))
725 				npc_name = "[Character unknown]";
726 
727 			return var_return_string(npc_name, type, vt_rvalue);
728 		} else {
729 			sc_error("var_get_system: no referenced character yet\n");
730 			return var_return_string("[Character unknown]", type, vt_rvalue);
731 		}
732 	}
733 
734 	else if (strcmp(name, "heshe") == 0 || strcmp(name, "himher") == 0) {
735 		/* See if there is a referenced character. */
736 		if (vars->referenced_character != -1) {
737 			sc_vartype_t vt_key[3];
738 			sc_int gender;
739 			const sc_char *retval;
740 
741 			/* Return the appropriate character gender string. */
742 			vt_key[0].string = "NPCs";
743 			vt_key[1].integer = vars->referenced_character;
744 			vt_key[2].string = "Gender";
745 			gender = prop_get_integer(bundle, "I<-sis", vt_key);
746 			switch (gender) {
747 			case NPC_MALE:
748 				retval = (strcmp(name, "heshe") == 0) ? "he" : "him";
749 				break;
750 			case NPC_FEMALE:
751 				retval = (strcmp(name, "heshe") == 0) ? "she" : "her";
752 				break;
753 			case NPC_NEUTER:
754 				retval = "it";
755 				break;
756 
757 			default:
758 				sc_error("var_get_system: unknown gender, %ld\n", gender);
759 				retval = "[Gender unknown]";
760 				break;
761 			}
762 			return var_return_string(retval, type, vt_rvalue);
763 		} else {
764 			sc_error("var_get_system: no referenced character yet\n");
765 			return var_return_string("[Gender unknown]", type, vt_rvalue);
766 		}
767 	}
768 
769 	else if (strncmp(name, "in_", 3) == 0) {
770 		sc_int saved_ref_object = vars->referenced_object;
771 
772 		/* Check there's enough information to return a value. */
773 		if (!game) {
774 			sc_error("var_get_system: no game for in_\n");
775 			return var_return_string("[In_ unavailable]", type, vt_rvalue);
776 		}
777 		if (!uip_match("%object%", name + 3, game)) {
778 			sc_error("var_get_system: invalid object for in_\n");
779 			return var_return_string("[In_ unavailable]", type, vt_rvalue);
780 		}
781 
782 		/* Clear any current temporary for appends. */
783 		vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
784 		strcpy(vars->temporary, "");
785 
786 		/* Write what's in the object into temporary. */
787 		var_list_in_object(game, vars->referenced_object);
788 
789 		/* Restore saved referenced object and return. */
790 		vars->referenced_object = saved_ref_object;
791 		return var_return_string(vars->temporary, type, vt_rvalue);
792 	}
793 
794 	else if (strcmp(name, "maxscore") == 0) {
795 		sc_vartype_t vt_key[2];
796 		sc_int maxscore;
797 
798 		/* Return the maximum score. */
799 		vt_key[0].string = "Globals";
800 		vt_key[1].string = "MaxScore";
801 		maxscore = prop_get_integer(bundle, "I<-ss", vt_key);
802 
803 		return var_return_integer(maxscore, type, vt_rvalue);
804 	}
805 
806 	else if (strcmp(name, "modified") == 0) {
807 		sc_vartype_t vt_key;
808 		const sc_char *compiledate;
809 
810 		/* Return the game compilation date. */
811 		vt_key.string = "CompileDate";
812 		compiledate = prop_get_string(bundle, "S<-s", &vt_key);
813 		if (sc_strempty(compiledate))
814 			compiledate = "[Modified unknown]";
815 
816 		return var_return_string(compiledate, type, vt_rvalue);
817 	}
818 
819 	else if (strcmp(name, "number") == 0) {
820 		/* Return the referenced number, or 0 if none yet. */
821 		if (!vars->is_number_referenced)
822 			sc_error("var_get_system: no referenced number yet\n");
823 
824 		return var_return_integer(vars->referenced_number, type, vt_rvalue);
825 	}
826 
827 	else if (strcmp(name, "object") == 0) {
828 		/* See if we have a referenced object yet. */
829 		if (vars->referenced_object != -1) {
830 			/* Return object name with its prefix. */
831 			sc_vartype_t vt_key[3];
832 			const sc_char *prefix, *objname;
833 
834 			vt_key[0].string = "Objects";
835 			vt_key[1].integer = vars->referenced_object;
836 			vt_key[2].string = "Prefix";
837 			prefix = prop_get_string(bundle, "S<-sis", vt_key);
838 
839 			vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(prefix) + 1);
840 			strcpy(vars->temporary, prefix);
841 
842 			vt_key[2].string = "Short";
843 			objname = prop_get_string(bundle, "S<-sis", vt_key);
844 
845 			vars->temporary = (sc_char *)sc_realloc(vars->temporary,
846 			                                        strlen(vars->temporary)
847 			                                        + strlen(objname) + 2);
848 			strcat(vars->temporary, " ");
849 			strcat(vars->temporary, objname);
850 
851 			return var_return_string(vars->temporary, type, vt_rvalue);
852 		} else {
853 			sc_error("var_get_system: no referenced object yet\n");
854 			return var_return_string("[Object unknown]", type, vt_rvalue);
855 		}
856 	}
857 
858 	else if (strcmp(name, "obstate") == 0) {
859 		sc_vartype_t vt_key[3];
860 		sc_bool is_statussed;
861 		sc_char *state;
862 
863 		/* Check there's enough information to return a value. */
864 		if (!game) {
865 			sc_error("var_get_system: no game for obstate\n");
866 			return var_return_string("[Obstate unavailable]", type, vt_rvalue);
867 		}
868 		if (vars->referenced_object == -1) {
869 			sc_error("var_get_system: no object for obstate\n");
870 			return var_return_string("[Obstate unavailable]", type, vt_rvalue);
871 		}
872 
873 		/*
874 		 * If not a stateful object, Runner 4.0.45 crashes; we'll do something
875 		 * different here.
876 		 */
877 		vt_key[0].string = "Objects";
878 		vt_key[1].integer = vars->referenced_object;
879 		vt_key[2].string = "CurrentState";
880 		is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
881 		if (!is_statussed)
882 			return var_return_string("stateless", type, vt_rvalue);
883 
884 		/* Get state, and copy to temporary. */
885 		state = obj_state_name(game, vars->referenced_object);
886 		if (!state) {
887 			sc_error("var_get_system: invalid state for obstate\n");
888 			return var_return_string("[Obstate unknown]", type, vt_rvalue);
889 		}
890 		vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(state) + 1);
891 		strcpy(vars->temporary, state);
892 		sc_free(state);
893 
894 		/* Return temporary. */
895 		return var_return_string(vars->temporary, type, vt_rvalue);
896 	}
897 
898 	else if (strcmp(name, "obstatus") == 0) {
899 		sc_vartype_t vt_key[3];
900 		sc_bool is_openable;
901 		sc_int openness;
902 		const sc_char *retval;
903 
904 		/* Check there's enough information to return a value. */
905 		if (!game) {
906 			sc_error("var_get_system: no game for obstatus\n");
907 			return var_return_string("[Obstatus unavailable]", type, vt_rvalue);
908 		}
909 		if (vars->referenced_object == -1) {
910 			sc_error("var_get_system: no object for obstatus\n");
911 			return var_return_string("[Obstatus unavailable]", type, vt_rvalue);
912 		}
913 
914 		/* If not an openable object, return unopenable to match Adrift. */
915 		vt_key[0].string = "Objects";
916 		vt_key[1].integer = vars->referenced_object;
917 		vt_key[2].string = "Openable";
918 		is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
919 		if (!is_openable)
920 			return var_return_string("unopenable", type, vt_rvalue);
921 
922 		/* Return one of open, closed, or locked. */
923 		openness = gs_object_openness(game, vars->referenced_object);
924 		switch (openness) {
925 		case OBJ_OPEN:
926 			retval = "open";
927 			break;
928 		case OBJ_CLOSED:
929 			retval = "closed";
930 			break;
931 		case OBJ_LOCKED:
932 			retval = "locked";
933 			break;
934 		default:
935 			retval = "[Obstatus unknown]";
936 			break;
937 		}
938 		return var_return_string(retval, type, vt_rvalue);
939 	}
940 
941 	else if (strncmp(name, "on_", 3) == 0) {
942 		sc_int saved_ref_object = vars->referenced_object;
943 
944 		/* Check there's enough information to return a value. */
945 		if (!game) {
946 			sc_error("var_get_system: no game for on_\n");
947 			return var_return_string("[On_ unavailable]", type, vt_rvalue);
948 		}
949 		if (!uip_match("%object%", name + 3, game)) {
950 			sc_error("var_get_system: invalid object for on_\n");
951 			return var_return_string("[On_ unavailable]", type, vt_rvalue);
952 		}
953 
954 		/* Clear any current temporary for appends. */
955 		vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
956 		strcpy(vars->temporary, "");
957 
958 		/* Write what's on the object into temporary. */
959 		var_list_on_object(game, vars->referenced_object);
960 
961 		/* Restore saved referenced object and return. */
962 		vars->referenced_object = saved_ref_object;
963 		return var_return_string(vars->temporary, type, vt_rvalue);
964 	}
965 
966 	else if (strncmp(name, "onin_", 5) == 0) {
967 		sc_int saved_ref_object = vars->referenced_object;
968 
969 		/* Check there's enough information to return a value. */
970 		if (!game) {
971 			sc_error("var_get_system: no game for onin_\n");
972 			return var_return_string("[Onin_ unavailable]", type, vt_rvalue);
973 		}
974 		if (!uip_match("%object%", name + 5, game)) {
975 			sc_error("var_get_system: invalid object for onin_\n");
976 			return var_return_string("[Onin_ unavailable]", type, vt_rvalue);
977 		}
978 
979 		/* Clear any current temporary for appends. */
980 		vars->temporary = (sc_char *)sc_realloc(vars->temporary, 1);
981 		strcpy(vars->temporary, "");
982 
983 		/* Write what's on/in the object into temporary. */
984 		var_list_onin_object(game, vars->referenced_object);
985 
986 		/* Restore saved referenced object and return. */
987 		vars->referenced_object = saved_ref_object;
988 		return var_return_string(vars->temporary, type, vt_rvalue);
989 	}
990 
991 	else if (strcmp(name, "player") == 0) {
992 		sc_vartype_t vt_key[2];
993 		const sc_char *playername;
994 
995 		/*
996 		 * Return player's name from properties, or just "Player" if not set
997 		 * in the properties.
998 		 */
999 		vt_key[0].string = "Globals";
1000 		vt_key[1].string = "PlayerName";
1001 		playername = prop_get_string(bundle, "S<-ss", vt_key);
1002 		if (sc_strempty(playername))
1003 			playername = "Player";
1004 
1005 		return var_return_string(playername, type, vt_rvalue);
1006 	}
1007 
1008 	else if (strcmp(name, "room") == 0) {
1009 		const sc_char *roomname;
1010 
1011 		/* Check there's enough information to return a value. */
1012 		if (!game) {
1013 			sc_error("var_get_system: no game for room\n");
1014 			return var_return_string("[Room unavailable]", type, vt_rvalue);
1015 		}
1016 
1017 		/* Return the current player room. */
1018 		roomname = lib_get_room_name(game, gs_playerroom(game));
1019 		return var_return_string(roomname, type, vt_rvalue);
1020 	}
1021 
1022 	else if (strcmp(name, "score") == 0) {
1023 		/* Check there's enough information to return a value. */
1024 		if (!game) {
1025 			sc_error("var_get_system: no game for score\n");
1026 			return var_return_integer(0, type, vt_rvalue);
1027 		}
1028 
1029 		/* Return the current game score. */
1030 		return var_return_integer(game->score, type, vt_rvalue);
1031 	}
1032 
1033 	else if (strncmp(name, "state_", 6) == 0) {
1034 		sc_int saved_ref_object = vars->referenced_object;
1035 		sc_vartype_t vt_key[3];
1036 		sc_bool is_statussed;
1037 		sc_char *state;
1038 
1039 		/* Check there's enough information to return a value. */
1040 		if (!game) {
1041 			sc_error("var_get_system: no game for state_\n");
1042 			return var_return_string("[State_ unavailable]", type, vt_rvalue);
1043 		}
1044 		if (!uip_match("%object%", name + 6, game)) {
1045 			sc_error("var_get_system: invalid object for state_\n");
1046 			return var_return_string("[State_ unavailable]", type, vt_rvalue);
1047 		}
1048 
1049 		/* Verify this is a stateful object. */
1050 		vt_key[0].string = "Objects";
1051 		vt_key[1].integer = vars->referenced_object;
1052 		vt_key[2].string = "CurrentState";
1053 		is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
1054 		if (!is_statussed) {
1055 			vars->referenced_object = saved_ref_object;
1056 			sc_error("var_get_system: stateless object for state_\n");
1057 			return var_return_string("[State_ unavailable]", type, vt_rvalue);
1058 		}
1059 
1060 		/* Get state, and copy to temporary. */
1061 		state = obj_state_name(game, vars->referenced_object);
1062 		if (!state) {
1063 			vars->referenced_object = saved_ref_object;
1064 			sc_error("var_get_system: invalid state for state_\n");
1065 			return var_return_string("[State_ unknown]", type, vt_rvalue);
1066 		}
1067 		vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(state) + 1);
1068 		strcpy(vars->temporary, state);
1069 		sc_free(state);
1070 
1071 		/* Restore saved referenced object and return. */
1072 		vars->referenced_object = saved_ref_object;
1073 		return var_return_string(vars->temporary, type, vt_rvalue);
1074 	}
1075 
1076 	else if (strncmp(name, "status_", 7) == 0) {
1077 		sc_int saved_ref_object = vars->referenced_object;
1078 		sc_vartype_t vt_key[3];
1079 		sc_bool is_openable;
1080 		sc_int openness;
1081 		const sc_char *retval;
1082 
1083 		/* Check there's enough information to return a value. */
1084 		if (!game) {
1085 			sc_error("var_get_system: no game for status_\n");
1086 			return var_return_string("[Status_ unavailable]", type, vt_rvalue);
1087 		}
1088 		if (!uip_match("%object%", name + 7, game)) {
1089 			sc_error("var_get_system: invalid object for status_\n");
1090 			return var_return_string("[Status_ unavailable]", type, vt_rvalue);
1091 		}
1092 
1093 		/* Verify this is an openable object. */
1094 		vt_key[0].string = "Objects";
1095 		vt_key[1].integer = vars->referenced_object;
1096 		vt_key[2].string = "Openable";
1097 		is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
1098 		if (!is_openable) {
1099 			vars->referenced_object = saved_ref_object;
1100 			sc_error("var_get_system: stateless object for status_\n");
1101 			return var_return_string("[Status_ unavailable]", type, vt_rvalue);
1102 		}
1103 
1104 		/* Return one of open, closed, or locked. */
1105 		openness = gs_object_openness(game, vars->referenced_object);
1106 		switch (openness) {
1107 		case OBJ_OPEN:
1108 			retval = "open";
1109 			break;
1110 		case OBJ_CLOSED:
1111 			retval = "closed";
1112 			break;
1113 		case OBJ_LOCKED:
1114 			retval = "locked";
1115 			break;
1116 		default:
1117 			retval = "[Status_ unknown]";
1118 			break;
1119 		}
1120 
1121 		/* Restore saved referenced object and return. */
1122 		vars->referenced_object = saved_ref_object;
1123 		return var_return_string(retval, type, vt_rvalue);
1124 	}
1125 
1126 	else if (strcmp(name, "t_number") == 0) {
1127 		/* See if we have a referenced number yet. */
1128 		if (vars->is_number_referenced) {
1129 			sc_int number;
1130 			const sc_char *retval;
1131 
1132 			/* Return the referenced number as a string. */
1133 			number = vars->referenced_number;
1134 			if (number >= 0 && number < VAR_NUMBERS_SIZE)
1135 				retval = VAR_NUMBERS[number];
1136 			else {
1137 				vars->temporary = (sc_char *)sc_realloc(vars->temporary, 32);
1138 				sprintf(vars->temporary, "%ld", number);
1139 				retval = vars->temporary;
1140 			}
1141 
1142 			return var_return_string(retval, type, vt_rvalue);
1143 		} else {
1144 			sc_error("var_get_system: no referenced number yet\n");
1145 			return var_return_string("[Number unknown]", type, vt_rvalue);
1146 		}
1147 	}
1148 
1149 	else if (strncmp(name, "t_", 2) == 0) {
1150 		sc_varref_t var;
1151 
1152 		/* Find the variable; must be a user, not a system, one. */
1153 		var = var_find(vars, name + 2);
1154 		if (!var) {
1155 			sc_error("var_get_system:"
1156 			         " no such variable, %s\n", name + 2);
1157 			return var_return_string("[Unknown variable]", type, vt_rvalue);
1158 		} else if (var->type != VAR_INTEGER) {
1159 			sc_error("var_get_system:"
1160 			         " not an integer variable, %s\n", name + 2);
1161 			return var_return_string(var->value.string, type, vt_rvalue);
1162 		} else {
1163 			sc_int number;
1164 			const sc_char *retval;
1165 
1166 			/* Return the variable value as a string. */
1167 			number = var->value.integer;
1168 			if (number >= 0 && number < VAR_NUMBERS_SIZE)
1169 				retval = VAR_NUMBERS[number];
1170 			else {
1171 				vars->temporary = (sc_char *)sc_realloc(vars->temporary, 32);
1172 				sprintf(vars->temporary, "%ld", number);
1173 				retval = vars->temporary;
1174 			}
1175 
1176 			return var_return_string(retval, type, vt_rvalue);
1177 		}
1178 	}
1179 
1180 	else if (strcmp(name, "text") == 0) {
1181 		const sc_char *retval;
1182 
1183 		/* Return any referenced text, otherwise a neutral string. */
1184 		if (vars->referenced_text)
1185 			retval = vars->referenced_text;
1186 		else {
1187 			sc_error("var_get_system: no text yet to reference\n");
1188 			retval = "[Text unknown]";
1189 		}
1190 
1191 		return var_return_string(retval, type, vt_rvalue);
1192 	}
1193 
1194 	else if (strcmp(name, "theobject") == 0) {
1195 		/* See if we have a referenced object yet. */
1196 		if (vars->referenced_object != -1) {
1197 			/* Return object name prefixed with "the"... */
1198 			sc_vartype_t vt_key[3];
1199 			const sc_char *prefix, *normalized, *objname;
1200 
1201 			vt_key[0].string = "Objects";
1202 			vt_key[1].integer = vars->referenced_object;
1203 			vt_key[2].string = "Prefix";
1204 			prefix = prop_get_string(bundle, "S<-sis", vt_key);
1205 
1206 			vars->temporary = (sc_char *)sc_realloc(vars->temporary, strlen(prefix) + 5);
1207 			strcpy(vars->temporary, "");
1208 
1209 			normalized = prefix;
1210 			if (sc_compare_word(prefix, "a", 1)) {
1211 				strcat(vars->temporary, "the");
1212 				normalized = prefix + 1;
1213 			} else if (sc_compare_word(prefix, "an", 2)) {
1214 				strcat(vars->temporary, "the");
1215 				normalized = prefix + 2;
1216 			} else if (sc_compare_word(prefix, "the", 3)) {
1217 				strcat(vars->temporary, "the");
1218 				normalized = prefix + 3;
1219 			} else if (sc_compare_word(prefix, "some", 4)) {
1220 				strcat(vars->temporary, "the");
1221 				normalized = prefix + 4;
1222 			} else if (sc_strempty(prefix))
1223 				strcat(vars->temporary, "the ");
1224 
1225 			if (!sc_strempty(normalized)) {
1226 				strcat(vars->temporary, normalized);
1227 				strcat(vars->temporary, " ");
1228 			} else if (normalized > prefix)
1229 				strcat(vars->temporary, " ");
1230 
1231 			vt_key[2].string = "Short";
1232 			objname = prop_get_string(bundle, "S<-sis", vt_key);
1233 			if (sc_compare_word(objname, "a", 1))
1234 				objname += 1;
1235 			else if (sc_compare_word(objname, "an", 2))
1236 				objname += 2;
1237 			else if (sc_compare_word(objname, "the", 3))
1238 				objname += 3;
1239 			else if (sc_compare_word(objname, "some", 4))
1240 				objname += 4;
1241 
1242 			vars->temporary = (sc_char *)sc_realloc(vars->temporary,
1243 			                                        strlen(vars->temporary)
1244 			                                        + strlen(objname) + 1);
1245 			strcat(vars->temporary, objname);
1246 
1247 			return var_return_string(vars->temporary, type, vt_rvalue);
1248 		} else {
1249 			sc_error("var_get_system: no referenced object yet\n");
1250 			return var_return_string("[Object unknown]", type, vt_rvalue);
1251 		}
1252 	}
1253 
1254 	else if (strcmp(name, "time") == 0) {
1255 		double delta;
1256 		sc_int retval;
1257 
1258 		/* Return the elapsed game time in seconds. */
1259 		delta = vars->timestamp - (g_vm->_events->getTotalPlayTicks() / 1000);
1260 		retval = (sc_int) delta + vars->time_offset;
1261 
1262 		return var_return_integer(retval, type, vt_rvalue);
1263 	}
1264 
1265 	else if (strcmp(name, "title") == 0) {
1266 		sc_vartype_t vt_key[2];
1267 		const sc_char *gamename;
1268 
1269 		/* Return the game's title. */
1270 		vt_key[0].string = "Globals";
1271 		vt_key[1].string = "GameName";
1272 		gamename = prop_get_string(bundle, "S<-ss", vt_key);
1273 		if (sc_strempty(gamename))
1274 			gamename = "[Title unknown]";
1275 
1276 		return var_return_string(gamename, type, vt_rvalue);
1277 	}
1278 
1279 	else if (strcmp(name, "turns") == 0) {
1280 		/* Check there's enough information to return a value. */
1281 		if (!game) {
1282 			sc_error("var_get_system: no game for turns\n");
1283 			return var_return_integer(0, type, vt_rvalue);
1284 		}
1285 
1286 		/* Return the count of game turns. */
1287 		return var_return_integer(game->turns, type, vt_rvalue);
1288 	}
1289 
1290 	else if (strcmp(name, "version") == 0) {
1291 		/* Return the Adrift emulation level of SCARE. */
1292 		return var_return_integer(SCARE_EMULATION, type, vt_rvalue);
1293 	}
1294 
1295 	else if (strcmp(name, "scare_version") == 0) {
1296 		/* Private system variable, return SCARE's version number. */
1297 		return var_return_integer(var_get_scare_version(), type, vt_rvalue);
1298 	}
1299 
1300 	return FALSE;
1301 }
1302 
1303 
1304 /*
1305  * var_get_user()
1306  *
1307  * Retrieve a user variable, and return its type and value, or FALSE if the
1308  * name passed in is not a defined user variable.
1309  */
var_get_user(sc_var_setref_t vars,const sc_char * name,sc_int * type,sc_vartype_t * vt_rvalue)1310 static sc_bool var_get_user(sc_var_setref_t vars, const sc_char *name,
1311 		sc_int *type, sc_vartype_t *vt_rvalue) {
1312 	sc_varref_t var;
1313 
1314 	/* Check user variables for a reference to the named variable. */
1315 	var = var_find(vars, name);
1316 	if (var) {
1317 		/* Copy out variable details. */
1318 		*type = var->type;
1319 		switch (var->type) {
1320 		case VAR_INTEGER:
1321 			vt_rvalue->integer = var->value.integer;
1322 			break;
1323 		case VAR_STRING:
1324 			vt_rvalue->string = var->value.string;
1325 			break;
1326 
1327 		default:
1328 			sc_fatal("var_get_user: invalid variable type, %ld\n", var->type);
1329 		}
1330 
1331 		/* Return success. */
1332 		return TRUE;
1333 	}
1334 
1335 	return FALSE;
1336 }
1337 
1338 
1339 /*
1340  * var_get()
1341  *
1342  * Retrieve a variable, and return its value and type.  Returns FALSE if the
1343  * named variable does not exist.
1344  */
var_get(sc_var_setref_t vars,const sc_char * name,sc_int * type,sc_vartype_t * vt_rvalue)1345 sc_bool var_get(sc_var_setref_t vars, const sc_char *name, sc_int *type, sc_vartype_t *vt_rvalue) {
1346 	sc_bool status;
1347 	assert(var_is_valid(vars));
1348 	assert(name && type && vt_rvalue);
1349 
1350 	/*
1351 	 * Check user and system variables for a reference to the name.  User
1352 	 * variables take precedence over system ones; that is, they may override
1353 	 * them in a game.
1354 	 */
1355 	status = var_get_user(vars, name, type, vt_rvalue);
1356 	if (!status)
1357 		status = var_get_system(vars, name, type, vt_rvalue);
1358 
1359 	if (var_trace) {
1360 		if (status) {
1361 			sc_trace("Variable: %%%s%% retrieved, ", name);
1362 			switch (*type) {
1363 			case VAR_INTEGER:
1364 				sc_trace("%ld", vt_rvalue->integer);
1365 				break;
1366 			case VAR_STRING:
1367 				sc_trace("\"%s\"", vt_rvalue->string);
1368 				break;
1369 
1370 			default:
1371 				sc_trace("Variable: invalid variable type, %ld\n", *type);
1372 				break;
1373 			}
1374 			sc_trace("\n");
1375 		} else
1376 			sc_trace("Variable: \"%s\", no such variable\n", name);
1377 	}
1378 
1379 	return status;
1380 }
1381 
1382 
1383 /*
1384  * var_put_integer()
1385  * var_get_integer()
1386  *
1387  * Convenience functions to store and retrieve an integer variable.  It is
1388  * an error for the variable not to exist or to have the wrong type.
1389  */
var_put_integer(sc_var_setref_t vars,const sc_char * name,sc_int value)1390 void var_put_integer(sc_var_setref_t vars, const sc_char *name, sc_int value) {
1391 	sc_vartype_t vt_value;
1392 	assert(var_is_valid(vars));
1393 
1394 	vt_value.integer = value;
1395 	var_put(vars, name, VAR_INTEGER, vt_value);
1396 }
1397 
var_get_integer(sc_var_setref_t vars,const sc_char * name)1398 sc_int var_get_integer(sc_var_setref_t vars, const sc_char *name) {
1399 	sc_vartype_t vt_rvalue;
1400 	sc_int type;
1401 	assert(var_is_valid(vars));
1402 
1403 	if (!var_get(vars, name, &type, &vt_rvalue))
1404 		sc_fatal("var_get_integer: no such variable, %s\n", name);
1405 	else if (type != VAR_INTEGER)
1406 		sc_fatal("var_get_integer: not an integer, %s\n", name);
1407 
1408 	return vt_rvalue.integer;
1409 }
1410 
1411 
1412 /*
1413  * var_put_string()
1414  * var_get_string()
1415  *
1416  * Convenience functions to store and retrieve a string variable.  It is
1417  * an error for the variable not to exist or to have the wrong type.
1418  */
var_put_string(sc_var_setref_t vars,const sc_char * name,const sc_char * string)1419 void var_put_string(sc_var_setref_t vars, const sc_char *name, const sc_char *string) {
1420 	sc_vartype_t vt_value;
1421 	assert(var_is_valid(vars));
1422 
1423 	vt_value.string = string;
1424 	var_put(vars, name, VAR_STRING, vt_value);
1425 }
1426 
var_get_string(sc_var_setref_t vars,const sc_char * name)1427 const sc_char *var_get_string(sc_var_setref_t vars, const sc_char *name) {
1428 	sc_vartype_t vt_rvalue;
1429 	sc_int type;
1430 	assert(var_is_valid(vars));
1431 
1432 	if (!var_get(vars, name, &type, &vt_rvalue))
1433 		sc_fatal("var_get_string: no such variable, %s\n", name);
1434 	else if (type != VAR_STRING)
1435 		sc_fatal("var_get_string: not a string, %s\n", name);
1436 
1437 	return vt_rvalue.string;
1438 }
1439 
1440 
1441 /*
1442  * var_create()
1443  *
1444  * Create and return a new set of variables.  Variables are created from the
1445  * properties bundle passed in.
1446  */
var_create(sc_prop_setref_t bundle)1447 sc_var_setref_t var_create(sc_prop_setref_t bundle) {
1448 	sc_var_setref_t vars;
1449 	sc_int var_count, index_;
1450 	sc_vartype_t vt_key[3];
1451 	assert(bundle);
1452 
1453 	/* Create a clean set of variables to fill from the bundle. */
1454 	vars = var_create_empty();
1455 	vars->bundle = bundle;
1456 
1457 	/* Retrieve the count of variables. */
1458 	vt_key[0].string = "Variables";
1459 	var_count = prop_get_child_count(bundle, "I<-s", vt_key);
1460 
1461 	/* Create a variable for each variable property held. */
1462 	for (index_ = 0; index_ < var_count; index_++) {
1463 		const sc_char *name;
1464 		sc_int var_type;
1465 		const sc_char *value;
1466 
1467 		/* Retrieve variable name, type, and string initial value. */
1468 		vt_key[1].integer = index_;
1469 		vt_key[2].string = "Name";
1470 		name = prop_get_string(bundle, "S<-sis", vt_key);
1471 
1472 		vt_key[2].string = "Type";
1473 		var_type = prop_get_integer(bundle, "I<-sis", vt_key);
1474 
1475 		vt_key[2].string = "Value";
1476 		value = prop_get_string(bundle, "S<-sis", vt_key);
1477 
1478 		/* Handle numerics and strings differently. */
1479 		switch (var_type) {
1480 		case TAFVAR_NUMERIC: {
1481 			sc_int integer_value;
1482 			if (sscanf(value, "%ld", &integer_value) != 1) {
1483 				sc_error("var_create:"
1484 				         " invalid numeric variable %s, %s\n", name, value);
1485 				integer_value = 0;
1486 			}
1487 			var_put_integer(vars, name, integer_value);
1488 			break;
1489 		}
1490 
1491 		case TAFVAR_STRING:
1492 			var_put_string(vars, name, value);
1493 			break;
1494 
1495 		default:
1496 			sc_fatal("var_create: invalid variable type, %ld\n", var_type);
1497 		}
1498 	}
1499 
1500 	return vars;
1501 }
1502 
1503 
1504 /*
1505  * var_register_game()
1506  *
1507  * Register the game, used by variables to satisfy requests for selected
1508  * system variables.  To ensure integrity, the game being registered must
1509  * reference this variable set.
1510  */
var_register_game(sc_var_setref_t vars,sc_gameref_t game)1511 void var_register_game(sc_var_setref_t vars, sc_gameref_t game) {
1512 	assert(var_is_valid(vars));
1513 	assert(gs_is_game_valid(game));
1514 
1515 	if (vars != gs_get_vars(game))
1516 		sc_fatal("var_register_game: game binding error\n");
1517 
1518 	vars->game = game;
1519 }
1520 
1521 
1522 /*
1523  * var_set_ref_character()
1524  * var_set_ref_object()
1525  * var_set_ref_number()
1526  * var_set_ref_text()
1527  *
1528  * Set the "referenced" character, object, number, and text.
1529  */
var_set_ref_character(sc_var_setref_t vars,sc_int character)1530 void var_set_ref_character(sc_var_setref_t vars, sc_int character) {
1531 	assert(var_is_valid(vars));
1532 	vars->referenced_character = character;
1533 }
1534 
var_set_ref_object(sc_var_setref_t vars,sc_int object)1535 void var_set_ref_object(sc_var_setref_t vars, sc_int object) {
1536 	assert(var_is_valid(vars));
1537 	vars->referenced_object = object;
1538 }
1539 
var_set_ref_number(sc_var_setref_t vars,sc_int number)1540 void var_set_ref_number(sc_var_setref_t vars, sc_int number) {
1541 	assert(var_is_valid(vars));
1542 	vars->referenced_number = number;
1543 	vars->is_number_referenced = TRUE;
1544 }
1545 
var_set_ref_text(sc_var_setref_t vars,const sc_char * text)1546 void var_set_ref_text(sc_var_setref_t vars, const sc_char *text) {
1547 	assert(var_is_valid(vars));
1548 
1549 	/* Take a copy of the string, and retain it. */
1550 	vars->referenced_text = (sc_char *)sc_realloc(vars->referenced_text, strlen(text) + 1);
1551 	strcpy(vars->referenced_text, text);
1552 }
1553 
1554 
1555 /*
1556  * var_get_ref_character()
1557  * var_get_ref_object()
1558  * var_get_ref_number()
1559  * var_get_ref_text()
1560  *
1561  * Get the "referenced" character, object, number, and text.
1562  */
var_get_ref_character(sc_var_setref_t vars)1563 sc_int var_get_ref_character(sc_var_setref_t vars) {
1564 	assert(var_is_valid(vars));
1565 	return vars->referenced_character;
1566 }
1567 
var_get_ref_object(sc_var_setref_t vars)1568 sc_int var_get_ref_object(sc_var_setref_t vars) {
1569 	assert(var_is_valid(vars));
1570 	return vars->referenced_object;
1571 }
1572 
var_get_ref_number(sc_var_setref_t vars)1573 sc_int var_get_ref_number(sc_var_setref_t vars) {
1574 	assert(var_is_valid(vars));
1575 	return vars->referenced_number;
1576 }
1577 
var_get_ref_text(sc_var_setref_t vars)1578 const sc_char *var_get_ref_text(sc_var_setref_t vars) {
1579 	assert(var_is_valid(vars));
1580 
1581 	/*
1582 	 * If currently nullptr, return "".  A game may check restrictions involving
1583 	 * referenced text before any value has been set; returning "" here for
1584 	 * this case prevents problems later (strcmp (nullptr, ...), for example).
1585 	 */
1586 	return vars->referenced_text ? vars->referenced_text : "";
1587 }
1588 
1589 
1590 /*
1591  * var_get_elapsed_seconds()
1592  * var_set_elapsed_seconds()
1593  *
1594  * Get a count of seconds elapsed since the variables were created (start
1595  * of game), and set the count to a given value (game restore).
1596  */
var_get_elapsed_seconds(sc_var_setref_t vars)1597 sc_uint var_get_elapsed_seconds(sc_var_setref_t vars) {
1598 	double delta;
1599 	assert(var_is_valid(vars));
1600 
1601 	delta = vars->timestamp - g_vm->_events->getTotalPlayTicks();
1602 	return (sc_uint) delta + vars->time_offset;
1603 }
1604 
var_set_elapsed_seconds(sc_var_setref_t vars,sc_uint seconds)1605 void var_set_elapsed_seconds(sc_var_setref_t vars, sc_uint seconds) {
1606 	assert(var_is_valid(vars));
1607 
1608 	/*
1609 	 * Reset the timestamp to now, and store seconds in offset.  This is sort-of
1610 	 * forced by the fact that ANSI offers difftime but no 'settime' -- here,
1611 	 * we'd really want to set the timestamp to now less seconds.
1612 	 */
1613 	vars->timestamp = g_vm->_events->getTotalPlayTicks() / 1000;
1614 	vars->time_offset = seconds;
1615 }
1616 
1617 
1618 /*
1619  * var_debug_trace()
1620  *
1621  * Set variable tracing on/off.
1622  */
var_debug_trace(sc_bool flag)1623 void var_debug_trace(sc_bool flag) {
1624 	var_trace = flag;
1625 }
1626 
1627 
1628 /*
1629  * var_debug_dump()
1630  *
1631  * Print out a complete variables set.
1632  */
var_debug_dump(sc_var_setref_t vars)1633 void var_debug_dump(sc_var_setref_t vars) {
1634 	sc_int index_;
1635 	sc_varref_t var;
1636 	assert(var_is_valid(vars));
1637 
1638 	/* Dump complete structure. */
1639 	sc_trace("Variable: debug dump follows...\n");
1640 	sc_trace("vars->bundle = %p\n", (void *) vars->bundle);
1641 	sc_trace("vars->referenced_character = %ld\n", vars->referenced_character);
1642 	sc_trace("vars->referenced_object = %ld\n", vars->referenced_object);
1643 	sc_trace("vars->referenced_number = %ld\n", vars->referenced_number);
1644 	sc_trace("vars->is_number_referenced = %s\n",
1645 	         vars->is_number_referenced ? "true" : "false");
1646 
1647 	sc_trace("vars->referenced_text = ");
1648 	if (vars->referenced_text)
1649 		sc_trace("\"%s\"\n", vars->referenced_text);
1650 	else
1651 		sc_trace("(nil)\n");
1652 
1653 	sc_trace("vars->temporary = %p\n", (void *) vars->temporary);
1654 	sc_trace("vars->timestamp = %lu\n", (sc_uint) vars->timestamp);
1655 	sc_trace("vars->game = %p\n", (void *) vars->game);
1656 
1657 	sc_trace("vars->variables =\n");
1658 	for (index_ = 0; index_ < VAR_HASH_TABLE_SIZE; index_++) {
1659 		for (var = vars->variable[index_]; var; var = var->next) {
1660 			if (var == vars->variable[index_])
1661 				sc_trace("%3ld : ", index_);
1662 			else
1663 				sc_trace("    : ");
1664 			switch (var->type) {
1665 			case VAR_STRING:
1666 				sc_trace("[String ] %s = \"%s\"", var->name, var->value.string);
1667 				break;
1668 			case VAR_INTEGER:
1669 				sc_trace("[Integer] %s = %ld", var->name, var->value.integer);
1670 				break;
1671 
1672 			default:
1673 				sc_trace("[Invalid] %s = %p", var->name, var->value.voidp);
1674 				break;
1675 			}
1676 			sc_trace("\n");
1677 		}
1678 	}
1679 }
1680 
1681 } // End of namespace Adrift
1682 } // End of namespace Glk
1683