scummvm/engines/glk/adrift/scdebug.cpp
Orgad Shaneh 0111a08560 GLK: Use nullptr
Using clang-tidy modernize-use-nullptr
2021-11-14 15:51:59 +02:00

2491 lines
69 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 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},
{nullptr, 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 = nullptr;
}
/*
* 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;
default:
if_print_debug(", Unknown openness");
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_ = nullptr;
filter_function = nullptr;
dumper_function = nullptr;
/* 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 = nullptr;
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_ = nullptr;
watchpoints = nullptr;
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
* <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(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 != nullptr;
}
} // End of namespace Adrift
} // End of namespace Glk