Files
scummvm-cursorfix/engines/glk/adrift/sctasks.cpp
2026-02-02 04:50:13 +01:00

1280 lines
35 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 {
/*
* Module notes:
*
* o Implements task return FALSE on no output, a slight extension of
* current jAsea behavior.
*/
/*
* Tasks can run other tasks, leading to the possibility of an infinite loop
* in the task calling sequence. It's a game error, and we'll apply a limit
* to the task recursion depth to try and catch it more controllably than
* waiting for memory exhaustion.
*/
enum { TASK_MAXIMUM_RECURSION = 128 };
/* Trace flag, set before running. */
static sc_bool task_trace = FALSE;
/*
* task_get_hint_common()
* task_get_hint_question()
* task_get_hint_subtle()
* task_get_hint_unsubtle()
* task_has_hints()
*
* Return the assorted hint text strings, and TRUE if the given task offers
* hints.
*/
static const sc_char *task_get_hint_common(sc_gameref_t game, sc_int task, const sc_char *hint) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
const sc_char *retval;
/* Look up and return the requested hint string. */
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = hint;
retval = prop_get_string(bundle, "S<-sis", vt_key);
return retval;
}
const sc_char *task_get_hint_question(sc_gameref_t game, sc_int task) {
return task_get_hint_common(game, task, "Question");
}
const sc_char *task_get_hint_subtle(sc_gameref_t game, sc_int task) {
return task_get_hint_common(game, task, "Hint1");
}
const sc_char *task_get_hint_unsubtle(sc_gameref_t game, sc_int task) {
return task_get_hint_common(game, task, "Hint2");
}
sc_bool task_has_hints(sc_gameref_t game, sc_int task) {
/* A non-empty question implies hints available. */
return !sc_strempty(task_get_hint_question(game, task));
}
/*
* task_can_run_task_directional()
*
* Return TRUE if player is in a room where the task can be run and the task
* is runnable in the given direction.
*/
sc_bool task_can_run_task_directional(sc_gameref_t game, sc_int task, sc_bool forwards) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[5];
sc_int type;
/* If already run, non-repeatable tasks are not re-runnable forwards. */
if (forwards && gs_task_done(game, task)) {
sc_bool repeatable;
const sc_char *repeattext;
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "Repeatable";
repeatable = prop_get_boolean(bundle, "B<-sis", vt_key);
if (!repeatable)
return FALSE;
vt_key[2].string = "RepeatText";
repeattext = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(repeattext))
return FALSE;
}
/* If checking for reverse, test the reversibility flag. */
if (!forwards) {
sc_bool reversible;
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "Reversible";
reversible = prop_get_boolean(bundle, "B<-sis", vt_key);
if (!reversible)
return FALSE;
}
/* Check room list for the task and return it. */
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "Where";
vt_key[3].string = "Type";
type = prop_get_integer(bundle, "I<-siss", vt_key);
switch (type) {
case ROOMLIST_NO_ROOMS:
return FALSE;
case ROOMLIST_ALL_ROOMS:
return TRUE;
case ROOMLIST_ONE_ROOM:
vt_key[3].string = "Room";
return prop_get_integer(bundle,
"I<-siss", vt_key) == gs_playerroom(game);
case ROOMLIST_SOME_ROOMS:
vt_key[3].string = "Rooms";
vt_key[4].integer = gs_playerroom(game);
return prop_get_boolean(bundle, "B<-sissi", vt_key);
default:
sc_fatal("task_can_run_task_directional: invalid type, %ld\n", type);
return FALSE;
}
}
/*
* task_can_run_task()
*
* Returns TRUE if the task can be run in either direction.
*/
sc_bool task_can_run_task(sc_gameref_t game, sc_int task) {
/*
* Testing reversible tasks first may be a little more efficient if they
* aren't common in games. There is, though, probably a little bit of
* redundant work going on here.
*/
return task_can_run_task_directional(game, task, FALSE)
|| task_can_run_task_directional(game, task, TRUE);
}
/*
* task_move_object()
*
* Move an object to a place.
*/
static void task_move_object(sc_gameref_t game, sc_int object, sc_int var2, sc_int var3) {
const sc_var_setref_t vars = gs_get_vars(game);
/* Select action depending on var2. */
switch (var2) {
case 0: /* To room */
if (var3 == 0) {
if (task_trace)
sc_trace("Task: moving object %ld to hidden\n", object);
gs_object_make_hidden(game, object);
} else {
if (task_trace) {
sc_trace("Task: moving object %ld to room %ld\n",
object, var3 - 1);
}
if (var3 == 0)
gs_object_player_get(game, object);
else
gs_object_to_room(game, object, var3 - 1);
}
break;
case 1: /* To roomgroup part */
if (task_trace) {
sc_trace("Task: moving object %ld to random room in group %ld\n",
object, var3);
}
gs_object_to_room(game, object,
lib_random_roomgroup_member(game, var3));
break;
case 2: /* Into object */
if (task_trace)
sc_trace("Task: moving object %ld into %ld\n", object, var3);
gs_object_move_into(game, object, obj_container_object(game, var3));
break;
case 3: /* Onto object */
if (task_trace)
sc_trace("Task: moving object %ld onto %ld\n", object, var3);
gs_object_move_onto(game, object, obj_surface_object(game, var3));
break;
case 4: /* Held by */
if (task_trace)
sc_trace("Task: moving object %ld to held by %ld\n", object, var3);
if (var3 == 0) /* Player */
gs_object_player_get(game, object);
else if (var3 == 1) /* Ref character */
gs_object_npc_get(game, object, var_get_ref_character(vars));
else /* NPC id */
gs_object_npc_get(game, object, var3 - 2);
break;
case 5: /* Worn by */
if (task_trace)
sc_trace("Task: moving object %ld to worn by %ld\n", object, var3);
if (var3 == 0) /* Player */
gs_object_player_wear(game, object);
else if (var3 == 1) /* Ref character */
gs_object_npc_wear(game, object, var_get_ref_character(vars));
else /* NPC id */
gs_object_npc_wear(game, object, var3 - 2);
break;
case 6: { /* Same room as */
sc_int room, npc;
if (task_trace) {
sc_trace("Task: moving object %ld to same room as %ld\n",
object, var3);
}
if (var3 == 0) /* Player */
room = gs_playerroom(game);
else if (var3 == 1) { /* Ref character */
npc = var_get_ref_character(vars);
room = gs_npc_location(game, npc) - 1;
} else { /* NPC id */
npc = var3 - 2;
room = gs_npc_location(game, npc) - 1;
}
gs_object_to_room(game, object, room);
break;
}
default:
sc_fatal("task_move_object: unknown move type, %ld\n", var2);
break;
}
}
/*
* task_run_move_object_action()
*
* Demultiplex an object move action and execute it.
*/
static void task_run_move_object_action(sc_gameref_t game, sc_int var1, sc_int var2, sc_int var3) {
const sc_var_setref_t vars = gs_get_vars(game);
sc_int object;
/* Select depending on value in var1. */
switch (var1) {
case 0: /* All held */
for (object = 0; object < gs_object_count(game); object++) {
if (gs_object_position(game, object) == OBJ_HELD_PLAYER)
task_move_object(game, object, var2, var3);
}
break;
case 1: /* All worn */
for (object = 0; object < gs_object_count(game); object++) {
if (gs_object_position(game, object) == OBJ_WORN_PLAYER)
task_move_object(game, object, var2, var3);
}
break;
case 2: /* Ref object */
object = var_get_ref_object(vars);
task_move_object(game, object, var2, var3);
break;
default: /* Dynamic object */
object = obj_dynamic_object(game, var1 - 3);
task_move_object(game, object, var2, var3);
break;
}
}
/*
* task_move_npc_to_room()
*
* Move an NPC to a given room.
*/
static void task_move_npc_to_room(sc_gameref_t game, sc_int npc, sc_int room) {
if (task_trace)
sc_trace("Task: moving NPC %ld to room %ld\n", npc, room);
/* Update the NPC's state. */
if (room < gs_room_count(game))
gs_set_npc_location(game, npc, room + 1);
else
gs_set_npc_location(game, npc,
lib_random_roomgroup_member(game,
room - gs_room_count(game)) + 1);
gs_set_npc_parent(game, npc, -1);
gs_set_npc_position(game, npc, 0);
}
/*
* task_run_move_npc_action()
*
* Move player or NPC.
*/
static void task_run_move_npc_action(sc_gameref_t game, sc_int var1, sc_int var2, sc_int var3) {
const sc_var_setref_t vars = gs_get_vars(game);
sc_int npc, room, ref_npc = -1;
/* Player or NPC? */
if (var1 == 0) {
/* Player -- decide where to move player to. */
switch (var2) {
case 0: /* To room */
gs_move_player_to_room(game, var3);
return;
case 1: /* To roomgroup part */
if (task_trace) {
sc_trace("Task: moving player to random room in group %ld\n",
var3);
}
gs_move_player_to_room(game,
lib_random_roomgroup_member(game, var3));
return;
case 2: /* To same room as... */
switch (var3) {
case 0: /* ...player! */
return;
case 1: /* ...referenced NPC */
npc = var_get_ref_character(vars);
break;
default: /* ...specified NPC */
npc = var3 - 2;
break;
}
if (task_trace)
sc_trace("Task: moving player to same room as NPC %ld\n", npc);
room = gs_npc_location(game, npc) - 1;
if (room < 0) {
if (task_trace)
sc_trace("Task: silently suppressed player move to hidden\n");
} else
gs_move_player_to_room(game, room);
return;
case 3: /* To standing on */
gs_set_playerposition(game, 0);
gs_set_playerparent(game, obj_standable_object(game, var3 - 1));
return;
case 4: /* To sitting on */
gs_set_playerposition(game, 1);
gs_set_playerparent(game, obj_standable_object(game, var3 - 1));
return;
case 5: /* To lying on */
gs_set_playerposition(game, 2);
gs_set_playerparent(game, obj_lieable_object(game, var3 - 1));
return;
default:
sc_fatal("task_run_move_npc_action:"
" unknown player move type, %ld\n", var2);
return;
}
} else {
/* NPC -- first find which NPC to move about. */
if (var1 == 1)
npc = var_get_ref_character(vars);
else
npc = var1 - 2;
/* Decide where to move the NPC to. */
switch (var2) {
case 0: /* To room */
task_move_npc_to_room(game, npc, var3 - 1);
return;
case 1: /* To roomgroup part */
if (task_trace) {
sc_trace("Task: moving NPC %ld to random room in group %ld\n",
npc, var3);
}
task_move_npc_to_room(game, npc,
lib_random_roomgroup_member(game, var3));
return;
case 2: /* To same room as... */
switch (var3) {
case 0: /* ...player */
if (task_trace) {
sc_trace("Task: moving NPC %ld to same room as player\n",
npc);
}
task_move_npc_to_room(game, npc, gs_playerroom(game));
break;
case 1: /* ...referenced NPC */
ref_npc = var_get_ref_character(vars);
if (task_trace) {
sc_trace("Task: moving NPC %ld to"
" same room as referenced NPC %ld\n", npc, ref_npc);
}
room = gs_npc_location(game, ref_npc) - 1;
task_move_npc_to_room(game, npc, room);
break;
default: /* ...specified NPC */
ref_npc = var3 - 2;
if (task_trace) {
sc_trace("Task: moving NPC %ld to"
" same room as NPC %ld\n", npc, ref_npc);
}
room = gs_npc_location(game, ref_npc) - 1;
task_move_npc_to_room(game, npc, room);
break;
}
return;
case 3: /* To standing on */
gs_set_npc_position(game, npc, 0);
gs_set_npc_parent(game, npc, obj_standable_object(game, var3));
return;
case 4: /* To sitting on */
gs_set_npc_position(game, npc, 1);
gs_set_npc_parent(game, npc, obj_standable_object(game, var3));
return;
case 5: /* To lying on */
gs_set_npc_position(game, npc, 2);
gs_set_npc_parent(game, npc, obj_lieable_object(game, var3));
return;
default:
sc_fatal("task_run_move_npc_action:"
" unknown NPC move type, %ld\n", var2);
return;
}
}
}
/*
* task_run_change_object_status()
*
* Change the status of an object.
*/
static void task_run_change_object_status(sc_gameref_t game, sc_int var1, sc_int var2) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int object, openable, lockable;
if (task_trace) {
sc_trace("Task: setting status of stateful object %ld to %ld\n",
var1, var2);
}
/* Identify the target object. */
object = obj_stateful_object(game, var1);
/* See if openable. */
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) {
/* See if lockable. */
vt_key[2].string = "Key";
lockable = prop_get_integer(bundle, "I<-sis", vt_key);
if (lockable >= 0) {
/* Lockable. */
if (var2 <= 2)
gs_set_object_openness(game, object, var2 + 5);
else
gs_set_object_state(game, object, var2 - 2);
} else {
/* Not lockable, though openable. */
if (var2 <= 1)
gs_set_object_openness(game, object, var2 + 5);
else
gs_set_object_state(game, object, var2 - 1);
}
} else
/* Not openable. */
gs_set_object_state(game, object, var2 + 1);
if (task_trace) {
sc_trace("Task: openness of object %ld is now %ld\n",
object, gs_object_openness(game, object));
sc_trace("Task: state of object %ld is now %ld\n",
object, gs_object_state(game, object));
}
}
/*
* task_run_change_variable_action()
*
* Change a variable's value in inscrutable ways.
*/
static void task_run_change_variable_action(sc_gameref_t game,
sc_int var1, sc_int var2, sc_int var3, const sc_char *expr, sc_int var5) {
const sc_filterref_t filter = gs_get_filter(game);
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];
const sc_char *name, *string;
sc_char *mutable_string;
sc_int type, value;
/*
* At this point, we need to checkpoint the filter. We're about to change
* a variable value, so interpolating here before doing that ensures that
* any currently buffered text gets the values that were set when the text
* was buffered.
*/
pf_checkpoint(filter, vars, bundle);
/* Get the name and type of the variable being addressed. */
vt_key[0].string = "Variables";
vt_key[1].integer = var1;
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: /* Integer */
/* Select again based on action type. */
switch (var2) {
case 0: /* Var = */
if (task_trace)
sc_trace("Task: variable %ld (%s) = %ld\n", var1, name, var3);
var_put_integer(vars, name, var3);
return;
case 1: /* Var += */
if (task_trace)
sc_trace("Task: variable %ld (%s) += %ld\n", var1, name, var3);
value = var_get_integer(vars, name) + var3;
var_put_integer(vars, name, value);
return;
case 2: /* Var = rnd(range) */
if (task_trace) {
sc_trace("Task: variable %ld (%s) = random(%ld,%ld)\n",
var1, name, var3, var5);
}
value = sc_randomint(var3, var5);
var_put_integer(vars, name, value);
return;
case 3: /* Var += rnd(range) */
if (task_trace) {
sc_trace("Task: variable %ld (%s) += random(%ld,%ld)\n",
var1, name, var3, var5);
}
value = var_get_integer(vars, name) + sc_randomint(var3, var5);
var_put_integer(vars, name, value);
return;
case 4: /* Var = ref */
value = var_get_ref_number(vars);
if (task_trace) {
sc_trace("Task: variable %ld (%s) = ref, %ld\n",
var1, name, value);
}
var_put_integer(vars, name, value);
return;
case 5: /* Var = expr */
if (!expr_eval_numeric_expression(expr, vars, &value)) {
sc_error("task_run_change_variable_action:"
" invalid expression, %s\n", expr);
value = 0;
}
if (task_trace) {
sc_trace("Task: variable %ld (%s) = %s, %ld\n",
var1, name, expr, value);
}
var_put_integer(vars, name, value);
return;
default:
sc_fatal("task_run_change_variable_action:"
" unknown integer change type, %ld\n", var2);
break;
}
break;
case TAFVAR_STRING: /* String */
/* Select again based on action type. */
switch (var2) {
case 0: /* Var = text literal */
if (task_trace) {
sc_trace("Task: variable %ld (%s) = \"%s\"\n",
var1, name, expr);
}
var_put_string(vars, name, expr);
return;
case 1: /* Var = ref */
string = var_get_ref_text(vars);
if (task_trace) {
sc_trace("Task: variable %ld (%s) = ref, \"%s\"\n",
var1, name, string);
}
var_put_string(vars, name, string);
return;
case 2: /* Var = expr */
if (!expr_eval_string_expression(expr, vars, &mutable_string)) {
sc_error("task_run_change_variable_action:"
" invalid string expression, %s\n", expr);
size_t ln = strlen("[expr error]") + 1;
mutable_string = (sc_char *)sc_malloc(ln);
Common::strcpy_s(mutable_string, ln, "[expr error]");
}
if (task_trace) {
sc_trace("Task: variable %ld (%s) = %s, %s\n",
var1, name, expr, mutable_string);
}
var_put_string(vars, name, mutable_string);
sc_free(mutable_string);
return;
default:
sc_fatal("task_run_change_variable_action:"
" unknown string change type, %ld\n", var2);
break;
}
break;
default:
sc_fatal("task_run_change_variable_action:"
" invalid variable type, %ld\n", type);
break;
}
}
/*
* task_run_change_score_action()
*
* Change game score.
*/
static void task_run_change_score_action(sc_gameref_t game, sc_int task, sc_int var1) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
/* Increasing or decreasing the score? */
if (var1 > 0) {
sc_bool increase_score;
/* See if this task is already scored. */
increase_score = !gs_task_scored(game, task);
if (!increase_score) {
sc_vartype_t vt_key[3];
sc_int version;
if (task_trace)
sc_trace("Task: already scored task %ld\n", var1);
/* Version 3.8 games permit tasks to rescore. */
vt_key[0].string = "Version";
version = prop_get_integer(bundle, "I<-s", vt_key);
if (version == TAF_VERSION_380) {
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "SingleScore";
increase_score = !prop_get_boolean(bundle, "B<-sis", vt_key);
if (increase_score) {
if (task_trace)
sc_trace("Task: rescoring version 3.8 task anyway\n");
}
}
}
/*
* Increase the score if not yet scored or a version 3.8 multiple
* scoring task, and note as a scored task.
*/
if (increase_score) {
if (task_trace)
sc_trace("Task: increased score by %ld\n", var1);
game->score += var1;
gs_set_task_scored(game, task, TRUE);
}
} else if (var1 < 0) {
/* Decrease the score. */
if (task_trace)
sc_trace("Task: decreased score by %ld\n", -(var1));
game->score += var1;
}
}
/*
* task_run_set_task_action()
*
* Redirect to another task.
*/
static sc_bool task_run_set_task_action(sc_gameref_t game, sc_int var1, sc_int var2) {
sc_bool status = FALSE;
/* Select based on var1. */
if (var1 == 0) {
/* Redirect forwards. */
if (task_can_run_task_directional(game, var2, TRUE)) {
if (task_trace)
sc_trace("Task: redirecting to task %ld\n", var2);
status = task_run_task(game, var2, TRUE);
} else {
if (task_trace)
sc_trace("Task: can't redirect to task %ld\n", var2);
}
} else {
/* Undo task. */
gs_set_task_done(game, var2, FALSE);
if (task_trace)
sc_trace("Task: reversing task %ld\n", var2);
}
return status;
}
/*
* task_run_end_game_action()
*
* End of game task action.
*/
static sc_bool task_run_end_game_action(sc_gameref_t game, sc_int var1) {
const sc_filterref_t filter = gs_get_filter(game);
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_bool status = FALSE;
/* Print a message based on var1. */
switch (var1) {
case 0: {
sc_vartype_t vt_key[2];
const sc_char *wintext;
/* Get game WinText. */
vt_key[0].string = "Header";
vt_key[1].string = "WinText";
wintext = prop_get_string(bundle, "S<-ss", vt_key);
/* Print WinText, if any defined, otherwise a default. */
if (!sc_strempty(wintext)) {
pf_buffer_string(filter, wintext);
pf_buffer_character(filter, '\n');
} else
pf_buffer_string(filter, "Congratulations!\n");
/* Handle any associated WinRes resource. */
vt_key[0].string = "Globals";
vt_key[1].string = "WinRes";
res_handle_resource(game, "ss", vt_key);
status = TRUE;
break;
}
case 1:
pf_buffer_string(filter, "Better luck next time.\n");
status = TRUE;
break;
case 2:
pf_buffer_string(filter, "I'm afraid you are dead!\n");
status = TRUE;
break;
case 3:
break;
default:
sc_fatal("task_run_end_game_action: invalid type, %ld\n", var1);
break;
}
/* Stop the game, and note that it's not resumeable. */
game->is_running = FALSE;
game->has_completed = TRUE;
return status;
}
/*
* task_run_task_action()
*
* Demultiplexer for task actions.
*/
static sc_bool task_run_task_action(sc_gameref_t game, sc_int task, sc_int action) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[5];
sc_int type, var1, var2, var3, var5;
const sc_char *expr;
sc_bool status = FALSE;
/* Get the task action type. */
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "Actions";
vt_key[3].integer = action;
vt_key[4].string = "Type";
type = prop_get_integer(bundle, "I<-sisis", vt_key);
/* Demultiplex depending on type. */
switch (type) {
case 0: /* Move object. */
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);
task_run_move_object_action(game, var1, var2, var3);
break;
case 1: /* Move player/NPC. */
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);
task_run_move_npc_action(game, var1, var2, var3);
break;
case 2: /* Change object status. */
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);
task_run_change_object_status(game, var1, var2);
break;
case 3: /* Change 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 = "Expr";
expr = prop_get_string(bundle, "S<-sisis", vt_key);
vt_key[4].string = "Var5";
var5 = prop_get_integer(bundle, "I<-sisis", vt_key);
task_run_change_variable_action(game, var1, var2, var3, expr, var5);
break;
case 4: /* Change score. */
vt_key[4].string = "Var1";
var1 = prop_get_integer(bundle, "I<-sisis", vt_key);
task_run_change_score_action(game, task, var1);
break;
case 5: /* Execute/unset task. */
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);
status = task_run_set_task_action(game, var1, var2);
break;
case 6: /* End game. */
vt_key[4].string = "Var1";
var1 = prop_get_integer(bundle, "I<-sisis", vt_key);
status = task_run_end_game_action(game, var1);
break;
case 7: /* Battle options, ignored for now... */
break;
default:
sc_fatal("task_run_task_action: unknown action type %ld\n", type);
break;
}
return status;
}
/*
* task_run_task_actions()
*
* Run every task action associated with the task. If any action ends the
* game, return immediately. Returns TRUE if any action ran and itself
* returned TRUE.
*/
static sc_bool task_run_task_actions(sc_gameref_t game, sc_int task) {
const sc_filterref_t filter = gs_get_filter(game);
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int action_count, action;
sc_bool status, muted;
/* Get the count of task actions. */
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "Actions";
action_count = prop_get_child_count(bundle, "I<-sis", vt_key);
if (action_count > 0) {
if (task_trace) {
sc_trace("Task: task %ld running %ld action%s\n",
task, action_count, action_count == 1 ? "" : "s");
}
}
/*
* Run all task actions, capturing any TRUE status returned. If any task
* ends the game, run the remaining tasks silently.
*
* This seems a little counterintuitive; a more conventional thing would be
* to just exit the actions loop early. However, Adrift appears to plough
* on, and there may be an action that changes the score in here somewhere,
* so we'll do the same.
*/
status = FALSE;
muted = FALSE;
for (action = 0; action < action_count; action++) {
sc_bool was_running;
was_running = game->is_running;
status |= task_run_task_action(game, task, action);
/* Did this action end the game? */
if (was_running && !game->is_running) {
if (task_trace) {
sc_trace("Task: task %ld action %ld ended game\n",
task, action);
}
/* Mute the filter, and note that we did it, but continue. */
pf_mute(filter);
muted = TRUE;
}
}
/* If this stack frame muted the filter, un-mute it now. */
if (muted)
pf_clear_mute(filter);
/* Return TRUE if any task action returned TRUE. */
return status;
}
/*
* task_start_npc_walks()
*
* Start NPC walks based on alerts.
*/
static void task_start_npc_walks(sc_gameref_t game, sc_int task) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[4];
sc_int alert_count, alert;
/* Get a count of NPC walk alerts. */
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "NPCWalkAlert";
alert_count = prop_get_child_count(bundle, "I<-sis", vt_key);
/* Check alerts, and start any walks that need starting. */
for (alert = 0; alert < alert_count; alert += 2) {
sc_int npc, walk;
vt_key[3].integer = alert;
npc = prop_get_integer(bundle, "I<-sisi", vt_key);
vt_key[3].integer = alert + 1;
walk = prop_get_integer(bundle, "I<-sisi", vt_key);
npc_start_npc_walk(game, npc, walk);
}
}
/*
* task_run_task_unrestricted()
*
* Run a task, providing restrictions permit, in the given direction. Return
* TRUE if the task ran, or we handled it in some complete way, for example by
* outputting a message describing what prevented it, or why it couldn't be
* done.
*/
static sc_bool task_run_task_unrestricted(sc_gameref_t game, sc_int task, sc_bool forwards) {
const sc_filterref_t filter = gs_get_filter(game);
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
const sc_char *completetext, *additionalmessage;
sc_int action_count, showroomdesc;
sc_bool status;
/* Start considering task output tracking. */
status = FALSE;
/*
* If reversing, print any reverse message for the task, and undo the task,
* then return.
*/
if (!forwards) {
const sc_char *reversemessage;
/* If not yet done, we can hardly reverse it. */
if (gs_task_done(game, task)) {
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "ReverseMessage";
reversemessage = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(reversemessage)) {
pf_buffer_string(filter, reversemessage);
pf_buffer_character(filter, '\n');
status |= TRUE;
}
/* Undo the task. */
gs_set_task_done(game, task, FALSE);
}
/* Return status of undo. */
return status;
}
/* See if we are trying to repeat a task that's not repeatable. */
if (gs_task_done(game, task)) {
sc_bool repeatable;
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "Repeatable";
repeatable = prop_get_boolean(bundle, "B<-sis", vt_key);
if (!repeatable) {
const sc_char *repeattext;
vt_key[2].string = "RepeatText";
repeattext = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(repeattext)) {
if (task_trace) {
sc_trace("Task:"
" trying to repeat completed action, aborting\n");
}
pf_buffer_string(filter, repeattext);
pf_buffer_character(filter, '\n');
status |= TRUE;
return status;
}
/*
* Task done, yet not repeatable, so don't consider this case
* handled.
*/
return status;
}
}
/* Mark the task as done. */
gs_set_task_done(game, task, TRUE);
/* Print any task completion text. */
vt_key[0].string = "Tasks";
vt_key[1].integer = task;
vt_key[2].string = "CompleteText";
completetext = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(completetext)) {
pf_buffer_string(filter, completetext);
pf_buffer_character(filter, '\n');
status |= TRUE;
}
/* Handle any task completion resource. */
vt_key[2].string = "Res";
res_handle_resource(game, "sis", vt_key);
/*
* Things get slightly tricky here. We need to filter the completion text
* for the task using any final variable values generated or modified by
* task actions, but other task text, run by actions, according to the
* variable value in effect when it runs.
*
* To do this, we take a local copy of the filter's current buffer at this
* point, remove it from the filter, run task actions with checkpointing,
* then prepend it back into the filter after all the actions are done.
*
* As an optimization, we can avoid doing this if there are no task actions.
*/
vt_key[2].string = "Actions";
action_count = prop_get_child_count(bundle, "I<-sis", vt_key);
if (action_count > 0) {
sc_char *buffer;
/*
* Take ownership of the current filter buffer text, then start NPC
* walks based on alerts, and run any and all task actions. Note that
* the buffer transferred out of the filter may be NULL if there is no
* text currently in the filter.
*/
buffer = pf_transfer_buffer(filter);
task_start_npc_walks(game, task);
status |= task_run_task_actions(game, task);
/* Prepend the saved buffer data back onto the front of the filter. */
if (buffer) {
pf_prepend_string(filter, buffer);
sc_free(buffer);
}
} else {
/* Start NPC walks only; there are no task actions. */
task_start_npc_walks(game, task);
}
/* Append any room description and additional message for the task. */
vt_key[2].string = "ShowRoomDesc";
showroomdesc = prop_get_integer(bundle, "I<-sis", vt_key);
if (showroomdesc != 0) {
lib_print_room_name(game, showroomdesc - 1);
lib_print_room_description(game, showroomdesc - 1);
status |= TRUE;
}
vt_key[2].string = "AdditionalMessage";
additionalmessage = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(additionalmessage)) {
pf_buffer_string(filter, additionalmessage);
pf_buffer_character(filter, '\n');
status |= TRUE;
}
/* Return status -- TRUE if matched and we output something. */
return status;
}
/*
* task_run_task()
*
* Run a task, providing restrictions permit, in the given direction. At the
* same time, check for signs of an infinite loop in game tasks, and fail the
* task with an error message if we seem to be in one. Checked by counting
* the call depth.
*/
sc_bool task_run_task(sc_gameref_t game, sc_int task, sc_bool forwards) {
static sc_int recursion_depth = 0;
const sc_filterref_t filter = gs_get_filter(game);
const sc_char *fail_message;
sc_bool restrictions_passed, status;
if (task_trace) {
sc_trace("Task: running task %ld %s, depth %ld\n",
task, forwards ? "forwards" : "backwards", recursion_depth);
}
/* Check restrictions. */
if (!restr_eval_task_restrictions(game, task,
&restrictions_passed, &fail_message)) {
sc_error("task_run_task: restrictions error, %ld\n", task);
return FALSE;
}
if (!restrictions_passed) {
if (task_trace) {
sc_trace("Task: restrictions failed, task %s\n",
fail_message ? "failed" : "aborted");
}
if (fail_message) {
/*
* Print a message, and return TRUE since we can consider this task
* "done" (more accurately, we've output text, so the task command
* searching in the main run loop can exit...).
*/
pf_buffer_string(filter, fail_message);
pf_buffer_character(filter, '\n');
return TRUE;
}
/* Task not done; look for more possibilities. */
return FALSE;
}
/* Check for infinite recursion. */
if (recursion_depth > TASK_MAXIMUM_RECURSION) {
sc_error("task_run_task: maximum recursion depth exceeded --"
" game task loop?\n");
return FALSE;
}
/* Increment depth, run the task, then decrement depth. */
recursion_depth++;
status = task_run_task_unrestricted(game, task, forwards);
recursion_depth--;
if (task_trace) {
sc_trace("Task: task %ld finished, return %s, depth %ld\n",
task, status ? "true" : "false", recursion_depth);
}
/* Return the task's status. */
return status;
}
/*
* task_debug_trace()
*
* Set task tracing on/off.
*/
void task_debug_trace(sc_bool flag) {
task_trace = flag;
}
} // End of namespace Adrift
} // End of namespace Glk