/* 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/adrift.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. */ enum sc_command_t { 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 }; enum sc_command_type_t { COMMAND_QUERY = 0, COMMAND_RANGE, COMMAND_ONE, COMMAND_ALL }; /* Table connecting debugging command strings to commands. */ struct sc_strings_t { const sc_char *const command_string; const sc_command_t command; }; 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. */ 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; }; typedef sc_debugger_s 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; vt_rvalue.voidp = vt_rvalue2.voidp = nullptr; 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 * , 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(CONTEXT, 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)); if (g_vm->shouldQuit()) return; } 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); CALL1(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); Context context; /* If debugging disallowed (not initialized), ignore the call. */ if (debug) { debug_dialog(context, 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(CONTEXT, 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"); CALL1(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) { CALL1(debug_dialog, game); } } } } void debug_game_ended(CONTEXT, 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. */ CALL1(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(CONTEXT, 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) { CALL1(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