aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/adrift/screstrs.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/glk/adrift/screstrs.cpp')
-rw-r--r--engines/glk/adrift/screstrs.cpp1163
1 files changed, 1163 insertions, 0 deletions
diff --git a/engines/glk/adrift/screstrs.cpp b/engines/glk/adrift/screstrs.cpp
new file mode 100644
index 0000000000..549ae5a50d
--- /dev/null
+++ b/engines/glk/adrift/screstrs.cpp
@@ -0,0 +1,1163 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/adrift/scare.h"
+#include "glk/adrift/scprotos.h"
+#include "glk/adrift/scgamest.h"
+
+namespace Glk {
+namespace Adrift {
+
+/* Assorted definitions and constants. */
+enum { MAX_NESTING_DEPTH = 32 };
+static const sc_char NUL = '\0';
+
+/* Trace flag, set before running. */
+static sc_bool restr_trace = FALSE;
+
+
+/*
+ * restr_integer_variable()
+ *
+ * Return the index of the n'th integer found.
+ */
+static sc_int
+restr_integer_variable (sc_gameref_t game, sc_int n)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ sc_vartype_t vt_key[3];
+ sc_int var_count, var, count;
+
+ /* Get the count of variables. */
+ vt_key[0].string = "Variables";
+ var_count = prop_get_child_count (bundle, "I<-s", vt_key);
+
+ /* Progress through variables until n integers found. */
+ count = n;
+ for (var = 0; var < var_count && count >= 0; var++)
+ {
+ sc_int type;
+
+ vt_key[1].integer = var;
+ vt_key[2].string = "Type";
+ type = prop_get_integer (bundle, "I<-sis", vt_key);
+ if (type == TAFVAR_NUMERIC)
+ count--;
+ }
+ return var - 1;
+}
+
+
+/*
+ * restr_object_in_place()
+ *
+ * Is object in a certain place, state, or condition.
+ */
+static sc_bool
+restr_object_in_place (sc_gameref_t game,
+ sc_int object, sc_int var2, sc_int var3)
+{
+ const sc_var_setref_t vars = gs_get_vars (game);
+ sc_int npc;
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr: checking"
+ " object in place, %ld, %ld, %ld\n", object, var2, var3);
+ }
+
+ /* Var2 controls what we do. */
+ switch (var2)
+ {
+ case 0:
+ case 6: /* In room */
+ if (var3 == 0)
+ return gs_object_position (game, object) == OBJ_HIDDEN;
+ else
+ return gs_object_position (game, object) == var3;
+
+ case 1:
+ case 7: /* Held by */
+ if (var3 == 0) /* Player */
+ return gs_object_position (game, object) == OBJ_HELD_PLAYER;
+ else if (var3 == 1) /* Ref character */
+ npc = var_get_ref_character (vars);
+ else
+ npc = var3 - 2;
+
+ return gs_object_position (game, object) == OBJ_HELD_NPC
+ && gs_object_parent (game, object) == npc;
+
+ case 2:
+ case 8: /* Worn by */
+ if (var3 == 0) /* Player */
+ return gs_object_position (game, object) == OBJ_WORN_PLAYER;
+ else if (var3 == 1) /* Ref character */
+ npc = var_get_ref_character (vars);
+ else
+ npc = var3 - 2;
+
+ return gs_object_position (game, object) == OBJ_WORN_NPC
+ && gs_object_parent (game, object) == npc;
+
+ case 3:
+ case 9: /* Visible to */
+ if (var3 == 0) /* Player */
+ return obj_indirectly_in_room (game,
+ object, gs_playerroom (game));
+ else if (var3 == 1) /* Ref character */
+ npc = var_get_ref_character (vars);
+ else
+ npc = var3 - 2;
+
+ return obj_indirectly_in_room (game, object,
+ gs_npc_location (game, npc) - 1);
+
+ case 4:
+ case 10: /* Inside */
+ if (var3 == 0) /* Nothing? */
+ return gs_object_position (game, object) != OBJ_IN_OBJECT;
+
+ return gs_object_position (game, object) == OBJ_IN_OBJECT
+ && gs_object_parent (game, object) == obj_container_object (game,
+ var3 - 1);
+
+ case 5:
+ case 11: /* On top of */
+ if (var3 == 0) /* Nothing? */
+ return gs_object_position (game, object) != OBJ_ON_OBJECT;
+
+ return gs_object_position (game, object) == OBJ_ON_OBJECT
+ && gs_object_parent (game, object) == obj_surface_object (game,
+ var3 - 1);
+
+ default:
+ sc_fatal ("restr_object_in_place: bad var2, %ld\n", var2);
+ return FALSE;
+ }
+}
+
+
+/*
+ * restr_pass_task_object_location()
+ *
+ * Evaluate restrictions relating to object location.
+ */
+static sc_bool
+restr_pass_task_object_location (sc_gameref_t game,
+ sc_int var1, sc_int var2, sc_int var3)
+{
+ const sc_var_setref_t vars = gs_get_vars (game);
+ sc_bool should_be;
+ sc_int object;
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr: running object"
+ " location restriction, %ld, %ld, %ld\n", var1, var2, var3);
+ }
+
+ /* Initialize variables to avoid gcc warnings. */
+ should_be = FALSE;
+ object = -1;
+
+ /* See how things should look. */
+ if (var2 >= 0 && var2 < 6)
+ should_be = TRUE;
+ else if (var2 >= 6 && var2 < 12)
+ should_be = FALSE;
+ else
+ sc_fatal ("restr_pass_task_object_location: bad var2, %ld\n", var2);
+
+ /* Now find the addressed object. */
+ if (var1 == 0)
+ {
+ object = -1; /* No object */
+ should_be = !should_be;
+ }
+ else if (var1 == 1)
+ object = -1; /* Any object */
+ else if (var1 == 2)
+ object = var_get_ref_object (vars);
+ else if (var1 >= 3)
+ object = obj_dynamic_object (game, var1 - 3);
+ else
+ sc_fatal ("restr_pass_task_object_location: bad var1, %ld\n", var1);
+
+ /*
+ * Here it seems that we have to special case static objects that may have
+ * crept in through the referenced object. The object in place function
+ * isn't built to handle these.
+ *
+ * TODO What is the meaning of applying object restrictions to static
+ * objects?
+ */
+ if (var1 == 2 && object != -1 && obj_is_static (game, object))
+ {
+ if (restr_trace)
+ {
+ sc_trace ("Restr:"
+ " restriction object %ld is static, rejecting\n", object);
+ }
+
+ return FALSE;
+ }
+
+ /* Try to put it all together. */
+ if (object == -1)
+ {
+ sc_int target;
+
+ for (target = 0; target < gs_object_count (game); target++)
+ {
+ if (restr_object_in_place (game, target, var2, var3))
+ return should_be;
+ }
+ return !should_be;
+ }
+ return should_be == restr_object_in_place (game, object, var2, var3);
+}
+
+
+/*
+ * restr_pass_task_object_state()
+ *
+ * Evaluate restrictions relating to object states. This function is called
+ * from the library by lib_pass_alt_room(), so cannot be static.
+ */
+sc_bool
+restr_pass_task_object_state (sc_gameref_t game, sc_int var1, sc_int var2)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ const sc_var_setref_t vars = gs_get_vars (game);
+ sc_vartype_t vt_key[3];
+ sc_int object, openable, key;
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr:"
+ " running object state restriction, %ld, %ld\n", var1, var2);
+ }
+
+ /* Find the object being addressed. */
+ if (var1 == 0)
+ object = var_get_ref_object (vars);
+ else
+ object = obj_stateful_object (game, var1 - 1);
+
+ /* We're interested only in openable objects. */
+ vt_key[0].string = "Objects";
+ vt_key[1].integer = object;
+ vt_key[2].string = "Openable";
+ openable = prop_get_integer (bundle, "I<-sis", vt_key);
+ if (openable > 0)
+ {
+ /* Is this object lockable? */
+ vt_key[2].string = "Key";
+ key = prop_get_integer (bundle, "I<-sis", vt_key);
+ if (key >= 0)
+ {
+ if (var2 <= 2)
+ return gs_object_openness (game, object) == var2 + 5;
+ else
+ return gs_object_state (game, object) == var2 - 2;
+ }
+ else
+ {
+ if (var2 <= 1)
+ return gs_object_openness (game, object) == var2 + 5;
+ else
+ return gs_object_state (game, object) == var2 - 1;
+ }
+ }
+ else
+ return gs_object_state (game, object) == var2 + 1;
+}
+
+
+/*
+ * restr_pass_task_task_state()
+ *
+ * Evaluate restrictions relating to task states.
+ */
+static sc_bool
+restr_pass_task_task_state (sc_gameref_t game, sc_int var1, sc_int var2)
+{
+ sc_bool should_be;
+
+ if (restr_trace)
+ sc_trace ("Restr: running task restriction, %ld, %ld\n", var1, var2);
+
+ /* Initialize variables to avoid gcc warnings. */
+ should_be = FALSE;
+
+ /* See if the task should be done or not done. */
+ if (var2 == 0)
+ should_be = TRUE;
+ else if (var2 == 1)
+ should_be = FALSE;
+ else
+ sc_fatal ("restr_pass_task_task_state: bad var2, %ld\n", var2);
+
+ /* Check all tasks? */
+ if (var1 == 0)
+ {
+ sc_int task;
+
+ for (task = 0; task < gs_task_count (game); task++)
+ {
+ if (gs_task_done (game, task) == should_be)
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /* Check just the given task. */
+ return gs_task_done (game, var1 - 1) == should_be;
+}
+
+
+/*
+ * restr_pass_task_char()
+ *
+ * Evaluate restrictions relating to player and NPCs.
+ */
+static sc_bool
+restr_pass_task_char (sc_gameref_t game, sc_int var1, sc_int var2, sc_int var3)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ const sc_var_setref_t vars = gs_get_vars (game);
+ sc_int npc1, npc2;
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr:"
+ " running char restriction, %ld, %ld, %ld\n", var1, var2, var3);
+ }
+
+ /* Handle var2 types 1 and 2. */
+ if (var2 == 1) /* Not in same room as */
+ return !restr_pass_task_char (game, var1, 0, var3);
+ else if (var2 == 2) /* Alone */
+ return !restr_pass_task_char (game, var1, 3, var3);
+
+ /* Decode NPC number, -1 if none. */
+ npc1 = npc2 = -1;
+ if (var1 == 1)
+ npc1 = var_get_ref_character (vars);
+ else if (var1 > 1)
+ npc1 = var1 - 2;
+
+ /* Player or NPC? */
+ if (var1 == 0)
+ {
+ sc_vartype_t vt_key[2];
+ sc_int gender;
+
+ /* Player -- decode based on var2. */
+ switch (var2)
+ {
+ case 0: /* In same room as */
+ if (var3 == 1)
+ npc2 = var_get_ref_character (vars);
+ else if (var3 > 1)
+ npc2 = var3 - 2;
+ if (var3 == 0) /* Player */
+ return TRUE;
+ else
+ return npc_in_room (game, npc2, gs_playerroom (game));
+
+ case 3: /* Not alone */
+ return npc_count_in_room (game, gs_playerroom (game)) > 1;
+
+ case 4: /* Standing on */
+ return gs_playerposition (game) == 0
+ && gs_playerparent (game) == obj_standable_object (game,
+ var3 - 1);
+
+ case 5: /* Sitting on */
+ return gs_playerposition (game) == 1
+ && gs_playerparent (game) == obj_standable_object (game,
+ var3 - 1);
+
+ case 6: /* Lying on */
+ return gs_playerposition (game) == 2
+ && gs_playerparent (game) == obj_lieable_object (game,
+ var3 - 1);
+
+ case 7: /* Player gender */
+ vt_key[0].string = "Globals";
+ vt_key[1].string = "PlayerGender";
+ gender = prop_get_integer (bundle, "I<-ss", vt_key);
+ return gender == var3;
+
+ default:
+ sc_fatal ("restr_pass_task_char: invalid type, %ld\n", var2);
+ return FALSE;
+ }
+ }
+ else
+ {
+ sc_vartype_t vt_key[3];
+ sc_int gender;
+
+ /* NPC -- decode based on var2. */
+ switch (var2)
+ {
+ case 0: /* In same room as */
+ if (var3 == 0)
+ return npc_in_room (game, npc1, gs_playerroom (game));
+ if (var3 == 1)
+ npc2 = var_get_ref_character (vars);
+ else if (var3 > 1)
+ npc2 = var3 - 2;
+ return npc_in_room (game, npc1, gs_npc_location (game, npc2) - 1);
+
+ case 3: /* Not alone */
+ return npc_count_in_room (game, gs_npc_location (game, npc1) - 1) > 1;
+
+ case 4: /* Standing on */
+ return gs_npc_position (game, npc1) == 0
+ && gs_playerparent (game) == obj_standable_object (game, var3);
+
+ case 5: /* Sitting on */
+ return gs_npc_position (game, npc1) == 1
+ && gs_playerparent (game) == obj_standable_object (game, var3);
+
+ case 6: /* Lying on */
+ return gs_npc_position (game, npc1) == 2
+ && gs_playerparent (game) == obj_lieable_object (game, var3);
+
+ case 7: /* NPC gender */
+ vt_key[0].string = "NPCs";
+ vt_key[1].integer = npc1;
+ vt_key[2].string = "Gender";
+ gender = prop_get_integer (bundle, "I<-sis", vt_key);
+ return gender == var3;
+
+ default:
+ sc_fatal ("restr_pass_task_char: invalid type, %ld\n", var2);
+ return FALSE;
+ }
+ }
+}
+
+
+/*
+ * restr_pass_task_int_var()
+ *
+ * Helper for restr_pass_task_var(), handles integer variable restrictions.
+ */
+static sc_bool
+restr_pass_task_int_var (sc_gameref_t game,
+ sc_int var2, sc_int var3, sc_int value)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ const sc_var_setref_t vars = gs_get_vars (game);
+ sc_vartype_t vt_key[3];
+ sc_int value2;
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr: running"
+ " integer var restriction, %ld, %ld, %ld\n", var2, var3, value);
+ }
+
+ /* Compare against var3 if that's what var2 says. */
+ switch (var2)
+ {
+ case 0:
+ return value < var3;
+ case 1:
+ return value <= var3;
+ case 2:
+ return value == var3;
+ case 3:
+ return value >= var3;
+ case 4:
+ return value > var3;
+ case 5:
+ return value != var3;
+
+ default:
+ /*
+ * Compare against the integer var numbered in var3 - 1, or the
+ * referenced number if var3 is zero. Make sure that we're comparing
+ * integer variables.
+ */
+ if (var3 == 0)
+ value2 = var_get_ref_number (vars);
+ else
+ {
+ const sc_char *name;
+ sc_int ivar, type;
+
+ ivar = restr_integer_variable (game, var3 - 1);
+ vt_key[0].string = "Variables";
+ vt_key[1].integer = ivar;
+ vt_key[2].string = "Name";
+ name = prop_get_string (bundle, "S<-sis", vt_key);
+ vt_key[2].string = "Type";
+ type = prop_get_integer (bundle, "I<-sis", vt_key);
+
+ if (type != TAFVAR_NUMERIC)
+ {
+ sc_fatal ("restr_pass_task_int_var:"
+ " non-integer in comparison, %s\n", name);
+ }
+
+ /* Get the value in variable numbered in var3 - 1. */
+ value2 = var_get_integer (vars, name);
+ }
+
+ switch (var2)
+ {
+ case 10:
+ return value < value2;
+ case 11:
+ return value <= value2;
+ case 12:
+ return value == value2;
+ case 13:
+ return value >= value2;
+ case 14:
+ return value > value2;
+ case 15:
+ return value != value2;
+
+ default:
+ sc_fatal ("restr_pass_task_int_var:"
+ " unknown int comparison, %ld\n", var2);
+ return FALSE;
+ }
+ }
+}
+
+
+/*
+ * restr_pass_task_string_var()
+ *
+ * Helper for restr_pass_task_var(), handles string variable restrictions.
+ */
+static sc_bool
+restr_pass_task_string_var (sc_int var2,
+ const sc_char *var4, const sc_char *value)
+{
+ if (restr_trace)
+ {
+ sc_trace ("Restr: running string"
+ " var restriction, %ld, \"%s\", \"%s\"\n", var2, var4, value);
+ }
+
+ /* Make comparison against var4 based on var2 value. */
+ switch (var2)
+ {
+ case 0:
+ return strcmp (value, var4) == 0; /* == */
+ case 1:
+ return strcmp (value, var4) != 0; /* != */
+
+ default:
+ sc_fatal ("restr_pass_task_string_var:"
+ " unknown string comparison, %ld\n", var2);
+ return FALSE;
+ }
+}
+
+
+/*
+ * restr_pass_task_var()
+ *
+ * Evaluate restrictions relating to variables.
+ */
+static sc_bool
+restr_pass_task_var (sc_gameref_t game,
+ sc_int var1, sc_int var2, sc_int var3,
+ const sc_char *var4)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ const sc_var_setref_t vars = gs_get_vars (game);
+ sc_vartype_t vt_key[3];
+ sc_int type, value;
+ const sc_char *name, *string;
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr: running var restriction,"
+ " %ld, %ld, %ld, \"%s\"\n", var1, var2, var3, var4);
+ }
+
+ /*
+ * For var1=0, compare against referenced number. For var1=1, compare
+ * against referenced text.
+ */
+ if (var1 == 0)
+ {
+ value = var_get_ref_number (vars);
+ return restr_pass_task_int_var (game, var2, var3, value);
+ }
+ else if (var1 == 1)
+ {
+ string = var_get_ref_text (vars);
+ return restr_pass_task_string_var (var2, var4, string);
+ }
+
+ /* Get the name and type of the variable being addressed. */
+ vt_key[0].string = "Variables";
+ vt_key[1].integer = var1 - 2;
+ vt_key[2].string = "Name";
+ name = prop_get_string (bundle, "S<-sis", vt_key);
+ vt_key[2].string = "Type";
+ type = prop_get_integer (bundle, "I<-sis", vt_key);
+
+ /* Select first based on variable type. */
+ switch (type)
+ {
+ case TAFVAR_NUMERIC:
+ value = var_get_integer (vars, name);
+ return restr_pass_task_int_var (game, var2, var3, value);
+
+ case TAFVAR_STRING:
+ string = var_get_string (vars, name);
+ return restr_pass_task_string_var (var2, var4, string);
+
+ default:
+ sc_fatal ("restr_pass_task_var: invalid variable type, %ld\n", type);
+ return FALSE;
+ }
+}
+
+
+/*
+ * restr_pass_task_restriction()
+ *
+ * Demultiplexer for task restrictions.
+ */
+static sc_bool
+restr_pass_task_restriction (sc_gameref_t game, sc_int task, sc_int restriction)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ sc_vartype_t vt_key[5];
+ sc_int type, var1, var2, var3;
+ const sc_char *var4;
+ sc_bool result = FALSE;
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr:"
+ " evaluating task %ld restriction %ld\n", task, restriction);
+ }
+
+ /* Get the task restriction type. */
+ vt_key[0].string = "Tasks";
+ vt_key[1].integer = task;
+ vt_key[2].string = "Restrictions";
+ vt_key[3].integer = restriction;
+ vt_key[4].string = "Type";
+ type = prop_get_integer (bundle, "I<-sisis", vt_key);
+
+ /* Demultiplex depending on type. */
+ switch (type)
+ {
+ case 0: /* Object location. */
+ vt_key[4].string = "Var1";
+ var1 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var2";
+ var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var3";
+ var3 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ result = restr_pass_task_object_location (game, var1, var2, var3);
+ break;
+
+ case 1: /* Object state. */
+ vt_key[4].string = "Var1";
+ var1 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var2";
+ var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ result = restr_pass_task_object_state (game, var1, var2);
+ break;
+
+ case 2: /* Task state. */
+ vt_key[4].string = "Var1";
+ var1 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var2";
+ var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ result = restr_pass_task_task_state (game, var1, var2);
+ break;
+
+ case 3: /* Player and NPCs. */
+ vt_key[4].string = "Var1";
+ var1 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var2";
+ var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var3";
+ var3 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ result = restr_pass_task_char (game, var1, var2, var3);
+ break;
+
+ case 4: /* Variable. */
+ vt_key[4].string = "Var1";
+ var1 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var2";
+ var2 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var3";
+ var3 = prop_get_integer (bundle, "I<-sisis", vt_key);
+ vt_key[4].string = "Var4";
+ var4 = prop_get_string (bundle, "S<-sisis", vt_key);
+ result = restr_pass_task_var (game, var1, var2, var3, var4);
+ break;
+
+ default:
+ sc_fatal ("restr_pass_task_restriction:"
+ " unknown restriction type %ld\n", type);
+ }
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr: task %ld restriction"
+ " %ld is %s\n", task, restriction, result ? "PASS" : "FAIL");
+ }
+
+ return result;
+}
+
+
+/* Enumeration of restrictions combination string tokens. */
+enum
+{ TOK_RESTRICTION = '#',
+ TOK_AND = 'A',
+ TOK_OR = 'O',
+ TOK_LPAREN = '(',
+ TOK_RPAREN = ')',
+ TOK_EOS = '\0'
+};
+
+/* #O#A(#O#)-style expression, for tokenizing. */
+static const sc_char *restr_expression = NULL;
+static sc_int restr_index = 0;
+
+/*
+ * restr_tokenize_start()
+ * restr_tokenize_end()
+ *
+ * Start and wrap up restrictions combinations string tokenization.
+ */
+static void
+restr_tokenize_start (const sc_char *expression)
+{
+ /* Save expression, and restart index. */
+ restr_expression = expression;
+ restr_index = 0;
+}
+
+static void
+restr_tokenize_end (void)
+{
+ restr_expression = NULL;
+ restr_index = 0;
+}
+
+
+/*
+ * restr_next_token()
+ *
+ * Simple tokenizer for restrictions combination expressions.
+ */
+static sc_char
+restr_next_token (void)
+{
+ assert (restr_expression);
+
+ /* Find the next non-space, and return it. */
+ while (TRUE)
+ {
+ /* Return NUL if at string end. */
+ if (restr_expression[restr_index] == NUL)
+ return restr_expression[restr_index];
+
+ /* Spin on whitespace. */
+ restr_index++;
+ if (sc_isspace (restr_expression[restr_index - 1]))
+ continue;
+
+ /* Return the character just passed. */
+ return restr_expression[restr_index - 1];
+ }
+}
+
+
+/* Evaluation values stack. */
+static sc_bool restr_eval_values[MAX_NESTING_DEPTH];
+static sc_int restr_eval_stack = 0;
+
+/*
+ * The restriction number to evaluate. This advances with each call to
+ * evaluate and stack a restriction result.
+ */
+static sc_int restr_eval_restriction = 0;
+
+/* The current game used to evaluate restrictions, and the task in question. */
+static sc_gameref_t restr_eval_game = NULL;
+static sc_int restr_eval_task = 0;
+
+/* The id of the lowest-indexed failing restriction. */
+static sc_int restr_lowest_fail = -1;
+
+/*
+ * restr_eval_start()
+ *
+ * Reset the evaluation stack to an empty state, and note the things we have
+ * to note for when we need to evaluate a restriction.
+ */
+static void
+restr_eval_start (sc_gameref_t game, sc_int task)
+{
+ /* Clear stack. */
+ restr_eval_stack = 0;
+ restr_eval_restriction = 0;
+
+ /* Note evaluation details. */
+ restr_eval_game = game;
+ restr_eval_task = task;
+
+ /* Clear lowest indexed failing restriction. */
+ restr_lowest_fail = -1;
+}
+
+
+/*
+ * restr_eval_push()
+ *
+ * Push a value onto the values stack.
+ */
+static void
+restr_eval_push (sc_bool value)
+{
+ if (restr_eval_stack >= MAX_NESTING_DEPTH)
+ sc_fatal ("restr_eval_push: stack overflow\n");
+
+ restr_eval_values[restr_eval_stack++] = value;
+}
+
+
+/*
+ * expr_restr_action()
+ *
+ * Evaluate the effect of an and/or into the values stack.
+ */
+static void
+restr_eval_action (sc_char token)
+{
+ /* Select action based on parsed token. */
+ switch (token)
+ {
+ /* Handle evaluating and pushing a restriction result. */
+ case TOK_RESTRICTION:
+ {
+ sc_bool result;
+
+ /* Evaluate and push the next restriction. */
+ result = restr_pass_task_restriction (restr_eval_game,
+ restr_eval_task,
+ restr_eval_restriction);
+ restr_eval_push (result);
+
+ /*
+ * If the restriction failed, and there isn't yet a first failing one
+ * set, note this one as the first to fail.
+ */
+ if (restr_lowest_fail == -1 && !result)
+ restr_lowest_fail = restr_eval_restriction;
+
+ /* Increment restriction sequence identifier. */
+ restr_eval_restriction++;
+ break;
+ }
+
+ /* Handle cases of or-ing/and-ing restrictions. */
+ case TOK_OR:
+ case TOK_AND:
+ {
+ sc_bool val1, val2, result = FALSE;
+ assert (restr_eval_stack >= 2);
+
+ /* Get the top two stack values. */
+ val1 = restr_eval_values[restr_eval_stack - 2];
+ val2 = restr_eval_values[restr_eval_stack - 1];
+
+ /* Or, or and, into result. */
+ switch (token)
+ {
+ case TOK_OR:
+ result = val1 || val2;
+ break;
+ case TOK_AND:
+ result = val1 && val2;
+ break;
+
+ default:
+ sc_fatal ("restr_eval_action: bad token, '%c'\n", token);
+ }
+
+ /* Put result back at top of stack. */
+ restr_eval_stack--;
+ restr_eval_values[restr_eval_stack - 1] = result;
+ break;
+ }
+
+ default:
+ sc_fatal ("restr_eval_action: bad token, '%c'\n", token);
+ }
+}
+
+
+/*
+ * restr_eval_result()
+ *
+ * Return the top of the values stack as the evaluation result.
+ */
+static sc_int
+restr_eval_result (sc_int *lowest_fail)
+{
+ if (restr_eval_stack != 1)
+ sc_fatal ("restr_eval_result: values stack not completed\n");
+
+ *lowest_fail = restr_lowest_fail;
+ return restr_eval_values[0];
+}
+
+
+/* Parse error jump buffer. */
+static jmp_buf restr_parse_error;
+
+/* Single lookahead token for parser. */
+static sc_char restr_lookahead = '\0';
+
+/*
+ * restr_match()
+ *
+ * Match a token with an expectation.
+ */
+static void
+restr_match (sc_char c)
+{
+ if (restr_lookahead == c)
+ restr_lookahead = restr_next_token ();
+ else
+ {
+ sc_error ("restr_match:"
+ " syntax error, expected %d, got %d\n", c, restr_lookahead);
+ longjmp (restr_parse_error, 1);
+ }
+}
+
+
+/* Forward declaration for recursion. */
+static void restr_bexpr (void);
+
+/*
+ * restr_andexpr()
+ * restr_orexpr()
+ * restr_bexpr()
+ *
+ * Expression parsers. Here we go again...
+ */
+static void
+restr_andexpr (void)
+{
+ restr_bexpr ();
+ while (restr_lookahead == TOK_AND)
+ {
+ restr_match (TOK_AND);
+ restr_bexpr ();
+ restr_eval_action (TOK_AND);
+ }
+}
+
+static void
+restr_orexpr (void)
+{
+ restr_andexpr ();
+ while (restr_lookahead == TOK_OR)
+ {
+ restr_match (TOK_OR);
+ restr_andexpr ();
+ restr_eval_action (TOK_OR);
+ }
+}
+
+static void
+restr_bexpr (void)
+{
+ switch (restr_lookahead)
+ {
+ case TOK_RESTRICTION:
+ restr_match (TOK_RESTRICTION);
+ restr_eval_action (TOK_RESTRICTION);
+ break;
+
+ case TOK_LPAREN:
+ restr_match (TOK_LPAREN);
+ restr_orexpr ();
+ restr_match (TOK_RPAREN);
+ break;
+
+ default:
+ sc_error ("restr_bexpr: syntax error, unexpected %d\n", restr_lookahead);
+ longjmp (restr_parse_error, 1);
+ }
+}
+
+
+/*
+ * restr_get_fail_message()
+ *
+ * Get the FailMessage for the given task restriction; NULL if none.
+ */
+static const sc_char *
+restr_get_fail_message (sc_gameref_t game, sc_int task, sc_int restriction)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ sc_vartype_t vt_key[5];
+ const sc_char *message;
+
+ /* Get the restriction message. */
+ vt_key[0].string = "Tasks";
+ vt_key[1].integer = task;
+ vt_key[2].string = "Restrictions";
+ vt_key[3].integer = restriction;
+ vt_key[4].string = "FailMessage";
+ message = prop_get_string (bundle, "S<-sisis", vt_key);
+
+ /* Return it, or NULL if empty. */
+ return !sc_strempty (message) ? message : NULL;
+}
+
+
+/*
+ * restr_debug_trace()
+ *
+ * Set restrictions tracing on/off.
+ */
+void
+restr_debug_trace (sc_bool flag)
+{
+ restr_trace = flag;
+}
+
+
+/*
+ * restr_eval_task_restrictions()
+ *
+ * Main handler for a given set of task restrictions. Returns TRUE in pass
+ * if the restrictions pass, FALSE if not. On FALSE pass returns, it also
+ * returns a fail message string from the restriction deemed to have caused
+ * the failure (that is, the first one with a FailMessage property), or NULL
+ * if no failing restriction has a FailMessage. The function's main return
+ * value is TRUE if restrictions parsed successfully, FALSE otherwise.
+ */
+sc_bool
+restr_eval_task_restrictions (sc_gameref_t game,
+ sc_int task, sc_bool *pass,
+ const sc_char **fail_message)
+{
+ const sc_prop_setref_t bundle = gs_get_bundle (game);
+ sc_vartype_t vt_key[3];
+ sc_int restr_count, lowest_fail;
+ const sc_char *pattern;
+ sc_bool result;
+ assert (pass && fail_message);
+
+ /* Get the count of restrictions on the task. */
+ vt_key[0].string = "Tasks";
+ vt_key[1].integer = task;
+ vt_key[2].string = "Restrictions";
+ restr_count = prop_get_child_count (bundle, "I<-sis", vt_key);
+
+ /* If none, stop now, acting as if all passed. */
+ if (restr_count == 0)
+ {
+ if (restr_trace)
+ sc_trace ("Restr: task %ld has no restrictions\n", task);
+
+ *pass = TRUE;
+ *fail_message = NULL;
+ return TRUE;
+ }
+
+ /* Get the task's restriction combination pattern. */
+ vt_key[2].string = "RestrMask";
+ pattern = prop_get_string (bundle, "S<-sis", vt_key);
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr: task %ld"
+ " has %ld restrictions, %s\n", task, restr_count, pattern);
+ }
+
+ /* Set up the evaluation stack and tokenizer. */
+ restr_eval_start (game, task);
+ restr_tokenize_start (pattern);
+
+ /* Try parsing the pattern, and catch errors. */
+ if (setjmp (restr_parse_error) == 0)
+ {
+ /* Parse the pattern, and ensure it ends at string end. */
+ restr_lookahead = restr_next_token ();
+ restr_orexpr ();
+ restr_match (TOK_EOS);
+ }
+ else
+ {
+ /* Parse error -- clean up tokenizer and return fail. */
+ restr_tokenize_end ();
+ return FALSE;
+ }
+
+ /* Clean up tokenizer and get the evaluation result. */
+ restr_tokenize_end ();
+ result = restr_eval_result (&lowest_fail);
+
+ if (restr_trace)
+ {
+ sc_trace ("Restr: task %ld"
+ " restrictions %s\n", task, result ? "PASS" : "FAIL");
+ }
+
+ /*
+ * Return the result, and if a restriction fails, then return the
+ * FailMessage of the lowest indexed failing restriction (or NULL if this
+ * restriction has no FailMessage).
+ *
+ * Then return TRUE since parsing and running the restrictions succeeded
+ * (even if the restrictions themselves didn't).
+ */
+ *pass = result;
+ if (result)
+ *fail_message = NULL;
+ else
+ *fail_message = restr_get_fail_message (game, task, lowest_fail);
+ return TRUE;
+}
+
+} // End of namespace Adrift
+} // End of namespace Glk