From a2b94cc03cbc4de4c8cd2440747e31335285d835 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 5 Nov 2023 10:38:05 +0000 Subject: [PATCH] Lua: Migrate and organize the rest of debug cmds Fully migrates debug commands to Lua and organizes them into logical groups. The CLI `+` syntax now runs Lua, e.g.: ```bash build/devilutionx '+dev.player.trn.plr("infra")' ``` Chat hotkeys run Lua code if they start with `/lua`, e.g.: ```ini [NetMsg] QuickMessage1=/lua message(dev.player.info()) ``` --- Source/CMakeLists.txt | 16 +- Source/control.cpp | 16 +- Source/debug.cpp | 1002 +--------------------- Source/debug.h | 37 +- Source/diablo.cpp | 5 +- Source/items.cpp | 30 +- Source/lua/autocomplete.cpp | 1 + Source/lua/metadoc.hpp | 2 +- Source/lua/modules/dev.cpp | 194 +---- Source/lua/modules/dev/display.cpp | 133 +++ Source/lua/modules/dev/display.hpp | 10 + Source/lua/modules/dev/items.cpp | 73 ++ Source/lua/modules/dev/items.hpp | 10 + Source/lua/modules/dev/level.cpp | 124 +++ Source/lua/modules/dev/level.hpp | 10 + Source/lua/modules/dev/level/map.cpp | 41 + Source/lua/modules/dev/level/map.hpp | 10 + Source/lua/modules/dev/level/warp.cpp | 82 ++ Source/lua/modules/dev/level/warp.hpp | 10 + Source/lua/modules/dev/monsters.cpp | 175 ++++ Source/lua/modules/dev/monsters.hpp | 10 + Source/lua/modules/dev/player.cpp | 109 +++ Source/lua/modules/dev/player.hpp | 11 + Source/lua/modules/dev/player/gold.cpp | 101 +++ Source/lua/modules/dev/player/gold.hpp | 10 + Source/lua/modules/dev/player/spells.cpp | 39 + Source/lua/modules/dev/player/spells.hpp | 10 + Source/lua/modules/dev/player/stats.cpp | 100 +++ Source/lua/modules/dev/player/stats.hpp | 10 + Source/lua/modules/dev/quests.cpp | 22 +- Source/lua/modules/dev/search.cpp | 56 ++ Source/lua/modules/dev/search.hpp | 10 + Source/lua/modules/dev/towners.cpp | 93 ++ Source/lua/modules/dev/towners.hpp | 10 + Source/panels/console.cpp | 72 +- Source/panels/console.hpp | 2 + Source/towners.cpp | 8 +- Source/towners.h | 2 +- 38 files changed, 1429 insertions(+), 1227 deletions(-) create mode 100644 Source/lua/modules/dev/display.cpp create mode 100644 Source/lua/modules/dev/display.hpp create mode 100644 Source/lua/modules/dev/items.cpp create mode 100644 Source/lua/modules/dev/items.hpp create mode 100644 Source/lua/modules/dev/level.cpp create mode 100644 Source/lua/modules/dev/level.hpp create mode 100644 Source/lua/modules/dev/level/map.cpp create mode 100644 Source/lua/modules/dev/level/map.hpp create mode 100644 Source/lua/modules/dev/level/warp.cpp create mode 100644 Source/lua/modules/dev/level/warp.hpp create mode 100644 Source/lua/modules/dev/monsters.cpp create mode 100644 Source/lua/modules/dev/monsters.hpp create mode 100644 Source/lua/modules/dev/player.cpp create mode 100644 Source/lua/modules/dev/player.hpp create mode 100644 Source/lua/modules/dev/player/gold.cpp create mode 100644 Source/lua/modules/dev/player/gold.hpp create mode 100644 Source/lua/modules/dev/player/spells.cpp create mode 100644 Source/lua/modules/dev/player/spells.hpp create mode 100644 Source/lua/modules/dev/player/stats.cpp create mode 100644 Source/lua/modules/dev/player/stats.hpp create mode 100644 Source/lua/modules/dev/search.cpp create mode 100644 Source/lua/modules/dev/search.hpp create mode 100644 Source/lua/modules/dev/towners.cpp create mode 100644 Source/lua/modules/dev/towners.hpp diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 24b8da73d..a1d62bfe1 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -134,12 +134,24 @@ set(libdevilutionx_SRCS lua/autocomplete.cpp lua/lua.cpp - lua/repl.cpp lua/modules/audio.cpp lua/modules/dev.cpp + lua/modules/dev/display.cpp + lua/modules/dev/items.cpp + lua/modules/dev/level.cpp + lua/modules/dev/level/map.cpp + lua/modules/dev/level/warp.cpp + lua/modules/dev/monsters.cpp + lua/modules/dev/player.cpp + lua/modules/dev/player/gold.cpp + lua/modules/dev/player/spells.cpp + lua/modules/dev/player/stats.cpp lua/modules/dev/quests.cpp - lua/modules/render.cpp + lua/modules/dev/search.cpp + lua/modules/dev/towners.cpp lua/modules/log.cpp + lua/modules/render.cpp + lua/repl.cpp panels/charpanel.cpp panels/console.cpp diff --git a/Source/control.cpp b/Source/control.cpp index 29bd9b3ab..6a3569b38 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -38,6 +38,7 @@ #include "missiles.h" #include "options.h" #include "panels/charpanel.hpp" +#include "panels/console.hpp" #include "panels/mainpanel.hpp" #include "panels/spell_book.hpp" #include "panels/spell_icons.hpp" @@ -579,10 +580,6 @@ bool CheckTextCommand(const std::string_view text) void ResetTalkMsg() { -#ifdef _DEBUG - if (CheckDebugTextCommand(TalkMessage)) - return; -#endif if (CheckTextCommand(TalkMessage)) return; @@ -1628,14 +1625,15 @@ void DiabloHotkeyMsg(uint32_t dwMsg) assert(dwMsg < QUICK_MESSAGE_OPTIONS); - for (auto &msg : sgOptions.Chat.szHotKeyMsgs[dwMsg]) { - + for (const std::string &msg : sgOptions.Chat.szHotKeyMsgs[dwMsg]) { #ifdef _DEBUG - if (CheckDebugTextCommand(msg)) + constexpr std::string_view LuaPrefix = "/lua "; + if (msg.starts_with(LuaPrefix)) { + InitConsole(); + RunInConsole(std::string_view(msg).substr(LuaPrefix.size())); continue; + } #endif - if (CheckTextCommand(msg)) - continue; char charMsg[MAX_SEND_STR_LEN]; CopyUtf8(charMsg, msg, sizeof(charMsg)); NetSendCmdString(0xFFFFFF, charMsg); diff --git a/Source/debug.cpp b/Source/debug.cpp index c69f2bc76..1252f8b80 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -13,33 +13,14 @@ #include "debug.h" #include "automap.h" -#include "control.h" #include "cursor.h" -#include "diablo_msg.hpp" -#include "engine/backbuffer_state.hpp" -#include "engine/events.hpp" #include "engine/load_cel.hpp" #include "engine/point.hpp" -#include "inv.h" -#include "levels/setmaps.h" #include "lighting.h" -#include "lua/lua.hpp" -#include "monstdat.h" #include "monster.h" -#include "pack.h" #include "plrmsg.h" -#include "quests.h" -#include "spells.h" -#include "towners.h" -#include "utils/algorithm/container.hpp" -#include "utils/endian_stream.hpp" -#include "utils/file_util.h" -#include "utils/language.h" -#include "utils/log.hpp" -#include "utils/parse_int.hpp" #include "utils/str_case.hpp" #include "utils/str_cat.hpp" -#include "utils/str_split.hpp" namespace devilution { @@ -62,36 +43,6 @@ uint32_t glEndSeed[NUMLEVELS]; namespace { -enum class DebugGridTextItem : uint16_t { - None, - dPiece, - dTransVal, - dLight, - dPreLight, - dFlags, - dPlayer, - dMonster, - dCorpse, - dObject, - dItem, - dSpecial, - - coords, - cursorcoords, - objectindex, - - // take dPiece as index - Solid, - Transparent, - Trap, - - // megatiles - AutomapView, - dungeon, - pdungeon, - Protected, -}; - DebugGridTextItem SelectedDebugGridTextItem; int DebugMonsterId; @@ -121,910 +72,6 @@ void PrintDebugMonster(const Monster &monster) EventPlrMsg(StrCat("Active List = ", bActive ? 1 : 0, ", Squelch = ", monster.activeForTicks), UiFlags::ColorWhite); } -struct DebugCmdItem { - const std::string_view text; - const std::string_view description; - const std::string_view requiredParameter; - std::string (*actionProc)(const std::string_view); -}; - -extern std::vector DebugCmdList; - -std::string DebugCmdHelp(const std::string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "Available Debug Commands: "; - bool first = true; - for (const auto &dbgCmd : DebugCmdList) { - if (first) - first = false; - else - ret.append(" - "); - ret.append(std::string(dbgCmd.text)); - } - return ret; - } - auto debugCmdIterator = c_find_if(DebugCmdList, [&](const DebugCmdItem &elem) { return elem.text == parameter; }); - if (debugCmdIterator == DebugCmdList.end()) - return StrCat("Debug command ", parameter, " wasn't found"); - auto &dbgCmdItem = *debugCmdIterator; - if (dbgCmdItem.requiredParameter.empty()) - return StrCat("Description: ", dbgCmdItem.description, "\nParameters: No additional parameter needed."); - return StrCat("Description: ", dbgCmdItem.description, "\nParameters: ", dbgCmdItem.requiredParameter); -} - -std::string DebugCmdWarpToLevel(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[goto] "; - - const ParseIntResult parsedParam = ParseInt(parameter, /*min=*/0); - if (!parsedParam.has_value()) - return StrCat(cmdLabel, "Missing level number!"); - const int level = parsedParam.value(); - if (level > (gbIsHellfire ? 24 : 16)) - return StrCat(cmdLabel, "Level ", level, " does not exist!"); - if (!setlevel && myPlayer.isOnLevel(level)) - return StrCat(cmdLabel, "You are already on level ", level, "!"); - - StartNewLvl(myPlayer, (level != 21) ? interface_mode::WM_DIABNEXTLVL : interface_mode::WM_DIABTOWNWARP, level); - return StrCat(cmdLabel, "Moved you to level ", level, "."); -} - -std::string DebugCmdLoadQuestMap(const std::string_view parameter) -{ - std::string cmdLabel = "[questmap] "; - - if (parameter.empty()) { - std::string ret = StrCat(cmdLabel, "Missing quest level number!"); - for (auto &quest : Quests) { - if (quest._qslvl <= 0) - continue; - StrAppend(ret, " ", quest._qslvl, " (", QuestLevelNames[quest._qslvl], ")"); - } - return ret; - } - - const ParseIntResult parsedParam = ParseInt(parameter, /*min=*/0); - if (!parsedParam.has_value()) - return StrCat(cmdLabel, "Invalid quest level number!"); - const int level = parsedParam.value(); - if (level < 1) - return StrCat(cmdLabel, "Quest level number must be 1 or higher!"); - if (setlevel && setlvlnum == level) - return StrCat(cmdLabel, "You are already on quest level", level, "!"); - - for (auto &quest : Quests) { - if (level != quest._qslvl) - continue; - - setlvltype = quest._qlvltype; - StartNewLvl(*MyPlayer, WM_DIABSETLVL, level); - - return StrCat(cmdLabel, "Moved you to quest level ", QuestLevelNames[level], "."); - } - - return StrCat(cmdLabel, "Quest level ", level, " does not exist!"); -} - -std::string DebugCmdLoadMap(const std::string_view parameter) -{ - TestMapPath.clear(); - int mapType = 0; - Point spawn = {}; - std::string cmdLabel = "[map] "; - - int count = 0; - for (std::string_view arg : SplitByChar(parameter, ' ')) { - switch (count) { - case 0: - TestMapPath = StrCat(arg, ".dun"); - break; - case 1: { - const ParseIntResult parsedArg = ParseInt(arg, /*min=*/0); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument 1 as integer!"); - mapType = parsedArg.value(); - } break; - case 2: { - const ParseIntResult parsedArg = ParseInt(arg); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument 2 as integer!"); - spawn.x = parsedArg.value(); - } break; - case 3: { - const ParseIntResult parsedArg = ParseInt(arg); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument 3 as integer!"); - spawn.y = parsedArg.value(); - } break; - } - count++; - } - - if (TestMapPath.empty() || mapType < DTYPE_CATHEDRAL || mapType > DTYPE_LAST || !InDungeonBounds(spawn)) - return StrCat(cmdLabel, "Invalid parameters!"); - - setlvltype = static_cast(mapType); - ViewPosition = spawn; - - StartNewLvl(*MyPlayer, WM_DIABSETLVL, SL_NONE); - - return StrCat(cmdLabel, "Moved you to ", TestMapPath, "."); -} - -std::string ExportDun(const std::string_view parameter) -{ - std::string levelName = StrCat(currlevel, "-", glSeedTbl[currlevel], ".dun"); - std::string cmdLabel = "[exportdun] "; - - FILE *dunFile = OpenFile(levelName.c_str(), "ab"); - - WriteLE16(dunFile, DMAXX); - WriteLE16(dunFile, DMAXY); - - /** Tiles. */ - for (int y = 0; y < DMAXY; y++) { - for (int x = 0; x < DMAXX; x++) { - WriteLE16(dunFile, dungeon[x][y]); - } - } - - /** Padding */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - WriteLE16(dunFile, 0); - } - } - - /** Monsters */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - uint16_t monsterId = 0; - if (dMonster[x][y] > 0) { - for (int i = 0; i < 157; i++) { - if (MonstConvTbl[i] == Monsters[std::abs(dMonster[x][y]) - 1].type().type) { - monsterId = i + 1; - break; - } - } - } - WriteLE16(dunFile, monsterId); - } - } - - /** Objects */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - uint16_t objectId = 0; - Object *object = FindObjectAtPosition({ x, y }, false); - if (object != nullptr) { - for (int i = 0; i < 147; i++) { - if (ObjTypeConv[i] == object->_otype) { - objectId = i; - break; - } - } - } - WriteLE16(dunFile, objectId); - } - } - - /** Transparency */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - WriteLE16(dunFile, dTransVal[x][y]); - } - } - std::fclose(dunFile); - - return StrCat(cmdLabel, "Successfully exported ", levelName, "."); -} - -std::unordered_map TownerShortNameToTownerId = { - { "griswold", _talker_id::TOWN_SMITH }, - { "smith", _talker_id::TOWN_SMITH }, - { "pepin", _talker_id::TOWN_HEALER }, - { "healer", _talker_id::TOWN_HEALER }, - { "ogden", _talker_id::TOWN_TAVERN }, - { "tavern", _talker_id::TOWN_TAVERN }, - { "cain", _talker_id::TOWN_STORY }, - { "story", _talker_id::TOWN_STORY }, - { "farnham", _talker_id::TOWN_DRUNK }, - { "drunk", _talker_id::TOWN_DRUNK }, - { "adria", _talker_id::TOWN_WITCH }, - { "witch", _talker_id::TOWN_WITCH }, - { "gillian", _talker_id::TOWN_BMAID }, - { "bmaid", _talker_id::TOWN_BMAID }, - { "wirt", _talker_id ::TOWN_PEGBOY }, - { "pegboy", _talker_id ::TOWN_PEGBOY }, - { "lester", _talker_id ::TOWN_FARMER }, - { "farmer", _talker_id ::TOWN_FARMER }, - { "girl", _talker_id ::TOWN_GIRL }, - { "nut", _talker_id::TOWN_COWFARM }, - { "cowfarm", _talker_id::TOWN_COWFARM }, -}; - -std::string DebugCmdVisitTowner(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[visit] "; - - if (setlevel || !myPlayer.isOnLevel(0)) - return StrCat(cmdLabel, "This command is only available in Town!"); - - if (parameter.empty()) { - std::string ret; - ret = StrCat(cmdLabel, "Please provide the name of a Towner: "); - for (const auto &[name, _] : TownerShortNameToTownerId) { - ret += ' '; - ret.append(name); - } - return ret; - } - - auto it = TownerShortNameToTownerId.find(parameter); - if (it == TownerShortNameToTownerId.end()) - return StrCat(cmdLabel, parameter, " is invalid!"); - - for (auto &towner : Towners) { - if (towner._ttype != it->second) - continue; - - CastSpell( - MyPlayerId, - SpellID::Teleport, - myPlayer.position.tile, - towner.position, - 1); - - return StrCat(cmdLabel, "Moved you to ", parameter, "."); - } - - return StrCat(cmdLabel, "Unable to locate ", parameter, "!"); -} - -std::string DebugCmdResetLevel(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[restart] "; - - auto args = SplitByChar(parameter, ' '); - auto it = args.begin(); - if (it == args.end()) - return StrCat(cmdLabel, "Missing level number!"); - int level; - { - const ParseIntResult parsedArg = ParseInt(*it, /*min=*/0); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument 1 as integer!"); - level = parsedArg.value(); - } - if (level > (gbIsHellfire ? 24 : 16)) - return StrCat(cmdLabel, "Level ", level, " does not exist!"); - myPlayer._pLvlVisited[level] = false; - DeltaClearLevel(level); - - if (++it != args.end()) { - const ParseIntResult parsedArg = ParseInt(*it); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument 2 as uint32_t!"); - glSeedTbl[level] = parsedArg.value(); - } - - if (myPlayer.isOnLevel(level)) - return StrCat(cmdLabel, "Unable to reset dungeon levels occupied by players!"); - return StrCat(cmdLabel, "Successfully reset level ", level, "."); -} - -std::string DebugCmdGodMode(const std::string_view parameter) -{ - std::string cmdLabel = "[god] "; - - DebugGodMode = !DebugGodMode; - - return StrCat(cmdLabel, "God mode: ", DebugGodMode ? "On" : "Off"); -} - -std::string DebugCmdLighting(const std::string_view parameter) -{ - std::string cmdLabel = "[fullbright] "; - - ToggleLighting(); - - return StrCat(cmdLabel, "Fullbright: ", DisableLighting ? "On" : "Off"); -} - -std::string DebugCmdVision(const std::string_view parameter) -{ - std::string cmdLabel = "[drawvision] "; - - DebugVision = !DebugVision; - - return StrCat(cmdLabel, "Vision highlighting: ", DebugVision ? "On" : "Off"); -} - -std::string DebugCmdPath(const std::string_view parameter) -{ - std::string cmdLabel = "[drawpath] "; - - DebugPath = !DebugPath; - - return StrCat(cmdLabel, "Path highlighting: ", DebugPath ? "On" : "Off"); -} - -std::string DebugCmdRefillHealthMana(const std::string_view parameter) -{ - std::string cmdLabel = "[fill] "; - - Player &myPlayer = *MyPlayer; - myPlayer.RestoreFullLife(); - myPlayer.RestoreFullMana(); - RedrawComponent(PanelDrawComponent::Health); - RedrawComponent(PanelDrawComponent::Mana); - - return StrCat(cmdLabel, "Restored life and mana to full."); -} - -std::string DebugCmdChangeHealth(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[changehp] "; - int change = -1; - - if (!parameter.empty()) { - const ParseIntResult parsedArg = ParseInt(parameter); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument as integer!"); - change = parsedArg.value(); - } - - if (change == 0) - return StrCat(cmdLabel, "Enter a value not equal to 0 to change life!"); - - int newHealth = myPlayer._pHitPoints + (change * 64); - SetPlayerHitPoints(myPlayer, newHealth); - if (newHealth <= 0) - SyncPlrKill(myPlayer, DeathReason::MonsterOrTrap); - - return StrCat(cmdLabel, "Changed life by ", change); -} - -std::string DebugCmdChangeMana(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[changemana] "; - int change = -1; - - if (!parameter.empty()) { - const ParseIntResult parsedArg = ParseInt(parameter); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument as integer!"); - change = parsedArg.value(); - } - - if (change == 0) - return StrCat(cmdLabel, "Enter a value not equal to 0 to change mana!"); - - int newMana = myPlayer._pMana + (change * 64); - myPlayer._pMana = newMana; - myPlayer._pManaBase = myPlayer._pMana + myPlayer._pMaxManaBase - myPlayer._pMaxMana; - RedrawComponent(PanelDrawComponent::Mana); - - return StrCat(cmdLabel, "Changed mana by ", change); -} - -std::string DebugCmdGenerateUniqueItem(const std::string_view parameter) -{ - return DebugSpawnUniqueItem(parameter.data()); -} - -std::string DebugCmdGenerateItem(const std::string_view parameter) -{ - return DebugSpawnItem(parameter.data()); -} - -std::string DebugCmdExit(const std::string_view parameter) -{ - std::string cmdLabel = "[exit] "; - gbRunGame = false; - gbRunGameResult = false; - return StrCat(cmdLabel, "Exiting game."); -} - -std::string DebugCmdArrow(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[arrow] "; - - myPlayer._pIFlags &= ~ItemSpecialEffect::FireArrows; - myPlayer._pIFlags &= ~ItemSpecialEffect::LightningArrows; - - if (parameter == "normal") { - // we removed the parameter at the top - } else if (parameter == "fire") { - myPlayer._pIFlags |= ItemSpecialEffect::FireArrows; - } else if (parameter == "lightning") { - myPlayer._pIFlags |= ItemSpecialEffect::LightningArrows; - } else if (parameter == "spectral") { - myPlayer._pIFlags |= (ItemSpecialEffect::FireArrows | ItemSpecialEffect::LightningArrows); - } else { - return StrCat(cmdLabel, "Invalid parameter!"); - } - - return StrCat(cmdLabel, "Arrows changed to: ", parameter); -} - -std::string DebugCmdTalkToTowner(const std::string_view parameter) -{ - std::string cmdLabel = "[talkto] "; - - if (DebugTalkToTowner(parameter.data())) { - return StrCat(cmdLabel, "Opened ", parameter, " talk window."); - } - return StrCat(cmdLabel, "Towner not found!"); -} - -std::string DebugCmdSpawnUniqueMonster(const std::string_view parameter) -{ - std::string cmdLabel = "[spawnu] "; - - if (leveltype == DTYPE_TOWN) - return StrCat(cmdLabel, "This command is not available in Town!"); - - std::string name; - int count = 1; - for (std::string_view arg : SplitByChar(parameter, ' ')) { - const ParseIntResult parsedArg = ParseInt(arg); - if (!parsedArg.has_value()) { - name.append(arg); - name += ' '; - continue; - } - const int num = parsedArg.value(); - if (num > 0) { - count = num; - break; - } - } - if (name.empty()) - return StrCat(cmdLabel, "Missing monster name!"); - - name.pop_back(); // remove last space - AsciiStrToLower(name); - - int mtype = -1; - UniqueMonsterType uniqueIndex = UniqueMonsterType::None; - for (size_t i = 0; UniqueMonstersData[i].mtype != MT_INVALID; i++) { - auto mondata = UniqueMonstersData[i]; - const std::string monsterName = AsciiStrToLower(mondata.mName); - if (monsterName.find(name) == std::string::npos) - continue; - mtype = mondata.mtype; - uniqueIndex = static_cast(i); - if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given - break; - } - - if (mtype == -1) - return StrCat(cmdLabel, "Monster not found!"); - - size_t id = MaxLvlMTypes - 1; - bool found = false; - - for (size_t i = 0; i < LevelMonsterTypeCount; i++) { - if (LevelMonsterTypes[i].type == mtype) { - id = i; - found = true; - break; - } - } - - if (!found) { - CMonster &monsterType = LevelMonsterTypes[id]; - monsterType.type = static_cast<_monster_id>(mtype); - InitMonsterGFX(monsterType); - InitMonsterSND(monsterType); - monsterType.placeFlags |= PLACE_SCATTER; - monsterType.corpseId = 1; - } - - Player &myPlayer = *MyPlayer; - - int spawnedMonster = 0; - - auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { - Point pos = myPlayer.position.tile + displacement; - if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) - return {}; - if (!IsTileWalkable(pos)) - return {}; - - Monster *monster = AddMonster(pos, myPlayer._pdir, id, true); - if (monster == nullptr) - return StrCat(cmdLabel, "Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); - PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[static_cast(uniqueIndex)]); - monster->corpseId = 1; - spawnedMonster += 1; - - if (spawnedMonster >= count) - return StrCat(cmdLabel, "Spawned ", spawnedMonster, " monsters."); - - return {}; - }); - - if (!ret) - ret = StrCat(cmdLabel, "Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); - return *ret; -} - -std::string DebugCmdSpawnMonster(const std::string_view parameter) -{ - std::string cmdLabel = "[spawn] "; - - if (leveltype == DTYPE_TOWN) - return StrCat(cmdLabel, "This command is not available in Town!"); - - std::string name; - int count = 1; - for (std::string_view arg : SplitByChar(parameter, ' ')) { - const ParseIntResult parsedArg = ParseInt(arg); - if (!parsedArg.has_value()) { - name.append(arg); - name += ' '; - continue; - } - const int num = parsedArg.value(); - if (num > 0) { - count = num; - break; - } - } - if (name.empty()) - return StrCat(cmdLabel, "Missing monster name!"); - - name.pop_back(); // remove last space - AsciiStrToLower(name); - - int mtype = -1; - - for (int i = 0; i < NUM_MTYPES; i++) { - auto mondata = MonstersData[i]; - const std::string monsterName = AsciiStrToLower(mondata.name); - if (monsterName.find(name) == std::string::npos) - continue; - mtype = i; - if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given - break; - } - - if (mtype == -1) - return StrCat(cmdLabel, "Monster not found!"); - - size_t id = MaxLvlMTypes - 1; - bool found = false; - - for (size_t i = 0; i < LevelMonsterTypeCount; i++) { - if (LevelMonsterTypes[i].type == mtype) { - id = i; - found = true; - break; - } - } - - if (!found) { - CMonster &monsterType = LevelMonsterTypes[id]; - monsterType.type = static_cast<_monster_id>(mtype); - InitMonsterGFX(monsterType); - InitMonsterSND(monsterType); - monsterType.placeFlags |= PLACE_SCATTER; - monsterType.corpseId = 1; - } - - Player &myPlayer = *MyPlayer; - - int spawnedMonster = 0; - - auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { - Point pos = myPlayer.position.tile + displacement; - if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) - return {}; - if (!IsTileWalkable(pos)) - return {}; - - if (AddMonster(pos, myPlayer._pdir, id, true) == nullptr) - return StrCat(cmdLabel, "Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); - spawnedMonster += 1; - - if (spawnedMonster >= count) - return StrCat(cmdLabel, "Spawned ", spawnedMonster, " monsters."); - - return {}; - }); - - if (!ret) - return StrCat(cmdLabel, "Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); - return *ret; -} - -std::string DebugCmdShowTileData(const std::string_view parameter) -{ - std::string cmdLabel = "[tiledata] "; - - std::string paramList[] = { - "dPiece", - "dTransVal", - "dLight", - "dPreLight", - "dFlags", - "dPlayer", - "dMonster", - "dCorpse", - "dObject", - "dItem", - "dSpecial", - "coords", - "cursorcoords", - "objectindex", - "solid", - "transparent", - "trap", - "AutomapView", - "dungeon", - "pdungeon", - "Protected", - }; - - if (parameter == "clear") { - SelectedDebugGridTextItem = DebugGridTextItem::None; - return StrCat(cmdLabel, "Tile data cleared."); - } - if (parameter == "") { - std::string list = "clear"; - for (const auto ¶m : paramList) { - list += " / " + param; - } - return list; - } - bool found = false; - int index = 0; - for (const auto ¶m : paramList) { - index++; - if (parameter != param) - continue; - found = true; - auto newGridText = static_cast(index); - if (newGridText == SelectedDebugGridTextItem) { - SelectedDebugGridTextItem = DebugGridTextItem::None; - return StrCat(cmdLabel, "Tile data: Off"); - } - SelectedDebugGridTextItem = newGridText; - break; - } - if (!found) - return StrCat(cmdLabel, "Invalid name! Check names using tiledata command."); - - return StrCat(cmdLabel, "Tile data: On"); -} - -std::string DebugCmdScrollView(const std::string_view parameter) -{ - std::string cmdLabel = "[scrollview] "; - - DebugScrollViewEnabled = !DebugScrollViewEnabled; - if (DebugScrollViewEnabled) - return StrCat(cmdLabel, "Scroll view: On"); - InitMultiView(); - return StrCat(cmdLabel, "Scroll view: Off"); -} - -std::string DebugCmdItemInfo(const std::string_view parameter) -{ - std::string cmdLabel = "[iteminfo] "; - - Player &myPlayer = *MyPlayer; - Item *pItem = nullptr; - if (!myPlayer.HoldItem.isEmpty()) { - pItem = &myPlayer.HoldItem; - } else if (pcursinvitem != -1) { - if (pcursinvitem <= INVITEM_INV_LAST) - pItem = &myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]; - else - pItem = &myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]; - } else if (pcursitem != -1) { - pItem = &Items[pcursitem]; - } - if (pItem != nullptr) { - std::string_view netPackValidation { "N/A" }; - if (gbIsMultiplayer) { - ItemNetPack itemPack; - Item unpacked; - PackNetItem(*pItem, itemPack); - netPackValidation = UnPackNetItem(myPlayer, itemPack, unpacked) ? "Success" : "Failure"; - } - return StrCat("Name: ", pItem->_iIName, - "\nIDidx: ", pItem->IDidx, " (", AllItemsList[pItem->IDidx].iName, ")", - "\nSeed: ", pItem->_iSeed, - "\nCreateInfo: ", pItem->_iCreateInfo, - "\nLevel: ", pItem->_iCreateInfo & CF_LEVEL, - "\nOnly Good: ", ((pItem->_iCreateInfo & CF_ONLYGOOD) == 0) ? "False" : "True", - "\nUnique Monster: ", ((pItem->_iCreateInfo & CF_UPER15) == 0) ? "False" : "True", - "\nDungeon Item: ", ((pItem->_iCreateInfo & CF_UPER1) == 0) ? "False" : "True", - "\nUnique Item: ", ((pItem->_iCreateInfo & CF_UNIQUE) == 0) ? "False" : "True", - "\nSmith: ", ((pItem->_iCreateInfo & CF_SMITH) == 0) ? "False" : "True", - "\nSmith Premium: ", ((pItem->_iCreateInfo & CF_SMITHPREMIUM) == 0) ? "False" : "True", - "\nBoy: ", ((pItem->_iCreateInfo & CF_BOY) == 0) ? "False" : "True", - "\nWitch: ", ((pItem->_iCreateInfo & CF_WITCH) == 0) ? "False" : "True", - "\nHealer: ", ((pItem->_iCreateInfo & CF_HEALER) == 0) ? "False" : "True", - "\nPregen: ", ((pItem->_iCreateInfo & CF_PREGEN) == 0) ? "False" : "True", - "\nNet Validation: ", netPackValidation); - } - return StrCat(cmdLabel, "Numitems: ", ActiveItemCount); -} - -std::string DebugCmdPlayerInfo(const std::string_view parameter) -{ - std::string cmdLabel = "[playerinfo] "; - - if (parameter.empty()) { - return StrCat(cmdLabel, "Provide a player ID between 0 and ", Players.size() - 1); - } - const ParseIntResult parsedArg = ParseInt(parameter); - if (!parsedArg.has_value()) { - return StrCat(cmdLabel, "Failed to parse argument as size_t in range!"); - } - const size_t playerId = parsedArg.value(); - if (static_cast(playerId) >= Players.size()) - return StrCat(cmdLabel, "Invalid playerId!"); - Player &player = Players[playerId]; - if (!player.plractive) - return StrCat(cmdLabel, "Player is not active!"); - - const Point target = player.GetTargetPosition(); - return StrCat(cmdLabel, "Plr ", playerId, " is ", player._pName, - "\nLvl: ", player.plrlevel, " Changing: ", player._pLvlChanging, - "\nTile.x: ", player.position.tile.x, " Tile.y: ", player.position.tile.y, " Target.x: ", target.x, " Target.y: ", target.y, - "\nMode: ", player._pmode, " destAction: ", player.destAction, " walkpath[0]: ", player.walkpath[0], - "\nInvincible:", player._pInvincible ? 1 : 0, " HitPoints:", player._pHitPoints); -} - -std::string DebugCmdToggleFPS(const std::string_view parameter) -{ - std::string cmdLabel = "[fps] "; - - frameflag = !frameflag; - - return StrCat(cmdLabel, "FPS counter: ", frameflag ? "On" : "Off"); -} - -std::string DebugCmdChangeTRN(const std::string_view parameter) -{ - std::string cmdLabel = "[trn] "; - - std::string out; - const auto parts = SplitByChar(parameter, ' '); - auto it = parts.begin(); - if (it != parts.end()) { - const std::string_view first = *it; - if (++it != parts.end()) { - const std::string_view second = *it; - std::string_view prefix; - if (first == "mon") { - prefix = "monsters\\monsters\\"; - } else if (first == "plr") { - prefix = "plrgfx\\"; - } - debugTRN = StrCat(prefix, second, ".trn"); - } else { - debugTRN = StrCat(first, ".trn"); - } - out = StrCat(cmdLabel, "TRN loaded: ", debugTRN); - } else { - debugTRN = ""; - out = StrCat(cmdLabel, "TRN disabled."); - } - auto &player = *MyPlayer; - InitPlayerGFX(player); - StartStand(player, player._pdir); - return out; -} - -std::string DebugCmdSearchMonster(const std::string_view parameter) -{ - std::string cmdLabel = "[searchmonster] "; - - if (parameter.empty()) { - std::string ret = StrCat(cmdLabel, "Missing monster name!"); - return ret; - } - - std::string name; - name.append(parameter); - AsciiStrToLower(name); - SearchMonsters.push_back(name); - - return StrCat(cmdLabel, "Added automap marker for monster ", name, "."); -} - -std::string DebugCmdSearchItem(const std::string_view parameter) -{ - std::string cmdLabel = "[searchitem] "; - - if (parameter.empty()) { - std::string ret = StrCat(cmdLabel, "Missing item name!"); - return ret; - } - - std::string name; - name.append(parameter); - AsciiStrToLower(name); - SearchItems.push_back(name); - - return StrCat(cmdLabel, "Added automap marker for item ", name, "."); -} - -std::string DebugCmdSearchObject(const std::string_view parameter) -{ - std::string cmdLabel = "[searchobject] "; - - if (parameter.empty()) { - std::string ret = StrCat(cmdLabel, "Missing object name!"); - return ret; - } - - std::string name; - name.append(parameter); - AsciiStrToLower(name); - SearchObjects.push_back(name); - - return StrCat(cmdLabel, "Added automap marker for object ", name, "."); -} - -std::string DebugCmdClearSearch(const std::string_view parameter) -{ - std::string cmdLabel = "[clearsearch] "; - - SearchMonsters.clear(); - SearchItems.clear(); - SearchObjects.clear(); - - return StrCat(cmdLabel, "Removed all automap search markers."); -} - -std::vector DebugCmdList = { - { "help", "Prints help overview or help for a specific command.", "({command})", &DebugCmdHelp }, - { "goto", "Moves to specifided {level} (use 0 for town).", "{level}", &DebugCmdWarpToLevel }, - { "questmap", "Load a quest level {level}.", "{level}", &DebugCmdLoadQuestMap }, - { "map", "Load custom level from a given {path}.dun.", "{path} {type} {x} {y}", &DebugCmdLoadMap }, - { "exportdun", "Save the current level as a dun-file.", "", &ExportDun }, - { "visit", "Visit a towner.", "{towner}", &DebugCmdVisitTowner }, - { "restart", "Resets specified {level}.", "{level} ({seed})", &DebugCmdResetLevel }, - { "god", "Toggles godmode.", "", &DebugCmdGodMode }, - { "drawvision", "Toggles vision debug rendering.", "", &DebugCmdVision }, - { "drawpath", "Toggles path debug rendering.", "", &DebugCmdPath }, - { "fullbright", "Toggles whether light shading is in effect.", "", &DebugCmdLighting }, - { "fill", "Refills health and mana.", "", &DebugCmdRefillHealthMana }, - { "changehp", "Changes health by {value} (Use a negative value to remove health).", "{value}", &DebugCmdChangeHealth }, - { "changemp", "Changes mana by {value} (Use a negative value to remove mana).", "{value}", &DebugCmdChangeMana }, - { "dropu", "Attempts to generate unique item {name}.", "{name}", &DebugCmdGenerateUniqueItem }, - { "drop", "Attempts to generate item {name}.", "{name}", &DebugCmdGenerateItem }, - { "talkto", "Interacts with a NPC whose name contains {name}.", "{name}", &DebugCmdTalkToTowner }, - { "exit", "Exits the game.", "", &DebugCmdExit }, - { "arrow", "Changes arrow effect (normal, fire, lightning, explosion).", "{effect}", &DebugCmdArrow }, - { "spawnu", "Spawns unique monster {name}.", "{name} ({count})", &DebugCmdSpawnUniqueMonster }, - { "spawn", "Spawns monster {name}.", "{name} ({count})", &DebugCmdSpawnMonster }, - { "tiledata", "Toggles showing tile data {name} (leave name empty to see a list).", "{name}", &DebugCmdShowTileData }, - { "scrollview", "Toggles scroll view feature (with shift+mouse).", "", &DebugCmdScrollView }, - { "iteminfo", "Shows info of currently selected item.", "", &DebugCmdItemInfo }, - { "playerinfo", "Shows info of player.", "{playerid}", &DebugCmdPlayerInfo }, - { "fps", "Toggles displaying FPS", "", &DebugCmdToggleFPS }, - { "trn", "Makes player use TRN {trn} - Write 'plr' before it to look in plrgfx\\ or 'mon' to look in monsters\\monsters\\ - example: trn plr infra is equal to 'plrgfx\\infra.trn'", "{trn}", &DebugCmdChangeTRN }, - { "searchmonster", "Searches the automap for {monster}", "{monster}", &DebugCmdSearchMonster }, - { "searchitem", "Searches the automap for {item}", "{item}", &DebugCmdSearchItem }, - { "searchobject", "Searches the automap for {object}", "{object}", &DebugCmdSearchObject }, - { "clearsearch", "Search in the auto map is cleared", "", &DebugCmdClearSearch }, -}; - } // namespace void LoadDebugGFX() @@ -1066,23 +113,6 @@ void SetDebugLevelSeedInfos(uint32_t mid1Seed, uint32_t mid2Seed, uint32_t mid3S glEndSeed[currlevel] = endSeed; } -bool CheckDebugTextCommand(const std::string_view text) -{ - auto debugCmdIterator = c_find_if(DebugCmdList, [&](const DebugCmdItem &elem) { return text.find(elem.text) == 0 && (text.length() == elem.text.length() || text[elem.text.length()] == ' '); }); - if (debugCmdIterator == DebugCmdList.end()) - return false; - - auto &dbgCmd = *debugCmdIterator; - std::string_view parameter = ""; - if (text.length() > (dbgCmd.text.length() + 1)) - parameter = text.substr(dbgCmd.text.length() + 1); - const auto result = dbgCmd.actionProc(parameter); - Log("DebugCmd: {} Result: {}", text, result); - if (result != "") - EventPlrMsg(result, UiFlags::ColorRed); - return true; -} - bool IsDebugGridTextNeeded() { return SelectedDebugGridTextItem != DebugGridTextItem::None; @@ -1101,6 +131,16 @@ bool IsDebugGridInMegatiles() } } +DebugGridTextItem GetDebugGridTextType() +{ + return SelectedDebugGridTextItem; +} + +void SetDebugGridTextType(DebugGridTextItem value) +{ + SelectedDebugGridTextItem = value; +} + bool GetDebugGridText(Point dungeonCoords, char *debugGridTextBuffer) { int info = 0; @@ -1228,6 +268,28 @@ bool ShouldHighlightDebugAutomapTile(Point position) return false; } +void AddDebugAutomapMonsterHighlight(std::string_view name) +{ + SearchMonsters.emplace_back(name); +} + +void AddDebugAutomapItemHighlight(std::string_view name) +{ + SearchItems.emplace_back(name); +} + +void AddDebugAutomapObjectHighlight(std::string_view name) +{ + SearchObjects.emplace_back(name); +} + +void ClearDebugAutomapHighlights() +{ + SearchMonsters.clear(); + SearchItems.clear(); + SearchObjects.clear(); +} + } // namespace devilution #endif diff --git a/Source/debug.h b/Source/debug.h index c5931887d..29d994fc9 100644 --- a/Source/debug.h +++ b/Source/debug.h @@ -30,16 +30,51 @@ extern uint32_t glMid2Seed[NUMLEVELS]; extern uint32_t glMid3Seed[NUMLEVELS]; extern uint32_t glEndSeed[NUMLEVELS]; +enum class DebugGridTextItem : uint16_t { + None, + dPiece, + dTransVal, + dLight, + dPreLight, + dFlags, + dPlayer, + dMonster, + dCorpse, + dObject, + dItem, + dSpecial, + + coords, + cursorcoords, + objectindex, + + // take dPiece as index + Solid, + Transparent, + Trap, + + // megatiles + AutomapView, + dungeon, + pdungeon, + Protected, +}; + void FreeDebugGFX(); void LoadDebugGFX(); void GetDebugMonster(); void NextDebugMonster(); void SetDebugLevelSeedInfos(uint32_t mid1Seed, uint32_t mid2Seed, uint32_t mid3Seed, uint32_t endSeed); -bool CheckDebugTextCommand(const std::string_view text); bool IsDebugGridTextNeeded(); bool IsDebugGridInMegatiles(); +DebugGridTextItem GetDebugGridTextType(); +void SetDebugGridTextType(DebugGridTextItem value); bool GetDebugGridText(Point dungeonCoords, char *debugGridTextBuffer); bool IsDebugAutomapHighlightNeeded(); bool ShouldHighlightDebugAutomapTile(Point position); +void AddDebugAutomapMonsterHighlight(std::string_view name); +void AddDebugAutomapItemHighlight(std::string_view name); +void AddDebugAutomapObjectHighlight(std::string_view name); +void ClearDebugAutomapHighlights(); } // namespace devilution diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 6a9def32d..f1bdca0f5 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -814,8 +814,9 @@ void RunGameLoop(interface_mode uMsg) #ifdef _DEBUG if (!gbGameLoopStartup && !DebugCmdsFromCommandLine.empty()) { - for (auto &cmd : DebugCmdsFromCommandLine) { - CheckDebugTextCommand(cmd); + InitConsole(); + for (const std::string &cmd : DebugCmdsFromCommandLine) { + RunInConsole(cmd); } DebugCmdsFromCommandLine.clear(); } diff --git a/Source/items.cpp b/Source/items.cpp index 3c282f419..be5149bfd 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -4577,10 +4577,7 @@ void PutItemRecord(uint32_t nSeed, uint16_t wCI, int nIndex) std::mt19937 BetterRng; std::string DebugSpawnItem(std::string itemName) { - std::string cmdLabel = "[drop] "; - - if (ActiveItemCount >= MAXITEMS) - return StrCat(cmdLabel, "No space to generate the item!"); + if (ActiveItemCount >= MAXITEMS) return "No space to generate the item!"; const int max_time = 3000; const int max_iter = 1000000; @@ -4596,10 +4593,10 @@ std::string DebugSpawnItem(std::string itemName) std::uniform_int_distribution dist(0, INT_MAX); SetRndSeed(dist(BetterRng)); if (SDL_GetTicks() - begin > max_time) - return StrCat(cmdLabel, "Item not found in ", max_time / 1000, " seconds!"); + return StrCat("Item not found in ", max_time / 1000, " seconds!"); if (i > max_iter) - return StrCat(cmdLabel, "Item not found in ", max_iter, " tries!"); + return StrCat("Item not found in ", max_iter, " tries!"); const int8_t monsterLevel = dist(BetterRng) % CF_LEVEL + 1; _item_indexes idx = RndItemForMonsterLevel(monsterLevel); @@ -4621,15 +4618,12 @@ std::string DebugSpawnItem(std::string itemName) Point pos = MyPlayer->position.tile; GetSuperItemSpace(pos, ii); NetSendCmdPItem(false, CMD_SPAWNITEM, item.position, item); - return StrCat(cmdLabel, "Item generated successfully - iterations: ", i); + return StrCat("Item generated successfully - iterations: ", i); } std::string DebugSpawnUniqueItem(std::string itemName) { - std::string cmdLabel = "[dropu] "; - - if (ActiveItemCount >= MAXITEMS) - return StrCat(cmdLabel, "No space to generate the item!"); + if (ActiveItemCount >= MAXITEMS) return "No space to generate the item!"; AsciiStrToLower(itemName); UniqueItem uniqueItem; @@ -4648,8 +4642,7 @@ std::string DebugSpawnUniqueItem(std::string itemName) break; } } - if (!foundUnique) - return StrCat(cmdLabel, "No unique item found!"); + if (!foundUnique) return "No unique item found!"; _item_indexes uniqueBaseIndex = IDI_GOLD; for (std::underlying_type_t<_item_indexes> j = IDI_GOLD; j <= IDI_LAST; j++) { @@ -4661,8 +4654,7 @@ std::string DebugSpawnUniqueItem(std::string itemName) } } - if (uniqueBaseIndex == IDI_GOLD) - return StrCat(cmdLabel, "Base item not available!"); + if (uniqueBaseIndex == IDI_GOLD) return "Base item not available!"; auto &baseItemData = AllItemsList[static_cast(uniqueBaseIndex)]; @@ -4672,11 +4664,11 @@ std::string DebugSpawnUniqueItem(std::string itemName) for (uint32_t begin = SDL_GetTicks();; i++) { constexpr int max_time = 3000; if (SDL_GetTicks() - begin > max_time) - return StrCat(cmdLabel, "Item not found in ", max_time / 1000, " seconds!"); + return StrCat("Item not found in ", max_time / 1000, " seconds!"); constexpr int max_iter = 1000000; if (i > max_iter) - return StrCat(cmdLabel, "Item not found in ", max_iter, " tries!"); + return StrCat("Item not found in ", max_iter, " tries!"); testItem = {}; testItem._iMiscId = baseItemData.iMiscId; @@ -4695,7 +4687,7 @@ std::string DebugSpawnUniqueItem(std::string itemName) const std::string tmp = AsciiStrToLower(testItem._iIName); if (tmp.find(itemName) != std::string::npos) break; - return StrCat(cmdLabel, "Impossible to generate!"); + return "Impossible to generate!"; } int ii = AllocateItem(); @@ -4706,7 +4698,7 @@ std::string DebugSpawnUniqueItem(std::string itemName) item._iIdentified = true; NetSendCmdPItem(false, CMD_SPAWNITEM, item.position, item); - return StrCat(cmdLabel, "Item generated successfully - iterations: ", i); + return StrCat("Item generated successfully - iterations: ", i); } #endif diff --git a/Source/lua/autocomplete.cpp b/Source/lua/autocomplete.cpp index afc3147ff..0a86a9e38 100644 --- a/Source/lua/autocomplete.cpp +++ b/Source/lua/autocomplete.cpp @@ -184,6 +184,7 @@ void GetLuaAutocompleteSuggestions(std::string_view text, const sol::environment }; if (token.empty()) { + if (prevChar == '.') return; addSuggestions(lua); const auto fallback = lua.get>("_G"); if (fallback.has_value() && fallback->get_type() == sol::type::table) { diff --git a/Source/lua/metadoc.hpp b/Source/lua/metadoc.hpp index 2b0b7f329..9f69da4dd 100644 --- a/Source/lua/metadoc.hpp +++ b/Source/lua/metadoc.hpp @@ -19,7 +19,7 @@ inline std::string LuaDocstringKey(std::string_view key) } template -void SetWithSignatureAndDoc(sol::table &table, std::string_view key, std::string_view signature, std::string_view doc, T &&value) +void SetDocumented(sol::table &table, std::string_view key, std::string_view signature, std::string_view doc, T &&value) { table[key] = std::forward(value); table[LuaSignatureKey(key)] = signature; diff --git a/Source/lua/modules/dev.cpp b/Source/lua/modules/dev.cpp index 56c84e8d1..e3de74633 100644 --- a/Source/lua/modules/dev.cpp +++ b/Source/lua/modules/dev.cpp @@ -3,193 +3,29 @@ #include -#include "automap.h" -#include "debug.h" -#include "items.h" #include "lua/metadoc.hpp" +#include "lua/modules/dev/display.hpp" +#include "lua/modules/dev/items.hpp" +#include "lua/modules/dev/level.hpp" +#include "lua/modules/dev/monsters.hpp" +#include "lua/modules/dev/player.hpp" #include "lua/modules/dev/quests.hpp" -#include "player.h" -#include "spells.h" -#include "utils/str_cat.hpp" +#include "lua/modules/dev/search.hpp" +#include "lua/modules/dev/towners.hpp" namespace devilution { -namespace { - -std::string DebugCmdShowGrid() -{ - DebugGrid = !DebugGrid; - return StrCat("Tile grid highlighting: ", DebugGrid ? "On" : "Off"); -} - -std::string DebugCmdLevelUp(int levels = 1) -{ - if (levels <= 0) return "amount must be positive"; - Player &myPlayer = *MyPlayer; - for (int i = 0; i < levels; i++) - NetSendCmd(true, CMD_CHEAT_EXPERIENCE); - return StrCat("New character level: ", myPlayer.getCharacterLevel() + levels); -} - -std::string DebugCmdMaxStats() -{ - Player &myPlayer = *MyPlayer; - ModifyPlrStr(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength) - myPlayer._pBaseStr); - ModifyPlrMag(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic) - myPlayer._pBaseMag); - ModifyPlrDex(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity) - myPlayer._pBaseDex); - ModifyPlrVit(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Vitality) - myPlayer._pBaseVit); - return "Set all character base attributes to maximum."; -} - -std::string DebugCmdMinStats() -{ - Player &myPlayer = *MyPlayer; - ModifyPlrStr(myPlayer, -myPlayer._pBaseStr); - ModifyPlrMag(myPlayer, -myPlayer._pBaseMag); - ModifyPlrDex(myPlayer, -myPlayer._pBaseDex); - ModifyPlrVit(myPlayer, -myPlayer._pBaseVit); - return "Set all character base attributes to minimum."; -} - -std::string DebugCmdGiveGoldCheat(int goldToAdd = GOLD_MAX_LIMIT * InventoryGridCells) -{ - if (goldToAdd <= 0) return "amount must be positive"; - Player &myPlayer = *MyPlayer; - const int goldAmountBefore = myPlayer._pGold; - for (int8_t &itemIndex : myPlayer.InvGrid) { - if (itemIndex < 0) - continue; - - Item &item = myPlayer.InvList[itemIndex != 0 ? itemIndex - 1 : myPlayer._pNumInv]; - - if (itemIndex != 0) { - if ((!item.isGold() && !item.isEmpty()) || (item.isGold() && item._ivalue == GOLD_MAX_LIMIT)) - continue; - } else { - if (item.isEmpty()) { - MakeGoldStack(item, 0); - myPlayer._pNumInv++; - itemIndex = myPlayer._pNumInv; - } - } - - int goldThatCanBeAdded = (GOLD_MAX_LIMIT - item._ivalue); - if (goldThatCanBeAdded >= goldToAdd) { - item._ivalue += goldToAdd; - myPlayer._pGold += goldToAdd; - break; - } - - item._ivalue += goldThatCanBeAdded; - goldToAdd -= goldThatCanBeAdded; - myPlayer._pGold += goldThatCanBeAdded; - } - - CalcPlrInv(myPlayer, true); - - return StrCat("Set your gold to ", myPlayer._pGold, ", added ", myPlayer._pGold - goldAmountBefore, "."); -} - -std::string DebugCmdTakeGoldCheat(int goldToRemove = GOLD_MAX_LIMIT * InventoryGridCells) -{ - Player &myPlayer = *MyPlayer; - if (goldToRemove <= 0) return "amount must be positive"; - - const int goldAmountBefore = myPlayer._pGold; - for (auto itemIndex : myPlayer.InvGrid) { - itemIndex -= 1; - - if (itemIndex < 0) - continue; - - Item &item = myPlayer.InvList[itemIndex]; - if (!item.isGold()) - continue; - - if (item._ivalue >= goldToRemove) { - myPlayer._pGold -= goldToRemove; - item._ivalue -= goldToRemove; - if (item._ivalue == 0) - myPlayer.RemoveInvItem(itemIndex); - break; - } - - myPlayer._pGold -= item._ivalue; - goldToRemove -= item._ivalue; - myPlayer.RemoveInvItem(itemIndex); - } - - return StrCat("Set your gold to ", myPlayer._pGold, ", removed ", goldAmountBefore - myPlayer._pGold, "."); -} - -std::string DebugCmdSetSpellsLevel(uint8_t level) -{ - for (uint8_t i = static_cast(SpellID::Firebolt); i < MAX_SPELLS; i++) { - if (GetSpellBookLevel(static_cast(i)) != -1) { - NetSendCmdParam2(true, CMD_CHANGE_SPELL_LEVEL, i, level); - } - } - if (level == 0) - MyPlayer->_pMemSpells = 0; - - return StrCat("Set all spell levels to ", level); -} - -std::string DebugCmdMapReveal() -{ - for (int x = 0; x < DMAXX; x++) - for (int y = 0; y < DMAXY; y++) - UpdateAutomapExplorer({ x, y }, MAP_EXP_SHRINE); - return "Automap fully explored."; -} - -std::string DebugCmdMapHide() -{ - for (int x = 0; x < DMAXX; x++) - for (int y = 0; y < DMAXY; y++) - AutomapView[x][y] = MAP_EXP_NONE; - return "Automap exploration removed."; -} - -} // namespace - sol::table LuaDevModule(sol::state_view &lua) { sol::table table = lua.create_table(); - SetWithSignatureAndDoc(table, "grid", "()", - "Toggles showing grid.", &DebugCmdShowGrid); - SetWithSignatureAndDoc(table, "giveGold", "(amount: number = MAX)", - "Gives the player gold.", - sol::overload( - []() { return DebugCmdGiveGoldCheat(); }, - [](int amount) { return DebugCmdGiveGoldCheat(amount); })); - SetWithSignatureAndDoc(table, "giveLvl", "(amount: number = 1)", - "Levels the player up.", - sol::overload( - []() { return DebugCmdLevelUp(); }, - [](int amount) { return DebugCmdLevelUp(amount); })); - SetWithSignatureAndDoc(table, "giveMap", "()", - "Reveal the map.", - &DebugCmdMapReveal); - SetWithSignatureAndDoc(table, "quests", "", - "Quest-related commands.", - LuaDevQuestsModule(lua)); - SetWithSignatureAndDoc(table, "takeGold", "(amount: number = MAX)", - "Takes the player's gold away.", - sol::overload( - []() { return DebugCmdTakeGoldCheat(); }, - [](int amount) { return DebugCmdTakeGoldCheat(amount); })); - SetWithSignatureAndDoc(table, "takeMap", "()", - "Hide the map.", - &DebugCmdMapHide); - SetWithSignatureAndDoc(table, "maxStats", "()", - "Sets all stat values to maximum.", - &DebugCmdMaxStats); - SetWithSignatureAndDoc(table, "minStats", "()", - "Sets all stat values to minimum.", - &DebugCmdMinStats); - SetWithSignatureAndDoc(table, "setSpells", "(level: number)", - "Set spell level for all spells.", &DebugCmdSetSpellsLevel); + SetDocumented(table, "display", "", "Debugging HUD and rendering commands.", LuaDevDisplayModule(lua)); + SetDocumented(table, "items", "", "Item-related commands.", LuaDevItemsModule(lua)); + SetDocumented(table, "level", "", "Level-related commands.", LuaDevLevelModule(lua)); + SetDocumented(table, "monsters", "", "Monster-related commands.", LuaDevMonstersModule(lua)); + SetDocumented(table, "player", "", "Player-related commands.", LuaDevPlayerModule(lua)); + SetDocumented(table, "quests", "", "Quest-related commands.", LuaDevQuestsModule(lua)); + SetDocumented(table, "search", "", "Search the map for monsters / items / objects.", LuaDevSearchModule(lua)); + SetDocumented(table, "towners", "", "Town NPC commands.", LuaDevTownersModule(lua)); return table; } diff --git a/Source/lua/modules/dev/display.cpp b/Source/lua/modules/dev/display.cpp new file mode 100644 index 000000000..9ae89f1c9 --- /dev/null +++ b/Source/lua/modules/dev/display.cpp @@ -0,0 +1,133 @@ +#ifdef _DEBUG +#include "lua/modules/dev/display.hpp" + +#include +#include +#include + +#include + +#include "debug.h" +#include "lighting.h" +#include "lua/metadoc.hpp" +#include "player.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdShowGrid(std::optional on) +{ + DebugGrid = on.value_or(!DebugGrid); + return StrCat("Tile grid highlighting: ", DebugGrid ? "On" : "Off"); +} + +std::string DebugCmdVision(std::optional on) +{ + DebugVision = on.value_or(!DebugVision); + return StrCat("Vision highlighting: ", DebugVision ? "On" : "Off"); +} + +std::string DebugCmdPath(std::optional on) +{ + DebugPath = on.value_or(!DebugPath); + return StrCat("Path highlighting: ", DebugPath ? "On" : "Off"); +} + +std::string DebugCmdFullbright(std::optional on) +{ + ToggleLighting(); + return StrCat("Fullbright: ", DisableLighting ? "On" : "Off"); +} + +std::string DebugCmdShowTileData(std::optional dataType) +{ + static const std::array DataTypes { + "dPiece", + "dTransVal", + "dLight", + "dPreLight", + "dFlags", + "dPlayer", + "dMonster", + "dCorpse", + "dObject", + "dItem", + "dSpecial", + "coords", + "cursorcoords", + "objectindex", + "solid", + "transparent", + "trap", + "AutomapView", + "dungeon", + "pdungeon", + "Protected", + }; + if (!dataType.has_value()) { + std::string result = "Valid values for the first argument:\nclear"; + for (const std::string_view &str : DataTypes) + StrAppend(result, ", ", str); + return result; + } + if (*dataType == "clear") { + SetDebugGridTextType(DebugGridTextItem::None); + return "Tile data cleared."; + } + bool found = false; + int index = 0; + for (const std::string_view ¶m : DataTypes) { + index++; + if (*dataType != param) + continue; + found = true; + auto newGridText = static_cast(index); + if (newGridText == GetDebugGridTextType()) { + SetDebugGridTextType(DebugGridTextItem::None); + return "Tile data: Off"; + } + SetDebugGridTextType(newGridText); + break; + } + if (!found) { + std::string result = "Invalid name! Valid names are:\nclear"; + for (const std::string_view &str : DataTypes) + StrAppend(result, ", ", str); + return result; + } + + return "Tile data: On"; +} + +std::string DebugCmdScrollView(std::optional on) +{ + DebugScrollViewEnabled = on.value_or(!DebugScrollViewEnabled); + if (!DebugScrollViewEnabled) + InitMultiView(); + return StrCat("Scroll view: ", DebugScrollViewEnabled ? "On" : "Off"); +} + +std::string DebugCmdToggleFPS(std::optional on) +{ + frameflag = on.value_or(!frameflag); + return StrCat("FPS counter: ", frameflag ? "On" : "Off"); +} + +} // namespace + +sol::table LuaDevDisplayModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "fps", "(name: string = nil)", "Toggle FPS display.", &DebugCmdToggleFPS); + SetDocumented(table, "fullbright", "(on: boolean = nil)", "Toggle light shading.", &DebugCmdFullbright); + SetDocumented(table, "grid", "(on: boolean = nil)", "Toggle showing the grid.", &DebugCmdShowGrid); + SetDocumented(table, "path", "(on: boolean = nil)", "Toggle path debug rendering.", &DebugCmdPath); + SetDocumented(table, "scrollView", "(on: boolean = nil)", "Toggle view scrolling via Shift+Mouse.", &DebugCmdScrollView); + SetDocumented(table, "tileData", "(name: string = nil)", "Toggle showing tile data.", &DebugCmdShowTileData); + SetDocumented(table, "vision", "(on: boolean = nil)", "Toggle vision debug rendering.", &DebugCmdVision); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/display.hpp b/Source/lua/modules/dev/display.hpp new file mode 100644 index 000000000..fb7adb669 --- /dev/null +++ b/Source/lua/modules/dev/display.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevDisplayModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/items.cpp b/Source/lua/modules/dev/items.cpp new file mode 100644 index 000000000..6d6d95100 --- /dev/null +++ b/Source/lua/modules/dev/items.cpp @@ -0,0 +1,73 @@ +#ifdef _DEBUG +#include "lua/modules/dev/items.hpp" + +#include + +#include + +#include "cursor.h" +#include "items.h" +#include "lua/metadoc.hpp" +#include "pack.h" +#include "player.h" +#include "utils/str_cat.hpp" + +namespace devilution { + +namespace { + +std::string DebugCmdItemInfo() +{ + Player &myPlayer = *MyPlayer; + Item *pItem = nullptr; + if (!myPlayer.HoldItem.isEmpty()) { + pItem = &myPlayer.HoldItem; + } else if (pcursinvitem != -1) { + if (pcursinvitem <= INVITEM_INV_LAST) + pItem = &myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]; + else + pItem = &myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]; + } else if (pcursitem != -1) { + pItem = &Items[pcursitem]; + } + if (pItem != nullptr) { + std::string_view netPackValidation { "N/A" }; + if (gbIsMultiplayer) { + ItemNetPack itemPack; + Item unpacked; + PackNetItem(*pItem, itemPack); + netPackValidation = UnPackNetItem(myPlayer, itemPack, unpacked) ? "Success" : "Failure"; + } + return StrCat("Name: ", pItem->_iIName, + "\nIDidx: ", pItem->IDidx, " (", AllItemsList[pItem->IDidx].iName, ")", + "\nSeed: ", pItem->_iSeed, + "\nCreateInfo: ", pItem->_iCreateInfo, + "\nLevel: ", pItem->_iCreateInfo & CF_LEVEL, + "\nOnly Good: ", ((pItem->_iCreateInfo & CF_ONLYGOOD) == 0) ? "False" : "True", + "\nUnique Monster: ", ((pItem->_iCreateInfo & CF_UPER15) == 0) ? "False" : "True", + "\nDungeon Item: ", ((pItem->_iCreateInfo & CF_UPER1) == 0) ? "False" : "True", + "\nUnique Item: ", ((pItem->_iCreateInfo & CF_UNIQUE) == 0) ? "False" : "True", + "\nSmith: ", ((pItem->_iCreateInfo & CF_SMITH) == 0) ? "False" : "True", + "\nSmith Premium: ", ((pItem->_iCreateInfo & CF_SMITHPREMIUM) == 0) ? "False" : "True", + "\nBoy: ", ((pItem->_iCreateInfo & CF_BOY) == 0) ? "False" : "True", + "\nWitch: ", ((pItem->_iCreateInfo & CF_WITCH) == 0) ? "False" : "True", + "\nHealer: ", ((pItem->_iCreateInfo & CF_HEALER) == 0) ? "False" : "True", + "\nPregen: ", ((pItem->_iCreateInfo & CF_PREGEN) == 0) ? "False" : "True", + "\nNet Validation: ", netPackValidation); + } + return StrCat("Num items: ", ActiveItemCount); +} + +} // namespace + +sol::table LuaDevItemsModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "info", "()", "Show info of currently selected item.", &DebugCmdItemInfo); + SetDocumented(table, "spawn", "(name: string)", "Attempt to generate an item.", &DebugSpawnItem); + SetDocumented(table, "spawnUnique", "(name: string)", "Attempt to generate a unique item.", &DebugSpawnUniqueItem); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/items.hpp b/Source/lua/modules/dev/items.hpp new file mode 100644 index 000000000..9e7b868f6 --- /dev/null +++ b/Source/lua/modules/dev/items.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevItemsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level.cpp b/Source/lua/modules/dev/level.cpp new file mode 100644 index 000000000..002e57be4 --- /dev/null +++ b/Source/lua/modules/dev/level.cpp @@ -0,0 +1,124 @@ +#ifdef _DEBUG +#include "lua/modules/dev/level.hpp" + +#include +#include +#include + +#include + +#include "diablo.h" +#include "levels/gendung.h" +#include "lua/metadoc.hpp" +#include "lua/modules/dev/level/map.hpp" +#include "lua/modules/dev/level/warp.hpp" +#include "monster.h" +#include "objects.h" +#include "player.h" +#include "utils/endian_stream.hpp" +#include "utils/file_util.h" + +namespace devilution { + +namespace { + +std::string ExportDun() +{ + std::string levelName = StrCat(currlevel, "-", glSeedTbl[currlevel], ".dun"); + std::string cmdLabel = "[exportdun] "; + + FILE *dunFile = OpenFile(levelName.c_str(), "ab"); + + WriteLE16(dunFile, DMAXX); + WriteLE16(dunFile, DMAXY); + + /** Tiles. */ + for (int y = 0; y < DMAXY; y++) { + for (int x = 0; x < DMAXX; x++) { + WriteLE16(dunFile, dungeon[x][y]); + } + } + + /** Padding */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + WriteLE16(dunFile, 0); + } + } + + /** Monsters */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + uint16_t monsterId = 0; + if (dMonster[x][y] > 0) { + for (int i = 0; i < 157; i++) { + if (MonstConvTbl[i] == Monsters[std::abs(dMonster[x][y]) - 1].type().type) { + monsterId = i + 1; + break; + } + } + } + WriteLE16(dunFile, monsterId); + } + } + + /** Objects */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + uint16_t objectId = 0; + Object *object = FindObjectAtPosition({ x, y }, false); + if (object != nullptr) { + for (int i = 0; i < 147; i++) { + if (ObjTypeConv[i] == object->_otype) { + objectId = i; + break; + } + } + } + WriteLE16(dunFile, objectId); + } + } + + /** Transparency */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + WriteLE16(dunFile, dTransVal[x][y]); + } + } + std::fclose(dunFile); + + return StrCat("Successfully exported ", levelName, "."); +} + +std::string DebugCmdResetLevel(uint8_t level, std::optional seed) +{ + Player &myPlayer = *MyPlayer; + if (level > (gbIsHellfire ? 24 : 16)) + return StrCat("Level ", level, " does not exist!"); + if (myPlayer.isOnLevel(level)) + return "Unable to reset dungeon levels occupied by players!"; + + myPlayer._pLvlVisited[level] = false; + DeltaClearLevel(level); + + if (seed.has_value()) { + glSeedTbl[level] = *seed; + return StrCat("Successfully reset level ", level, " with seed ", *seed, "."); + } + return StrCat("Successfully reset level ", level, "."); +} + +} // namespace + +sol::table LuaDevLevelModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "exportDun", "()", "Save the current level as a dun-file.", &ExportDun); + SetDocumented(table, "map", "", "Automap-related commands.", LuaDevLevelMapModule(lua)); + SetDocumented(table, "reset", "(n: number, seed: number = nil)", "Resets specified level.", &DebugCmdResetLevel); + SetDocumented(table, "warp", "", "Warp to a level or a custom map.", LuaDevLevelWarpModule(lua)); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level.hpp b/Source/lua/modules/dev/level.hpp new file mode 100644 index 000000000..61253bc0d --- /dev/null +++ b/Source/lua/modules/dev/level.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevLevelModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/map.cpp b/Source/lua/modules/dev/level/map.cpp new file mode 100644 index 000000000..b1067fcad --- /dev/null +++ b/Source/lua/modules/dev/level/map.cpp @@ -0,0 +1,41 @@ +#ifdef _DEBUG +#include "lua/modules/dev/level/map.hpp" + +#include + +#include + +#include "automap.h" +#include "lua/metadoc.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdMapReveal() +{ + for (int x = 0; x < DMAXX; x++) + for (int y = 0; y < DMAXY; y++) + UpdateAutomapExplorer({ x, y }, MAP_EXP_SHRINE); + return "Automap fully explored."; +} + +std::string DebugCmdMapHide() +{ + for (int x = 0; x < DMAXX; x++) + for (int y = 0; y < DMAXY; y++) + AutomapView[x][y] = MAP_EXP_NONE; + return "Automap exploration removed."; +} + +} // namespace + +sol::table LuaDevLevelMapModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "hide", "()", "Hide the map.", &DebugCmdMapHide); + SetDocumented(table, "reveal", "()", "Reveal the map.", &DebugCmdMapReveal); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/map.hpp b/Source/lua/modules/dev/level/map.hpp new file mode 100644 index 000000000..895bdbcda --- /dev/null +++ b/Source/lua/modules/dev/level/map.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevLevelMapModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/warp.cpp b/Source/lua/modules/dev/level/warp.cpp new file mode 100644 index 000000000..655a36289 --- /dev/null +++ b/Source/lua/modules/dev/level/warp.cpp @@ -0,0 +1,82 @@ +#ifdef _DEBUG +#include "lua/modules/dev/level/warp.hpp" + +#include +#include +#include + +#include + +#include "debug.h" +#include "interfac.h" +#include "levels/setmaps.h" +#include "lua/metadoc.hpp" +#include "player.h" +#include "quests.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdWarpToDungeonLevel(uint8_t level) +{ + Player &myPlayer = *MyPlayer; + if (level > (gbIsHellfire ? 24 : 16)) + return StrCat("Level ", level, " does not exist!"); + if (!setlevel && myPlayer.isOnLevel(level)) + return StrCat("You are already on level ", level, "!"); + + StartNewLvl(myPlayer, (level != 21) ? interface_mode::WM_DIABNEXTLVL : interface_mode::WM_DIABTOWNWARP, level); + return StrCat("Moved you to level ", level, "."); +} + +std::string DebugCmdWarpToQuestLevel(uint8_t level) +{ + if (level < 1) + return StrCat("Quest level number must be 1 or higher!"); + if (setlevel && setlvlnum == level) + return StrCat("You are already on quest level", level, "!"); + + for (Quest &quest : Quests) { + if (level != quest._qslvl) + continue; + + setlvltype = quest._qlvltype; + StartNewLvl(*MyPlayer, WM_DIABSETLVL, level); + + return StrCat("Moved you to quest level ", QuestLevelNames[level], "."); + } + + return StrCat("Quest level ", level, " does not exist!"); +} + +std::string DebugCmdWarpToCustomMap(std::string_view path, int dunType, int x, int y) +{ + if (path.empty()) return "path is required"; + if (dunType < DTYPE_CATHEDRAL || dunType > DTYPE_LAST) return "invalid dunType"; + + const Point spawn { x, y }; + if (!InDungeonBounds(spawn)) return "spawn location is out of bounds"; + + TestMapPath = StrCat(path, ".dun"); + setlvltype = static_cast(dunType); + ViewPosition = spawn; + + StartNewLvl(*MyPlayer, WM_DIABSETLVL, SL_NONE); + + return StrCat("Moved you to ", TestMapPath, "."); +} + +} // namespace + +sol::table LuaDevLevelWarpModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "dungeon", "(n: number)", "Go to dungeon level (0 for town).", &DebugCmdWarpToDungeonLevel); + SetDocumented(table, "map", "(path: string, dunType: number, x: number, y: number)", "Go to custom {path}.dun level", &DebugCmdWarpToCustomMap); + SetDocumented(table, "quest", "(n: number)", "Go to quest level.", &DebugCmdWarpToQuestLevel); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/warp.hpp b/Source/lua/modules/dev/level/warp.hpp new file mode 100644 index 000000000..7ff3401a7 --- /dev/null +++ b/Source/lua/modules/dev/level/warp.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevLevelWarpModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/monsters.cpp b/Source/lua/modules/dev/monsters.cpp new file mode 100644 index 000000000..c970bd5e8 --- /dev/null +++ b/Source/lua/modules/dev/monsters.cpp @@ -0,0 +1,175 @@ +#ifdef _DEBUG +#include "lua/modules/dev/monsters.hpp" + +#include +#include + +#include + +#include "levels/gendung.h" +#include "lighting.h" +#include "lua/metadoc.hpp" +#include "monstdat.h" +#include "monster.h" +#include "player.h" +#include "utils/str_case.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { + +namespace { + +std::string DebugCmdSpawnUniqueMonster(std::string name, std::optional countOpt) +{ + if (leveltype == DTYPE_TOWN) return "Can't spawn monsters in town"; + if (name.empty()) return "name is required"; + const unsigned count = countOpt.value_or(1); + if (count < 1) return "count must be positive"; + + AsciiStrToLower(name); + + int mtype = -1; + UniqueMonsterType uniqueIndex = UniqueMonsterType::None; + for (size_t i = 0; UniqueMonstersData[i].mtype != MT_INVALID; i++) { + auto mondata = UniqueMonstersData[i]; + const std::string monsterName = AsciiStrToLower(mondata.mName); + if (monsterName.find(name) == std::string::npos) + continue; + mtype = mondata.mtype; + uniqueIndex = static_cast(i); + if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given + break; + } + + if (mtype == -1) return "Monster not found"; + + size_t id = MaxLvlMTypes - 1; + bool found = false; + + for (size_t i = 0; i < LevelMonsterTypeCount; i++) { + if (LevelMonsterTypes[i].type == mtype) { + id = i; + found = true; + break; + } + } + + if (!found) { + CMonster &monsterType = LevelMonsterTypes[id]; + monsterType.type = static_cast<_monster_id>(mtype); + InitMonsterGFX(monsterType); + InitMonsterSND(monsterType); + monsterType.placeFlags |= PLACE_SCATTER; + monsterType.corpseId = 1; + } + + Player &myPlayer = *MyPlayer; + + unsigned spawnedMonster = 0; + + auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { + Point pos = myPlayer.position.tile + displacement; + if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) + return {}; + if (!IsTileWalkable(pos)) + return {}; + + Monster *monster = AddMonster(pos, myPlayer._pdir, id, true); + if (monster == nullptr) + return StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); + PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[static_cast(uniqueIndex)]); + monster->corpseId = 1; + spawnedMonster += 1; + + if (spawnedMonster >= count) + return StrCat("Spawned ", spawnedMonster, " monsters."); + + return {}; + }); + + if (!ret.has_value()) + ret = StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); + return *ret; +} + +std::string DebugCmdSpawnMonster(std::string name, std::optional countOpt) +{ + if (leveltype == DTYPE_TOWN) return "Can't spawn monsters in town"; + if (name.empty()) return "name is required"; + const unsigned count = countOpt.value_or(1); + if (count < 1) return "count must be positive"; + + AsciiStrToLower(name); + + int mtype = -1; + + for (int i = 0; i < NUM_MTYPES; i++) { + auto mondata = MonstersData[i]; + const std::string monsterName = AsciiStrToLower(mondata.name); + if (monsterName.find(name) == std::string::npos) + continue; + mtype = i; + if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given + break; + } + + if (mtype == -1) return "Monster not found"; + + size_t id = MaxLvlMTypes - 1; + bool found = false; + + for (size_t i = 0; i < LevelMonsterTypeCount; i++) { + if (LevelMonsterTypes[i].type == mtype) { + id = i; + found = true; + break; + } + } + + if (!found) { + CMonster &monsterType = LevelMonsterTypes[id]; + monsterType.type = static_cast<_monster_id>(mtype); + InitMonsterGFX(monsterType); + InitMonsterSND(monsterType); + monsterType.placeFlags |= PLACE_SCATTER; + monsterType.corpseId = 1; + } + + Player &myPlayer = *MyPlayer; + + unsigned spawnedMonster = 0; + + auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { + Point pos = myPlayer.position.tile + displacement; + if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) + return {}; + if (!IsTileWalkable(pos)) + return {}; + + if (AddMonster(pos, myPlayer._pdir, id, true) == nullptr) + return StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); + spawnedMonster += 1; + + if (spawnedMonster >= count) + return StrCat("Spawned ", spawnedMonster, " monsters."); + + return {}; + }); + + if (!ret.has_value()) + return StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); + return *ret; +} + +} // namespace + +sol::table LuaDevMonstersModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "spawn", "(name: string, count: number = 1)", "Spawn monster(s)", &DebugCmdSpawnMonster); + SetDocumented(table, "spawnUnique", "(name: string, count: number = 1)", "Spawn unique monster(s)", &DebugCmdSpawnUniqueMonster); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/monsters.hpp b/Source/lua/modules/dev/monsters.hpp new file mode 100644 index 000000000..cbb6a5d13 --- /dev/null +++ b/Source/lua/modules/dev/monsters.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevMonstersModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player.cpp b/Source/lua/modules/dev/player.cpp new file mode 100644 index 000000000..c41f58188 --- /dev/null +++ b/Source/lua/modules/dev/player.cpp @@ -0,0 +1,109 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player.hpp" + +#include +#include + +#include + +#include "debug.h" +#include "engine/assets.hpp" +#include "lua/metadoc.hpp" +#include "lua/modules/dev/player/gold.hpp" +#include "lua/modules/dev/player/spells.hpp" +#include "lua/modules/dev/player/stats.hpp" +#include "player.h" + +namespace devilution { + +namespace { + +std::string DebugCmdArrow(std::string_view effect) +{ + Player &myPlayer = *MyPlayer; + + myPlayer._pIFlags &= ~ItemSpecialEffect::FireArrows; + myPlayer._pIFlags &= ~ItemSpecialEffect::LightningArrows; + + if (effect == "normal") { + // we removed the parameter at the top + } else if (effect == "fire") { + myPlayer._pIFlags |= ItemSpecialEffect::FireArrows; + } else if (effect == "lightning") { + myPlayer._pIFlags |= ItemSpecialEffect::LightningArrows; + } else if (effect == "spectral") { + myPlayer._pIFlags |= (ItemSpecialEffect::FireArrows | ItemSpecialEffect::LightningArrows); + } else { + return "Invalid effect!"; + } + + return StrCat("Arrows changed to: ", effect); +} + +std::string DebugCmdGodMode(std::optional on) +{ + DebugGodMode = on.value_or(!DebugGodMode); + return StrCat("God mode: ", DebugGodMode ? "On" : "Off"); +} + +std::string DebugCmdPlayerInfo(std::optional id) +{ + const uint8_t playerId = id.value_or(0); + if (playerId >= Players.size()) + return StrCat("Invalid player ID (max: ", Players.size() - 1, ")"); + Player &player = Players[playerId]; + if (!player.plractive) + return StrCat("Player ", playerId, " is not active!"); + + const Point target = player.GetTargetPosition(); + return StrCat("Plr ", playerId, " is ", player._pName, + "\nLvl: ", player.plrlevel, " Changing: ", player._pLvlChanging, + "\nTile.x: ", player.position.tile.x, " Tile.y: ", player.position.tile.y, " Target.x: ", target.x, " Target.y: ", target.y, + "\nMode: ", player._pmode, " destAction: ", player.destAction, " walkpath[0]: ", player.walkpath[0], + "\nInvincible: ", player._pInvincible ? 1 : 0, " HitPoints: ", player._pHitPoints); +} + +std::string DebugSetPlayerTrn(std::string_view path) +{ + if (!path.empty()) { + if (const AssetRef ref = FindAsset(path); !ref.ok()) { + const char *error = ref.error(); + return error == nullptr || *error == '\0' ? StrCat("File not found: ", path) : error; + } + } + debugTRN = path; + Player &player = *MyPlayer; + InitPlayerGFX(player); + StartStand(player, player._pdir); + return path.empty() ? "TRN unset" : "TRN set"; +} + +sol::table LuaDevPlayerTrnModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "mon", "(name: string)", "Set player TRN to monsters\\monsters\\${name}.trn", + [](std::string_view name) { return DebugSetPlayerTrn(StrCat("monsters\\monsters\\", name, ".trn")); }); + SetDocumented(table, "plr", "(name: string)", "Set player TRN to plrgfx\\${name}.trn", + [](std::string_view name) { return DebugSetPlayerTrn(StrCat("plrgfx\\", name, ".trn")); }); + SetDocumented(table, "clear", "()", "Unset player TRN", + []() { return DebugSetPlayerTrn(""); }); + return table; +} + +} // namespace + +sol::table LuaDevPlayerModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "arrow", "(effect: 'normal'|'fire'|'lightning'|'explosion')", "Set arrow effect.", &DebugCmdArrow); + SetDocumented(table, "god", "(on: boolean = nil)", "Toggle god mode.", &DebugCmdGodMode); + SetDocumented(table, "gold", "", "Adjust player gold.", LuaDevPlayerGoldModule(lua)); + SetDocumented(table, "info", "(id: number = 0)", "Show player info.", &DebugCmdPlayerInfo); + SetDocumented(table, "spells", "", "Adjust player spells.", LuaDevPlayerSpellsModule(lua)); + SetDocumented(table, "stats", "", "Adjust player stats (Strength, HP, etc).", LuaDevPlayerStatsModule(lua)); + SetDocumented(table, "trn", "", "Set player TRN to '${name}.trn'", LuaDevPlayerTrnModule(lua)); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player.hpp b/Source/lua/modules/dev/player.hpp new file mode 100644 index 000000000..e5c7d7522 --- /dev/null +++ b/Source/lua/modules/dev/player.hpp @@ -0,0 +1,11 @@ +#pragma once +#ifdef _DEBUG + +#include + +namespace devilution { + +sol::table LuaDevPlayerModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/gold.cpp b/Source/lua/modules/dev/player/gold.cpp new file mode 100644 index 000000000..679f2a047 --- /dev/null +++ b/Source/lua/modules/dev/player/gold.cpp @@ -0,0 +1,101 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player/gold.hpp" + +#include +#include +#include + +#include + +#include "items.h" +#include "lua/metadoc.hpp" +#include "player.h" + +namespace devilution { +namespace { + +std::string DebugCmdGiveGoldCheat(std::optional amount) +{ + int goldToAdd = amount.value_or(GOLD_MAX_LIMIT * InventoryGridCells); + if (goldToAdd <= 0) return "amount must be positive"; + Player &myPlayer = *MyPlayer; + const int goldAmountBefore = myPlayer._pGold; + for (int8_t &itemIndex : myPlayer.InvGrid) { + if (itemIndex < 0) + continue; + + Item &item = myPlayer.InvList[itemIndex != 0 ? itemIndex - 1 : myPlayer._pNumInv]; + + if (itemIndex != 0) { + if ((!item.isGold() && !item.isEmpty()) || (item.isGold() && item._ivalue == GOLD_MAX_LIMIT)) + continue; + } else { + if (item.isEmpty()) { + MakeGoldStack(item, 0); + myPlayer._pNumInv++; + itemIndex = myPlayer._pNumInv; + } + } + + int goldThatCanBeAdded = (GOLD_MAX_LIMIT - item._ivalue); + if (goldThatCanBeAdded >= goldToAdd) { + item._ivalue += goldToAdd; + myPlayer._pGold += goldToAdd; + break; + } + + item._ivalue += goldThatCanBeAdded; + goldToAdd -= goldThatCanBeAdded; + myPlayer._pGold += goldThatCanBeAdded; + } + + CalcPlrInv(myPlayer, true); + + return StrCat("Set your gold to ", myPlayer._pGold, ", added ", myPlayer._pGold - goldAmountBefore, "."); +} + +std::string DebugCmdTakeGoldCheat(std::optional amount) +{ + Player &myPlayer = *MyPlayer; + int goldToRemove = amount.value_or(GOLD_MAX_LIMIT * InventoryGridCells); + if (goldToRemove <= 0) return "amount must be positive"; + + const int goldAmountBefore = myPlayer._pGold; + for (auto itemIndex : myPlayer.InvGrid) { + itemIndex -= 1; + + if (itemIndex < 0) + continue; + + Item &item = myPlayer.InvList[itemIndex]; + if (!item.isGold()) + continue; + + if (item._ivalue >= goldToRemove) { + myPlayer._pGold -= goldToRemove; + item._ivalue -= goldToRemove; + if (item._ivalue == 0) + myPlayer.RemoveInvItem(itemIndex); + break; + } + + myPlayer._pGold -= item._ivalue; + goldToRemove -= item._ivalue; + myPlayer.RemoveInvItem(itemIndex); + } + + return StrCat("Set your gold to ", myPlayer._pGold, ", removed ", goldAmountBefore - myPlayer._pGold, "."); +} + +} // namespace + +sol::table LuaDevPlayerGoldModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "give", "(amount: number = MAX)", "Gives the player gold.", &DebugCmdGiveGoldCheat); + SetDocumented(table, "take", "(amount: number = MAX)", "Takes the player's gold away.", &DebugCmdTakeGoldCheat); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/gold.hpp b/Source/lua/modules/dev/player/gold.hpp new file mode 100644 index 000000000..2bff32418 --- /dev/null +++ b/Source/lua/modules/dev/player/gold.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevPlayerGoldModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/spells.cpp b/Source/lua/modules/dev/player/spells.cpp new file mode 100644 index 000000000..c46700a88 --- /dev/null +++ b/Source/lua/modules/dev/player/spells.cpp @@ -0,0 +1,39 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player/spells.hpp" + +#include +#include + +#include + +#include "lua/metadoc.hpp" +#include "msg.h" +#include "spelldat.h" +#include "spells.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { +std::string DebugCmdSetSpellsLevel(uint8_t level) +{ + for (uint8_t i = static_cast(SpellID::Firebolt); i < MAX_SPELLS; i++) { + if (GetSpellBookLevel(static_cast(i)) != -1) { + NetSendCmdParam2(true, CMD_CHANGE_SPELL_LEVEL, i, level); + } + } + if (level == 0) + MyPlayer->_pMemSpells = 0; + + return StrCat("Set all spell levels to ", level); +} +} // namespace + +sol::table LuaDevPlayerSpellsModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "setLevel", "(level: number)", "Set spell level for all spells.", &DebugCmdSetSpellsLevel); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/spells.hpp b/Source/lua/modules/dev/player/spells.hpp new file mode 100644 index 000000000..2f1fd1777 --- /dev/null +++ b/Source/lua/modules/dev/player/spells.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevPlayerSpellsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/stats.cpp b/Source/lua/modules/dev/player/stats.cpp new file mode 100644 index 000000000..db50a9395 --- /dev/null +++ b/Source/lua/modules/dev/player/stats.cpp @@ -0,0 +1,100 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player/stats.hpp" + +#include + +#include + +#include "engine/backbuffer_state.hpp" +#include "lua/metadoc.hpp" +#include "player.h" +#include "utils/str_cat.hpp" + +namespace devilution { + +namespace { + +std::string DebugCmdLevelUp(std::optional levels) +{ + if (!levels.has_value()) *levels = 1; + if (*levels <= 0) return "amount must be positive"; + Player &myPlayer = *MyPlayer; + for (int i = 0; i < *levels; i++) + NetSendCmd(true, CMD_CHEAT_EXPERIENCE); + return StrCat("New character level: ", myPlayer.getCharacterLevel() + *levels); +} + +std::string DebugCmdMaxStats() +{ + Player &myPlayer = *MyPlayer; + ModifyPlrStr(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength) - myPlayer._pBaseStr); + ModifyPlrMag(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic) - myPlayer._pBaseMag); + ModifyPlrDex(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity) - myPlayer._pBaseDex); + ModifyPlrVit(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Vitality) - myPlayer._pBaseVit); + return "Set all character base attributes to maximum."; +} + +std::string DebugCmdMinStats() +{ + Player &myPlayer = *MyPlayer; + ModifyPlrStr(myPlayer, -myPlayer._pBaseStr); + ModifyPlrMag(myPlayer, -myPlayer._pBaseMag); + ModifyPlrDex(myPlayer, -myPlayer._pBaseDex); + ModifyPlrVit(myPlayer, -myPlayer._pBaseVit); + return "Set all character base attributes to minimum."; +} + +std::string DebugCmdRefillHealthMana() +{ + Player &myPlayer = *MyPlayer; + myPlayer.RestoreFullLife(); + myPlayer.RestoreFullMana(); + RedrawComponent(PanelDrawComponent::Health); + RedrawComponent(PanelDrawComponent::Mana); + return StrCat("Restored life and mana to full."); +} + +std::string DebugCmdChangeHealth(int change) +{ + Player &myPlayer = *MyPlayer; + if (change == 0) + return StrCat("Enter a value not equal to 0 to change life!"); + + int newHealth = myPlayer._pHitPoints + (change * 64); + SetPlayerHitPoints(myPlayer, newHealth); + if (newHealth <= 0) + SyncPlrKill(myPlayer, DeathReason::MonsterOrTrap); + + return StrCat("Changed life by ", change); +} + +std::string DebugCmdChangeMana(int change) +{ + Player &myPlayer = *MyPlayer; + if (change == 0) + return StrCat("Enter a value not equal to 0 to change mana!"); + + int newMana = myPlayer._pMana + (change * 64); + myPlayer._pMana = newMana; + myPlayer._pManaBase = myPlayer._pMana + myPlayer._pMaxManaBase - myPlayer._pMaxMana; + RedrawComponent(PanelDrawComponent::Mana); + + return StrCat("Changed mana by ", change); +} + +} // namespace + +sol::table LuaDevPlayerStatsModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "adjustHealth", "(amount: number)", "Adjust HP (amount can be negative)", &DebugCmdChangeHealth); + SetDocumented(table, "adjustMana", "(amount: number)", "Adjust mana (amount can be negative)", &DebugCmdChangeMana); + SetDocumented(table, "levelUp", "(amount: number = 1)", "Level the player up.", &DebugCmdLevelUp); + SetDocumented(table, "rejuvenate", "()", "Refill health", &DebugCmdRefillHealthMana); + SetDocumented(table, "setAttrToMax", "()", "Set Str, Mag, Dex, and Vit to maximum.", &DebugCmdMaxStats); + SetDocumented(table, "setAttrToMin", "()", "Set Str, Mag, Dex, and Vit to minimum.", &DebugCmdMinStats); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/stats.hpp b/Source/lua/modules/dev/player/stats.hpp new file mode 100644 index 000000000..8ae5f45ab --- /dev/null +++ b/Source/lua/modules/dev/player/stats.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevPlayerStatsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/quests.cpp b/Source/lua/modules/dev/quests.cpp index eb2d13eb9..a3329e82d 100644 --- a/Source/lua/modules/dev/quests.cpp +++ b/Source/lua/modules/dev/quests.cpp @@ -1,8 +1,12 @@ #ifdef _DEBUG #include "lua/modules/dev/quests.hpp" +#include +#include + #include +#include "engine.h" #include "lua/metadoc.hpp" #include "quests.h" #include "utils/str_cat.hpp" @@ -31,7 +35,7 @@ std::string DebugCmdEnableQuests() quest._qactive = QUEST_ACTIVE; quest._qlog = true; } - return StrCat("Activated all quests."); + return "Activated all quests."; } std::string DebugCmdQuestInfo(const uint8_t questId) @@ -58,18 +62,10 @@ std::string DebugCmdQuestsInfo() sol::table LuaDevQuestsModule(sol::state_view &lua) { sol::table table = lua.create_table(); - SetWithSignatureAndDoc(table, "activate", "(id: number)", - "Activates the given quest.", - &DebugCmdEnableQuest); - SetWithSignatureAndDoc(table, "activateAll", "()", - "Activates all available quests.", - &DebugCmdEnableQuests); - SetWithSignatureAndDoc(table, "info", "(id: number)", - "Information on the given quest.", - &DebugCmdQuestInfo); - SetWithSignatureAndDoc(table, "all", "()", - "Information on all available quest.", - &DebugCmdQuestsInfo); + SetDocumented(table, "activate", "(id: number)", "Activate the given quest.", &DebugCmdEnableQuest); + SetDocumented(table, "activateAll", "()", "Activate all available quests.", &DebugCmdEnableQuests); + SetDocumented(table, "all", "()", "Information on all available quest.", &DebugCmdQuestsInfo); + SetDocumented(table, "info", "(id: number)", "Information on the given quest.", &DebugCmdQuestInfo); return table; } diff --git a/Source/lua/modules/dev/search.cpp b/Source/lua/modules/dev/search.cpp new file mode 100644 index 000000000..5718af215 --- /dev/null +++ b/Source/lua/modules/dev/search.cpp @@ -0,0 +1,56 @@ +#ifdef _DEBUG +#include "lua/modules/dev/quests.hpp" + +#include + +#include + +#include "debug.h" +#include "lua/metadoc.hpp" +#include "utils/str_case.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdSearchMonster(std::string_view name) +{ + if (name.empty()) return "Missing monster name!"; + AddDebugAutomapMonsterHighlight(AsciiStrToLower(name)); + return StrCat("Added automap marker for monster ", name, "."); +} + +std::string DebugCmdSearchItem(std::string_view name) +{ + if (name.empty()) return "Missing item name!"; + AddDebugAutomapItemHighlight(AsciiStrToLower(name)); + return StrCat("Added automap marker for item ", name, "."); +} + +std::string DebugCmdSearchObject(std::string_view name) +{ + if (name.empty()) return "Missing object name!"; + AddDebugAutomapObjectHighlight(AsciiStrToLower(name)); + return StrCat("Added automap marker for object ", name, "."); +} + +std::string DebugCmdClearSearch() +{ + ClearDebugAutomapHighlights(); + return "Removed all automap search markers."; +} + +} // namespace + +sol::table LuaDevSearchModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "clear", "()", "Clear search results from the map.", &DebugCmdClearSearch); + SetDocumented(table, "item", "(name: string)", "Search the map for an item.", &DebugCmdSearchItem); + SetDocumented(table, "monster", "(name: string)", "Search the map for a monster.", &DebugCmdSearchMonster); + SetDocumented(table, "object", "(name: string)", "Search the map for an object.", &DebugCmdSearchObject); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/search.hpp b/Source/lua/modules/dev/search.hpp new file mode 100644 index 000000000..446ae075a --- /dev/null +++ b/Source/lua/modules/dev/search.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevSearchModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/towners.cpp b/Source/lua/modules/dev/towners.cpp new file mode 100644 index 000000000..4d9784688 --- /dev/null +++ b/Source/lua/modules/dev/towners.cpp @@ -0,0 +1,93 @@ +#ifdef _DEBUG +#include "lua/modules/dev/towners.hpp" + +#include + +#include + +#include "lua/metadoc.hpp" +#include "player.h" +#include "spells.h" +#include "towners.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::unordered_map TownerShortNameToTownerId = { + { "griswold", _talker_id::TOWN_SMITH }, + { "smith", _talker_id::TOWN_SMITH }, + { "pepin", _talker_id::TOWN_HEALER }, + { "healer", _talker_id::TOWN_HEALER }, + { "ogden", _talker_id::TOWN_TAVERN }, + { "tavern", _talker_id::TOWN_TAVERN }, + { "cain", _talker_id::TOWN_STORY }, + { "story", _talker_id::TOWN_STORY }, + { "farnham", _talker_id::TOWN_DRUNK }, + { "drunk", _talker_id::TOWN_DRUNK }, + { "adria", _talker_id::TOWN_WITCH }, + { "witch", _talker_id::TOWN_WITCH }, + { "gillian", _talker_id::TOWN_BMAID }, + { "bmaid", _talker_id::TOWN_BMAID }, + { "wirt", _talker_id ::TOWN_PEGBOY }, + { "pegboy", _talker_id ::TOWN_PEGBOY }, + { "lester", _talker_id ::TOWN_FARMER }, + { "farmer", _talker_id ::TOWN_FARMER }, + { "girl", _talker_id ::TOWN_GIRL }, + { "nut", _talker_id::TOWN_COWFARM }, + { "cowfarm", _talker_id::TOWN_COWFARM }, +}; + +std::string DebugCmdVisitTowner(std::string_view name) +{ + Player &myPlayer = *MyPlayer; + + if (setlevel || !myPlayer.isOnLevel(0)) + return StrCat("This command is only available in Town!"); + + if (name.empty()) { + std::string ret; + ret = StrCat("Please provide the name of a Towner: "); + for (const auto &[name, _] : TownerShortNameToTownerId) { + ret += ' '; + ret.append(name); + } + return ret; + } + + auto it = TownerShortNameToTownerId.find(name); + if (it == TownerShortNameToTownerId.end()) + return StrCat(name, " is invalid!"); + + for (const Towner &towner : Towners) { + if (towner._ttype != it->second) continue; + CastSpell( + static_cast(MyPlayerId), + SpellID::Teleport, + myPlayer.position.tile, + towner.position, + /*spllvl=*/1); + return StrCat("Moved you to ", name, "."); + } + + return StrCat("Unable to locate ", name, "!"); +} + +std::string DebugCmdTalkToTowner(std::string_view name) +{ + if (!DebugTalkToTowner(name)) return StrCat("Towner not found!"); + return StrCat("Opened ", name, " talk window."); +} + +} // namespace + +sol::table LuaDevTownersModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "talk", "(name: string)", "Talk to towner.", &DebugCmdTalkToTowner); + SetDocumented(table, "visit", "(name: string)", "Teleport to towner.", &DebugCmdVisitTowner); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/towners.hpp b/Source/lua/modules/dev/towners.hpp new file mode 100644 index 000000000..6a5c7409a --- /dev/null +++ b/Source/lua/modules/dev/towners.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevTownersModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/panels/console.cpp b/Source/panels/console.cpp index cc85b3345..32b44657d 100644 --- a/Source/panels/console.cpp +++ b/Source/panels/console.cpp @@ -93,6 +93,7 @@ struct ConsoleLine { }; std::vector ConsoleLines; +size_t NumPreparedConsoleLines; int ConsoleLinesTotalHeight; // Index of the currently filled input/output, counting from end. @@ -140,33 +141,21 @@ int GetConsoleLinesInnerWidth() return OuterRect.size.width - 2 * TextPaddingX; } -void AddConsoleLine(ConsoleLine &&consoleLine) +void PrepareForRender(ConsoleLine &consoleLine) { consoleLine.wrapped = WordWrapString(consoleLine.text, GetConsoleLinesInnerWidth(), TextFontSize, TextSpacing); consoleLine.numLines += static_cast(c_count(consoleLine.wrapped, '\n')) + 1; ConsoleLinesTotalHeight += consoleLine.numLines * LineHeight; +} + +void AddConsoleLine(ConsoleLine &&consoleLine) +{ ConsoleLines.emplace_back(std::move(consoleLine)); - ScrollOffset = 0; } void SendInput() { - const std::string_view input = ConsoleInputState.value(); - AddConsoleLine(ConsoleLine { .type = ConsoleLine::Input, .text = StrCat(Prompt, input) }); - tl::expected result = RunLuaReplLine(input); - - if (result.has_value()) { - if (!result->empty()) { - AddConsoleLine(ConsoleLine { .type = ConsoleLine::Output, .text = *std::move(result) }); - } - } else { - if (!result.error().empty()) { - AddConsoleLine(ConsoleLine { .type = ConsoleLine::Error, .text = std::move(result).error() }); - } else { - AddConsoleLine(ConsoleLine { .type = ConsoleLine::Error, .text = "Unknown error" }); - } - } - + RunInConsole(ConsoleInputState.value()); ConsoleInputState.clear(); DraftInput.clear(); HistoryIndex = -1; @@ -273,6 +262,15 @@ void DrawConsoleLines(const Surface &out) ScrollOffset += innerHeight * PendingScrollPages; PendingScrollPages = 0; } + + if (NumPreparedConsoleLines != ConsoleLines.size()) { + for (size_t i = NumPreparedConsoleLines; i < ConsoleLines.size(); ++i) { + PrepareForRender(ConsoleLines[i]); + } + NumPreparedConsoleLines = ConsoleLines.size(); + ScrollOffset = 0; + } + ScrollOffset = std::clamp(ScrollOffset, 0, std::max(0, ConsoleLinesTotalHeight - innerHeight)); int lineYEnd = innerHeight + ScrollOffset; @@ -418,17 +416,11 @@ void ClearConsole() ConsoleLines.clear(); HistoryIndex = -1; ScrollOffset = 0; + NumPreparedConsoleLines = 0; + ConsoleLinesTotalHeight = 0; AddInitialConsoleLines(); } -void InitConsole() -{ - ConsolePrelude = LoadAsset("lua\\repl_prelude.lua"); - AddInitialConsoleLines(); - if (ConsolePrelude->has_value()) - RunLuaReplLine(std::string_view(**ConsolePrelude)); -} - } // namespace bool IsConsoleOpen() @@ -623,6 +615,34 @@ void DrawConsole(const Surface &out) BltFast(&sdlRect, &sdlRect); } +void InitConsole() +{ + if (!ConsoleLines.empty()) + return; + ConsolePrelude = LoadAsset("lua\\repl_prelude.lua"); + AddInitialConsoleLines(); + if (ConsolePrelude->has_value()) + RunLuaReplLine(std::string_view(**ConsolePrelude)); +} + +void RunInConsole(std::string_view code) +{ + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Input, .text = StrCat(Prompt, code) }); + tl::expected result = RunLuaReplLine(code); + + if (result.has_value()) { + if (!result->empty()) { + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Output, .text = *std::move(result) }); + } + } else { + if (!result.error().empty()) { + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Error, .text = std::move(result).error() }); + } else { + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Error, .text = "Unknown error" }); + } + } +} + void PrintToConsole(std::string_view text) { AddConsoleLine(ConsoleLine { .type = ConsoleLine::Output, .text = std::string(text) }); diff --git a/Source/panels/console.hpp b/Source/panels/console.hpp index 401d50204..234ba6477 100644 --- a/Source/panels/console.hpp +++ b/Source/panels/console.hpp @@ -9,10 +9,12 @@ namespace devilution { +void InitConsole(); bool IsConsoleOpen(); void OpenConsole(); bool ConsoleHandleEvent(const SDL_Event &event); void DrawConsole(const Surface &out); +void RunInConsole(std::string_view code); void PrintToConsole(std::string_view text); void PrintWarningToConsole(std::string_view text); diff --git a/Source/towners.cpp b/Source/towners.cpp index e1d740d08..9795dc1ef 100644 --- a/Source/towners.cpp +++ b/Source/towners.cpp @@ -910,12 +910,12 @@ void UpdateCowFarmerAnimAfterQuestComplete() } #ifdef _DEBUG -bool DebugTalkToTowner(std::string targetName) +bool DebugTalkToTowner(std::string_view targetName) { SetupTownStores(); - AsciiStrToLower(targetName); + const std::string lowercaseName = AsciiStrToLower(targetName); Player &myPlayer = *MyPlayer; - for (auto &townerData : TownersData) { + for (const TownerData &townerData : TownersData) { if (!IsTownerPresent(townerData.type)) continue; // cows have an init function that differs from the rest and isn't compatible with this code, skip them :( @@ -925,7 +925,7 @@ bool DebugTalkToTowner(std::string targetName) townerData.init(fakeTowner, townerData); fakeTowner.position = myPlayer.position.tile; const std::string npcName = AsciiStrToLower(fakeTowner.name); - if (npcName.find(targetName) != std::string::npos) { + if (npcName.find(lowercaseName) != std::string::npos) { townerData.talk(myPlayer, fakeTowner); return true; } diff --git a/Source/towners.h b/Source/towners.h index dc5041748..1788f53a1 100644 --- a/Source/towners.h +++ b/Source/towners.h @@ -88,7 +88,7 @@ void UpdateGirlAnimAfterQuestComplete(); void UpdateCowFarmerAnimAfterQuestComplete(); #ifdef _DEBUG -bool DebugTalkToTowner(std::string targetName); +bool DebugTalkToTowner(std::string_view targetName); #endif extern _speech_id QuestDialogTable[NUM_TOWNER_TYPES][MAXQUESTS];