1051 lines
28 KiB
C++
1051 lines
28 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "glk/adrift/scare.h"
|
|
#include "glk/adrift/scprotos.h"
|
|
#include "glk/adrift/scgamest.h"
|
|
|
|
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 = nullptr;
|
|
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 = nullptr;
|
|
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 = nullptr;
|
|
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];
|
|
}
|
|
|
|
|
|
/* Single lookahead token for parser. */
|
|
static sc_char restr_lookahead = '\0';
|
|
|
|
/*
|
|
* restr_match()
|
|
*
|
|
* Match a token with an expectation.
|
|
*/
|
|
static void restr_match(CONTEXT, 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);
|
|
LONG_JUMP;
|
|
}
|
|
}
|
|
|
|
|
|
/* Forward declaration for recursion. */
|
|
static void restr_bexpr(CONTEXT);
|
|
|
|
/*
|
|
* restr_andexpr()
|
|
* restr_orexpr()
|
|
* restr_bexpr()
|
|
*
|
|
* Expression parsers. Here we go again...
|
|
*/
|
|
static void restr_andexpr(CONTEXT) {
|
|
CALL0(restr_bexpr);
|
|
while (restr_lookahead == TOK_AND) {
|
|
CALL1(restr_match, TOK_AND);
|
|
CALL0(restr_bexpr);
|
|
restr_eval_action(TOK_AND);
|
|
}
|
|
}
|
|
|
|
static void restr_orexpr(CONTEXT) {
|
|
CALL0(restr_andexpr);
|
|
while (restr_lookahead == TOK_OR) {
|
|
CALL1(restr_match, TOK_OR);
|
|
CALL0(restr_andexpr);
|
|
restr_eval_action(TOK_OR);
|
|
}
|
|
}
|
|
|
|
static void restr_bexpr(CONTEXT) {
|
|
switch (restr_lookahead) {
|
|
case TOK_RESTRICTION:
|
|
CALL1(restr_match, TOK_RESTRICTION);
|
|
restr_eval_action(TOK_RESTRICTION);
|
|
break;
|
|
|
|
case TOK_LPAREN:
|
|
CALL1(restr_match, TOK_LPAREN);
|
|
CALL0(restr_orexpr);
|
|
CALL1(restr_match, TOK_RPAREN);
|
|
break;
|
|
|
|
default:
|
|
sc_error("restr_bexpr: syntax error, unexpected %d\n", restr_lookahead);
|
|
LONG_JUMP;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 : nullptr;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
Context context;
|
|
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 = nullptr;
|
|
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);
|
|
|
|
// Parse the pattern, and ensure it ends at string end
|
|
restr_lookahead = restr_next_token();
|
|
restr_orexpr(context);
|
|
if (!context._break)
|
|
restr_match(context, TOK_EOS);
|
|
|
|
if (context._break) {
|
|
// 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 = nullptr;
|
|
else
|
|
*fail_message = restr_get_fail_message(game, task, lowest_fail);
|
|
return TRUE;
|
|
}
|
|
|
|
} // End of namespace Adrift
|
|
} // End of namespace Glk
|