diff options
Diffstat (limited to 'engines/glk/adrift/scnpcs.cpp')
| -rw-r--r-- | engines/glk/adrift/scnpcs.cpp | 640 | 
1 files changed, 640 insertions, 0 deletions
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  | 
