/* 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 . * */ #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(ctl); CombatController *cc = dynamic_cast(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 "); 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 [distance]"); return true; } Direction dir = (argc >= 2) ? directionFromName(argv[1]) : DIR_NONE; int range = (argc >= 3) ? strToInt(argv[2]) : -1; CombatController *cc = dynamic_cast(eventHandler->getController()); GameController *gc = dynamic_cast(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 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(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 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(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 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 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 "); return isDebuggerActive(); } else { printN("Destroy Object\nDir: "); dir = gameGetDirection(); } if (dir == DIR_NONE) return isDebuggerActive(); Std::vector 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 "); } } 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(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 "); 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(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 "); 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 "); 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 "); 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( eventHandler->getController())->getMap()) != nullptr && map->isDungeonRoom()) { Dungeon *dungeon = dynamic_cast(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 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