diff options
author | Paul Gilbert | 2019-09-02 20:57:19 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-09-25 20:13:26 -0700 |
commit | 60c860c6a6c37b95ab8265d658cbcc144d51153b (patch) | |
tree | e21dac197fcd374bb8d348873c91230141e1fddf /engines/glk/adrift/scdebug.cpp | |
parent | c893c5a60b8298aa99ae1a47dea51c6f04c419bc (diff) | |
download | scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.tar.gz scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.tar.bz2 scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.zip |
GLK: ADRIFT: Skeleton sub-engine commit
Diffstat (limited to 'engines/glk/adrift/scdebug.cpp')
-rw-r--r-- | engines/glk/adrift/scdebug.cpp | 2717 |
1 files changed, 2717 insertions, 0 deletions
diff --git a/engines/glk/adrift/scdebug.cpp b/engines/glk/adrift/scdebug.cpp new file mode 100644 index 0000000000..e2472cc77c --- /dev/null +++ b/engines/glk/adrift/scdebug.cpp @@ -0,0 +1,2717 @@ +/* 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 { + +/* Assorted definitions and constants. */ +static const sc_uint DEBUG_MAGIC = 0xc4584d2e; +enum { DEBUG_BUFFER_SIZE = 256 }; + +/* Debugging command and command argument type. */ +typedef enum +{ DEBUG_NONE = 0, DEBUG_CONTINUE, DEBUG_STEP, DEBUG_BUFFER, DEBUG_RESOURCES, + DEBUG_HELP, DEBUG_GAME, + DEBUG_PLAYER, DEBUG_ROOMS, DEBUG_OBJECTS, DEBUG_NPCS, DEBUG_EVENTS, + DEBUG_TASKS, DEBUG_VARIABLES, + DEBUG_OLDPLAYER, DEBUG_OLDROOMS, DEBUG_OLDOBJECTS, DEBUG_OLDNPCS, + DEBUG_OLDEVENTS, DEBUG_OLDTASKS, DEBUG_OLDVARIABLES, + DEBUG_WATCHPLAYER, DEBUG_WATCHOBJECTS, DEBUG_WATCHNPCS, DEBUG_WATCHEVENTS, + DEBUG_WATCHTASKS, DEBUG_WATCHVARIABLES, + DEBUG_CLEARPLAYER, DEBUG_CLEAROBJECTS, DEBUG_CLEARNPCS, DEBUG_CLEAREVENTS, + DEBUG_CLEARTASKS, DEBUG_CLEARVARIABLES, + DEBUG_WATCHALL, DEBUG_CLEARALL, DEBUG_RANDOM, + DEBUG_QUIT +} +sc_command_t; + +typedef enum +{ COMMAND_QUERY = 0, COMMAND_RANGE, COMMAND_ONE, COMMAND_ALL } +sc_command_type_t; + +/* Table connecting debugging command strings to commands. */ +typedef struct +{ + const sc_char *const command_string; + const sc_command_t command; +} sc_strings_t; +static const sc_strings_t DEBUG_COMMANDS[] = { + {"continue", DEBUG_CONTINUE}, {"step", DEBUG_STEP}, {"buffer", DEBUG_BUFFER}, + {"resources", DEBUG_RESOURCES}, {"help", DEBUG_HELP}, {"game", DEBUG_GAME}, + {"player", DEBUG_PLAYER}, {"rooms", DEBUG_ROOMS}, {"objects", DEBUG_OBJECTS}, + {"npcs", DEBUG_NPCS}, {"events", DEBUG_EVENTS}, {"tasks", DEBUG_TASKS}, + {"variables", DEBUG_VARIABLES}, + {"oldplayer", DEBUG_OLDPLAYER}, {"oldrooms", DEBUG_OLDROOMS}, + {"oldobjects", DEBUG_OLDOBJECTS}, {"oldnpcs", DEBUG_OLDNPCS}, + {"oldevents", DEBUG_OLDEVENTS}, {"oldtasks", DEBUG_OLDTASKS}, + {"oldvariables", DEBUG_OLDVARIABLES}, + {"watchplayer", DEBUG_WATCHPLAYER}, {"clearplayer", DEBUG_CLEARPLAYER}, + {"watchobjects", DEBUG_WATCHOBJECTS}, {"watchnpcs", DEBUG_WATCHNPCS}, + {"watchevents", DEBUG_WATCHEVENTS}, {"watchtasks", DEBUG_WATCHTASKS}, + {"watchvariables", DEBUG_WATCHVARIABLES}, + {"clearobjects", DEBUG_CLEAROBJECTS}, {"clearnpcs", DEBUG_CLEARNPCS}, + {"clearevents", DEBUG_CLEAREVENTS}, {"cleartasks", DEBUG_CLEARTASKS}, + {"clearvariables", DEBUG_CLEARVARIABLES}, {"watchall", DEBUG_WATCHALL}, + {"clearall", DEBUG_CLEARALL}, {"random", DEBUG_RANDOM}, {"quit", DEBUG_QUIT}, + {NULL, DEBUG_NONE} +}; + +/* + * Debugging control information structure. The structure is created and + * added to the game on enabling debug, and removed and destroyed on + * disabling debugging. + */ +typedef struct sc_debugger_s +{ + sc_uint magic; + sc_bool *watch_objects; + sc_bool *watch_npcs; + sc_bool *watch_events; + sc_bool *watch_tasks; + sc_bool *watch_variables; + sc_bool watch_player; + sc_bool single_step; + sc_bool quit_pending; + sc_uint elapsed_seconds; +} sc_debugger_t; + + +/* + * debug_is_valid() + * + * Return TRUE if pointer is a valid debugger, FALSE otherwise. + */ +static sc_bool +debug_is_valid (sc_debuggerref_t debug) +{ + return debug && debug->magic == DEBUG_MAGIC; +} + + +/* + * debug_get_debugger() + * + * Return the debugger reference from a game, or NULL if none. + */ +static sc_debuggerref_t +debug_get_debugger (sc_gameref_t game) +{ + assert (gs_is_game_valid (game)); + + return game->debugger; +} + + +/* + * debug_variable_count() + * + * Common helper to return the count of variables defined in a game. + */ +static sc_int +debug_variable_count (sc_gameref_t game) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key; + sc_int variable_count; + + /* Find and return the variables count. */ + vt_key.string = "Variables"; + variable_count = prop_get_child_count (bundle, "I<-s", &vt_key); + + return variable_count; +} + + +/* + * debug_initialize() + * + * Create a new set of debug control information, and append it to the + * game passed in. + */ +static void +debug_initialize (sc_gameref_t game) +{ + sc_debuggerref_t debug; + + /* Create the easy bits of the new debugging set. */ + debug = (sc_debuggerref_t)sc_malloc (sizeof (*debug)); + debug->magic = DEBUG_MAGIC; + debug->watch_player = FALSE; + debug->single_step = FALSE; + debug->quit_pending = FALSE; + debug->elapsed_seconds = 0; + + /* Allocate watchpoints for everything we can watch. */ + debug->watch_objects = (sc_bool *)sc_malloc (gs_object_count (game) + * sizeof (*debug->watch_objects)); + debug->watch_npcs = (sc_bool *)sc_malloc (gs_npc_count (game) + * sizeof (*debug->watch_npcs)); + debug->watch_events = (sc_bool *)sc_malloc (gs_event_count (game) + * sizeof (*debug->watch_events)); + debug->watch_tasks = (sc_bool *)sc_malloc (gs_task_count (game) + * sizeof (*debug->watch_tasks)); + debug->watch_variables = (sc_bool *)sc_malloc (debug_variable_count (game) + * sizeof (*debug->watch_variables)); + + /* Clear all watchpoint arrays. */ + memset (debug->watch_objects, FALSE, + gs_object_count (game) * sizeof (*debug->watch_objects)); + memset (debug->watch_npcs, FALSE, + gs_npc_count (game) * sizeof (*debug->watch_npcs)); + memset (debug->watch_events, FALSE, + gs_event_count (game) * sizeof (*debug->watch_events)); + memset (debug->watch_tasks, FALSE, + gs_task_count (game) * sizeof (*debug->watch_tasks)); + memset (debug->watch_variables, FALSE, + debug_variable_count (game) * sizeof (*debug->watch_variables)); + + /* Append the new debugger set to the game. */ + assert (!game->debugger); + game->debugger = debug; +} + + +/* + * debug_finalize() + * + * Destroy a debug data set, free its heap memory, and remove its reference + * from the game. + */ +static void +debug_finalize (sc_gameref_t game) +{ + sc_debuggerref_t debug = debug_get_debugger (game); + assert (debug_is_valid (debug)); + + /* Free all allocated watchpoint arrays. */ + sc_free (debug->watch_objects); + sc_free (debug->watch_npcs); + sc_free (debug->watch_events); + sc_free (debug->watch_tasks); + sc_free (debug->watch_variables); + + /* Poison and free the debugger itself. */ + memset (debug, 0xaa, sizeof (*debug)); + sc_free (debug); + + /* Remove the debug reference from the game. */ + game->debugger = NULL; +} + + +/* + * debug_help() + * + * Print debugging help. + */ +static void +debug_help (sc_command_t topic) +{ + /* Is help general, or specific? */ + if (topic == DEBUG_NONE) + { + if_print_debug ( + "The following debugging commands examine game state:\n\n"); + if_print_debug ( + " game -- Print general game information," + " and class counts\n" + " player -- Show the player location and position\n" + " rooms [Range] -- Print information on game rooms\n" + " objects [Range] -- Print information on objects in the game\n" + " npcs [Range] -- Print information on game NPCs\n" + " events [Range] -- Print information on the game's events\n" + " tasks [Range] -- Print information on the game's tasks\n" + " variables [Range] -- Show variables defined by the game\n\n"); + if_print_debug ( + "Most commands take range inputs. This can be a single number, to" + " apply the command to just that item, a range such as '0 to 10' (or" + " '0 - 10', '0 .. 10', or simply '0 10') to apply to that range of" + " items, or '*' to apply the command to all items of the class. If" + " omitted, the command is applied only to the items of the class" + " 'relevant' to the current game state; see the help for specific" + " commands for more on what is 'relevant'.\n\n"); + if_print_debug ( + "The 'player', 'objects', 'npcs', 'events', 'tasks', and 'variables'" + " commands may be prefixed with 'old', in which case the values" + " printed will be those for the previous game turn, rather than the" + " current values.\n\n"); + if_print_debug ( + "These debugging commands manage watchpoints:\n\n"); + if_print_debug ( + "The 'player', 'objects', 'npcs', 'events', 'tasks', and 'variables'" + " commands may be prefixed with 'watch', to set watchpoints." + " Watchpoints automatically enter the debugger when the item changes" + " state during a game turn. For example 'watchobject 10' monitors" + " object 10 for changes, and 'watchnpc *' monitors all NPCs. A" + " 'watch' command with no range prints out all watchpoints set for" + " that class.\n\n"); + if_print_debug ( + "Prefix commands with 'clear' to clear watchpoints, for example" + " 'clearnpcs *'. Use 'watchall' to obtain a complete list of every" + " watchpoint set, and 'clearall' to clear all watchpoints in one go." + " A 'clear' command with no range behaves the same as a 'watch'" + " command with no range.\n\n"); + if_print_debug ( + "These debugging commands print details of game output and control the" + " debugger and interpreter:\n\n"); + if_print_debug ( + " buffer -- Show the current buffered game text\n" + " resources -- Show current and requested game resources\n" + " random [Seed] -- Control the random number generator\n" + " step -- Run one game turn, then re-enter the debugger\n" + " continue -- Leave the debugger and resume the game\n" + " quit -- Exit the interpreter main loop\n" + " help [Command] -- Print help specific to Command\n\n"); + if_print_debug ( + "Debugging commands may be abbreviated to their shortest unambiguous" + " form.\n\n"); + if_print_debug ( + "Use the 'debug' or '#debug' command in a game, typed at the usual" + " game prompt, to return to the debugger.\n"); + return; + } + + /* Command-specific help. */ + switch (topic) + { + case DEBUG_HELP: + if_print_debug ( + "Give the name of the command you want help on, for example 'help" + " continue'.\n"); + break; + + case DEBUG_CONTINUE: + if_print_debug ( + "Leave the debugger and resume the game. Use the 'debug' or '#debug'" + " command in a game, typed at the usual game prompt, to return to the" + " debugger.\n"); + break; + + case DEBUG_STEP: + if_print_debug ( + "Run one game turn, then re-enter the debugger. Useful for games that" + " intercept empty input lines, which otherwise catch the 'debug'" + " command before SCARE can get to it.\n"); + break; + + case DEBUG_QUIT: + if_print_debug ( + "Exit the interpreter main loop. Equivalent to a confirmed 'quit'" + " from within the game itself, this ends the interpreter session.\n"); + break; + + case DEBUG_BUFFER: + if_print_debug ( + "Print the current text that the game has buffered for output. The" + " debugger catches games before they have printed their turn output" + " -- this is the text that will be filtered and printed on exiting the" + " debugger.\n"); + break; + + case DEBUG_RESOURCES: + if_print_debug ( + "Print any resources currently active, and any requested by the game" + " on the current turn. The requested resources will become the active" + " ones on exiting the debugger.\n"); + break; + + case DEBUG_RANDOM: + if_print_debug ( + "If no seed is given, report the current random number generator" + " setting. Otherwise, seed the random number generator with the value" + " given. This is useful for persuading games with random sections to" + " behave predictably. A new seed value of zero is invalid.\n"); + break; + + case DEBUG_GAME: + if_print_debug ( + "Print general game information, including the number of rooms," + " objects, events, tasks, and variables that the game defines\n"); + break; + + case DEBUG_PLAYER: + if_print_debug ( + "Print out the current player room and position, and any parent object" + " of the player character.\n"); + break; + + case DEBUG_OLDPLAYER: + if_print_debug ( + "Print out the player room and position from the previous turn, and" + " any parent object of the player character.\n"); + break; + + case DEBUG_ROOMS: + if_print_debug ( + "Print out the name and contents of rooms in the range. If no range," + " print details of the room containing the player.\n"); + break; + + case DEBUG_OLDROOMS: + if_print_debug ( + "Print out the name and contents of rooms in the range for the" + " previous turn. If no range, print details of the room that" + " contained the player on the previous turn.\n"); + break; + + case DEBUG_OBJECTS: + if_print_debug ( + "Print out details of all objects in the range. If no range, print" + " details of objects in the room containing the player, and visible to" + " the player.\n"); + break; + + case DEBUG_OLDOBJECTS: + if_print_debug ( + "Print out details of all objects in the range for the previous turn." + " If no range, print details of objects in the room that contained" + " the player, and were visible to the player.\n"); + break; + + case DEBUG_NPCS: + if_print_debug ( + "Print out details of all NPCs in the range. If no range, print" + " details of only NPCs in the room containing the player.\n"); + break; + + case DEBUG_OLDNPCS: + if_print_debug ( + "Print out details of all NPCs in the range for the previous turn." + " If no range, print details of only NPCs in the room that contained" + " the player.\n"); + break; + + case DEBUG_EVENTS: + if_print_debug ( + "Print out details of all events in the range. If no range, print" + " details of only events currently running.\n"); + break; + + case DEBUG_OLDEVENTS: + if_print_debug ( + "Print out details of all events in the range for the previous turn." + " If no range, print details of only events running on the previous" + " turn.\n"); + break; + + case DEBUG_TASKS: + if_print_debug ( + "Print out details of all tasks in the range. If no range, print" + " details of only tasks that are runnable, for the current state of" + " the game.\n"); + break; + + case DEBUG_OLDTASKS: + if_print_debug ( + "Print out details of all tasks in the range for the previous turn." + " If no range, print details of only tasks that were runnable, for" + " the previous state of the game.\n"); + break; + + case DEBUG_VARIABLES: + if_print_debug ( + "Print out the names, types, and values of all game variables in the" + " range. If no range, print details of all variables (equivalent to" + " 'variables *').\n"); + break; + + case DEBUG_OLDVARIABLES: + if_print_debug ( + "Print out the names, types, and values at the previous turn of all" + " game variables in the range. If no range, print details of all" + " variables (equivalent to 'variables *').\n"); + break; + + case DEBUG_WATCHPLAYER: + if_print_debug ( + "If no range is given, list any watchpoint on player movement. If" + " range '0' is given, set a watchpoint on player movement. Other" + " usages of 'watchplayer' behave as if no range is given.\n"); + break; + + case DEBUG_WATCHOBJECTS: + if_print_debug ( + "Set watchpoints on all objects in the range. If no range, list out" + " object watchpoints currently set.\n"); + break; + + case DEBUG_WATCHNPCS: + if_print_debug ( + "Set watchpoints on all NPCs in the range. If no range, list out NPC" + " watchpoints currently set.\n"); + break; + + case DEBUG_WATCHEVENTS: + if_print_debug ( + "Set watchpoints on all events in the range. If no range, list out" + " event watchpoints currently set.\n"); + break; + + case DEBUG_WATCHTASKS: + if_print_debug ( + "Set watchpoints on all tasks in the range. If no range, list out" + " task watchpoints currently set.\n"); + break; + + case DEBUG_WATCHVARIABLES: + if_print_debug ( + "Set watchpoints on all game variables in the range. If no range," + " list variable watchpoints currently set.\n"); + break; + + case DEBUG_CLEARPLAYER: + if_print_debug ( + "Clear any watchpoint set on player movements.\n"); + break; + + case DEBUG_CLEAROBJECTS: + if_print_debug ( + "Clear watchpoints on all objects in the range. If no range, list" + " out object watchpoints currently set.\n"); + break; + + case DEBUG_CLEARNPCS: + if_print_debug ( + "Clear watchpoints on all NPCs in the range. If no range, list out" + " NPC watchpoints currently set.\n"); + break; + + case DEBUG_CLEAREVENTS: + if_print_debug ( + "Clear watchpoints on all events in the range. If no range, list out" + " event watchpoints currently set.\n"); + break; + + case DEBUG_CLEARTASKS: + if_print_debug ( + "Clear watchpoints on all tasks in the range. If no range, list out" + " task watchpoints currently set.\n"); + break; + + case DEBUG_CLEARVARIABLES: + if_print_debug ( + "Clear watchpoints on all game variables in the range. If no range," + " list variable watchpoints currently set.\n"); + break; + + case DEBUG_WATCHALL: + if_print_debug ( + "Print out a list of all all watchpoints set for all the classes of" + " item on which watchpoints can be used.\n"); + break; + + case DEBUG_CLEARALL: + if_print_debug ( + "Clear all watchpoints set, on all classes of item on which" + " watchpoints can be used.\n"); + break; + + default: + if_print_debug ( + "Sorry, there is no help available on that at the moment.\n"); + break; + } +} + + +/* + * debug_print_quoted() + * debug_print_player() + * debug_print_room() + * debug_print_object() + * debug_print_npc() + * debug_print_event() + * debug_print_task() + * debug_print_variable() + * + * Low level output helpers. + */ +static void +debug_print_quoted (const sc_char *string) +{ + if_print_debug_character ('"'); + if_print_debug (string); + if_print_debug_character ('"'); +} + +static void +debug_print_player (sc_gameref_t game) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[2]; + const sc_char *playername; + + vt_key[0].string = "Globals"; + vt_key[1].string = "PlayerName"; + playername = prop_get_string (bundle, "S<-ss", vt_key); + if_print_debug ("Player "); + debug_print_quoted (playername); +} + +static void +debug_print_room (sc_gameref_t game, sc_int room) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[3]; + sc_char buffer[32]; + const sc_char *name; + + if_print_debug ("Room "); + if (room < 0 || room >= gs_room_count (game)) + { + sprintf (buffer, "%ld ", room); + if_print_debug (buffer); + if_print_debug ("[Out of range]"); + return; + } + + vt_key[0].string = "Rooms"; + vt_key[1].integer = room; + vt_key[2].string = "Short"; + name = prop_get_string (bundle, "S<-sis", vt_key); + sprintf (buffer, "%ld ", room); + if_print_debug (buffer); + debug_print_quoted (name); +} + +static void +debug_print_object (sc_gameref_t game, sc_int object) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[3]; + sc_bool bstatic; + sc_char buffer[32]; + const sc_char *prefix, *name; + + if (object < 0 || object >= gs_object_count (game)) + { + if_print_debug ("Object "); + sprintf (buffer, "%ld ", object); + if_print_debug (buffer); + if_print_debug ("[Out of range]"); + return; + } + + vt_key[0].string = "Objects"; + vt_key[1].integer = object; + vt_key[2].string = "Static"; + bstatic = prop_get_boolean (bundle, "B<-sis", vt_key); + vt_key[2].string = "Prefix"; + prefix = prop_get_string (bundle, "S<-sis", vt_key); + vt_key[2].string = "Short"; + name = prop_get_string (bundle, "S<-sis", vt_key); + if (bstatic) + if_print_debug ("Static "); + else + if_print_debug ("Dynamic "); + sprintf (buffer, "%ld ", object); + if_print_debug (buffer); + debug_print_quoted (prefix); + if_print_debug_character (' '); + debug_print_quoted (name); +} + +static void +debug_print_npc (sc_gameref_t game, sc_int npc) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[3]; + sc_char buffer[32]; + const sc_char *prefix, *name; + + if_print_debug ("NPC "); + if (npc < 0 || npc >= gs_npc_count (game)) + { + sprintf (buffer, "%ld ", npc); + if_print_debug (buffer); + if_print_debug ("[Out of range]"); + return; + } + + vt_key[0].string = "NPCs"; + vt_key[1].integer = npc; + vt_key[2].string = "Prefix"; + prefix = prop_get_string (bundle, "S<-sis", vt_key); + vt_key[2].string = "Name"; + name = prop_get_string (bundle, "S<-sis", vt_key); + sprintf (buffer, "%ld ", npc); + if_print_debug (buffer); + debug_print_quoted (prefix); + if_print_debug_character (' '); + debug_print_quoted (name); +} + +static void +debug_print_event (sc_gameref_t game, sc_int event) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[3]; + sc_char buffer[32]; + const sc_char *name; + + if_print_debug ("Event "); + if (event < 0 || event >= gs_event_count (game)) + { + sprintf (buffer, "%ld ", event); + if_print_debug (buffer); + if_print_debug ("[Out of range]"); + return; + } + + vt_key[0].string = "Events"; + vt_key[1].integer = event; + vt_key[2].string = "Short"; + name = prop_get_string (bundle, "S<-sis", vt_key); + sprintf (buffer, "%ld ", event); + if_print_debug (buffer); + debug_print_quoted (name); +} + +static void +debug_print_task (sc_gameref_t game, sc_int task) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_vartype_t vt_key[4]; + sc_char buffer[32]; + const sc_char *command; + + if_print_debug ("Task "); + if (task < 0 || task >= gs_task_count (game)) + { + sprintf (buffer, "%ld ", task); + if_print_debug (buffer); + if_print_debug ("[Out of range]"); + return; + } + + vt_key[0].string = "Tasks"; + vt_key[1].integer = task; + vt_key[2].string = "Command"; + vt_key[3].integer = 0; + command = prop_get_string (bundle, "S<-sisi", vt_key); + sprintf (buffer, "%ld ", task); + if_print_debug (buffer); + debug_print_quoted (command); +} + +static void +debug_print_variable (sc_gameref_t game, sc_int variable) +{ + 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], vt_rvalue; + sc_char buffer[32]; + sc_int var_type; + const sc_char *name; + + if (variable < 0 || variable >= debug_variable_count (game)) + { + if_print_debug ("Variable "); + sprintf (buffer, "%ld ", variable); + if_print_debug (buffer); + if_print_debug ("[Out of range]"); + return; + } + + vt_key[0].string = "Variables"; + vt_key[1].integer = variable; + vt_key[2].string = "Name"; + name = prop_get_string (bundle, "S<-sis", vt_key); + + if (var_get (vars, name, &var_type, &vt_rvalue)) + { + switch (var_type) + { + case VAR_INTEGER: + if_print_debug ("Integer "); + break; + case VAR_STRING: + if_print_debug ("String "); + break; + default: + if_print_debug ("[Invalid type] "); + break; + } + } + else + if_print_debug ("[Invalid variable] "); + sprintf (buffer, "%ld ", variable); + if_print_debug (buffer); + debug_print_quoted (name); +} + + +/* + * debug_game() + * + * Display overall game details. + */ +static void +debug_game (sc_gameref_t game, sc_command_type_t type) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + const sc_debuggerref_t debug = debug_get_debugger (game); + sc_vartype_t vt_key[2]; + const sc_char *version, *gamename, *compiledate, *gameauthor; + sc_int perspective, waitturns; + sc_bool has_sound, has_graphics, has_battle; + sc_char buffer[32]; + assert (debug_is_valid (debug)); + + if (type != COMMAND_QUERY) + { + if_print_debug ("The Game command takes no arguments.\n"); + return; + } + + if_print_debug ("Game "); + vt_key[0].string = "Globals"; + vt_key[1].string = "GameName"; + gamename = prop_get_string (bundle, "S<-ss", vt_key); + debug_print_quoted (gamename); + if_print_debug_character ('\n'); + + if_print_debug (" Compiled "); + vt_key[0].string = "CompileDate"; + compiledate = prop_get_string (bundle, "S<-s", vt_key); + debug_print_quoted (compiledate); + + if_print_debug (", Author "); + vt_key[0].string = "Globals"; + vt_key[1].string = "GameAuthor"; + gameauthor = prop_get_string (bundle, "S<-ss", vt_key); + debug_print_quoted (gameauthor); + if_print_debug_character ('\n'); + + vt_key[0].string = "VersionString"; + version = prop_get_string (bundle, "S<-s", vt_key); + if_print_debug (" Version "); + if_print_debug (version); + + vt_key[0].string = "Globals"; + vt_key[1].string = "Perspective"; + perspective = prop_get_integer (bundle, "I<-ss", vt_key); + switch (perspective) + { + case 0: + if_print_debug (", First person"); + break; + case 1: + if_print_debug (", Second person"); + break; + case 2: + if_print_debug (", Third person"); + break; + default: + if_print_debug (", [Unknown perspective]"); + break; + } + + vt_key[0].string = "Globals"; + vt_key[1].string = "WaitTurns"; + waitturns = prop_get_integer (bundle, "I<-ss", vt_key); + if_print_debug (", Waitturns "); + sprintf (buffer, "%ld", waitturns); + if_print_debug (buffer); + + vt_key[0].string = "Globals"; + vt_key[1].string = "Sound"; + has_sound = prop_get_boolean (bundle, "B<-ss", vt_key); + vt_key[1].string = "Graphics"; + has_graphics = prop_get_boolean (bundle, "B<-ss", vt_key); + if (has_sound) + if_print_debug (", Sound"); + if (has_graphics) + if_print_debug (", Graphics"); + if_print_debug_character ('\n'); + + vt_key[0].string = "Globals"; + vt_key[1].string = "BattleSystem"; + has_battle = prop_get_boolean (bundle, "B<-ss", vt_key); + if (has_battle) + if_print_debug (" Battle system\n"); + + if_print_debug (" Room count "); + sprintf (buffer, "%ld", gs_room_count (game)); + if_print_debug (buffer); + + if_print_debug (", Object count "); + sprintf (buffer, "%ld", gs_object_count (game)); + if_print_debug (buffer); + + if_print_debug (", NPC count "); + sprintf (buffer, "%ld", gs_npc_count (game)); + if_print_debug (buffer); + if_print_debug_character ('\n'); + + if_print_debug (" Event count "); + sprintf (buffer, "%ld", gs_event_count (game)); + if_print_debug (buffer); + + if_print_debug (", Task count "); + sprintf (buffer, "%ld", gs_task_count (game)); + if_print_debug (buffer); + + if_print_debug (", Variable count "); + sprintf (buffer, "%ld", debug_variable_count (game)); + if_print_debug (buffer); + if_print_debug_character ('\n'); + + if (game->is_running) + if_print_debug (" Running"); + else + if_print_debug (" Not running"); + if (game->has_completed) + if_print_debug (", Completed"); + else + if_print_debug (", Not completed"); + if (game->verbose) + if_print_debug (", Verbose"); + else + if_print_debug (", Not verbose"); + if (game->bold_room_names) + if_print_debug (", Bold"); + else + if_print_debug (", Not bold"); + if (game->undo_available) + if_print_debug (", Undo"); + else + if_print_debug (", No undo"); + if_print_debug_character ('\n'); + + if_print_debug (" Score "); + sprintf (buffer, "%ld", game->score); + if_print_debug (buffer); + if_print_debug (", Turns "); + sprintf (buffer, "%ld", game->turns); + if_print_debug (buffer); + if_print_debug (", Seconds "); + sprintf (buffer, "%lu", debug->elapsed_seconds); + if_print_debug (buffer); + if_print_debug_character ('\n'); +} + + +/* + * debug_player() + * + * Print a few brief details about the player status. + */ +static void +debug_player (sc_gameref_t game, + sc_command_t command, sc_command_type_t type) +{ + if (type != COMMAND_QUERY) + { + if_print_debug ("The Player command takes no arguments.\n"); + return; + } + + if (command == DEBUG_OLDPLAYER) + { + if (!game->undo_available) + { + if_print_debug ("There is no previous game state to examine.\n"); + return; + } + + game = game->undo; + assert (gs_is_game_valid (game)); + } + + debug_print_player (game); + if_print_debug_character ('\n'); + + if (gs_playerroom (game) == -1) + if_print_debug (" Hidden!\n"); + else + { + if_print_debug (" In "); + debug_print_room (game, gs_playerroom (game)); + if_print_debug_character ('\n'); + } + + switch (gs_playerposition (game)) + { + case 0: + if_print_debug (" Standing\n"); + break; + case 1: + if_print_debug (" Sitting\n"); + break; + case 2: + if_print_debug (" Lying\n"); + break; + default: + if_print_debug (" [Invalid position]\n"); + break; + } + + if (gs_playerparent (game) != -1) + { + if_print_debug (" Parent is "); + debug_print_object (game, gs_playerparent (game)); + if_print_debug_character ('\n'); + } +} + + +/* + * debug_normalize_arguments() + * + * Normalize a set of arguments parsed from a debugger command line, for + * debug commands that take ranges. + */ +static sc_bool +debug_normalize_arguments (sc_command_type_t type, + sc_int *arg1, sc_int *arg2, sc_int limit) +{ + sc_int low = 0, high = 0; + + /* Set range low and high depending on the command type. */ + switch (type) + { + case COMMAND_QUERY: + case COMMAND_ALL: + low = 0; + high = limit - 1; + break; + case COMMAND_ONE: + low = *arg1; + high = *arg1; + break; + case COMMAND_RANGE: + low = *arg1; + high = *arg2; + break; + default: + sc_fatal ("debug_normalize_arguments: bad command type\n"); + } + + /* If range is valid, copy out and return TRUE. */ + if (low >= 0 && low < limit && high >= 0 && high < limit && high >= low) + { + *arg1 = low; + *arg2 = high; + return TRUE; + } + + /* Input range is invalid. */ + return FALSE; +} + + +/* + * debug_filter_room() + * debug_dump_room() + * + * Print details of rooms and their direct contents. + */ +static sc_bool +debug_filter_room (sc_gameref_t game, sc_int room) +{ + return room == gs_playerroom (game); +} + +static void +debug_dump_room (sc_gameref_t game, sc_int room) +{ + sc_int object, npc; + + debug_print_room (game, room); + if_print_debug_character ('\n'); + + if (gs_room_seen (game, room)) + if_print_debug (" Visited\n"); + else + if_print_debug (" Not visited\n"); + + if (gs_playerroom (game) == room) + { + if_print_debug (" "); + debug_print_player (game); + if_print_debug_character ('\n'); + } + + for (object = 0; object < gs_object_count (game); object++) + { + if (obj_indirectly_in_room (game, object, room)) + { + if_print_debug (" "); + debug_print_object (game, object); + if_print_debug_character ('\n'); + } + } + + for (npc = 0; npc < gs_npc_count (game); npc++) + { + if (npc_in_room (game, npc, room)) + { + if_print_debug (" "); + debug_print_npc (game, npc); + if_print_debug_character ('\n'); + } + } +} + + +/* + * debug_filter_object() + * debug_dump_object() + * + * Print the changeable details of game objects. + */ +static sc_bool +debug_filter_object (sc_gameref_t game, sc_int object) +{ + return obj_indirectly_in_room (game, object, gs_playerroom (game)); +} + +static void +debug_dump_object (sc_gameref_t game, sc_int object) +{ + const sc_prop_setref_t bundle = gs_get_bundle (game); + sc_int openness; + sc_vartype_t vt_key[3]; + sc_bool bstatic, is_statussed; + sc_int position, parent; + + debug_print_object (game, object); + if_print_debug_character ('\n'); + + vt_key[0].string = "Objects"; + vt_key[1].integer = object; + vt_key[2].string = "Static"; + bstatic = prop_get_boolean (bundle, "B<-sis", vt_key); + + if (gs_object_seen (game, object)) + if_print_debug (" Seen"); + else + if_print_debug (" Not seen"); + if (bstatic) + { + if (gs_object_static_unmoved (game, object)) + if_print_debug (", Not relocated"); + else + if_print_debug (", Relocated"); + } + else + { + vt_key[2].string = "OnlyWhenNotMoved"; + if (prop_get_integer (bundle, "I<-sis", vt_key) == 1) + { + if (gs_object_unmoved (game, object)) + if_print_debug (", Not moved"); + else + if_print_debug (", Moved"); + } + } + openness = gs_object_openness (game, object); + switch (openness) + { + case OBJ_OPEN: + if_print_debug (", Open"); + break; + case OBJ_CLOSED: + if_print_debug (", Closed"); + break; + case OBJ_LOCKED: + if_print_debug (", Locked"); + break; + } + if_print_debug_character ('\n'); + + position = gs_object_position (game, object); + parent = gs_object_parent (game, object); + switch (position) + { + case OBJ_HIDDEN: + if (bstatic) + if_print_debug (" Static default\n"); + else + if_print_debug (" Hidden\n"); + break; + case OBJ_HELD_PLAYER: + if_print_debug (" Held by "); + debug_print_player (game); + if_print_debug_character ('\n'); + break; + case OBJ_HELD_NPC: + if_print_debug (" Held by "); + debug_print_npc (game, parent); + if_print_debug_character ('\n'); + break; + case OBJ_WORN_PLAYER: + if_print_debug (" Worn by "); + debug_print_player (game); + if_print_debug_character ('\n'); + break; + case OBJ_WORN_NPC: + if_print_debug (" Worn by "); + debug_print_npc (game, parent); + if_print_debug_character ('\n'); + break; + case OBJ_PART_NPC: + if_print_debug (" Part of "); + if (parent == -1) + debug_print_player (game); + else + debug_print_npc (game, parent); + if_print_debug_character ('\n'); + break; + case OBJ_ON_OBJECT: + if_print_debug (" On "); + debug_print_object (game, parent); + if_print_debug_character ('\n'); + break; + case OBJ_IN_OBJECT: + if_print_debug (" Inside "); + debug_print_object (game, parent); + if_print_debug_character ('\n'); + break; + default: + if_print_debug (" In "); + debug_print_room (game, position - 1); + if_print_debug_character ('\n'); + break; + } + + vt_key[0].string = "Objects"; + vt_key[1].integer = object; + vt_key[2].string = "CurrentState"; + is_statussed = prop_get_integer (bundle, "I<-sis", vt_key) != 0; + if (is_statussed) + { + sc_char buffer[32]; + const sc_char *states; + + if_print_debug (" State "); + sprintf (buffer, "%ld", gs_object_state (game, object)); + if_print_debug (buffer); + + vt_key[2].string = "States"; + states = prop_get_string (bundle, "S<-sis", vt_key); + if_print_debug (" of "); + debug_print_quoted (states); + if_print_debug_character ('\n'); + } +} + + +/* + * debug_filter_npc() + * debug_dump_npc() + * + * Print stuff about NPCs. + */ +static sc_bool +debug_filter_npc (sc_gameref_t game, sc_int npc) +{ + return npc_in_room (game, npc, gs_playerroom (game)); +} + +static void +debug_dump_npc (sc_gameref_t game, sc_int npc) +{ + debug_print_npc (game, npc); + if_print_debug_character ('\n'); + + if (gs_npc_seen (game, npc)) + if_print_debug (" Seen\n"); + else + if_print_debug (" Not seen\n"); + + if (gs_npc_location (game, npc) - 1 == -1) + if_print_debug (" Hidden\n"); + else + { + if_print_debug (" In "); + debug_print_room (game, gs_npc_location (game, npc) - 1); + if_print_debug_character ('\n'); + } + + switch (gs_npc_position (game, npc)) + { + case 0: + if_print_debug (" Standing\n"); + break; + case 1: + if_print_debug (" Sitting\n"); + break; + case 2: + if_print_debug (" Lying\n"); + break; + default: + if_print_debug (" [Invalid position]\n"); + break; + } + + if (gs_npc_parent (game, npc) != -1) + { + if_print_debug (" Parent is "); + debug_print_object (game, gs_npc_parent (game, npc)); + if_print_debug_character ('\n'); + } + + if (gs_npc_walkstep_count (game, npc) > 0) + { + sc_char buffer[32]; + sc_int walk; + + if_print_debug (" Walkstep count "); + sprintf (buffer, "%ld", gs_npc_walkstep_count (game, npc)); + if_print_debug (buffer); + if_print_debug (", Walks { "); + for (walk = 0; walk < gs_npc_walkstep_count (game, npc); walk++) + { + sprintf (buffer, "%ld", gs_npc_walkstep (game, npc, walk)); + if_print_debug (buffer); + if_print_debug_character (' '); + } + if_print_debug ("}.\n"); + } +} + + +/* + * debug_filter_event() + * debug_dump_event() + * + * Print stuff about events. + */ +static sc_bool +debug_filter_event (sc_gameref_t game, sc_int event) +{ + return gs_event_state (game, event) == ES_RUNNING; +} + +static void +debug_dump_event (sc_gameref_t game, sc_int event) +{ + sc_char buffer[32]; + + debug_print_event (game, event); + if_print_debug_character ('\n'); + + switch (gs_event_state (game, event)) + { + case ES_WAITING: + if_print_debug (" Waiting\n"); + break; + case ES_RUNNING: + if_print_debug (" Running\n"); + break; + case ES_AWAITING: + if_print_debug (" Awaiting\n"); + break; + case ES_FINISHED: + if_print_debug (" Finished\n"); + break; + case ES_PAUSED: + if_print_debug (" Paused\n"); + break; + default: + if_print_debug (" [Invalid state]\n"); + break; + } + + if_print_debug (" Time "); + sprintf (buffer, "%ld\n", gs_event_time (game, event)); + if_print_debug (buffer); +} + + +/* + * debug_filter_task() + * debug_dump_task() + * + * Print stuff about tasks. + */ +static sc_bool +debug_filter_task (sc_gameref_t game, sc_int task) +{ + return task_can_run_task (game, task); +} + +static void +debug_dump_task (sc_gameref_t game, sc_int task) +{ + debug_print_task (game, task); + if_print_debug_character ('\n'); + + if (task_can_run_task (game, task)) + if_print_debug (" Runnable"); + else + if_print_debug (" Not runnable"); + if (gs_task_done (game, task)) + if_print_debug (", Done"); + else + if_print_debug (", Not done"); + if (gs_task_scored (game, task)) + if_print_debug (", Scored\n"); + else + if_print_debug (", Not scored\n"); +} + + +/* + * debug_dump_variable() + * + * Print stuff about variables. + */ +static void +debug_dump_variable (sc_gameref_t game, sc_int variable) +{ + 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], vt_rvalue; + const sc_char *name; + sc_int var_type; + + debug_print_variable (game, variable); + if_print_debug_character ('\n'); + + vt_key[0].string = "Variables"; + vt_key[1].integer = variable; + vt_key[2].string = "Name"; + name = prop_get_string (bundle, "S<-sis", vt_key); + + if_print_debug (" Value = "); + if (var_get (vars, name, &var_type, &vt_rvalue)) + { + switch (var_type) + { + case VAR_INTEGER: + { + sc_char buffer[32]; + + sprintf (buffer, "%ld", vt_rvalue.integer); + if_print_debug (buffer); + break; + } + case VAR_STRING: + debug_print_quoted (vt_rvalue.string); + break; + default: + if_print_debug ("[Unknown]"); + break; + } + } + else + if_print_debug ("[Unknown]"); + if_print_debug_character ('\n'); +} + + +/* + * debug_dump_common() + * + * Common handler for iterating dumps of classes. + */ +static void +debug_dump_common (sc_gameref_t game, sc_command_t command, + sc_command_type_t type, sc_int arg1, sc_int arg2) +{ + sc_int low = arg1, high = arg2; + sc_int limit, index_; + const sc_char *class_; + sc_bool (*filter_function) (sc_gameref_t, sc_int); + void (*dumper_function) (sc_gameref_t, sc_int); + sc_bool printed = FALSE; + + /* Initialize variables to avoid gcc warnings. */ + limit = 0; + class_ = NULL; + filter_function = NULL; + dumper_function = NULL; + + /* Switch to undo game on relevant commands. */ + switch (command) + { + case DEBUG_OLDROOMS: + case DEBUG_OLDOBJECTS: + case DEBUG_OLDNPCS: + case DEBUG_OLDEVENTS: + case DEBUG_OLDTASKS: + case DEBUG_OLDVARIABLES: + if (!game->undo_available) + { + if_print_debug ("There is no previous game state to examine.\n"); + return; + } + + game = game->undo; + assert (gs_is_game_valid (game)); + break; + + default: + break; + } + + /* Demultiplex dump command. */ + switch (command) + { + case DEBUG_ROOMS: + case DEBUG_OLDROOMS: + class_ = "Room"; + filter_function = debug_filter_room; + dumper_function = debug_dump_room; + limit = gs_room_count (game); + break; + case DEBUG_OBJECTS: + case DEBUG_OLDOBJECTS: + class_ = "Object"; + filter_function = debug_filter_object; + dumper_function = debug_dump_object; + limit = gs_object_count (game); + break; + case DEBUG_NPCS: + case DEBUG_OLDNPCS: + class_ = "NPC"; + filter_function = debug_filter_npc; + dumper_function = debug_dump_npc; + limit = gs_npc_count (game); + break; + case DEBUG_EVENTS: + case DEBUG_OLDEVENTS: + class_ = "Event"; + filter_function = debug_filter_event; + dumper_function = debug_dump_event; + limit = gs_event_count (game); + break; + case DEBUG_TASKS: + case DEBUG_OLDTASKS: + class_ = "Task"; + filter_function = debug_filter_task; + dumper_function = debug_dump_task; + limit = gs_task_count (game); + break; + case DEBUG_VARIABLES: + case DEBUG_OLDVARIABLES: + class_ = "Variable"; + filter_function = NULL; + dumper_function = debug_dump_variable; + limit = debug_variable_count (game); + break; + default: + sc_fatal ("debug_dump_common: invalid command\n"); + } + + /* Normalize to this limit. */ + if (!debug_normalize_arguments (type, &low, &high, limit)) + { + if (limit == 0) + { + if_print_debug ("There is nothing of type "); + debug_print_quoted (class_); + if_print_debug (" to print.\n"); + } + else + { + if_print_debug ("Invalid item or range for "); + debug_print_quoted (class_); + if (limit == 1) + if_print_debug ("; only 0 is valid.\n"); + else + { + sc_char buffer[32]; + + if_print_debug ("; valid values are 0 to "); + sprintf (buffer, "%ld", limit - 1); + if_print_debug (buffer); + if_print_debug (".\n"); + } + } + return; + } + + /* Print each item of the class, filtering on query commands. */ + for (index_ = low; index_ <= high; index_++) + { + if (type == COMMAND_QUERY + && filter_function && !filter_function (game, index_)) + continue; + + if (printed) + if_print_debug_character ('\n'); + dumper_function (game, index_); + printed = TRUE; + } + if (!printed) + { + if_print_debug ("Nothing of type "); + debug_print_quoted (class_); + if_print_debug (" is relevant.\nTry \""); + if_print_debug (class_); + if_print_debug (" *\" to show all items of this type.\n"); + } +} + + +/* + * debug_buffer() + * + * Print the current raw printfilter contents. + */ +static void +debug_buffer (sc_gameref_t game, sc_command_type_t type) +{ + const sc_filterref_t filter = gs_get_filter (game); + const sc_char *buffer; + + if (type != COMMAND_QUERY) + { + if_print_debug ("The Buffer command takes no arguments.\n"); + return; + } + + buffer = pf_get_buffer (filter); + if (buffer) + if_print_debug (buffer); + else + if_print_debug ("There is no game text buffered.\n"); +} + + +/* + * debug_print_resource() + * + * Helper for debug_resources(). + */ +static void +debug_print_resource (const sc_resource_t *resource) +{ + sc_char buffer[32]; + + debug_print_quoted (resource->name); + if_print_debug (", offset "); + sprintf (buffer, "%ld", resource->offset); + if_print_debug (buffer); + if_print_debug (", length "); + sprintf (buffer, "%ld", resource->length); + if_print_debug (buffer); +} + + +/* + * debug_resources() + * + * Print any active and requested resources. + */ +static void +debug_resources (sc_gameref_t game, sc_command_type_t type) +{ + sc_bool printed = FALSE; + + if (type != COMMAND_QUERY) + { + if_print_debug ("The Resources command takes no arguments.\n"); + return; + } + + if (game->stop_sound) + { + if_print_debug ("Sound stop"); + if (strlen (game->requested_sound.name) > 0) + if_print_debug (" before new sound"); + if_print_debug (" requested"); + if (game->sound_active) + if_print_debug (", sound active"); + if_print_debug (".\n"); + printed = TRUE; + } + if (!res_compare_resource (&game->requested_sound, + &game->playing_sound)) + { + if_print_debug ("Requested Sound "); + debug_print_resource (&game->requested_sound); + if_print_debug (".\n"); + printed = TRUE; + } + if (!res_compare_resource (&game->requested_graphic, + &game->displayed_graphic)) + { + if_print_debug ("Requested Graphic "); + debug_print_resource (&game->requested_graphic); + if_print_debug (".\n"); + printed = TRUE; + } + + if (strlen (game->playing_sound.name) > 0) + { + if_print_debug ("Playing Sound "); + debug_print_resource (&game->playing_sound); + if_print_debug (".\n"); + printed = TRUE; + } + if (strlen (game->displayed_graphic.name) > 0) + { + if_print_debug ("Displaying Graphic "); + debug_print_resource (&game->displayed_graphic); + if_print_debug (".\n"); + printed = TRUE; + } + + if (!printed) + if_print_debug ("There is no game resource activity.\n"); +} + + +/* + * debug_random() + * + * Report the PRNG in use, and seed the random number generator to the + * given value. + */ +static void +debug_random (sc_command_type_t type, sc_int new_seed) +{ + const sc_char *random_type; + sc_char buffer[32]; + + if (type != COMMAND_ONE && type != COMMAND_QUERY) + { + if_print_debug ("The Random command takes either one argument or" + " no arguments.\n"); + return; + } + + random_type = sc_is_congruential_random () ? "congruential" : "platform"; + + if (type == COMMAND_QUERY) + { + if_print_debug ("The "); + if_print_debug (random_type); + if_print_debug (" random number generator is selected.\n"); + return; + } + + if (new_seed == 0) + { + if_print_debug ("The seed value may not be zero.\n"); + return; + } + + sc_seed_random (new_seed); + + if_print_debug ("Set seed "); + sprintf (buffer, "%ld", new_seed); + if_print_debug (buffer); + if_print_debug (" for the "); + if_print_debug (random_type); + if_print_debug (" random number generator.\n"); +} + + +/* + * debug_watchpoint_common() + * + * Common handler for setting and clearing watchpoints. + */ +static void +debug_watchpoint_common (sc_gameref_t game, sc_command_t command, + sc_command_type_t type, sc_int arg1, sc_int arg2) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + sc_int low = arg1, high = arg2; + sc_int limit, index_; + const sc_char *class_; + sc_bool *watchpoints, action; + sc_char buffer[32]; + assert (debug_is_valid (debug)); + + /* Initialize variables to avoid gcc warnings. */ + limit = 0; + class_ = NULL; + watchpoints = NULL; + action = FALSE; + + /* Set action to TRUE or FALSE, for setting/clearing watchpoints. */ + switch (command) + { + case DEBUG_WATCHPLAYER: + case DEBUG_WATCHOBJECTS: + case DEBUG_WATCHNPCS: + case DEBUG_WATCHEVENTS: + case DEBUG_WATCHTASKS: + case DEBUG_WATCHVARIABLES: + action = TRUE; + break; + case DEBUG_CLEARPLAYER: + case DEBUG_CLEAROBJECTS: + case DEBUG_CLEARNPCS: + case DEBUG_CLEAREVENTS: + case DEBUG_CLEARTASKS: + case DEBUG_CLEARVARIABLES: + action = FALSE; + break; + default: + sc_fatal ("debug_watchpoint_common: invalid command\n"); + } + + /* Handle player watchpoint setting. */ + if (command == DEBUG_WATCHPLAYER || command == DEBUG_CLEARPLAYER) + { + if (command == DEBUG_CLEARPLAYER) + { + debug->watch_player = action; + if_print_debug ("Cleared Player watchpoint.\n"); + } + else if (type == COMMAND_ONE && arg1 == 0) + { + debug->watch_player = action; + if_print_debug ("Set Player watchpoint.\n"); + } + else + { + if (debug->watch_player) + if_print_debug ("Player watchpoint is set.\n"); + else + if_print_debug ("No Player watchpoint is set; to set one, use" + " \"Watchplayer 0\".\n"); + } + return; + } + + /* Demultiplex watchpoint command. */ + switch (command) + { + case DEBUG_WATCHOBJECTS: + case DEBUG_CLEAROBJECTS: + class_ = "Object"; + watchpoints = debug->watch_objects; + limit = gs_object_count (game); + break; + case DEBUG_WATCHNPCS: + case DEBUG_CLEARNPCS: + class_ = "NPC"; + watchpoints = debug->watch_npcs; + limit = gs_npc_count (game); + break; + case DEBUG_WATCHEVENTS: + case DEBUG_CLEAREVENTS: + class_ = "Event"; + watchpoints = debug->watch_events; + limit = gs_event_count (game); + break; + case DEBUG_WATCHTASKS: + case DEBUG_CLEARTASKS: + class_ = "Task"; + watchpoints = debug->watch_tasks; + limit = gs_task_count (game); + break; + case DEBUG_WATCHVARIABLES: + case DEBUG_CLEARVARIABLES: + class_ = "Variable"; + watchpoints = debug->watch_variables; + limit = debug_variable_count (game); + break; + default: + sc_fatal ("debug_watchpoint_common: invalid command\n"); + } + + /* Normalize to this limit. */ + if (!debug_normalize_arguments (type, &low, &high, limit)) + { + if (limit == 0) + { + if_print_debug ("There is nothing of type "); + debug_print_quoted (class_); + if_print_debug (" to watch.\n"); + } + else + { + if_print_debug ("Invalid item or range for "); + debug_print_quoted (class_); + if (limit == 1) + if_print_debug ("; only 0 is valid.\n"); + else + { + if_print_debug ("; valid values are 0 to "); + sprintf (buffer, "%ld", limit - 1); + if_print_debug (buffer); + if_print_debug (".\n"); + } + } + return; + } + + /* On query, search the array for set flags, and print out. */ + if (type == COMMAND_QUERY) + { + sc_bool printed = FALSE; + + /* Scan for set watchpoints, and list each found. */ + for (index_ = low; index_ <= high; index_++) + { + if (watchpoints[index_]) + { + if (!printed) + { + if_print_debug ("Watchpoints are set for "); + if_print_debug (class_); + if_print_debug (" { "); + } + sprintf (buffer, "%ld", index_); + if_print_debug (buffer); + if_print_debug_character (' '); + printed = TRUE; + } + } + if (printed) + if_print_debug ("}.\n"); + else + { + if_print_debug ("No "); + if_print_debug (class_); + if_print_debug (" watchpoints are set.\n"); + } + return; + } + + /* + * For non-queries, set watchpoint flags as defined in action for + * the range determined, and print confirmation. + */ + for (index_ = low; index_ <= high; index_++) + watchpoints[index_] = action; + + if (action) + if_print_debug ("Set "); + else + if_print_debug ("Cleared "); + sprintf (buffer, "%ld ", high - low + 1); + if_print_debug (buffer); + if_print_debug (class_); + if (high == low) + if_print_debug (" watchpoint.\n"); + else + if_print_debug (" watchpoints.\n"); +} + + +/* + * debug_watchall_common() + * + * Common handler to list out and clear all set watchpoints at a stroke. + */ +static void +debug_watchall_common (sc_gameref_t game, + sc_command_t command, sc_command_type_t type) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + assert (debug_is_valid (debug)); + + if (type != COMMAND_QUERY) + { + if (command == DEBUG_WATCHALL) + if_print_debug ("The Watchall command takes no arguments.\n"); + else + if_print_debug ("The Clearall command takes no arguments.\n"); + return; + } + + /* Query all set watchpoints using common watchpoint handler... */ + if (command == DEBUG_WATCHALL) + { + debug_watchpoint_common (game, + DEBUG_WATCHPLAYER, COMMAND_QUERY, 0, 0); + debug_watchpoint_common (game, + DEBUG_WATCHOBJECTS, COMMAND_QUERY, 0, 0); + debug_watchpoint_common (game, + DEBUG_WATCHNPCS, COMMAND_QUERY, 0, 0); + debug_watchpoint_common (game, + DEBUG_WATCHEVENTS, COMMAND_QUERY, 0, 0); + debug_watchpoint_common (game, + DEBUG_WATCHTASKS, COMMAND_QUERY, 0, 0); + debug_watchpoint_common (game, + DEBUG_WATCHVARIABLES, COMMAND_QUERY, 0, 0); + return; + } + + /* ...but reset all the fast way, with memset(). */ + assert (command == DEBUG_CLEARALL); + debug->watch_player = FALSE; + memset (debug->watch_objects, FALSE, + gs_object_count (game) * sizeof (*debug->watch_objects)); + memset (debug->watch_npcs, FALSE, + gs_npc_count (game) * sizeof (*debug->watch_npcs)); + memset (debug->watch_events, FALSE, + gs_event_count (game) * sizeof (*debug->watch_events)); + memset (debug->watch_tasks, FALSE, + gs_task_count (game) * sizeof (*debug->watch_tasks)); + memset (debug->watch_variables, FALSE, + debug_variable_count (game) * sizeof (*debug->watch_variables)); + if_print_debug ("Cleared all watchpoints.\n"); +} + + +/* + * debug_compare_object() + * + * Compare two objects, and return TRUE if the same. + */ +static sc_bool +debug_compare_object (sc_gameref_t from, sc_gameref_t with, sc_int object) +{ + const sc_objectstate_t *from_object = from->objects + object; + const sc_objectstate_t *with_object = with->objects + object; + + return from_object->unmoved == with_object->unmoved + && from_object->static_unmoved == with_object->static_unmoved + && from_object->position == with_object->position + && from_object->parent == with_object->parent + && from_object->openness == with_object->openness + && from_object->state == with_object->state + && from_object->seen == with_object->seen; +} + + +/* + * debug_compare_npc() + * + * Compare two NPCs, and return TRUE if the same. + */ +static sc_bool +debug_compare_npc (sc_gameref_t from, sc_gameref_t with, sc_int npc) +{ + const sc_npcstate_t *from_npc = from->npcs + npc; + const sc_npcstate_t *with_npc = with->npcs + npc; + + if (from_npc->walkstep_count != with_npc->walkstep_count) + sc_fatal ("debug_compare_npc: walkstep count error\n"); + + return from_npc->location == with_npc->location + && from_npc->position == with_npc->position + && from_npc->parent == with_npc->parent + && from_npc->seen == with_npc->seen + && memcmp (from_npc->walksteps, with_npc->walksteps, + from_npc->walkstep_count + * sizeof (*from_npc->walksteps)) == 0; +} + + +/* + * debug_compare_event() + * + * Compare two events, and return TRUE if the same. + */ +static sc_bool +debug_compare_event (sc_gameref_t from, sc_gameref_t with, sc_int event) +{ + const sc_eventstate_t *from_event = from->events + event; + const sc_eventstate_t *with_event = with->events + event; + + return from_event->state == with_event->state + && from_event->time == with_event->time; +} + + +/* + * debug_compare_task() + * + * Compare two tasks, and return TRUE if the same. + */ +static sc_bool +debug_compare_task (sc_gameref_t from, sc_gameref_t with, sc_int task) +{ + const sc_taskstate_t *from_task = from->tasks + task; + const sc_taskstate_t *with_task = with->tasks + task; + + return from_task->done == with_task->done + && from_task->scored == with_task->scored; +} + + +/* + * debug_compare_variable() + * + * Compare two variables, and return TRUE if the same. + */ +static sc_bool +debug_compare_variable (sc_gameref_t from, sc_gameref_t with, sc_int variable) +{ + const sc_prop_setref_t bundle = from->bundle; + const sc_var_setref_t from_var = from->vars; + const sc_var_setref_t with_var = with->vars; + sc_vartype_t vt_key[3], vt_rvalue, vt_rvalue2; + const sc_char *name; + sc_int var_type, var_type2; + sc_bool equal = FALSE; + + if (from->bundle != with->bundle) + sc_fatal ("debug_compare_variable: property sharing malfunction\n"); + + vt_key[0].string = "Variables"; + vt_key[1].integer = variable; + vt_key[2].string = "Name"; + name = prop_get_string (bundle, "S<-sis", vt_key); + + if (!var_get (from_var, name, &var_type, &vt_rvalue) + || !var_get (with_var, name, &var_type2, &vt_rvalue2)) + sc_fatal ("debug_compare_variable: can't find variable %s\n", name); + else if (var_type != var_type2) + sc_fatal ("debug_compare_variable: variable type mismatch %s\n", name); + + switch (var_type) + { + case VAR_INTEGER: + equal = (vt_rvalue.integer == vt_rvalue2.integer); + break; + case VAR_STRING: + equal = !strcmp (vt_rvalue.string, vt_rvalue2.string); + break; + default: + sc_fatal ("debug_compare_variable:" + " invalid variable type, %ld\n", var_type); + } + + return equal; +} + + +/* + * debug_check_class() + * + * Central handler for checking watchpoints. Compares a number of items + * of a class using the comparison function given, where indicated by a + * watchpoints flags array. Prints entries that differ, and returns TRUE + * if any differed. + */ +static sc_bool +debug_check_class (sc_gameref_t from, sc_gameref_t with, + const sc_char *class_, sc_int class_count, + const sc_bool *watchpoints, + sc_bool (*const compare_function) + (sc_gameref_t, sc_gameref_t, sc_int)) +{ + sc_int index_; + sc_bool triggered = FALSE; + + /* + * Scan the watchpoints array for set watchpoints, comparing classes + * where the watchpoint flag is set. + */ + for (index_ = 0; index_ < class_count; index_++) + { + if (!watchpoints[index_]) + continue; + + if (!compare_function (from, with, index_)) + { + sc_char buffer[32]; + + if (!triggered) + { + if_print_debug ("--- "); + if_print_debug (class_); + if_print_debug (" watchpoint triggered { "); + } + sprintf (buffer, "%ld ", index_); + if_print_debug (buffer); + triggered = TRUE; + } + } + if (triggered) + if_print_debug ("}.\n"); + + /* Return TRUE if anything differed. */ + return triggered; +} + + +/* + * debug_check_watchpoints() + * + * Checks the game against the undo game for all set watchpoints. Returns + * TRUE if any triggered, FALSE if none (or if the undo game isn't available, + * in which case no check is possible). + */ +static sc_bool +debug_check_watchpoints (sc_gameref_t game) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + const sc_gameref_t undo = game->undo; + sc_bool triggered; + assert (debug_is_valid (debug) && gs_is_game_valid (undo)); + + /* If no undo is present, no check is possible. */ + if (!game->undo_available) + return FALSE; + + /* Check first for player watchpoint. */ + triggered = FALSE; + if (debug->watch_player) + { + if (gs_playerroom (game) != gs_playerroom (undo) + || gs_playerposition (game) != gs_playerposition (undo) + || gs_playerparent (game) != gs_playerparent (undo)) + { + if_print_debug ("--- Player watchpoint triggered.\n"); + triggered |= TRUE; + } + } + + /* Now check other classes of watchpoint. */ + triggered |= debug_check_class (game, undo, + "Object", gs_object_count (game), + debug->watch_objects, debug_compare_object); + triggered |= debug_check_class (game, undo, + "NPC", gs_npc_count (game), + debug->watch_npcs, debug_compare_npc); + triggered |= debug_check_class (game, undo, + "Event", gs_event_count (game), + debug->watch_events, debug_compare_event); + triggered |= debug_check_class (game, undo, + "Task", gs_task_count (game), + debug->watch_tasks, debug_compare_task); + triggered |= debug_check_class (game, undo, + "Variable", debug_variable_count (game), + debug->watch_variables, + debug_compare_variable); + + return triggered; +} + + +/* + * debug_parse_command() + * + * Given a debugging command string, try to parse it and return the + * appropriate command and its arguments. Returns DEBUG_NONE if the parse + * fails. + */ +static sc_command_t +debug_parse_command (const sc_char *command_string, + sc_command_type_t *type, + sc_int *arg1, sc_int *arg2, sc_command_t *help_topic) +{ + sc_command_t return_command; + sc_command_type_t return_type; + sc_int val1, val2, converted, matches; + sc_char *help, *string, junk, wildcard; + sc_bool is_help, is_parsed, is_wildcard; + const sc_strings_t *entry; + + /* Allocate temporary strings long enough to take a copy of the input. */ + string = (sc_char *)sc_malloc (strlen (command_string) + 1); + help = (sc_char *)sc_malloc (strlen (command_string) + 1); + + /* + * Parse the input line, in a very simplistic fashion. The argument count + * is one less than sscanf converts. + */ + is_parsed = is_wildcard = is_help = FALSE; + val1 = val2 = 0; + converted = sscanf (command_string, " %s %s %c", help, string, &junk); + if (converted == 2 && sc_strcasecmp (help, "help") == 0) + { + is_help = TRUE; + is_parsed = TRUE; + } + sc_free (help); + if (!is_parsed) + { + converted = sscanf (command_string, + " %s %ld to %ld %c", string, &val1, &val2, &junk); + if (converted != 3) + converted = sscanf (command_string, + " %s %ld - %ld %c", string, &val1, &val2, &junk); + if (converted != 3) + converted = sscanf (command_string, + " %s %ld .. %ld %c", string, &val1, &val2, &junk); + if (converted != 3) + converted = sscanf (command_string, + " %s %ld %ld %c", string, &val1, &val2, &junk); + is_parsed |= converted == 3; + } + if (!is_parsed) + { + converted = sscanf (command_string, + " %s %ld %c", string, &val1, &junk); + is_parsed |= converted == 2; + } + if (!is_parsed) + { + converted = sscanf (command_string, + " %s %c %c", string, &wildcard, &junk); + if (converted == 2 && wildcard == '*') + { + is_wildcard = TRUE; + is_parsed = TRUE; + } + else + is_parsed |= converted == 1; + } + if (!is_parsed) + { + if_print_debug ("Invalid debug command."); + if_print_debug (" Type 'help' for a list of valid commands.\n"); + sc_free (string); + return DEBUG_NONE; + } + + /* Decide on a command type based on the parse. */ + if (is_wildcard) + return_type = COMMAND_ALL; + else if (converted == 3) + return_type = COMMAND_RANGE; + else if (converted == 2) + return_type = COMMAND_ONE; + else + return_type = COMMAND_QUERY; + + /* + * Find the first unambiguous command matching the string. If none, + * return DEBUG_NONE. + */ + matches = 0; + return_command = DEBUG_NONE; + for (entry = DEBUG_COMMANDS; entry->command_string; entry++) + { + if (sc_strncasecmp (string, entry->command_string, strlen (string)) == 0) + { + matches++; + return_command = entry->command; + } + } + if (matches != 1) + { + if (matches > 1) + if_print_debug ("Ambiguous debug command."); + else + if_print_debug ("Unrecognized debug command."); + if_print_debug (" Type 'help' for a list of valid commands.\n"); + sc_free (string); + return DEBUG_NONE; + } + + /* Done with temporary command parse area. */ + sc_free (string); + + /* + * Return the command type, arguments, and the debugging command. For help + * <topic>, the command is help, with the command on which help requested + * in *help_topic. All clear, then? + */ + *type = return_type; + *arg1 = val1; + *arg2 = val2; + *help_topic = is_help ? return_command : DEBUG_NONE; + return is_help ? DEBUG_HELP : return_command; +} + + +/* + * debug_dispatch() + * + * Dispatch a debugging command to the appropriate handler. + */ +static void +debug_dispatch (sc_gameref_t game, + sc_command_t command, sc_command_type_t type, + sc_int arg1, sc_int arg2, sc_command_t help_topic) +{ + /* Demultiplex debugging command, and call handlers. */ + switch (command) + { + case DEBUG_HELP: + debug_help (help_topic); + break; + case DEBUG_BUFFER: + debug_buffer (game, type); + break; + case DEBUG_RESOURCES: + debug_resources (game, type); + break; + case DEBUG_RANDOM: + debug_random (type, arg1); + break; + case DEBUG_GAME: + debug_game (game, type); + break; + case DEBUG_PLAYER: + case DEBUG_OLDPLAYER: + debug_player (game, command, type); + break; + case DEBUG_ROOMS: + case DEBUG_OBJECTS: + case DEBUG_NPCS: + case DEBUG_EVENTS: + case DEBUG_TASKS: + case DEBUG_VARIABLES: + case DEBUG_OLDROOMS: + case DEBUG_OLDOBJECTS: + case DEBUG_OLDNPCS: + case DEBUG_OLDEVENTS: + case DEBUG_OLDTASKS: + case DEBUG_OLDVARIABLES: + debug_dump_common (game, command, type, arg1, arg2); + break; + case DEBUG_WATCHPLAYER: + case DEBUG_WATCHOBJECTS: + case DEBUG_WATCHNPCS: + case DEBUG_WATCHEVENTS: + case DEBUG_WATCHTASKS: + case DEBUG_WATCHVARIABLES: + case DEBUG_CLEARPLAYER: + case DEBUG_CLEAROBJECTS: + case DEBUG_CLEARNPCS: + case DEBUG_CLEAREVENTS: + case DEBUG_CLEARTASKS: + case DEBUG_CLEARVARIABLES: + debug_watchpoint_common (game, command, type, arg1, arg2); + break; + case DEBUG_WATCHALL: + case DEBUG_CLEARALL: + debug_watchall_common (game, command, type); + break; + case DEBUG_NONE: + break; + default: + sc_fatal ("debug_dispatch: invalid debug command\n"); + } +} + + +/* + * debug_dialog() + * + * Create a small debugging dialog with the user. + */ +static void +debug_dialog (sc_gameref_t game) +{ + const sc_filterref_t filter = gs_get_filter (game); + const sc_debuggerref_t debug = debug_get_debugger (game); + const sc_var_setref_t vars = gs_get_vars (game); + assert (debug_is_valid (debug)); + + /* + * Note elapsed seconds, so time stands still while debugging, and clear + * any pending game quit left over from prior dialogs (just in case). + */ + debug->elapsed_seconds = var_get_elapsed_seconds (vars); + debug->quit_pending = FALSE; + + /* Handle debug commands until debugger quit or game quit. */ + while (TRUE) + { + sc_char buffer[DEBUG_BUFFER_SIZE]; + sc_command_t command, help_topic; + sc_command_type_t type; + sc_int arg1, arg2; + + /* Get a debugging command string from the user. */ + do + { + if_read_debug (buffer, sizeof (buffer)); + } + while (sc_strempty (buffer)); + + /* Parse the command read, and handle dialog exit commands. */ + command = debug_parse_command (buffer, + &type, &arg1, &arg2, &help_topic); + if (command == DEBUG_CONTINUE || command == DEBUG_STEP) + { + if (!game->is_running) + { + if_print_debug ("The game is no longer running.\n"); + continue; + } + + debug->single_step = (command == DEBUG_STEP); + break; + } + else if (command == DEBUG_QUIT) + { + /* + * If the game is not running, we can't halt it, and we don't need + * to confirm the quit (either the player "quit" or the game + * completed), so leave the dialog loop. + */ + if (!game->is_running) + break; + + /* + * The game is still running, so confirm quit by requiring a repeat, + * or if this is the confirmation, force the game to a halt. + */ + if (!debug->quit_pending) + { + if_print_debug ("Use 'quit' again to confirm, or another" + " debugger command to cancel.\n"); + debug->quit_pending = TRUE; + continue; + } + + /* Drop printfilter contents and quit the game. */ + pf_empty (filter); + run_quit (game); + + /* Just in case... */ + if_print_debug ("Unable to quit from the game. Sorry.\n"); + continue; + } + + /* Dispatch the remaining debugging commands, and clear quit flag. */ + debug_dispatch (game, command, type, arg1, arg2, help_topic); + debug->quit_pending = FALSE; + } + + /* Restart time, and clear any pending game quit. */ + var_set_elapsed_seconds (vars, debug->elapsed_seconds); + debug->quit_pending = FALSE; +} + + +/* + * debug_run_command() + * + * Handle a single debugging command line from the outside world. Returns + * TRUE if valid, FALSE if invalid (parse failed, not understood). + */ +sc_bool +debug_run_command (sc_gameref_t game, const sc_char *debug_command) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + sc_command_t command, help_topic; + sc_command_type_t type; + sc_int arg1, arg2; + + /* If debugging disallowed (not initialized), refuse the call. */ + if (debug) + { + /* + * Parse the command string passed in, and return FALSE if the parse + * fails, or if it returns DEBUG_CONTINUE, DEBUG_STEP, or DEBUG_QUIT, + * none of which make any sense in this context. + */ + command = debug_parse_command (debug_command, + &type, &arg1, &arg2, &help_topic); + if (command == DEBUG_NONE + || command == DEBUG_CONTINUE || command == DEBUG_STEP + || command == DEBUG_QUIT) + return FALSE; + + /* Dispatch the remaining debugging commands, return successfully. */ + debug_dispatch (game, command, type, arg1, arg2, help_topic); + return TRUE; + } + + return FALSE; +} + + +/* + * debug_cmd_debugger() + * + * Called by the run main loop on user "debug" command request. Prints + * a polite refusal if debugging is not enabled, otherwise runs a debugging + * dialog. Uses if_print_string() as this isn't debug output. + */ +sc_bool +debug_cmd_debugger (sc_gameref_t game) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + + /* If debugging disallowed (not initialized), ignore the call. */ + if (debug) + debug_dialog (game); + else + if_print_string ("SCARE's game debugger is not enabled. Sorry.\n"); + + /* + * Set as administrative command, so as not to consume a game turn, and + * return successfully. + */ + game->is_admin = TRUE; + return TRUE; +} + + +/* + * debug_game_started() + * debug_game_ended() + * + * The first is called on entry to the game main loop, and gives us a chance + * to look at things before any turns are run, and to set watchpoints to + * catch things in games that use catch-all command tasks on startup (The PK + * Girl, for example). + * + * The second is called on exit from the game, and may make a final sweep for + * watchpoints and offer the debug dialog one last time. + */ +void +debug_game_started (sc_gameref_t game) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + + /* If debugging disallowed (not initialized), ignore the call. */ + if (debug) + { + /* Starting a new game, or a restore or undo of an old one? */ + if (!gs_room_seen (game, gs_playerroom (game))) + { + /* + * It's a new game starting or restarting. Print a banner, and + * run the debugger dialog. + */ + if_print_debug ("\n--- SCARE " SCARE_VERSION SCARE_PATCH_LEVEL + " Game Debugger\n" + "--- Type 'help' for a list of commands.\n"); + debug_dialog (game); + } + else + { + /* + * It's a restore or undo through memos, so run the dialog only if + * single-stepping; no need to check watchpoints for this case as + * none can be set -- no undo. + */ + if (debug->single_step) + debug_dialog (game); + } + } +} + +void +debug_game_ended (sc_gameref_t game) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + + /* If debugging disallowed (not initialized), ignore the call. */ + if (debug) + { + /* + * Using our carnal knowledge of the run main loop, we know here that + * if the loop exited with do_restart or do_restore, we'll get a call to + * debug_game_start() when the loop restarts. So in this case, ignore + * the call (even if single stepping). + */ + if (game->do_restart || game->do_restore) + return; + + /* + * Check for any final watchpoints, and print a message describing why + * we're here. Suppress the check for watchpoints if the user exited + * the game, as it'll only be a repeat of any found last turn update. + */ + if (!game->is_running) + { + if (game->has_completed) + { + debug_check_watchpoints (game); + if_print_debug ("\n--- The game has completed.\n"); + } + else + if_print_debug ("\n--- The game has exited.\n"); + } + else + { + debug_check_watchpoints (game); + if_print_debug ("\n--- The game is still running!\n"); + } + + /* Run a final dialog. */ + debug_dialog (game); + } +} + + +/* + * debug_turn_update() + * + * Called after each turn by the main game loop. Checks for any set + * watchpoints, and triggers a debug dialog when any fire. + */ +void +debug_turn_update (sc_gameref_t game) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + + /* If debugging disallowed (not initialized), ignore the call. */ + if (debug) + { + /* + * Again using carnal knowledge of the run main loop, if we're in + * mid-wait, ignore the call. Also, ignore the call if the game is + * no longer running, as we'll see a debug_game_ended() call come + * along to handle that. + */ + if (game->waitcounter > 0 || !game->is_running) + return; + + /* + * Run debugger dialog if any watchpoints triggered, or if single + * stepping (even if none triggered). + */ + if (debug_check_watchpoints (game) || debug->single_step) + debug_dialog (game); + } +} + + +/* + * debug_set_enabled() + * debug_get_enabled() + * + * Enable/disable debugging, and return debugging status. Debugging is + * enabled when there is a debugger reference in the game, and disabled + * when it's NULL -- that's the flag. To avoid lugging about all the + * watchpoint memory with a game, debugger data is allocated on enabling, + * and free'd on disabling; as a result, any set watchpoints are lost on + * disabling. + */ +void +debug_set_enabled (sc_gameref_t game, sc_bool enable) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + + /* + * If enabling and not already enabled, or disabling and not already + * disabled, either initialize or finalize.. + */ + if ((enable && !debug) || (!enable && debug)) + { + /* Initialize or finalize debugging, as appropriate. */ + if (enable) + debug_initialize (game); + else + debug_finalize (game); + } +} + +sc_bool +debug_get_enabled (sc_gameref_t game) +{ + const sc_debuggerref_t debug = debug_get_debugger (game); + + return debug != NULL; +} + +} // End of namespace Adrift +} // End of namespace Glk |