772 lines
21 KiB
C++
772 lines
21 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 Event pause and resume tasks need more testing.
|
|
*/
|
|
|
|
/* Trace flag, set before running. */
|
|
static sc_bool evt_trace = FALSE;
|
|
|
|
|
|
/*
|
|
* evt_any_task_in_state()
|
|
*
|
|
* Return TRUE if any task at all matches the given completion state.
|
|
*/
|
|
static sc_bool evt_any_task_in_state(sc_gameref_t game, sc_bool state) {
|
|
sc_int task;
|
|
|
|
/* Scan tasks for any whose completion matches input. */
|
|
for (task = 0; task < gs_task_count(game); task++) {
|
|
if (gs_task_done(game, task) == state)
|
|
return TRUE;
|
|
}
|
|
|
|
/* No tasks matched. */
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_can_see_event()
|
|
*
|
|
* Return TRUE if player is in the right room for event text.
|
|
*/
|
|
sc_bool evt_can_see_event(sc_gameref_t game, sc_int event) {
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[5];
|
|
sc_int type;
|
|
|
|
/* Check room list for the event and return it. */
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
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("evt_can_see_event: invalid type, %ld\n", type);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_move_object()
|
|
*
|
|
* Move an object from within an event.
|
|
*/
|
|
static void evt_move_object(sc_gameref_t game, sc_int object, sc_int destination) {
|
|
/* Ignore negative values of object. */
|
|
if (object >= 0) {
|
|
if (evt_trace) {
|
|
sc_trace("Event: moving object %ld to room %ld\n",
|
|
object, destination);
|
|
}
|
|
|
|
/* Move object depending on destination. */
|
|
switch (destination) {
|
|
case -1: /* Hidden. */
|
|
gs_object_make_hidden(game, object);
|
|
break;
|
|
|
|
case 0: /* Held by player. */
|
|
gs_object_player_get(game, object);
|
|
break;
|
|
|
|
case 1: /* Same room as player. */
|
|
gs_object_to_room(game, object, gs_playerroom(game));
|
|
break;
|
|
|
|
default:
|
|
if (destination < gs_room_count(game) + 2)
|
|
gs_object_to_room(game, object, destination - 2);
|
|
else {
|
|
sc_int roomgroup, room;
|
|
|
|
roomgroup = destination - gs_room_count(game) - 2;
|
|
room = lib_random_roomgroup_member(game, roomgroup);
|
|
gs_object_to_room(game, object, room);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If static, mark as no longer unmoved.
|
|
*
|
|
* TODO Is this the only place static objects can be moved? And just
|
|
* how static is a static object if it's moveable, anyway?
|
|
*/
|
|
if (obj_is_static(game, object))
|
|
gs_set_object_static_unmoved(game, object, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_fixup_v390_v380_immediate_restart()
|
|
*
|
|
* Versions 3.9 and 3.8 differ from version 4.0 on immediate restart; they
|
|
* "miss" the event start actions and move one step into the event without
|
|
* comment. It's arguable if this is a feature or a bug; nevertheless, we
|
|
* can do the same thing here, though it's ugly.
|
|
*/
|
|
static sc_bool evt_fixup_v390_v380_immediate_restart(sc_gameref_t game, sc_int event) {
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[3];
|
|
sc_int version;
|
|
|
|
vt_key[0].string = "Version";
|
|
version = prop_get_integer(bundle, "I<-s", vt_key);
|
|
if (version < TAF_VERSION_400) {
|
|
sc_int time1, time2;
|
|
|
|
if (evt_trace)
|
|
sc_trace("Event: applying 3.9/3.8 restart fixup\n");
|
|
|
|
/* Set to running state. */
|
|
gs_set_event_state(game, event, ES_RUNNING);
|
|
|
|
/* Set up event time to be one less than a proper start. */
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
vt_key[2].string = "Time1";
|
|
time1 = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
vt_key[2].string = "Time2";
|
|
time2 = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
gs_set_event_time(game, event, sc_randomint(time1, time2) - 1);
|
|
}
|
|
|
|
/* Return TRUE if we applied the fixup. */
|
|
return version < TAF_VERSION_400;
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_start_event()
|
|
*
|
|
* Change an event from WAITING to RUNNING.
|
|
*/
|
|
static void evt_start_event(sc_gameref_t game, sc_int event) {
|
|
const sc_filterref_t filter = gs_get_filter(game);
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[4];
|
|
sc_int time1, time2, obj1, obj1dest;
|
|
|
|
if (evt_trace)
|
|
sc_trace("Event: starting event %ld\n", event);
|
|
|
|
/* If event is visible, print its start text. */
|
|
if (evt_can_see_event(game, event)) {
|
|
const sc_char *starttext;
|
|
|
|
/* Get and print start text. */
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
vt_key[2].string = "StartText";
|
|
starttext = prop_get_string(bundle, "S<-sis", vt_key);
|
|
if (!sc_strempty(starttext)) {
|
|
pf_buffer_string(filter, starttext);
|
|
pf_buffer_character(filter, '\n');
|
|
}
|
|
|
|
/* Handle any associated resource. */
|
|
vt_key[2].string = "Res";
|
|
vt_key[3].integer = 0;
|
|
res_handle_resource(game, "sisi", vt_key);
|
|
}
|
|
|
|
/* Move event object to destination. */
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
vt_key[2].string = "Obj1";
|
|
obj1 = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
|
|
vt_key[2].string = "Obj1Dest";
|
|
obj1dest = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
|
|
evt_move_object(game, obj1, obj1dest);
|
|
|
|
/* Set the event's state and time. */
|
|
gs_set_event_state(game, event, ES_RUNNING);
|
|
|
|
vt_key[2].string = "Time1";
|
|
time1 = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
vt_key[2].string = "Time2";
|
|
time2 = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
gs_set_event_time(game, event, sc_randomint(time1, time2));
|
|
|
|
if (evt_trace)
|
|
sc_trace("Event: start event handling done, %ld\n", event);
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_get_starter_type()
|
|
*
|
|
* Return the starter type for an event.
|
|
*/
|
|
static sc_int evt_get_starter_type(sc_gameref_t game, sc_int event) {
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[3];
|
|
sc_int startertype;
|
|
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
vt_key[2].string = "StarterType";
|
|
startertype = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
|
|
return startertype;
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_finish_event()
|
|
*
|
|
* Move an event to FINISHED, or restart it.
|
|
*/
|
|
static void evt_finish_event(sc_gameref_t game, sc_int event) {
|
|
const sc_filterref_t filter = gs_get_filter(game);
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[4];
|
|
sc_int obj2, obj2dest, obj3, obj3dest;
|
|
sc_int task, startertype, restarttype;
|
|
sc_bool taskdir;
|
|
|
|
if (evt_trace)
|
|
sc_trace("Event: finishing event %ld\n", event);
|
|
|
|
/* Set up invariant parts of the key. */
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
|
|
/* If event is visible, print its finish text. */
|
|
if (evt_can_see_event(game, event)) {
|
|
const sc_char *finishtext;
|
|
|
|
/* Get and print finish text. */
|
|
vt_key[2].string = "FinishText";
|
|
finishtext = prop_get_string(bundle, "S<-sis", vt_key);
|
|
if (!sc_strempty(finishtext)) {
|
|
pf_buffer_string(filter, finishtext);
|
|
pf_buffer_character(filter, '\n');
|
|
}
|
|
|
|
/* Handle any associated resource. */
|
|
vt_key[2].string = "Res";
|
|
vt_key[3].integer = 4;
|
|
res_handle_resource(game, "sisi", vt_key);
|
|
}
|
|
|
|
/* Move event objects to destination. */
|
|
vt_key[2].string = "Obj2";
|
|
obj2 = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
|
|
vt_key[2].string = "Obj2Dest";
|
|
obj2dest = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
|
|
evt_move_object(game, obj2, obj2dest);
|
|
|
|
vt_key[2].string = "Obj3";
|
|
obj3 = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
|
|
vt_key[2].string = "Obj3Dest";
|
|
obj3dest = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
|
|
evt_move_object(game, obj3, obj3dest);
|
|
|
|
/* See if there is an affected task. */
|
|
vt_key[2].string = "TaskAffected";
|
|
task = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
|
|
if (task >= 0) {
|
|
vt_key[2].string = "TaskFinished";
|
|
taskdir = !prop_get_boolean(bundle, "B<-sis", vt_key);
|
|
if (task_can_run_task_directional(game, task, taskdir)) {
|
|
if (evt_trace) {
|
|
sc_trace("Event: event running task %ld, %s\n",
|
|
task, taskdir ? "forwards" : "backwards");
|
|
}
|
|
|
|
task_run_task(game, task, taskdir);
|
|
} else {
|
|
if (evt_trace)
|
|
sc_trace("Event: event can't run task %ld\n", task);
|
|
}
|
|
}
|
|
|
|
/* Handle possible restart. */
|
|
vt_key[2].string = "RestartType";
|
|
restarttype = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
switch (restarttype) {
|
|
case 0: /* Don't restart. */
|
|
startertype = evt_get_starter_type(game, event);
|
|
switch (startertype) {
|
|
case 1: /* Immediate. */
|
|
case 2: /* Random delay. */
|
|
case 3: /* After task. */
|
|
gs_set_event_state(game, event, ES_FINISHED);
|
|
gs_set_event_time(game, event, 0);
|
|
break;
|
|
|
|
default:
|
|
sc_fatal("evt_finish_event:"
|
|
" unknown value for starter type, %ld\n", startertype);
|
|
}
|
|
break;
|
|
|
|
case 1: /* Restart immediately. */
|
|
if (evt_fixup_v390_v380_immediate_restart(game, event))
|
|
break;
|
|
else
|
|
evt_start_event(game, event);
|
|
break;
|
|
|
|
case 2: /* Restart after delay. */
|
|
startertype = evt_get_starter_type(game, event);
|
|
switch (startertype) {
|
|
case 1: /* Immediate. */
|
|
if (evt_fixup_v390_v380_immediate_restart(game, event))
|
|
break;
|
|
else
|
|
evt_start_event(game, event);
|
|
break;
|
|
|
|
case 2: { /* Random delay. */
|
|
sc_int start, end;
|
|
|
|
gs_set_event_state(game, event, ES_WAITING);
|
|
vt_key[2].string = "StartTime";
|
|
start = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
vt_key[2].string = "EndTime";
|
|
end = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
gs_set_event_time(game, event, sc_randomint(start, end));
|
|
break;
|
|
}
|
|
|
|
case 3: /* After task. */
|
|
gs_set_event_state(game, event, ES_AWAITING);
|
|
gs_set_event_time(game, event, 0);
|
|
break;
|
|
|
|
default:
|
|
sc_fatal("evt_finish_event: unknown StarterType\n");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
sc_fatal("evt_finish_event: unknown RestartType\n");
|
|
}
|
|
|
|
if (evt_trace)
|
|
sc_trace("Event: finish event handling done, %ld\n", event);
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_has_starter_task()
|
|
* evt_starter_task_is_complete()
|
|
* evt_pauser_task_is_complete()
|
|
* evt_resumer_task_is_complete()
|
|
*
|
|
* Return the status of start, pause and resume states of an event.
|
|
*/
|
|
static sc_bool evt_has_starter_task(sc_gameref_t game, sc_int event) {
|
|
sc_int startertype;
|
|
|
|
startertype = evt_get_starter_type(game, event);
|
|
return startertype == 3;
|
|
}
|
|
|
|
static sc_bool evt_starter_task_is_complete(sc_gameref_t game, sc_int event) {
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[3];
|
|
sc_int task;
|
|
sc_bool start;
|
|
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
vt_key[2].string = "TaskNum";
|
|
task = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
|
|
start = FALSE;
|
|
if (task == 0) {
|
|
if (evt_any_task_in_state(game, TRUE))
|
|
start = TRUE;
|
|
} else if (task > 0) {
|
|
if (gs_task_done(game, task - 1))
|
|
start = TRUE;
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
static sc_bool evt_pauser_task_is_complete(sc_gameref_t game, sc_int event) {
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[3];
|
|
sc_int pausetask;
|
|
sc_bool completed, pause;
|
|
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
|
|
vt_key[2].string = "PauseTask";
|
|
pausetask = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
vt_key[2].string = "PauserCompleted";
|
|
completed = !prop_get_boolean(bundle, "B<-sis", vt_key);
|
|
|
|
pause = FALSE;
|
|
if (pausetask == 1) {
|
|
if (evt_any_task_in_state(game, completed))
|
|
pause = TRUE;
|
|
} else if (pausetask > 1) {
|
|
if (completed == gs_task_done(game, pausetask - 2))
|
|
pause = TRUE;
|
|
}
|
|
|
|
return pause;
|
|
}
|
|
|
|
static sc_bool evt_resumer_task_is_complete(sc_gameref_t game, sc_int event) {
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[3];
|
|
sc_int resumetask;
|
|
sc_bool completed, resume;
|
|
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
|
|
vt_key[2].string = "ResumeTask";
|
|
resumetask = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
vt_key[2].string = "ResumerCompleted";
|
|
completed = !prop_get_boolean(bundle, "B<-sis", vt_key);
|
|
|
|
resume = FALSE;
|
|
if (resumetask == 1) {
|
|
if (evt_any_task_in_state(game, completed))
|
|
resume = TRUE;
|
|
} else if (resumetask > 1) {
|
|
if (completed == gs_task_done(game, resumetask - 2))
|
|
resume = TRUE;
|
|
}
|
|
|
|
return resume;
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_handle_preftime_notifications()
|
|
*
|
|
* Print messages and handle resources for the event where we're in mid-event
|
|
* and getting close to some number of turns from its end.
|
|
*/
|
|
static void evt_handle_preftime_notifications(sc_gameref_t game, sc_int event) {
|
|
const sc_filterref_t filter = gs_get_filter(game);
|
|
const sc_prop_setref_t bundle = gs_get_bundle(game);
|
|
sc_vartype_t vt_key[4];
|
|
sc_int preftime1, preftime2;
|
|
const sc_char *preftext;
|
|
|
|
vt_key[0].string = "Events";
|
|
vt_key[1].integer = event;
|
|
|
|
vt_key[2].string = "PrefTime1";
|
|
preftime1 = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
if (preftime1 == gs_event_time(game, event)) {
|
|
vt_key[2].string = "PrefText1";
|
|
preftext = prop_get_string(bundle, "S<-sis", vt_key);
|
|
if (!sc_strempty(preftext)) {
|
|
pf_buffer_string(filter, preftext);
|
|
pf_buffer_character(filter, '\n');
|
|
}
|
|
|
|
vt_key[2].string = "Res";
|
|
vt_key[3].integer = 2;
|
|
res_handle_resource(game, "sisi", vt_key);
|
|
}
|
|
|
|
vt_key[2].string = "PrefTime2";
|
|
preftime2 = prop_get_integer(bundle, "I<-sis", vt_key);
|
|
if (preftime2 == gs_event_time(game, event)) {
|
|
vt_key[2].string = "PrefText2";
|
|
preftext = prop_get_string(bundle, "S<-sis", vt_key);
|
|
if (!sc_strempty(preftext)) {
|
|
pf_buffer_string(filter, preftext);
|
|
pf_buffer_character(filter, '\n');
|
|
}
|
|
|
|
vt_key[2].string = "Res";
|
|
vt_key[3].integer = 3;
|
|
res_handle_resource(game, "sisi", vt_key);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_tick_event()
|
|
*
|
|
* Attempt to advance an event by one turn.
|
|
*/
|
|
static void evt_tick_event(sc_gameref_t game, sc_int event) {
|
|
if (evt_trace) {
|
|
sc_trace("Event: ticking event %ld: state %ld, time %ld\n", event,
|
|
gs_event_state(game, event), gs_event_time(game, event));
|
|
}
|
|
|
|
/* Handle call based on current event state. */
|
|
switch (gs_event_state(game, event)) {
|
|
case ES_WAITING: {
|
|
if (evt_trace)
|
|
sc_trace("Event: ticking waiting event %ld\n", event);
|
|
|
|
/*
|
|
* Because we also tick an event that goes from waiting to running,
|
|
* events started here will tick through RUNNING too, and have their
|
|
* time decremented. To get around this, so that the timer for one-
|
|
* shot events doesn't look one lower than it should after this
|
|
* transition, we need to set the initial time for events that start
|
|
* as soon as the game starts to one greater than that set by
|
|
* evt_start_time(). Here's the hack to do that; if the event starts
|
|
* immediately, its time will already be zero, even before decrement,
|
|
* which is how we tell which events to apply this hack to.
|
|
*
|
|
* TODO This seems to work, but also seems very dodgy.
|
|
*/
|
|
if (gs_event_time(game, event) == 0) {
|
|
evt_start_event(game, event);
|
|
|
|
/* If the event time was set to zero, finish immediately. */
|
|
if (gs_event_time(game, event) <= 0)
|
|
evt_finish_event(game, event);
|
|
else
|
|
gs_set_event_time(game, event, gs_event_time(game, event) + 1);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Decrement the event's time, and if it goes to zero, start running
|
|
* the event.
|
|
*/
|
|
gs_decrement_event_time(game, event);
|
|
|
|
if (gs_event_time(game, event) <= 0) {
|
|
evt_start_event(game, event);
|
|
|
|
/* If the event time was set to zero, finish immediately. */
|
|
if (gs_event_time(game, event) <= 0)
|
|
evt_finish_event(game, event);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ES_RUNNING: {
|
|
if (evt_trace)
|
|
sc_trace("Event: ticking running event %ld\n", event);
|
|
|
|
/*
|
|
* Re-check the starter task; if it's no longer completed, we need
|
|
* to set the event back to waiting on task.
|
|
*/
|
|
if (evt_has_starter_task(game, event)) {
|
|
if (!evt_starter_task_is_complete(game, event)) {
|
|
if (evt_trace)
|
|
sc_trace("Event: starter task not complete\n");
|
|
|
|
gs_set_event_state(game, event, ES_AWAITING);
|
|
gs_set_event_time(game, event, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the pauser has completed, but resumer not, pause this event. */
|
|
if (evt_pauser_task_is_complete(game, event)
|
|
&& !evt_resumer_task_is_complete(game, event)) {
|
|
if (evt_trace)
|
|
sc_trace("Event: pause complete\n");
|
|
|
|
gs_set_event_state(game, event, ES_PAUSED);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Decrement the event's time, and print any notifications for a set
|
|
* number of turns from the event end.
|
|
*/
|
|
gs_decrement_event_time(game, event);
|
|
|
|
if (evt_can_see_event(game, event))
|
|
evt_handle_preftime_notifications(game, event);
|
|
|
|
/* If the time goes to zero, finish running the event. */
|
|
if (gs_event_time(game, event) <= 0)
|
|
evt_finish_event(game, event);
|
|
}
|
|
break;
|
|
|
|
case ES_AWAITING: {
|
|
if (evt_trace)
|
|
sc_trace("Event: ticking awaiting event %ld\n", event);
|
|
|
|
/*
|
|
* Check the starter task. If it's completed, start running the
|
|
* event.
|
|
*/
|
|
if (evt_starter_task_is_complete(game, event)) {
|
|
evt_start_event(game, event);
|
|
|
|
/* If the event time was set to zero, finish immediately. */
|
|
if (gs_event_time(game, event) <= 0)
|
|
evt_finish_event(game, event);
|
|
else {
|
|
/*
|
|
* If the pauser has completed, but resumer not, immediately
|
|
* also pause this event.
|
|
*/
|
|
if (evt_pauser_task_is_complete(game, event)
|
|
&& !evt_resumer_task_is_complete(game, event)) {
|
|
if (evt_trace)
|
|
sc_trace("Event: pause complete, immediate pause\n");
|
|
|
|
gs_set_event_state(game, event, ES_PAUSED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ES_FINISHED: {
|
|
if (evt_trace)
|
|
sc_trace("Event: ticking finished event %ld\n", event);
|
|
|
|
/*
|
|
* Check the starter task; if it's not completed, we need to set the
|
|
* event back to waiting on task.
|
|
*
|
|
* A completed event needs to go back to waiting on its task, but we
|
|
* don't want to set it there as soon as the event finishes. We need
|
|
* to wait for the starter task to first become undone, otherwise the
|
|
* event just cycles endlessly, and they don't in Adrift itself. Here
|
|
* is where we wait for starter tasks to become undone.
|
|
*/
|
|
if (evt_has_starter_task(game, event)) {
|
|
if (!evt_starter_task_is_complete(game, event)) {
|
|
if (evt_trace)
|
|
sc_trace("Event: starter task not complete\n");
|
|
|
|
gs_set_event_state(game, event, ES_AWAITING);
|
|
gs_set_event_time(game, event, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ES_PAUSED: {
|
|
if (evt_trace)
|
|
sc_trace("Event: ticking paused event %ld\n", event);
|
|
|
|
/* If the resumer has completed, resume this event. */
|
|
if (evt_resumer_task_is_complete(game, event)) {
|
|
if (evt_trace)
|
|
sc_trace("Event: resume complete\n");
|
|
|
|
gs_set_event_state(game, event, ES_RUNNING);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
sc_fatal("evt_tick: invalid event state\n");
|
|
}
|
|
|
|
if (evt_trace) {
|
|
sc_trace("Event: after ticking event %ld: state %ld, time %ld\n", event,
|
|
gs_event_state(game, event), gs_event_time(game, event));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_tick_events()
|
|
*
|
|
* Attempt to advance each event by one turn.
|
|
*/
|
|
void evt_tick_events(sc_gameref_t game) {
|
|
sc_int event;
|
|
|
|
/*
|
|
* Tick all events. If an event transitions into a running state from a
|
|
* paused or waiting state, tick that event again.
|
|
*/
|
|
for (event = 0; event < gs_event_count(game); event++) {
|
|
sc_int prior_state, state;
|
|
|
|
/* Note current state, and tick event forwards. */
|
|
prior_state = gs_event_state(game, event);
|
|
evt_tick_event(game, event);
|
|
|
|
/*
|
|
* If the event went from paused or waiting to running, tick again.
|
|
* This looks dodgy, and probably is, but it does keep timers correct
|
|
* by only re-ticking events that have transitioned from non-running
|
|
* states to a running one, and not already-running events. This is
|
|
* in effect just adding a bit of turn processing to a tick that would
|
|
* otherwise change state alone; a bit of laziness, in other words.
|
|
*/
|
|
state = gs_event_state(game, event);
|
|
if (state == ES_RUNNING
|
|
&& (prior_state == ES_PAUSED || prior_state == ES_WAITING))
|
|
evt_tick_event(game, event);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* evt_debug_trace()
|
|
*
|
|
* Set event tracing on/off.
|
|
*/
|
|
void evt_debug_trace(sc_bool flag) {
|
|
evt_trace = flag;
|
|
}
|
|
|
|
} // End of namespace Adrift
|
|
} // End of namespace Glk
|