Files
scummvm-cursorfix/engines/ultima/ultima4/core/debugger.cpp
2026-02-02 04:50:13 +01:00

2003 lines
54 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/core/debugger.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/controllers/alpha_action_controller.h"
#include "ultima/ultima4/controllers/camp_controller.h"
#include "ultima/ultima4/controllers/read_choice_controller.h"
#include "ultima/ultima4/controllers/read_dir_controller.h"
#include "ultima/ultima4/controllers/ztats_controller.h"
#include "ultima/ultima4/game/armor.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/item.h"
#include "ultima/ultima4/game/moongate.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/game/weapon.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/views/dungeonview.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/ultima4.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
Debugger *g_debugger;
static bool strToBool(const char *s) {
return s && tolower(*s) == 't';
}
static int strToInt(const char *s) {
if (!*s)
// No string at all
return 0;
else if (toupper(s[strlen(s) - 1]) != 'H')
// Standard decimal string
return atoi(s);
// Hexadecimal string
uint tmp = 0;
int read = sscanf(s, "%xh", &tmp);
if (read < 1)
error("strToInt failed on string \"%s\"", s);
return (int)tmp;
}
static void splitString(const Common::String &str,
Common::StringArray &argv) {
// Clear the vector
argv.clear();
bool quoted = false;
Common::String::const_iterator it;
int ch;
Common::String arg;
for (it = str.begin(); it != str.end(); ++it) {
ch = *it;
// Toggle quoted string handling
if (ch == '\"') {
quoted = !quoted;
continue;
}
// Handle \\, \", \', \n, \r, \t
if (ch == '\\') {
Common::String::const_iterator next = it + 1;
if (next != str.end()) {
if (*next == '\\' || *next == '\"' || *next == '\'') {
ch = *next;
++it;
} else if (*next == 'n') {
ch = '\n';
++it;
} else if (*next == 'r') {
ch = '\r';
++it;
} else if (*next == 't') {
ch = '\t';
++it;
} else if (*next == ' ') {
ch = ' ';
++it;
}
}
}
// A space, a tab, line feed, carriage return
if (!quoted && (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) {
// If we are not empty then we are at the end of the arg
// otherwise we will ignore the extra chars
if (!arg.empty()) {
argv.push_back(arg);
arg.clear();
}
continue;
}
// Add the charater to the string
arg += ch;
}
// Push any arg if it's left
if (!arg.empty())
argv.push_back(arg);
}
Debugger::Debugger() : GUI::Debugger() {
g_debugger = this;
_collisionOverride = false;
_disableCombat = false;
_disableHunger = false;
_dontEndTurn = false;
registerCmd("move", WRAP_METHOD(Debugger, cmdMove));
registerCmd("attack", WRAP_METHOD(Debugger, cmdAttack));
registerCmd("board", WRAP_METHOD(Debugger, cmdBoard));
registerCmd("camp", WRAP_METHOD(Debugger, cmdCamp));
registerCmd("cast", WRAP_METHOD(Debugger, cmdCastSpell));
registerCmd("spell", WRAP_METHOD(Debugger, cmdCastSpell));
registerCmd("climb", WRAP_METHOD(Debugger, cmdClimb));
registerCmd("descend", WRAP_METHOD(Debugger, cmdDescend));
registerCmd("enter", WRAP_METHOD(Debugger, cmdEnter));
registerCmd("exit", WRAP_METHOD(Debugger, cmdExit));
registerCmd("fire", WRAP_METHOD(Debugger, cmdFire));
registerCmd("get", WRAP_METHOD(Debugger, cmdGetChest));
registerCmd("ignite", WRAP_METHOD(Debugger, cmdIgnite));
registerCmd("interact", WRAP_METHOD(Debugger, cmdInteract));
registerCmd("jimmy", WRAP_METHOD(Debugger, cmdJimmy));
registerCmd("locate", WRAP_METHOD(Debugger, cmdLocate));
registerCmd("mix", WRAP_METHOD(Debugger, cmdMixReagents));
registerCmd("open", WRAP_METHOD(Debugger, cmdOpenDoor));
registerCmd("order", WRAP_METHOD(Debugger, cmdNewOrder));
registerCmd("party", WRAP_METHOD(Debugger, cmdParty));
registerCmd("pass", WRAP_METHOD(Debugger, cmdPass));
registerCmd("peer", WRAP_METHOD(Debugger, cmdPeer));
registerCmd("quitAndSave", WRAP_METHOD(Debugger, cmdQuitAndSave));
registerCmd("ready", WRAP_METHOD(Debugger, cmdReadyWeapon));
registerCmd("search", WRAP_METHOD(Debugger, cmdSearch));
registerCmd("stats", WRAP_METHOD(Debugger, cmdStats));
registerCmd("talk", WRAP_METHOD(Debugger, cmdTalk));
registerCmd("use", WRAP_METHOD(Debugger, cmdUse));
registerCmd("wear", WRAP_METHOD(Debugger, cmdWearArmor));
registerCmd("yell", WRAP_METHOD(Debugger, cmdYell));
registerCmd("speed", WRAP_METHOD(Debugger, cmdSpeed));
registerCmd("combat_speed", WRAP_METHOD(Debugger, cmdCombatSpeed));
registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
registerCmd("abyss", WRAP_METHOD(Debugger, cmdAbyss));
registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
registerCmd("combat", WRAP_METHOD(Debugger, cmdCombat));
registerCmd("companions", WRAP_METHOD(Debugger, cmdCompanions));
registerCmd("destroy", WRAP_METHOD(Debugger, cmdDestroy));
registerCmd("destroy_creatures", WRAP_METHOD(Debugger, cmdDestroyCreatures));
registerCmd("dungeon", WRAP_METHOD(Debugger, cmdDungeon));
registerCmd("equipment", WRAP_METHOD(Debugger, cmdEquipment));
registerCmd("exit", WRAP_METHOD(Debugger, cmdExit));
registerCmd("flee", WRAP_METHOD(Debugger, cmdFlee));
registerCmd("fullstats", WRAP_METHOD(Debugger, cmdFullStats));
registerCmd("gate", WRAP_METHOD(Debugger, cmdGate));
registerCmd("goto", WRAP_METHOD(Debugger, cmdGoto));
registerCmd("hunger", WRAP_METHOD(Debugger, cmdHunger));
registerCmd("items", WRAP_METHOD(Debugger, cmdItems));
registerCmd("karma", WRAP_METHOD(Debugger, cmdKarma));
registerCmd("leave", WRAP_METHOD(Debugger, cmdLeave));
registerCmd("location", WRAP_METHOD(Debugger, cmdLocation));
registerCmd("lordbritish", WRAP_METHOD(Debugger, cmdLorddBritish));
registerCmd("mixtures", WRAP_METHOD(Debugger, cmdMixtures));
registerCmd("moon", WRAP_METHOD(Debugger, cmdMoon));
registerCmd("opacity", WRAP_METHOD(Debugger, cmdOpacity));
registerCmd("overhead", WRAP_METHOD(Debugger, cmdOverhead));
registerCmd("reagents", WRAP_METHOD(Debugger, cmdReagents));
registerCmd("summon", WRAP_METHOD(Debugger, cmdSummon));
registerCmd("torch", WRAP_METHOD(Debugger, cmdTorch));
registerCmd("transport", WRAP_METHOD(Debugger, cmdTransport));
registerCmd("triggers", WRAP_METHOD(Debugger, cmdListTriggers));
registerCmd("up", WRAP_METHOD(Debugger, cmdUp));
registerCmd("down", WRAP_METHOD(Debugger, cmdDown));
registerCmd("virtue", WRAP_METHOD(Debugger, cmdVirtue));
registerCmd("wind", WRAP_METHOD(Debugger, cmdWind));
}
Debugger::~Debugger() {
g_debugger = nullptr;
}
void Debugger::print(const char *fmt, ...) {
// Format the string
va_list va;
va_start(va, fmt);
Common::String str = Common::String::vformat(fmt, va);
va_end(va);
printN("%s\n", str.c_str());
}
void Debugger::printN(const char *fmt, ...) {
// Format the string
va_list va;
va_start(va, fmt);
Common::String str = Common::String::vformat(fmt, va);
va_end(va);
if (isDebuggerActive()) {
// Strip off any color special characters that aren't
// relevant for showing the text in the debugger
Common::String s;
for (const auto &c : str) {
if (c >= ' ' || c == '\n')
s += c;
}
debugPrintf("%s", s.c_str());
} else {
g_screen->screenMessage("%s", str.c_str());
}
}
void Debugger::prompt() {
if (isDebuggerActive())
g_screen->screenPrompt();
}
bool Debugger::handleCommand(int argc, const char **argv, bool &keepRunning) {
static const char *const DUNGEON_DISALLOWED[] = {
"attack", "board", "enter", "fire", "jimmy", "locate",
"open", "talk", "exit", "yell", nullptr
};
static const char *const COMBAT_DISALLOWED[] = {
"board", "climb", "descend", "enter", "exit", "fire", "hole",
"ignite", "jimmy", "mix", "order", "open", "peer", "quitAndSave",
"search", "wear", "yell", nullptr
};
if (g_context && g_context->_location) {
int ctx = g_context->_location->_context;
if (ctx & (CTX_DUNGEON | CTX_COMBAT)) {
Common::String method = argv[0];
const char *const *mth = (ctx & CTX_COMBAT) ?
COMBAT_DISALLOWED : DUNGEON_DISALLOWED;
for (; *mth; ++mth) {
if (method.equalsIgnoreCase(*mth)) {
print("%cNot here!%c", FG_GREY, FG_WHITE);
g_context->_location->_turnCompleter->finishTurn();
keepRunning = false;
return true;
}
}
}
}
bool result = GUI::Debugger::handleCommand(argc, argv, keepRunning);
if (result) {
Controller *ctl = eventHandler->getController();
if (g_context)
g_context->_lastCommandTime = g_system->getMillis();
if (!isActive() && !_dontEndTurn) {
GameController *gc = dynamic_cast<GameController *>(ctl);
CombatController *cc = dynamic_cast<CombatController *>(ctl);
if (gc)
gc->finishTurn();
else if (cc)
cc->finishTurn();
} else if (_dontEndTurn) {
if (ctl == g_game || ctl == g_combat) {
assert(g_context);
g_context->_location->_turnCompleter->finishTurn();
}
}
}
_dontEndTurn = false;
return result;
}
void Debugger::getChest(int player) {
Common::String param = Common::String::format("%d", player);
const char *argv[2] = { "get", param.c_str() };
cmdGetChest(2, argv);
}
bool Debugger::cmdMove(int argc, const char **argv) {
Direction dir;
if (argc == 2) {
dir = directionFromName(argv[1]);
} else {
print("move <direction>");
return isDebuggerActive();
}
Common::Path priorMap = g_context->_location->_map->_fname;
MoveResult retval = g_context->_location->move(dir, true);
// horse doubles speed (make sure we're on the same map as the previous move first)
if (retval & (MOVE_SUCCEEDED | MOVE_SLOWED) &&
(g_context->_transportContext == TRANSPORT_HORSE) && g_context->_horseSpeed) {
// to give it a smooth look of movement
gameUpdateScreen();
if (priorMap == g_context->_location->_map->_fname)
g_context->_location->move(dir, false);
}
// Let the movement handler decide to end the turn
bool endTurn = (retval & MOVE_END_TURN);
if (!endTurn)
dontEndTurn();
return false;
}
bool Debugger::cmdAttack(int argc, const char **argv) {
if (argc < 2 && isDebuggerActive()) {
print("attack <direction> [distance]");
return true;
}
Direction dir = (argc >= 2) ? directionFromName(argv[1]) : DIR_NONE;
int range = (argc >= 3) ? strToInt(argv[2]) : -1;
CombatController *cc = dynamic_cast<CombatController *>(eventHandler->getController());
GameController *gc = dynamic_cast<GameController *>(eventHandler->getController());
if (cc)
cc->attack(dir, range);
else if (gc)
gc->attack(dir);
return isDebuggerActive();
}
bool Debugger::cmdBoard(int argc, const char **argv) {
if (g_context->_transportContext != TRANSPORT_FOOT) {
print("Board: %cCan't!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
Object *obj = g_context->_location->_map->objectAt(g_context->_location->_coords);
if (!obj) {
print("%cBoard What?%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
const Tile *tile = obj->getTile().getTileType();
if (tile->isShip()) {
print("Board Frigate!");
if (g_context->_lastShip != obj)
g_context->_party->setShipHull(50);
} else if (tile->isHorse())
print("Mount Horse!");
else if (tile->isBalloon())
print("Board Balloon!");
else {
print("%cBoard What?%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
g_context->_party->setTransport(obj->getTile());
g_context->_location->_map->removeObject(obj);
return isDebuggerActive();
}
bool Debugger::cmdCastSpell(int argc, const char **argv) {
int player = -1;
if (argc >= 2)
player = strToInt(argv[1]);
print("Cast Spell!");
if (isCombat()) {
player = getCombatFocus();
} else if (player == -1) {
printN("Player: ");
player = gameGetPlayer(false, true);
}
if (player == -1)
return isDebuggerActive();
// get the spell to cast
g_context->_stats->setView(STATS_MIXTURES);
printN("Spell: ");
#ifdef IOS_ULTIMA4
// ### Put the iPad thing too.
U4IOS::IOSCastSpellHelper castSpellController;
#endif
int spell;
if (argc == 3) {
printN("Spell: ");
if (Common::isAlpha(argv[2][0])) {
spell = tolower(argv[2][0]) - 'a';
} else {
spell = -1;
}
} else {
spell = AlphaActionController::get('z', "Spell: ");
}
if (spell == -1) {
print("");
return isDebuggerActive();
}
print("%s!", g_spells->spellGetName(spell)); // Prints spell name at prompt
g_context->_stats->setView(STATS_PARTY_OVERVIEW);
// If we can't really cast this spell, skip the extra parameters
if (g_spells->spellCheckPrerequisites(spell, player) != CASTERR_NOERROR) {
gameCastSpell(spell, player, 0);
return isDebuggerActive();
}
// Get the final parameters for the spell
switch (g_spells->spellGetParamType(spell)) {
case Spell::PARAM_NONE:
gameCastSpell(spell, player, 0);
break;
case Spell::PARAM_PHASE: {
printN("To Phase: ");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper choiceController;
choiceController.fullSizeChoicePanel();
choiceController.updateGateSpellChoices();
#endif
int choice = ReadChoiceController::get("12345678 \033\n");
if (choice < '1' || choice > '8')
print("None");
else {
print("");
gameCastSpell(spell, player, choice - '1');
}
break;
}
case Spell::PARAM_PLAYER: {
printN("Who: ");
int subject = gameGetPlayer(true, false);
if (subject != -1)
gameCastSpell(spell, player, subject);
break;
}
case Spell::PARAM_DIR:
if (g_context->_location->_context == CTX_DUNGEON)
gameCastSpell(spell, player, g_ultima->_saveGame->_orientation);
else {
printN("Dir: ");
Direction dir = gameGetDirection();
if (dir != DIR_NONE)
gameCastSpell(spell, player, (int)dir);
}
break;
case Spell::PARAM_TYPEDIR: {
printN("Energy type? ");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper choiceController;
choiceController.fullSizeChoicePanel();
choiceController.updateEnergyFieldSpellChoices();
#endif
EnergyFieldType fieldType = ENERGYFIELD_NONE;
char key = ReadChoiceController::get("flps \033\n\r");
switch (key) {
case 'f':
fieldType = ENERGYFIELD_FIRE;
break;
case 'l':
fieldType = ENERGYFIELD_LIGHTNING;
break;
case 'p':
fieldType = ENERGYFIELD_POISON;
break;
case 's':
fieldType = ENERGYFIELD_SLEEP;
break;
default:
break;
}
if (fieldType != ENERGYFIELD_NONE) {
print("");
Direction dir;
if (g_context->_location->_context == CTX_DUNGEON)
dir = (Direction)g_ultima->_saveGame->_orientation;
else {
printN("Dir: ");
dir = gameGetDirection();
}
if (dir != DIR_NONE) {
/* Need to pack both dir and fieldType into param */
int param = fieldType << 4;
param |= (int)dir;
gameCastSpell(spell, player, param);
}
} else {
/* Invalid input here = spell failure */
print("Failed!");
/*
* Confirmed both mixture loss and mp loss in this situation in the
* original Ultima IV (at least, in the Amiga version.)
*/
//c->saveGame->_mixtures[castSpell]--;
g_context->_party->member(player)->adjustMp(
-g_spells->spellGetRequiredMP(spell));
}
break;
}
case Spell::PARAM_FROMDIR: {
printN("From Dir: ");
Direction dir = gameGetDirection();
if (dir != DIR_NONE)
gameCastSpell(spell, player, (int)dir);
break;
}
}
return false;
}
bool Debugger::cmdCamp(int argc, const char **argv) {
print("Hole up & Camp!");
if (!(g_context->_location->_context & (CTX_WORLDMAP | CTX_DUNGEON))) {
print("%cNot here!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
if (g_context->_transportContext != TRANSPORT_FOOT) {
print("%cOnly on foot!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
CombatController *cc = new CampController();
cc->init(nullptr);
cc->begin();
return isDebuggerActive();
}
bool Debugger::cmdClimb(int argc, const char **argv) {
if (!usePortalAt(g_context->_location, g_context->_location->_coords, ACTION_KLIMB)) {
if (g_context->_transportContext == TRANSPORT_BALLOON) {
g_ultima->_saveGame->_balloonState = 1;
g_context->_opacity = 0;
print("Klimb altitude");
} else
print("%cKlimb what?%c", FG_GREY, FG_WHITE);
}
return isDebuggerActive();
}
bool Debugger::cmdDescend(int argc, const char **argv) {
// unload the map for the second level of Lord British's Castle. The reason
// why is that Lord British's farewell is dependent on the number of party members.
// Instead of just redoing the dialog, it's a bit severe, but easier to unload the
// whole level.
bool cleanMap = (g_context->_party->size() == 1 && g_context->_location->_map->_id == 100);
if (!usePortalAt(g_context->_location, g_context->_location->_coords, ACTION_DESCEND)) {
if (g_context->_transportContext == TRANSPORT_BALLOON) {
print("Land Balloon");
if (!g_context->_party->isFlying())
print("%cAlready Landed!%c", FG_GREY, FG_WHITE);
else if (g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_OBJECTS)->canLandBalloon()) {
g_ultima->_saveGame->_balloonState = 0;
g_context->_opacity = 1;
} else {
print("%cNot Here!%c", FG_GREY, FG_WHITE);
}
} else {
print("%cDescend what?%c", FG_GREY, FG_WHITE);
}
} else {
if (cleanMap)
mapMgr->unloadMap(100);
}
return isDebuggerActive();
}
bool Debugger::cmdEnter(int argc, const char **argv) {
if (!usePortalAt(g_context->_location, g_context->_location->_coords, ACTION_ENTER)) {
if (!g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_ENTER))
print("%cEnter what?%c", FG_GREY, FG_WHITE);
} else {
dontEndTurn();
}
return isDebuggerActive();
}
bool Debugger::cmdExit(int argc, const char **argv) {
if ((g_context->_transportContext != TRANSPORT_FOOT) && !g_context->_party->isFlying()) {
Object *obj = g_context->_location->_map->addObject(g_context->_party->getTransport(), g_context->_party->getTransport(), g_context->_location->_coords);
if (g_context->_transportContext == TRANSPORT_SHIP)
g_context->_lastShip = obj;
Tile *avatar = g_context->_location->_map->_tileSet->getByName("avatar");
assertMsg(avatar, "no avatar tile found in tileset");
g_context->_party->setTransport(avatar->getId());
g_context->_horseSpeed = 0;
print("X-it");
} else {
print("%cX-it What?%c", FG_GREY, FG_WHITE);
}
return isDebuggerActive();
}
bool Debugger::cmdFire(int argc, const char **argv) {
if (g_context->_transportContext != TRANSPORT_SHIP) {
print("%cFire What?%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
printN("Fire Cannon!\nDir: ");
Direction dir = gameGetDirection();
if (dir == DIR_NONE)
return isDebuggerActive();
// can only fire broadsides
int broadsidesDirs = dirGetBroadsidesDirs(g_context->_party->getDirection());
if (!DIR_IN_MASK(dir, broadsidesDirs)) {
print("%cBroadsides Only!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
// nothing (not even mountains!) can block cannonballs
Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), broadsidesDirs, g_context->_location->_coords,
1, 3, nullptr, false);
for (const auto &coords : path) {
if (fireAt(coords, true))
return isDebuggerActive();
}
return isDebuggerActive();
}
bool Debugger::cmdGetChest(int argc, const char **argv) {
int player = -1;
if (argc == 2)
player = strToInt(argv[1]);
else if (isCombat())
player = getCombatFocus();
print("Get Chest!");
if (g_context->_party->isFlying()) {
print("%cDrift only!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
// first check to see if a chest exists at the current location
// if one exists, prompt the player for the opener, if necessary
MapCoords coords;
g_context->_location->getCurrentPosition(&coords);
const Tile *tile = g_context->_location->_map->tileTypeAt(coords, WITH_GROUND_OBJECTS);
/* get the object for the chest, if it is indeed an object */
Object *obj = g_context->_location->_map->objectAt(coords);
if (obj && !obj->getTile().getTileType()->isChest())
obj = nullptr;
if (tile->isChest() || obj) {
// if a spell was cast to open this chest,
// player will equal -2, otherwise player
// will default to -1 or the defult character
// number if one was earlier specified
if (player == -1) {
printN("Who opens? ");
player = gameGetPlayer(false, true);
}
if (player == -1)
return isDebuggerActive();
if (obj)
g_context->_location->_map->removeObject(obj);
else {
TileId newTile = g_context->_location->getReplacementTile(coords, tile);
g_context->_location->_map->_annotations->add(coords, newTile, false, true);
}
// see if the chest is trapped and handle it
getChestTrapHandler(player);
print("The Chest Holds: %d Gold", g_context->_party->getChest());
g_screen->screenPrompt();
if (isCity(g_context->_location->_map) && obj == nullptr)
g_context->_party->adjustKarma(KA_STOLE_CHEST);
} else {
print("%cNot Here!%c", FG_GREY, FG_WHITE);
}
return isDebuggerActive();
}
bool Debugger::cmdIgnite(int argc, const char **argv) {
print("Ignite torch!");
if (g_context->_location->_context == CTX_DUNGEON) {
if (!g_context->_party->lightTorch())
print("%cNone left!%c", FG_GREY, FG_WHITE);
} else {
print("%cNot here!%c", FG_GREY, FG_WHITE);
}
return isDebuggerActive();
}
bool Debugger::cmdInteract(int argc, const char **argv) {
if (!settings._enhancements || !settings._enhancementsOptions._smartEnterKey)
return isDebuggerActive();
// Attempt to guess based on the character's surroundings
if (g_context->_transportContext == TRANSPORT_FOOT) {
// When on foot, check for boarding
Object *obj = g_context->_location->_map->objectAt(g_context->_location->_coords);
if (obj && (obj->getTile().getTileType()->isShip() ||
obj->getTile().getTileType()->isHorse() ||
obj->getTile().getTileType()->isBalloon()))
return cmdBoard(argc, argv);
} else if (g_context->_transportContext == TRANSPORT_BALLOON) {
// Climb/Descend Balloon
if (g_context->_party->isFlying()) {
return cmdDescend(argc, argv);
} else {
#ifdef IOS_ULTIMA4
U4IOS::IOSSuperButtonHelper superHelper;
key = ReadChoiceController::get("xk \033\n");
#else
return cmdClimb(argc, argv);
#endif
}
} else {
// For all other transports, exit the transport
return cmdExit(argc, argv);
}
if ((g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_KLIMB) != nullptr))
// Climb
return cmdClimb(argc, argv);
else if ((g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_DESCEND) != nullptr))
// Descend
return cmdDescend(argc, argv);
if (g_context->_location->_context == CTX_DUNGEON) {
Dungeon *dungeon = static_cast<Dungeon *>(g_context->_location->_map);
bool up = dungeon->ladderUpAt(g_context->_location->_coords);
bool down = dungeon->ladderDownAt(g_context->_location->_coords);
if (up && down) {
#ifdef IOS_ULTIMA4
U4IOS::IOSClimbHelper climbHelper;
key = ReadChoiceController::get("kd \033\n");
#else
return cmdClimb(argc, argv);
#endif
} else if (up) {
return cmdClimb(argc, argv);
} else {
return cmdDescend(argc, argv);
}
}
if (g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_ENTER) != nullptr)
// Enter?
return cmdEnter(argc, argv);
if (!g_context->_party->isFlying()) {
// Get Chest?
MapTile *tile = g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
if (tile->getTileType()->isChest())
return cmdGetChest(argc, argv);
}
// Otherwise default to search
return cmdSearch(argc, argv);
}
bool Debugger::cmdJimmy(int argc, const char **argv) {
printN("Jimmy: ");
Direction dir = gameGetDirection();
if (dir == DIR_NONE)
return isDebuggerActive();
Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
1, 1, nullptr, true);
for (const auto &coords : path) {
if (jimmyAt(coords))
return isDebuggerActive();
}
print("%cJimmy what?%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
bool Debugger::cmdLocate(int argc, const char **argv) {
// Normally Locate isn't allowed in combat, but allow for a special
// debug display if this command is explicitly run in the debugger
if (isCombat() && isDebuggerActive()) {
CombatController *cc = static_cast<CombatController *>(eventHandler->getController());
Coords coords = cc->getCurrentPlayer()->getCoords();
print("Location: x:%d, y:%d, z:%d", coords.x, coords.y, coords.z);
dontEndTurn();
}
// Otherwise can't use sextant in dungeon or in combat
else if (g_context->_location->_context & ~(CTX_DUNGEON | CTX_COMBAT)) {
if (g_ultima->_saveGame->_sextants >= 1)
print("Locate position\nwith sextant\n Latitude: %c'%c\"\nLongitude: %c'%c\"",
g_context->_location->_coords.y / 16 + 'A', g_context->_location->_coords.y % 16 + 'A',
g_context->_location->_coords.x / 16 + 'A', g_context->_location->_coords.x % 16 + 'A');
else
print("%cLocate position with what?%c", FG_GREY, FG_WHITE);
} else {
print("%cNot here!%c", FG_GREY, FG_WHITE);
}
return isDebuggerActive();
}
bool Debugger::cmdMixReagents(int argc, const char **argv) {
/* uncomment this line to activate new spell mixing code */
// return mixReagentsSuper();
bool done = false;
while (!done) {
print("Mix reagents");
#ifdef IOS_ULTIMA4
U4IOS::beginMixSpellController();
return isDebuggerActive(); // Just return, the dialog takes control from here.
#endif
// Verify that there are reagents remaining in the inventory
bool found = false;
for (int i = 0; i < 8; i++) {
if (g_ultima->_saveGame->_reagents[i] > 0) {
found = true;
break;
}
}
if (!found) {
printN("%cNone Left!%c", FG_GREY, FG_WHITE);
done = true;
} else {
printN("For Spell: ");
g_context->_stats->setView(STATS_MIXTURES);
int choice = ReadChoiceController::get("abcdefghijklmnopqrstuvwxyz \033\n\r");
if (choice == -1 || choice == ' ' || choice == '\033' || choice == '\n' || choice == '\r')
break;
int spell = choice - 'a';
print("\n%s", g_spells->spellGetName(spell));
// ensure the mixtures for the spell isn't already maxed out
if (g_ultima->_saveGame->_mixtures[spell] == 99) {
print("\n%cYou cannot mix any more of that spell!%c", FG_GREY, FG_WHITE);
break;
}
// Reset the reagent spell mix menu by removing
// the menu highlight from the current item, and
// hiding reagents that you don't have
g_context->_stats->resetReagentsMenu();
g_context->_stats->setView(MIX_REAGENTS);
if (settings._enhancements && settings._enhancementsOptions._u5SpellMixing)
done = mixReagentsForSpellU5(spell);
else
done = mixReagentsForSpellU4(spell);
}
}
g_context->_stats->setView(STATS_PARTY_OVERVIEW);
print("");
return isDebuggerActive();
}
bool Debugger::cmdNewOrder(int argc, const char **argv) {
printN("New Order!\nExchange # ");
int player1 = gameGetPlayer(true, false);
if (player1 == -1)
return isDebuggerActive();
if (player1 == 0) {
print("%s, You must lead!", g_context->_party->member(0)->getName().c_str());
return isDebuggerActive();
}
printN(" with # ");
int player2 = gameGetPlayer(true, false);
if (player2 == -1)
return isDebuggerActive();
if (player2 == 0) {
print("%s, You must lead!", g_context->_party->member(0)->getName().c_str());
return isDebuggerActive();
}
if (player1 == player2) {
print("%cWhat?%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
g_context->_party->swapPlayers(player1, player2);
return isDebuggerActive();
}
bool Debugger::cmdOpenDoor(int argc, const char **argv) {
/// XXX: Pressing "o" should close any open door.
printN("Open: ");
if (g_context->_party->isFlying()) {
print("%cNot Here!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
Direction dir = gameGetDirection();
if (dir == DIR_NONE)
return isDebuggerActive();
Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
1, 1, nullptr, true);
for (const auto &coords : path) {
if (openAt(coords))
return isDebuggerActive();
}
print("%cNot Here!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
bool Debugger::cmdParty(int argc, const char **argv) {
if (settings._enhancements && settings._enhancementsOptions._activePlayer) {
int player = (argc == 2) ? strToInt(argv[1]) - 1 : -1;
gameSetActivePlayer(player);
} else {
print("%cBad command!%c", FG_GREY, FG_WHITE);
}
dontEndTurn();
return isDebuggerActive();
}
bool Debugger::cmdPass(int argc, const char **argv) {
print("Pass");
return isDebuggerActive();
}
bool Debugger::cmdPeer(int argc, const char **argv) {
bool useGem = (argc != 2) ? true : strToBool(argv[1]);
peer(useGem);
return isDebuggerActive();
}
bool Debugger::cmdQuitAndSave(int argc, const char **argv) {
print("Quit & Save...\n%d moves", g_ultima->_saveGame->_moves);
if (g_context->_location->_context & CTX_CAN_SAVE_GAME) {
(void)g_ultima->saveGameDialog();
g_ultima->quitGame();
return false;
} else {
print("%cNot here!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
}
bool Debugger::cmdReadyWeapon(int argc, const char **argv) {
int player = -1;
if (argc == 2)
player = strToInt(argv[1]);
else if (isCombat())
player = getCombatFocus();
// get the player if not provided
if (player == -1) {
printN("Ready a weapon for: ");
player = gameGetPlayer(true, false);
if (player == -1)
return isDebuggerActive();
}
// get the weapon to use
g_context->_stats->setView(STATS_WEAPONS);
printN("Weapon: ");
int weapon = AlphaActionController::get(WEAP_MAX + 'a' - 1, "Weapon: ");
g_context->_stats->setView(STATS_PARTY_OVERVIEW);
if (weapon == -1)
return isDebuggerActive();
PartyMember *p = g_context->_party->member(player);
const Weapon *w = g_weapons->get((WeaponType)weapon);
if (!w) {
print("");
return isDebuggerActive();
}
switch (p->setWeapon(w)) {
case EQUIP_SUCCEEDED:
print("%s", w->getName().c_str());
break;
case EQUIP_NONE_LEFT:
print("%cNone left!%c", FG_GREY, FG_WHITE);
break;
case EQUIP_CLASS_RESTRICTED:
{
Common::String indef_article;
switch (tolower(w->getName()[0])) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
case 'y':
indef_article = "an";
break;
default:
indef_article = "a";
break;
}
print("\n%cA %s may NOT use %s %s%c", FG_GREY, getClassName(p->getClass()),
indef_article.c_str(), w->getName().c_str(), FG_WHITE);
break;
}
}
return isDebuggerActive();
}
bool Debugger::cmdSearch(int argc, const char **argv) {
if (g_context->_location->_context == CTX_DUNGEON) {
dungeonSearch();
} else if (g_context->_party->isFlying()) {
print("Searching...\n%cDrift only!%c", FG_GREY, FG_WHITE);
} else if (g_context->_location->_map->_id == MAP_SCUMMVM &&
g_context->_location->_coords == Coords(52, 5, 0)) {
// Special hack for the ScummVM easter egg map. Searching on
// the given tile triggers the cheat to allow teleporting
print("Searching...\nFound teleport point!");
g_game->exitToParentMap();
g_music->playMapMusic();
return cmdGoto(argc, argv);
} else {
print("Searching...");
const ItemLocation *item = g_items->itemAtLocation(g_context->_location->_map, g_context->_location->_coords);
if (item) {
if (item->_isItemInInventory && (g_items->*(item->_isItemInInventory))(item->_data)) {
print("%cNothing Here!%c", FG_GREY, FG_WHITE);
} else {
if (item->_name)
print("You find...\n%s!", item->_name);
(g_items->*(item->_putItemInInventory))(item->_data);
}
} else if (usePortalAt(g_context->_location, g_context->_location->_coords, ACTION_ENTER)) {
print("");
} else {
print("%cNothing Here!%c", FG_GREY, FG_WHITE);
}
}
return isDebuggerActive();
}
bool Debugger::cmdSpeed(int argc, const char **argv) {
Common::String action = argv[1];
int oldCycles = settings._gameCyclesPerSecond;
if (action == "up") {
if (++settings._gameCyclesPerSecond > MAX_CYCLES_PER_SECOND)
settings._gameCyclesPerSecond = MAX_CYCLES_PER_SECOND;
} else if (action == "down") {
if (--settings._gameCyclesPerSecond == 0)
settings._gameCyclesPerSecond = 1;
} else if (action == "normal") {
settings._gameCyclesPerSecond = DEFAULT_CYCLES_PER_SECOND;
}
if (oldCycles != settings._gameCyclesPerSecond) {
settings._eventTimerGranularity = (1000 / settings._gameCyclesPerSecond);
eventHandler->getTimer()->reset(settings._eventTimerGranularity);
if (settings._gameCyclesPerSecond == DEFAULT_CYCLES_PER_SECOND)
print("Speed: Normal");
else if (action == "up")
print("Speed Up (%d)", settings._gameCyclesPerSecond);
else
print("Speed Down (%d)", settings._gameCyclesPerSecond);
} else if (settings._gameCyclesPerSecond == DEFAULT_CYCLES_PER_SECOND) {
print("Speed: Normal");
}
dontEndTurn();
return isDebuggerActive();
}
bool Debugger::cmdCombatSpeed(int argc, const char **argv) {
Common::String action = argv[1];
int oldSpeed = settings._battleSpeed;
if (action == "up" && ++settings._battleSpeed > MAX_BATTLE_SPEED)
settings._battleSpeed = MAX_BATTLE_SPEED;
else if (action == "down" && --settings._battleSpeed == 0)
settings._battleSpeed = 1;
else if (action == "normal")
settings._battleSpeed = DEFAULT_BATTLE_SPEED;
if (oldSpeed != settings._battleSpeed) {
if (settings._battleSpeed == DEFAULT_BATTLE_SPEED) {
print("Battle Speed:\nNormal");
} else if (action == "up") {
print("Battle Speed:\nUp (%d)", settings._battleSpeed);
} else {
print("Battle Speed:\nDown (%d)", settings._battleSpeed);
}
} else if (settings._battleSpeed == DEFAULT_BATTLE_SPEED) {
print("Battle Speed:\nNormal");
}
dontEndTurn();
return isDebuggerActive();
}
bool Debugger::cmdStats(int argc, const char **argv) {
int player = -1;
if (argc == 2)
player = strToInt(argv[1]);
else if (isCombat())
player = getCombatFocus();
// get the player if not provided
if (player == -1) {
printN("Ztats for: ");
player = gameGetPlayer(true, false);
if (player == -1)
return isDebuggerActive();
} else {
print("Ztats");
}
// Reset the reagent spell mix menu by removing
// the menu highlight from the current item, and
// hiding reagents that you don't have
g_context->_stats->resetReagentsMenu();
g_context->_stats->setView(StatsView(STATS_CHAR1 + player));
#ifdef IOS_ULTIMA4
U4IOS::IOSHideActionKeysHelper hideExtraControls;
#endif
ZtatsController ctrl;
eventHandler->pushController(&ctrl);
ctrl.waitFor();
return isDebuggerActive();
}
bool Debugger::cmdTalk(int argc, const char **argv) {
printN("Talk: ");
if (g_context->_party->isFlying()) {
print("%cDrift only!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
Direction dir = gameGetDirection();
if (dir == DIR_NONE)
return isDebuggerActive();
Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
1, 2, &Tile::canTalkOverTile, true);
for (const auto &coords : path) {
if (talkAt(coords))
return isDebuggerActive();
}
print("Funny, no response!");
return isDebuggerActive();
}
bool Debugger::cmdUse(int argc, const char **argv) {
print("Use which item:");
if (settings._enhancements) {
// a little xu4 enhancement: show items in inventory when prompted for an item to use
g_context->_stats->setView(STATS_ITEMS);
}
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Use which item?");
#endif
g_items->itemUse(gameGetInput().c_str());
return isDebuggerActive();
}
bool Debugger::cmdWearArmor(int argc, const char **argv) {
int player = -1;
if (argc == 2)
player = strToInt(argv[1]);
// get the player if not provided
if (player == -1) {
printN("Wear Armour\nfor: ");
player = gameGetPlayer(true, false);
if (player == -1)
return isDebuggerActive();
}
g_context->_stats->setView(STATS_ARMOR);
printN("Armour: ");
int armor = AlphaActionController::get(ARMR_MAX + 'a' - 1, "Armour: ");
g_context->_stats->setView(STATS_PARTY_OVERVIEW);
if (armor == -1)
return isDebuggerActive();
const Armor *a = g_armors->get((ArmorType)armor);
PartyMember *p = g_context->_party->member(player);
if (!a) {
print("");
return isDebuggerActive();
}
switch (p->setArmor(a)) {
case EQUIP_SUCCEEDED:
print("%s", a->getName().c_str());
break;
case EQUIP_NONE_LEFT:
print("%cNone left!%c", FG_GREY, FG_WHITE);
break;
case EQUIP_CLASS_RESTRICTED:
print("\n%cA %s may NOT use %s%c", FG_GREY, getClassName(p->getClass()), a->getName().c_str(), FG_WHITE);
break;
}
return isDebuggerActive();
}
bool Debugger::cmdYell(int argc, const char **argv) {
printN("Yell ");
if (g_context->_transportContext == TRANSPORT_HORSE) {
if (g_context->_horseSpeed == 0) {
print("Giddyup!");
g_context->_horseSpeed = 1;
} else {
print("Whoa!");
g_context->_horseSpeed = 0;
}
} else {
print("%cWhat?%c", FG_GREY, FG_WHITE);
}
return isDebuggerActive();
}
bool Debugger::cmd3d(int argc, const char **argv) {
if (g_context->_location->_context == CTX_DUNGEON) {
print("3-D view %s", DungeonViewer.toggle3DDungeonView() ? "on" : "off");
} else {
print("Not here");
}
return isDebuggerActive();
}
bool Debugger::cmdAbyss(int argc, const char **argv) {
// first teleport to the abyss
g_context->_location->_coords.x = 0xe9;
g_context->_location->_coords.y = 0xe9;
g_game->setMap(mapMgr->get(MAP_ABYSS), 1, nullptr);
// then to the final altar
g_context->_location->_coords.x = 7;
g_context->_location->_coords.y = 7;
g_context->_location->_coords.z = 7;
g_ultima->_saveGame->_orientation = DIR_NORTH;
g_context->_party->lightTorch(100, false);
cmdIgnite(0, nullptr);
return isDebuggerActive();
}
bool Debugger::cmdCollisions(int argc, const char **argv) {
_collisionOverride = !_collisionOverride;
print("Collision detection %s",
_collisionOverride ? "off" : "on");
return isDebuggerActive();
}
bool Debugger::cmdCompanions(int argc, const char **argv) {
for (int m = g_ultima->_saveGame->_members; m < 8; m++) {
if (g_context->_party->canPersonJoin(g_ultima->_saveGame->_players[m]._name, nullptr)) {
g_context->_party->join(g_ultima->_saveGame->_players[m]._name);
}
}
g_context->_stats->update();
print("Joined by companions");
return isDebuggerActive();
}
bool Debugger::cmdCombat(int argc, const char **argv) {
_disableCombat = !_disableCombat;
print("Combat encounters %s",
_disableCombat ? "off" : "on");
return isDebuggerActive();
}
bool Debugger::cmdDestroy(int argc, const char **argv) {
Direction dir;
if (argc == 2) {
dir = directionFromName(argv[1]);
} else if (isDebuggerActive()) {
print("destroy <direction>");
return isDebuggerActive();
} else {
printN("Destroy Object\nDir: ");
dir = gameGetDirection();
}
if (dir == DIR_NONE)
return isDebuggerActive();
Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir),
MASK_DIR_ALL, g_context->_location->_coords, 1, 1, nullptr, true);
for (const auto &coords : path) {
if (destroyAt(coords)) {
return false;
}
}
print("%cNothing there!%c", FG_GREY, FG_WHITE);
return isDebuggerActive();
}
bool Debugger::cmdDestroyCreatures(int argc, const char **argv) {
gameDestroyAllCreatures();
dontEndTurn();
return isDebuggerActive();
}
bool Debugger::cmdDungeon(int argc, const char **argv) {
if (g_context->_location->_context & CTX_WORLDMAP) {
if (argc == 2) {
int dungNum = strToInt(argv[1]);
if (dungNum >= 1 && dungNum <= 8) {
g_context->_location->_coords = g_context->_location->_map->_portals[dungNum + 15]->_coords;
return false;
} else if (dungNum == 9) {
g_game->setMap(mapMgr->get(MAP_DECEIT), 1, nullptr);
g_context->_location->_coords = MapCoords(1, 0, 7);
g_ultima->_saveGame->_orientation = DIR_SOUTH;
} else if (dungNum == 10) {
g_game->setMap(mapMgr->get(MAP_DESPISE), 1, nullptr);
g_context->_location->_coords = MapCoords(3, 2, 7);
g_ultima->_saveGame->_orientation = DIR_SOUTH;
} else if (dungNum == 11) {
g_game->setMap(mapMgr->get(MAP_DESTARD), 1, nullptr);
g_context->_location->_coords = MapCoords(7, 6, 7);
g_ultima->_saveGame->_orientation = DIR_SOUTH;
} else {
print("Invalid dungeon");
return isDebuggerActive();
}
return false;
} else {
print("dungeon <number>");
}
} else {
print("Not here");
}
return isDebuggerActive();
}
bool Debugger::cmdFlee(int argc, const char **argv) {
if (eventHandler->getController() == g_combat) {
// End the combat without losing karma
g_combat->end(false);
} else {
print("Bad command");
}
return isDebuggerActive();
}
bool Debugger::cmdEquipment(int argc, const char **argv) {
int i;
for (i = ARMR_NONE + 1; i < ARMR_MAX; ++i)
g_ultima->_saveGame->_armor[i] = 8;
for (i = WEAP_HANDS + 1; i < WEAP_MAX; ++i) {
const Weapon *weapon = g_weapons->get(static_cast<WeaponType>(i));
if (weapon->loseWhenUsed() || weapon->loseWhenRanged())
g_ultima->_saveGame->_weapons[i] = 99;
else
g_ultima->_saveGame->_weapons[i] = 8;
}
print("All equipment given");
return isDebuggerActive();
}
bool Debugger::cmdGate(int argc, const char **argv) {
int gateNum = (argc == 2) ? strToInt(argv[1]) : -1;
if (!g_context || !g_game || gateNum < 1 || gateNum > 8) {
print("Gate <1 to 8>");
} else {
if (!isDebuggerActive())
print("Gate %d!", gateNum);
if (g_context->_location->_map->isWorldMap()) {
const Coords *moongate = g_moongates->getGateCoordsForPhase(gateNum - 1);
if (moongate) {
g_context->_location->_coords = *moongate;
return false;
}
} else {
print("Not here!");
}
}
return isDebuggerActive();
}
bool Debugger::cmdGoto(int argc, const char **argv) {
Common::String dest;
PortalList &portals = g_context->_location->_map->_portals;
uint p;
if (argc == 2) {
dest = argv[1];
} else if (isDebuggerActive()) {
print("teleport <destination name>");
return true;
} else {
printN("Goto: ");
dest = gameGetInput(32);
print("");
}
dest.toLowercase();
if (dest == "britain")
dest = "britannia";
bool found = false;
p = strToInt(dest.c_str());
if (p > 0 && p <= portals.size()) {
g_context->_location->_coords = portals[p - 1]->_coords;
found = true;
}
for (p = 0; p < portals.size() && !found; p++) {
MapId destid = portals[p]->_destid;
Common::String destNameLower = mapMgr->get(destid)->getName();
destNameLower.toLowercase();
if (destNameLower.find(dest) != Common::String::npos) {
print("\n%s", mapMgr->get(destid)->getName().c_str());
g_context->_location->_coords = portals[p]->_coords;
found = true;
break;
}
}
if (!found) {
MapCoords coords = g_context->_location->_map->getLabel(dest);
if (coords != MapCoords::nowhere()) {
print("%s", dest.c_str());
g_context->_location->_coords = coords;
found = true;
}
}
if (found) {
return false;
} else {
if (isDebuggerActive())
print("Can't find %s", dest.c_str());
else
print("Can't find\n%s", dest.c_str());
return isDebuggerActive();
}
}
bool Debugger::cmdLorddBritish(int argc, const char **argv) {
if (!isDebuggerActive()) {
print("Help me LB!");
g_screen->screenPrompt();
}
// Help! send me to Lord British
g_game->setMap(mapMgr->get(100), 1, nullptr);
g_context->_location->_coords.x = 19;
g_context->_location->_coords.y = 8;
g_context->_location->_coords.z = 0;
return false;
}
bool Debugger::cmdItems(int argc, const char **argv) {
SaveGame &sg = *g_ultima->_saveGame;
sg._torches = 99;
sg._gems = 99;
sg._keys = 99;
sg._sextants = 1;
sg._items = ITEM_SKULL | ITEM_CANDLE | ITEM_BOOK | ITEM_BELL | ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T | ITEM_HORN | ITEM_WHEEL;
sg._stones = 0xff;
sg._runes = 0xff;
sg._food = 999900;
sg._gold = 9999;
g_context->_stats->update();
print("All items given");
return isDebuggerActive();
}
bool Debugger::cmdKarma(int argc, const char **argv) {
print("Karma!");
for (int i = 0; i < 8; ++i) {
Common::String line = Common::String::format("%s:",
getVirtueName(static_cast<Virtue>(i)));
while (line.size() < 13)
line += ' ';
if (g_ultima->_saveGame->_karma[i] > 0)
line += Common::String::format("%.2d", g_ultima->_saveGame->_karma[i]);
else
line += "--";
print("%s", line.c_str());
}
return isDebuggerActive();
}
bool Debugger::cmdLeave(int argc, const char **argv) {
if (!g_game->exitToParentMap()) {
print("Not Here");
} else {
g_music->playMapMusic();
print("Exited");
}
return isDebuggerActive();
}
bool Debugger::cmdLocation(int argc, const char **argv) {
const MapCoords &pos = g_context->_location->_coords;
if (argc == 3) {
Coords newPos;
if (strlen(argv[1]) == 2 && strlen(argv[2]) == 2
&& Common::isAlpha(argv[1][0]) && Common::isAlpha(argv[1][1])
&& Common::isAlpha(argv[2][0]) && Common::isAlpha(argv[2][1])
) {
newPos.y = (toupper(argv[1][0]) - 'A') * 16 + (toupper(argv[1][1]) - 'A');
newPos.x = (toupper(argv[2][0]) - 'A') * 16 + (toupper(argv[2][1]) - 'A');
} else {
newPos.x = strToInt(argv[1]);
newPos.y = strToInt(argv[2]);
}
if (newPos.x >= 0 && newPos.y >= 0
&& newPos.x < (int)g_context->_location->_map->_width
&& newPos.y < (int)g_context->_location->_map->_height) {
g_context->_location->_coords = newPos;
return false;
} else {
print("Invalid location!");
}
} else if (isDebuggerActive()) {
if (g_context->_location->_map->isWorldMap())
print("Location: %s x: %d, y: %d",
"World Map", pos.x, pos.y);
else
print("Location: %s x: %d, y: %d, z: %d",
g_context->_location->_map->getName().c_str(), pos.x, pos.y, pos.z);
} else {
if (g_context->_location->_map->isWorldMap())
print("\nLocation:\n%s\nx: %d\ny: %d", "World Map",
pos.x, pos.y);
else
print("\nLocation:\n%s\nx: %d\ny: %d\nz: %d",
g_context->_location->_map->getName().c_str(), pos.x, pos.y, pos.z);
}
return isDebuggerActive();
}
bool Debugger::cmdMixtures(int argc, const char **argv) {
for (int i = 0; i < SPELL_MAX; i++)
g_ultima->_saveGame->_mixtures[i] = 99;
print("All mixtures given");
return isDebuggerActive();
}
bool Debugger::cmdOverhead(int argc, const char **argv) {
if ((g_context->_location->_viewMode == VIEW_NORMAL) || (g_context->_location->_viewMode == VIEW_DUNGEON))
g_context->_location->_viewMode = VIEW_GEM;
else if (g_context->_location->_context == CTX_DUNGEON)
g_context->_location->_viewMode = VIEW_DUNGEON;
else
g_context->_location->_viewMode = VIEW_NORMAL;
print("Toggle view");
return isDebuggerActive();
}
bool Debugger::cmdMoon(int argc, const char **argv) {
int moonNum;
if (argc == 2) {
moonNum = strToInt(argv[1]);
if (moonNum < 0 || moonNum > 7) {
print("Invalid moon");
return true;
}
} else {
moonNum = (g_ultima->_saveGame->_trammelPhase + 1) & 7;
}
while (g_ultima->_saveGame->_trammelPhase != moonNum)
g_game->updateMoons(true);
print("Moons advanced");
return isDebuggerActive();
}
bool Debugger::cmdOpacity(int argc, const char **argv) {
g_context->_opacity = !g_context->_opacity;
print("Opacity is %s", g_context->_opacity ? "on" : "off");
return isDebuggerActive();
}
bool Debugger::cmdReagents(int argc, const char **argv) {
for (int i = 0; i < REAG_MAX; i++)
g_ultima->_saveGame->_reagents[i] = 99;
print("Reagents given");
return isDebuggerActive();
}
bool Debugger::cmdFullStats(int argc, const char **argv) {
for (int i = 0; i < g_ultima->_saveGame->_members; i++) {
g_ultima->_saveGame->_players[i]._str = 50;
g_ultima->_saveGame->_players[i]._dex = 50;
g_ultima->_saveGame->_players[i]._intel = 50;
if (g_ultima->_saveGame->_players[i]._hpMax < 800) {
g_ultima->_saveGame->_players[i]._xp = 9999;
g_ultima->_saveGame->_players[i]._hpMax = 800;
g_ultima->_saveGame->_players[i]._hp = 800;
}
}
g_context->_stats->update();
print("Full Stats given");
return isDebuggerActive();
}
bool Debugger::cmdHunger(int argc, const char **argv) {
_disableHunger = !_disableHunger;
print("Party hunger %s",
_disableHunger ? "off" : "on");
return isDebuggerActive();
}
bool Debugger::cmdSummon(int argc, const char **argv) {
Common::String creature;
if (argc == 2) {
creature = argv[1];
} else if (isDebuggerActive()) {
print("summon <creature name>");
return true;
} else {
print("Summon!");
print("What?");
creature = gameGetInput();
}
summonCreature(creature);
return isDebuggerActive();
}
bool Debugger::cmdTorch(int argc, const char **argv) {
print("Torch: %d", g_context->_party->getTorchDuration());
if (!isDebuggerActive())
g_screen->screenPrompt();
return isDebuggerActive();
}
bool Debugger::cmdTransport(int argc, const char **argv) {
if (!g_context->_location->_map->isWorldMap()) {
print("Not here!");
return isDebuggerActive();
}
_horse = g_context->_location->_map->_tileSet->getByName("horse")->getId();
_ship = g_context->_location->_map->_tileSet->getByName("ship")->getId();
_balloon = g_context->_location->_map->_tileSet->getByName("balloon")->getId();
MapCoords coords = g_context->_location->_coords;
MapTile *choice;
Tile *tile;
// Get the transport of choice
char transport;
if (argc >= 2) {
transport = argv[1][0];
} else if (isDebuggerActive()) {
print("transport <transport name>");
return isDebuggerActive();
} else {
transport = ReadChoiceController::get("shb \033\015");
}
switch (transport) {
case 's':
choice = &_ship;
break;
case 'h':
choice = &_horse;
break;
case 'b':
choice = &_balloon;
break;
default:
print("Unknown transport");
return isDebuggerActive();
}
tile = g_context->_location->_map->_tileSet->get(choice->getId());
Direction dir;
if (argc == 3) {
dir = directionFromName(argv[2]);
} else if (isDebuggerActive()) {
dir = DIR_NONE;
} else {
print("%s", tile->getName().c_str());
// Get the direction in which to create the transport
ReadDirController readDir;
eventHandler->pushController(&readDir);
printN("Dir: ");
dir = readDir.waitFor();
}
coords.move(dir, g_context->_location->_map);
if (coords != g_context->_location->_coords) {
bool ok;
MapTile *ground = g_context->_location->_map->tileAt(coords, WITHOUT_OBJECTS);
switch (transport) {
case 's':
ok = ground->getTileType()->isSailable();
break;
case 'h':
ok = ground->getTileType()->isWalkable();
break;
case 'b':
ok = ground->getTileType()->isWalkable();
break;
default:
ok = false;
break;
}
if (ok) {
g_context->_location->_map->addObject(*choice, *choice, coords);
print("%s created!", tile->getName().c_str());
} else if (!choice) {
print("Invalid transport!");
} else {
print("Can't place %s there!", tile->getName().c_str());
}
}
return isDebuggerActive();
}
bool Debugger::cmdUp(int argc, const char **argv) {
if ((g_context->_location->_context & CTX_DUNGEON) && (g_context->_location->_coords.z > 0)) {
g_context->_location->_coords.z--;
return false;
} else {
print("Leaving...");
g_game->exitToParentMap();
g_music->playMapMusic();
return isDebuggerActive();
}
}
bool Debugger::cmdDown(int argc, const char **argv) {
if ((g_context->_location->_context & CTX_DUNGEON) && (g_context->_location->_coords.z < 7)) {
g_context->_location->_coords.z++;
return false;
} else {
print("Not here");
return isDebuggerActive();
}
}
bool Debugger::cmdVirtue(int argc, const char **argv) {
if (argc == 1) {
for (int i = 0; i < 8; i++)
g_ultima->_saveGame->_karma[i] = 0;
g_context->_stats->update();
print("Full virtues");
} else {
int virtue = strToInt(argv[1]);
if (virtue <= 0 || virtue >= VIRT_MAX) {
print("Invalid virtue");
} else {
print("Improved %s", getVirtueName((Virtue)virtue));
if (g_ultima->_saveGame->_karma[virtue] == 99)
g_ultima->_saveGame->_karma[virtue] = 0;
else if (g_ultima->_saveGame->_karma[virtue] != 0)
g_ultima->_saveGame->_karma[virtue] += 10;
if (g_ultima->_saveGame->_karma[virtue] > 99)
g_ultima->_saveGame->_karma[virtue] = 99;
g_context->_stats->update();
}
}
return isDebuggerActive();
}
bool Debugger::cmdWind(int argc, const char **argv) {
Common::String windDir;
if (argc == 2) {
windDir = argv[1];
} else if (isDebuggerActive()) {
print("wind <direction or 'lock'>");
return true;
} else {
print("Wind Dir ('l' to lock)");
windDir = gameGetInput();
}
windDir.toLowercase();
if (windDir == "lock" || windDir == "l") {
g_context->_windLock = !g_context->_windLock;
print("Wind direction is %slocked",
g_context->_windLock ? "" : "un");
} else {
Direction dir = directionFromName(windDir);
if (dir == DIR_NONE) {
print("Unknown direction");
return isDebuggerActive();
} else {
g_context->_windDirection = dir;
}
}
return false;
}
bool Debugger::cmdListTriggers(int argc, const char **argv) {
CombatMap *map = nullptr;
if (isCombat() && (map = static_cast<CombatController *>(
eventHandler->getController())->getMap()) != nullptr
&& map->isDungeonRoom()) {
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_prev->_map);
assert(dungeon);
Trigger *triggers = dungeon->_rooms[dungeon->_currentRoom]._triggers;
assert(triggers);
int i;
print("Triggers!");
for (i = 0; i < 4; i++) {
print("%.1d)xy tile xy xy", i + 1);
print(" %.1X%.1X %.3d %.1X%.1X %.1X%.1X",
triggers[i].x, triggers[i].y,
triggers[i]._tile,
triggers[i]._changeX1, triggers[i]._changeY1,
triggers[i].changeX2, triggers[i].changeY2);
}
prompt();
dontEndTurn();
} else {
print("Not here!");
}
return isDebuggerActive();
}
void Debugger::executeCommand(const Common::String &cmd) {
// Split up the command, and form a const char * array
Common::StringArray args;
splitString(cmd, args);
Common::Array<const char *> argv;
for (uint idx = 0; idx < args.size(); ++idx)
argv.push_back(args[idx].c_str());
// Execute the command
executeCommand(argv.size(), &argv[0]);
}
void Debugger::executeCommand(int argc, const char **argv) {
if (argc <= 0)
return;
bool keepRunning = false;
if (!handleCommand(argc, argv, keepRunning)) {
debugPrintf("Unknown command - %s\n", argv[0]);
keepRunning = true;
}
// If any message occurred, then we need to ensure the debugger is opened if it isn't already
if (keepRunning)
attach();
}
} // End of namespace Ultima4
} // End of namespace Ultima