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