From 60c860c6a6c37b95ab8265d658cbcc144d51153b Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Mon, 2 Sep 2019 20:57:19 -0700 Subject: GLK: ADRIFT: Skeleton sub-engine commit --- engines/glk/adrift/scnpcs.cpp | 640 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 640 insertions(+) create mode 100644 engines/glk/adrift/scnpcs.cpp (limited to 'engines/glk/adrift/scnpcs.cpp') diff --git a/engines/glk/adrift/scnpcs.cpp b/engines/glk/adrift/scnpcs.cpp new file mode 100644 index 0000000000..ab11acc5ae --- /dev/null +++ b/engines/glk/adrift/scnpcs.cpp @@ -0,0 +1,640 @@ +/* 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 { + +/* Trace flag, set before running. */ +static sc_bool npc_trace = FALSE; + + +/* + * npc_in_room() + * + * Return TRUE if a given NPC is currently in a given room. + */ +sc_bool +npc_in_room (sc_gameref_t game, sc_int npc, sc_int room) +{ + if (npc_trace) + { + sc_trace ("NPC: checking NPC %ld in room %ld (NPC is in %ld)\n", + npc, room, gs_npc_location (game, npc)); + } + + return gs_npc_location (game, npc) - 1 == room; +} + + +/* + * npc_count_in_room() + * + * Return the count of characters in the room, including the player. + */ +sc_int +npc_count_in_room (sc_gameref_t game, sc_int room) +{ + sc_int count, npc; + + /* Start with the player. */ + count = gs_player_in_room (game, room) ? 1 : 0; + + /* Total up other NPCs inhabiting the room. */ + for (npc = 0; npc < gs_npc_count (game); npc++) + { + if (gs_npc_location (game, npc) - 1 == room) + count++; + } + return count; +} + + +/* + * npc_start_npc_walk() + * + * Start the given walk for the given NPC. + */ +void +npc_start_npc_walk (sc_gameref_t game, sc_int npc, sc_int walk) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[6]; + sc_int movetime; + + /* Retrieve movetime 0 for the NPC walk. */ + vt_key[0].string = "NPCs"; + vt_key[1].integer = npc; + vt_key[2].string = "Walks"; + vt_key[3].integer = walk; + vt_key[4].string = "MoveTimes"; + vt_key[5].integer = 0; + movetime = prop_get_integer (bundle, "I<-sisisi", vt_key) + 1; + + /* Set up walkstep. */ + gs_set_npc_walkstep (game, npc, walk, movetime); +} + + +/* + * npc_turn_update() + * npc_setup_initial() + * + * Set initial values for NPC states, and update on turns. + */ +void +npc_turn_update (sc_gameref_t game) +{ + sc_int index_; + + /* Set current values for NPC seen states. */ + for (index_ = 0; index_ < gs_npc_count (game); index_++) + { + if (!gs_npc_seen (game, index_) + && npc_in_room (game, index_, gs_playerroom (game))) + gs_set_npc_seen (game, index_, TRUE); + } +} + +void +npc_setup_initial (sc_gameref_t game) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[5]; + sc_int index_; + + /* Start any walks that do not depend on a StartTask */ + vt_key[0].string = "NPCs"; + for (index_ = 0; index_ < gs_npc_count (game); index_++) + { + sc_int walk; + + /* Set up invariant parts of the properties key. */ + vt_key[1].integer = index_; + vt_key[2].string = "Walks"; + + /* Process each walk, starting at the last and working backwards. */ + for (walk = gs_npc_walkstep_count (game, index_) - 1; walk >= 0; walk--) + { + sc_int starttask; + + /* If StartTask is zero, start walk at game start. */ + vt_key[3].integer = walk; + vt_key[4].string = "StartTask"; + starttask = prop_get_integer (bundle, "I<-sisis", vt_key); + if (starttask == 0) + npc_start_npc_walk (game, index_, walk); + } + } + + /* Update seen flags for initial states. */ + npc_turn_update (game); +} + + +/* + * npc_room_in_roomgroup() + * + * Return TRUE if a given room is in a given group. + */ +static sc_bool +npc_room_in_roomgroup (sc_gameref_t game, sc_int room, sc_int group) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[4]; + sc_int member; + + /* Check roomgroup membership. */ + vt_key[0].string = "RoomGroups"; + vt_key[1].integer = group; + vt_key[2].string = "List"; + vt_key[3].integer = room; + member = prop_get_integer (bundle, "I<-sisi", vt_key); + return member != 0; +} + + +/* List of direction names, for printing entry/exit messages. */ +static const sc_char *const DIRNAMES_4[] = { + "the north", "the east", "the south", "the west", "above", "below", + "inside", "outside", + NULL +}; +static const sc_char *const DIRNAMES_8[] = { + "the north", "the east", "the south", "the west", "above", "below", + "inside", "outside", + "the north-east", "the south-east", "the south-west", "the north-west", + NULL +}; + +/* + * npc_random_adjacent_roomgroup_member() + * + * Return a random member of group adjacent to given room. + */ +static sc_int +npc_random_adjacent_roomgroup_member (sc_gameref_t game, + sc_int room, sc_int group) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[5]; + sc_bool eightpointcompass; + sc_int roomlist[12], count, length, index_; + + /* If given room is "hidden", return nothing. */ + if (room == -1) + return -1; + + /* How many exits to consider? */ + vt_key[0].string = "Globals"; + vt_key[1].string = "EightPointCompass"; + eightpointcompass = prop_get_boolean (bundle, "B<-ss", vt_key); + if (eightpointcompass) + length = sizeof (DIRNAMES_8) / sizeof (DIRNAMES_8[0]) - 1; + else + length = sizeof (DIRNAMES_4) / sizeof (DIRNAMES_4[0]) - 1; + + /* Poll adjacent rooms. */ + vt_key[0].string = "Rooms"; + vt_key[1].integer = room; + vt_key[2].string = "Exits"; + count = 0; + for (index_ = 0; index_ < length; index_++) + { + sc_int adjacent; + + vt_key[3].integer = index_; + vt_key[4].string = "Dest"; + adjacent = prop_get_child_count (bundle, "I<-sisis", vt_key); + + if (adjacent > 0 && npc_room_in_roomgroup (game, adjacent - 1, group)) + { + roomlist[count] = adjacent - 1; + count++; + } + } + + /* Return a random adjacent room, or -1 if nothing is adjacent. */ + return (count > 0) ? roomlist[sc_randomint (0, count - 1)] : -1; +} + + +/* + * npc_announce() + * + * Helper for npc_tick_npc(). + */ +static void +npc_announce (sc_gameref_t game, sc_int npc, + sc_int room, sc_bool is_exit, sc_int npc_room) +{ + const sc_filterref_t filter = gs_get_filter (game); + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[5], vt_rvalue; + const sc_char *text, *name, *const *dirnames; + sc_int dir, dir_match; + sc_bool eightpointcompass, showenterexit, found; + + /* If no announcement required, return immediately. */ + vt_key[0].string = "NPCs"; + vt_key[1].integer = npc; + vt_key[2].string = "ShowEnterExit"; + showenterexit = prop_get_boolean (bundle, "B<-sis", vt_key); + if (!showenterexit) + return; + + /* Get exit or entry text, and NPC name. */ + vt_key[2].string = is_exit ? "ExitText" : "EnterText"; + text = prop_get_string (bundle, "S<-sis", vt_key); + vt_key[2].string = "Name"; + name = prop_get_string (bundle, "S<-sis", vt_key); + + /* Decide on four or eight point compass names list. */ + vt_key[0].string = "Globals"; + vt_key[1].string = "EightPointCompass"; + eightpointcompass = prop_get_boolean (bundle, "B<-ss", vt_key); + dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4; + + /* Set invariant key for room exit search. */ + vt_key[0].string = "Rooms"; + vt_key[1].integer = room; + vt_key[2].string = "Exits"; + + /* Find the room exit that matches the NPC room. */ + found = FALSE; + dir_match = 0; + for (dir = 0; dirnames[dir]; dir++) + { + vt_key[3].integer = dir; + if (prop_get (bundle, "I<-sisi", &vt_rvalue, vt_key)) + { + sc_int dest; + + /* Get room's direction destination, and compare. */ + vt_key[4].string = "Dest"; + dest = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + if (dest == npc_room) + { + dir_match = dir; + found = TRUE; + break; + } + } + } + + /* Print NPC exit/entry details. */ + pf_buffer_character (filter, '\n'); + pf_new_sentence (filter); + pf_buffer_string (filter, name); + pf_buffer_character (filter, ' '); + pf_buffer_string (filter, text); + if (found) + { + pf_buffer_string (filter, is_exit ? " to " : " from "); + pf_buffer_string (filter, dirnames[dir_match]); + } + pf_buffer_string (filter, ".\n"); + + /* Handle any associated resource. */ + vt_key[0].string = "NPCs"; + vt_key[1].integer = npc; + vt_key[2].string = "Res"; + vt_key[3].integer = is_exit ? 3 : 2; + res_handle_resource (game, "sisi", vt_key); +} + + +/* + * npc_tick_npc_walk() + * + * Helper for npc_tick_npc(). + */ +static void +npc_tick_npc_walk (sc_gameref_t game, sc_int npc, sc_int walk) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[6]; + sc_int roomgroups, movetimes, walkstep, start, dest, destnum; + sc_int chartask, objecttask; + + if (npc_trace) + { + sc_trace ("NPC: ticking NPC %ld, walk %ld: step %ld\n", + npc, walk, gs_npc_walkstep (game, npc, walk)); + } + + /* Count roomgroups for later use. */ + vt_key[0].string = "RoomGroups"; + roomgroups = prop_get_child_count (bundle, "I<-s", vt_key); + + /* Get move times array length. */ + vt_key[0].string = "NPCs"; + vt_key[1].integer = npc; + vt_key[2].string = "Walks"; + vt_key[3].integer = walk; + vt_key[4].string = "MoveTimes"; + movetimes = prop_get_child_count (bundle, "I<-sisis", vt_key); + + /* Find a step to match the movetime. */ + for (walkstep = 0; walkstep < movetimes - 1; walkstep++) + { + sc_int movetime; + + vt_key[5].integer = walkstep + 1; + movetime = prop_get_integer (bundle, "I<-sisisi", vt_key); + if (gs_npc_walkstep (game, npc, walk) > movetime) + break; + } + + /* Sort out a destination. */ + dest = start = gs_npc_location (game, npc) - 1; + + vt_key[4].string = "Rooms"; + vt_key[5].integer = walkstep; + destnum = prop_get_integer (bundle, "I<-sisisi", vt_key); + + if (destnum == 0) /* Hidden. */ + dest = -1; + else if (destnum == 1) /* Follow player. */ + dest = gs_playerroom (game); + else if (destnum < gs_room_count (game) + 2) + dest = destnum - 2; /* To room. */ + else if (destnum < gs_room_count (game) + 2 + roomgroups) + { + sc_int initial; + + /* For roomgroup walks, move only if walksteps has just refreshed. */ + vt_key[4].string = "MoveTimes"; + vt_key[5].integer = 0; + initial = prop_get_integer (bundle, "I<-sisisi", vt_key); + if (gs_npc_walkstep (game, npc, walk) == initial) + { + sc_int group; + + group = destnum - 2 - gs_room_count (game); + dest = npc_random_adjacent_roomgroup_member (game, start, group); + if (dest == -1) + dest = lib_random_roomgroup_member (game, group); + } + } + + /* See if the NPC actually moved. */ + if (start != dest) + { + if (npc_trace) + sc_trace ("NPC: walking NPC %ld moved to %ld\n", npc, dest); + + /* Move NPC to destination. */ + gs_set_npc_location (game, npc, dest + 1); + + /* Announce NPC movements, and handle meeting characters and objects. */ + if (gs_player_in_room (game, start)) + npc_announce (game, npc, start, TRUE, dest); + else if (gs_player_in_room (game, dest)) + npc_announce (game, npc, dest, FALSE, start); + } + + /* Handle meeting characters and objects. */ + vt_key[4].string = "CharTask"; + chartask = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + if (chartask >= 0) + { + sc_int meetchar; + + /* Run meetchar task if appropriate. */ + vt_key[4].string = "MeetChar"; + meetchar = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + if ((meetchar == -1 && gs_player_in_room (game, dest)) + || (meetchar >= 0 && dest == gs_npc_location (game, meetchar) - 1)) + { + if (task_can_run_task (game, chartask)) + task_run_task (game, chartask, TRUE); + } + } + + vt_key[4].string = "ObjectTask"; + objecttask = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + if (objecttask >= 0) + { + sc_int meetobject; + + /* Run meetobject task if appropriate. */ + vt_key[4].string = "MeetObject"; + meetobject = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + if (meetobject >= 0 && obj_directly_in_room (game, meetobject, dest)) + { + if (task_can_run_task (game, objecttask)) + task_run_task (game, objecttask, TRUE); + } + } +} + + +/* + * npc_tick_npc() + * + * Move an NPC one step along current walk. + */ +static void +npc_tick_npc (sc_gameref_t game, sc_int npc) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[6]; + sc_int walk; + sc_bool has_moved = FALSE; + + if (npc_trace) + sc_trace ("NPC: ticking NPC %ld\n", npc); + + /* Set up invariant key parts. */ + vt_key[0].string = "NPCs"; + vt_key[1].integer = npc; + vt_key[2].string = "Walks"; + + /* Find active walk, and if any found, make a step along it. */ + for (walk = gs_npc_walkstep_count (game, npc) - 1; walk >= 0; walk--) + { + sc_int starttask, stoppingtask; + + /* Ignore finished walks. */ + if (gs_npc_walkstep (game, npc, walk) <= 0) + continue; + + /* Get start task. */ + vt_key[3].integer = walk; + vt_key[4].string = "StartTask"; + starttask = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + + /* + * Check that the starter is still complete, and if not, stop walk. + * Then keep on looking for an active walk. + */ + if (starttask >= 0 && !gs_task_done (game, starttask)) + { + if (npc_trace) + sc_trace ("NPC: stopping NPC %ld walk, start task undone\n", npc); + + gs_set_npc_walkstep (game, npc, walk, -1); + continue; + } + + /* Get stopping task. */ + vt_key[4].string = "StoppingTask"; + stoppingtask = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + + /* + * If any stopping task has completed, ignore this walk but don't + * actually finish it; more like an event pauser, then. + * + * TODO Is this right? + */ + if (stoppingtask >= 0 && gs_task_done (game, stoppingtask)) + { + if (npc_trace) + sc_trace ("NPC: ignoring NPC %ld walk, stop task done\n", npc); + + continue; + } + + /* Decrement steps. */ + gs_decrement_npc_walkstep (game, npc, walk); + + /* If we just hit a walk end, loop if called for. */ + if (gs_npc_walkstep (game, npc, walk) == 0) + { + sc_bool is_loop; + + /* If walk is a loop, restart it. */ + vt_key[4].string = "Loop"; + is_loop = prop_get_boolean (bundle, "B<-sisis", vt_key); + if (is_loop) + { + vt_key[4].string = "MoveTimes"; + vt_key[5].integer = 0; + gs_set_npc_walkstep (game, npc, walk, + prop_get_integer (bundle, + "I<-sisisi", vt_key)); + } + else + gs_set_npc_walkstep (game, npc, walk, -1); + } + + /* + * If not yet made a move on this walk, make one, and once made, make + * no other + */ + if (!has_moved) + { + npc_tick_npc_walk (game, npc, walk); + has_moved = TRUE; + } + } +} + + +/* + * npc_tick_npcs() + * + * Move each NPC one step along current walk. + */ +void +npc_tick_npcs (sc_gameref_t game) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + const sc_gameref_t undo = game->undo; + sc_int npc; + + /* + * Compare the player location to last turn, to see if the player has moved + * this turn. If moved, look for meetings with NPCs. + * + * TODO Is this the right place to do this. After ticking each NPC, rather + * than before, seems more appropriate. But the messages come out in the + * right order by putting it here. + * + * Also, note that we take the shortcut of using the undo gamestate here, + * rather than properly recording the prior location of the player, and + * perhaps also NPCs, in the live gamestate. + */ + if (undo && !gs_player_in_room (undo, gs_playerroom (game))) + { + for (npc = 0; npc < gs_npc_count (game); npc++) + { + sc_int walk; + + /* Iterate each NPC's walks. */ + for (walk = gs_npc_walkstep_count (game, npc) - 1; walk >= 0; walk--) + { + sc_vartype_t vt_key[5]; + sc_int chartask; + + /* Ignore finished walks. */ + if (gs_npc_walkstep (game, npc, walk) <= 0) + continue; + + /* Retrieve any character meeting task for the NPC. */ + vt_key[0].string = "NPCs"; + vt_key[1].integer = npc; + vt_key[2].string = "Walks"; + vt_key[3].integer = walk; + vt_key[4].string = "CharTask"; + chartask = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + if (chartask >= 0) + { + sc_int meetchar; + + /* Run meetchar task if appropriate. */ + vt_key[4].string = "MeetChar"; + meetchar = prop_get_integer (bundle, "I<-sisis", vt_key) - 1; + if (meetchar == -1 && + gs_player_in_room (game, gs_npc_location (game, npc) - 1)) + { + if (task_can_run_task (game, chartask)) + task_run_task (game, chartask, TRUE); + } + } + } + } + } + + /* Iterate and tick each individual NPC. */ + for (npc = 0; npc < gs_npc_count (game); npc++) + npc_tick_npc (game, npc); +} + + +/* + * npc_debug_trace() + * + * Set NPC tracing on/off. + */ +void +npc_debug_trace (sc_bool flag) +{ + npc_trace = flag; +} + +} // End of namespace Adrift +} // End of namespace Glk -- cgit v1.2.3