From 8bffbedcb4c887f7ad28bc5a04d7da7ec884bdb7 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 4 Nov 2023 11:16:26 +0000 Subject: [PATCH] Migrate some debug.cpp commands to Lua Introduces a new `devilutionx.dev` Lua module, automatically loaded in the console prelude. Arguments and basic help are shown in autocompletion. --- .../resources/assets/lua/repl_prelude.lua | 1 + Source/CMakeLists.txt | 2 + Source/debug.cpp | 253 ------------------ Source/lua/autocomplete.cpp | 68 +++-- Source/lua/lua.cpp | 13 + Source/lua/metadoc.hpp | 46 ++++ Source/lua/modules/audio.cpp | 12 +- Source/lua/modules/dev.cpp | 197 ++++++++++++++ Source/lua/modules/dev.hpp | 10 + Source/lua/modules/dev/quests.cpp | 78 ++++++ Source/lua/modules/dev/quests.hpp | 10 + Source/panels/console.cpp | 11 +- 12 files changed, 425 insertions(+), 276 deletions(-) create mode 100644 Source/lua/metadoc.hpp create mode 100644 Source/lua/modules/dev.cpp create mode 100644 Source/lua/modules/dev.hpp create mode 100644 Source/lua/modules/dev/quests.cpp create mode 100644 Source/lua/modules/dev/quests.hpp diff --git a/Packaging/resources/assets/lua/repl_prelude.lua b/Packaging/resources/assets/lua/repl_prelude.lua index b0b272e8f..c659b05e0 100644 --- a/Packaging/resources/assets/lua/repl_prelude.lua +++ b/Packaging/resources/assets/lua/repl_prelude.lua @@ -2,4 +2,5 @@ log = require('devilutionx.log') audio = require('devilutionx.audio') render = require('devilutionx.render') message = require('devilutionx.message') +if _DEBUG then dev = require('devilutionx.dev') end inspect = require('inspect') diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 181a6459b..24b8da73d 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -136,6 +136,8 @@ set(libdevilutionx_SRCS lua/lua.cpp lua/repl.cpp lua/modules/audio.cpp + lua/modules/dev.cpp + lua/modules/dev/quests.cpp lua/modules/render.cpp lua/modules/log.cpp diff --git a/Source/debug.cpp b/Source/debug.cpp index e9676ced7..c69f2bc76 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -153,92 +153,6 @@ std::string DebugCmdHelp(const std::string_view parameter) return StrCat("Description: ", dbgCmdItem.description, "\nParameters: ", dbgCmdItem.requiredParameter); } -std::string DebugCmdGiveGoldCheat(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[givegold] "; - - int goldToAdd = 0; - - if (parameter.empty()) - goldToAdd = GOLD_MAX_LIMIT * InventoryGridCells; - else - goldToAdd = ParseInt(parameter, /*min=*/1).value_or(1); - - 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(cmdLabel, "Set your gold to ", myPlayer._pGold, ", added ", myPlayer._pGold - goldAmountBefore, "."); -} - -std::string DebugCmdTakeGoldCheat(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[takegold] "; - - int goldToRemove = 0; - - if (parameter.empty()) - goldToRemove = GOLD_MAX_LIMIT * InventoryGridCells; - else - goldToRemove = ParseInt(parameter, /*min=*/1).value_or(1); - - 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(cmdLabel, "Set your gold to ", myPlayer._pGold, ", removed ", goldAmountBefore - myPlayer._pGold, "."); -} - std::string DebugCmdWarpToLevel(const std::string_view parameter) { Player &myPlayer = *MyPlayer; @@ -521,28 +435,6 @@ std::string DebugCmdLighting(const std::string_view parameter) return StrCat(cmdLabel, "Fullbright: ", DisableLighting ? "On" : "Off"); } -std::string DebugCmdMapReveal(const std::string_view parameter) -{ - std::string cmdLabel = "[givemap] "; - - for (int x = 0; x < DMAXX; x++) - for (int y = 0; y < DMAXY; y++) - UpdateAutomapExplorer({ x, y }, MAP_EXP_SHRINE); - - return StrCat(cmdLabel, "Automap fully explored."); -} - -std::string DebugCmdMapHide(const std::string_view parameter) -{ - std::string cmdLabel = "[takemap] "; - - for (int x = 0; x < DMAXX; x++) - for (int y = 0; y < DMAXY; y++) - AutomapView[x][y] = MAP_EXP_NONE; - - return StrCat(cmdLabel, "Automap exploration removed."); -} - std::string DebugCmdVision(const std::string_view parameter) { std::string cmdLabel = "[drawvision] "; @@ -561,106 +453,6 @@ std::string DebugCmdPath(const std::string_view parameter) return StrCat(cmdLabel, "Path highlighting: ", DebugPath ? "On" : "Off"); } -std::string DebugCmdQuest(const std::string_view parameter) -{ - std::string cmdLabel = "[givequest] "; - - if (parameter.empty()) { - std::string ret = StrCat(cmdLabel, "Missing quest id! (This can be: all)"); - for (auto &quest : Quests) { - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - continue; - StrAppend(ret, ", ", quest._qidx, " (", QuestsData[quest._qidx]._qlstr, ")"); - } - return ret; - } - - if (parameter.compare("all") == 0) { - for (auto &quest : Quests) { - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - continue; - - quest._qactive = QUEST_ACTIVE; - quest._qlog = true; - } - - return StrCat(cmdLabel, "Activated all quests."); - } - - const ParseIntResult parsedArg = ParseInt(parameter, /*min=*/0); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument as integer!"); - const int questId = parsedArg.value(); - - if (questId >= MAXQUESTS) - return StrCat(cmdLabel, "Quest ", questId, " does not exist!"); - auto &quest = Quests[questId]; - - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - return StrCat(cmdLabel, QuestsData[questId]._qlstr, " is already active!"); - - quest._qactive = QUEST_ACTIVE; - quest._qlog = true; - - return StrCat(cmdLabel, QuestsData[questId]._qlstr, " activated."); -} - -std::string DebugCmdLevelUp(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[givelvl] "; - - const int levels = std::min(ParseInt(parameter, /*min=*/1).value_or(1), GetMaximumCharacterLevel() - myPlayer.getCharacterLevel()); - for (int i = 0; i < levels; i++) - NetSendCmd(true, CMD_CHEAT_EXPERIENCE); - return StrCat(cmdLabel, "New character level: ", myPlayer.getCharacterLevel() + levels); -} - -std::string DebugCmdMaxStats(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[maxstats] "; - - 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 StrCat(cmdLabel, "Set all character base attributes to maximum."); -} - -std::string DebugCmdMinStats(const std::string_view parameter) -{ - Player &myPlayer = *MyPlayer; - std::string cmdLabel = "[minstats] "; - - ModifyPlrStr(myPlayer, -myPlayer._pBaseStr); - ModifyPlrMag(myPlayer, -myPlayer._pBaseMag); - ModifyPlrDex(myPlayer, -myPlayer._pBaseDex); - ModifyPlrVit(myPlayer, -myPlayer._pBaseVit); - - return StrCat(cmdLabel, "Set all character base attributes to minimum."); -} - -std::string DebugCmdSetSpellsLevel(const std::string_view parameter) -{ - std::string cmdLabel = "[setspells] "; - - const ParseIntResult parsedArg = ParseInt(parameter); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument as uint8_t!"); - const uint8_t level = parsedArg.value(); - 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(cmdLabel, "Set all spell levels to ", level); -} - std::string DebugCmdRefillHealthMana(const std::string_view parameter) { std::string cmdLabel = "[fill] "; @@ -773,15 +565,6 @@ std::string DebugCmdTalkToTowner(const std::string_view parameter) return StrCat(cmdLabel, "Towner not found!"); } -std::string DebugCmdShowGrid(const std::string_view parameter) -{ - std::string cmdLabel = "[grid] "; - - DebugGrid = !DebugGrid; - - return StrCat(cmdLabel, "Tile grid highlighting: ", DebugGrid ? "On" : "Off"); -} - std::string DebugCmdSpawnUniqueMonster(const std::string_view parameter) { std::string cmdLabel = "[spawnu] "; @@ -1079,31 +862,6 @@ std::string DebugCmdItemInfo(const std::string_view parameter) return StrCat(cmdLabel, "Numitems: ", ActiveItemCount); } -std::string DebugCmdQuestInfo(const std::string_view parameter) -{ - std::string cmdLabel = "[questinfo] "; - - if (parameter.empty()) { - std::string ret = StrCat(cmdLabel, "You must provide an id! This could be: "); - for (auto &quest : Quests) { - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - continue; - StrAppend(ret, ", ", quest._qidx, " (", QuestsData[quest._qidx]._qlstr, ")"); - } - return ret; - } - - const ParseIntResult parsedArg = ParseInt(parameter, /*min=*/0); - if (!parsedArg.has_value()) - return StrCat(cmdLabel, "Failed to parse argument as integer!"); - const int questId = parsedArg.value(); - - if (questId >= MAXQUESTS) - return StrCat(cmdLabel, "Quest ", questId, " does not exist!"); - auto &quest = Quests[questId]; - return StrCat(cmdLabel, "\nQuest: ", QuestsData[quest._qidx]._qlstr, "\nActive: ", quest._qactive, " Var1: ", quest._qvar1, " Var2: ", quest._qvar2); -} - std::string DebugCmdPlayerInfo(const std::string_view parameter) { std::string cmdLabel = "[playerinfo] "; @@ -1235,15 +993,6 @@ std::string DebugCmdClearSearch(const std::string_view parameter) std::vector DebugCmdList = { { "help", "Prints help overview or help for a specific command.", "({command})", &DebugCmdHelp }, - { "givegold", "Gives player {amount} of gold or max amount if didn't specify.", "{amount}", &DebugCmdGiveGoldCheat }, - { "givelvl", "Levels the player up (min 1 level or {levels}).", "({levels})", &DebugCmdLevelUp }, - { "maxstats", "Sets all stat values to maximum.", "", &DebugCmdMaxStats }, - { "minstats", "Sets all stat values to minimum.", "", &DebugCmdMinStats }, - { "setspells", "Set spell level to {level} for all spells.", "{level}", &DebugCmdSetSpellsLevel }, - { "takegold", "Removes {amount} gold or if didn't specify - all gold from inventory.", "{amount}", &DebugCmdTakeGoldCheat }, - { "givequest", "Enable a given quest.", "({id})", &DebugCmdQuest }, - { "givemap", "Reveal the map.", "", &DebugCmdMapReveal }, - { "takemap", "Hide the map.", "", &DebugCmdMapHide }, { "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 }, @@ -1262,13 +1011,11 @@ std::vector DebugCmdList = { { "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 }, - { "grid", "Toggles showing grid.", "", &DebugCmdShowGrid }, { "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 }, - { "questinfo", "Shows info of quests.", "{id}", &DebugCmdQuestInfo }, { "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 }, diff --git a/Source/lua/autocomplete.cpp b/Source/lua/autocomplete.cpp index 8f4975f5e..73e6213ac 100644 --- a/Source/lua/autocomplete.cpp +++ b/Source/lua/autocomplete.cpp @@ -9,7 +9,10 @@ #include +#include "lua/metadoc.hpp" #include "utils/algorithm/container.hpp" +#include "utils/str_cat.hpp" +#include "utils/str_split.hpp" namespace devilution { @@ -20,23 +23,39 @@ std::string_view GetLastToken(std::string_view text) if (text.empty()) return {}; size_t i = text.size(); - while (i > 0 && text[i - 1] != ' ') + while (i > 0 && text[i - 1] != ' ' && text[i - 1] != '(' && text[i - 1] != ',') --i; return text.substr(i); } -bool IsCallable(const sol::object &value) +struct ValueInfo { + bool callable = false; + std::string signature; + std::string docstring; +}; + +ValueInfo GetValueInfo(const sol::table &table, std::string_view key, const sol::object &value) { - if (value.get_type() == sol::type::function) - return true; - if (!value.is()) - return false; - const auto table = value.as(); - const auto metatable = table.get>(sol::metatable_key); + ValueInfo info; + if (std::optional signature = GetSignature(table, key); signature.has_value()) { + info.signature = *std::move(signature); + } + if (std::optional docstring = GetDocstring(table, key); docstring.has_value()) { + info.docstring = *std::move(docstring); + } + if (value.get_type() == sol::type::function) { + info.callable = true; + return info; + } + if (!value.is()) return info; + const auto valueAsTable = value.as(); + const auto metatable = valueAsTable.get>(sol::metatable_key); if (!metatable || !metatable->is()) - return false; - const auto callFn = metatable->as().get>(sol::meta_function::call); - return callFn.has_value(); + return info; + const auto metatableTbl = metatable->as(); + const auto callFn = metatableTbl.get>(sol::meta_function::call); + info.callable = callFn.has_value(); + return info; } void SuggestionsFromTable(const sol::table &table, std::string_view prefix, @@ -54,12 +73,23 @@ void SuggestionsFromTable(const sol::table &table, std::string_view prefix, || keyStr.find("☢") != std::string::npos || keyStr.find("🔩") != std::string::npos) continue; + ValueInfo info = GetValueInfo(table, keyStr, value); std::string completionText = keyStr.substr(prefix.size()); LuaAutocompleteSuggestion suggestion { std::move(keyStr), std::move(completionText) }; - if (IsCallable(value)) { + if (info.callable) { suggestion.completionText.append("()"); suggestion.cursorAdjust = -1; } + if (!info.signature.empty()) { + StrAppend(suggestion.displayText, info.signature); + } + if (!info.docstring.empty()) { + std::string_view firstLine = info.docstring; + if (const size_t newlinePos = firstLine.find('\n'); newlinePos != std::string_view::npos) { + firstLine = firstLine.substr(0, newlinePos); + } + StrAppend(suggestion.displayText, " - ", firstLine); + } out.insert(std::move(suggestion)); if (out.size() == maxSuggestions) break; @@ -77,9 +107,10 @@ void GetLuaAutocompleteSuggestions(std::string_view text, const sol::environment size_t maxSuggestions, std::vector &out) { out.clear(); - if (text.empty()) - return; + if (text.empty()) return; std::string_view token = GetLastToken(text); + const char prevChar = token.data() == text.data() ? '\0' : *(token.data() - 1); + if (prevChar == '(' || prevChar == ',') return; const size_t dotPos = token.rfind('.'); const std::string_view prefix = token.substr(dotPos + 1); token.remove_suffix(token.size() - (dotPos == std::string_view::npos ? 0 : dotPos)); @@ -96,9 +127,12 @@ void GetLuaAutocompleteSuggestions(std::string_view text, const sol::environment addSuggestions(fallback->as()); } } else { - const auto obj = lua.traverse_get>(token); - if (!obj.has_value()) - return; + std::optional obj = lua; + for (const std::string_view part : SplitByChar(token, '.')) { + obj = obj->as().get>(part); + if (!obj.has_value() || obj->get_type() != sol::type::table) + return; + } if (obj->get_type() == sol::type::table) { addSuggestions(obj->as()); } diff --git a/Source/lua/lua.cpp b/Source/lua/lua.cpp index 1ed0d1990..015502ff4 100644 --- a/Source/lua/lua.cpp +++ b/Source/lua/lua.cpp @@ -18,6 +18,10 @@ #include "utils/log.hpp" #include "utils/str_cat.hpp" +#ifdef _DEBUG +#include "lua/modules/dev.hpp" +#endif + namespace devilution { namespace { @@ -172,11 +176,20 @@ void LuaInitialize() // Registering globals lua.set( "print", LuaPrint, + "_DEBUG", +#ifdef _DEBUG + true, +#else + false, +#endif "_VERSION", LUA_VERSION); // Registering devilutionx object table CheckResult(lua.safe_script(RequireGenSrc), /*optional=*/false); const sol::table loaded = lua.create_table_with( +#ifdef _DEBUG + "devilutionx.dev", LuaDevModule(lua), +#endif "devilutionx.version", PROJECT_VERSION, "devilutionx.log", LuaLogModule(lua), "devilutionx.audio", LuaAudioModule(lua), diff --git a/Source/lua/metadoc.hpp b/Source/lua/metadoc.hpp new file mode 100644 index 000000000..2b0b7f329 --- /dev/null +++ b/Source/lua/metadoc.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include + +#include "utils/str_cat.hpp" + +namespace devilution { + +inline std::string LuaSignatureKey(std::string_view key) +{ + return StrCat("__sig_", key); +} + +inline std::string LuaDocstringKey(std::string_view key) +{ + return StrCat("__doc_", key); +} + +template +void SetWithSignatureAndDoc(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; + table[LuaDocstringKey(key)] = doc; +} + +template +void SetWithSignature(sol::table &table, std::string_view key, std::string_view signature, T &&value) +{ + table[key] = std::forward(value); + table[LuaSignatureKey(key)] = signature; +} + +inline std::optional GetSignature(const sol::table &table, std::string_view key) +{ + return table.get>(LuaSignatureKey(key)); +} + +inline std::optional GetDocstring(const sol::table &table, std::string_view key) +{ + return table.get>(LuaDocstringKey(key)); +} + +} // namespace devilution diff --git a/Source/lua/modules/audio.cpp b/Source/lua/modules/audio.cpp index 291a0f874..e090ded05 100644 --- a/Source/lua/modules/audio.cpp +++ b/Source/lua/modules/audio.cpp @@ -3,6 +3,7 @@ #include #include "effects.h" +#include "lua/metadoc.hpp" namespace devilution { @@ -17,9 +18,14 @@ bool IsValidSfx(int16_t psfx) sol::table LuaAudioModule(sol::state_view &lua) { - return lua.create_table_with( - "playSfx", [](int psfx) { if (IsValidSfx(psfx)) PlaySFX(static_cast(psfx)); }, - "playSfxLoc", [](int psfx, int x, int y) { if (IsValidSfx(psfx)) PlaySfxLoc(static_cast(psfx), { x, y }); }); + sol::table table = lua.create_table(); + SetWithSignature(table, + "playSfx", "(id: number)", + [](int16_t psfx) { if (IsValidSfx(psfx)) PlaySFX(static_cast(psfx)); }); + SetWithSignature(table, + "playSfxLoc", "(id: number, x: number, y: number)", + [](int16_t psfx, int x, int y) { if (IsValidSfx(psfx)) PlaySfxLoc(static_cast(psfx), { x, y }); }); + return table; } } // namespace devilution diff --git a/Source/lua/modules/dev.cpp b/Source/lua/modules/dev.cpp new file mode 100644 index 000000000..56c84e8d1 --- /dev/null +++ b/Source/lua/modules/dev.cpp @@ -0,0 +1,197 @@ +#ifdef _DEBUG +#include "lua/modules/dev.hpp" + +#include + +#include "automap.h" +#include "debug.h" +#include "items.h" +#include "lua/metadoc.hpp" +#include "lua/modules/dev/quests.hpp" +#include "player.h" +#include "spells.h" +#include "utils/str_cat.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); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev.hpp b/Source/lua/modules/dev.hpp new file mode 100644 index 000000000..ca71c6440 --- /dev/null +++ b/Source/lua/modules/dev.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/quests.cpp b/Source/lua/modules/dev/quests.cpp new file mode 100644 index 000000000..72a8ddc5a --- /dev/null +++ b/Source/lua/modules/dev/quests.cpp @@ -0,0 +1,78 @@ +#pragma once +#ifdef _DEBUG +#include "lua/modules/dev/quests.hpp" + +#include + +#include "lua/metadoc.hpp" +#include "quests.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdEnableQuest(uint8_t questId) +{ + if (questId >= MAXQUESTS) return StrCat("Quest ", questId, " does not exist!"); + Quest &quest = Quests[questId]; + + if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) + return StrCat(QuestsData[questId]._qlstr, " is already active!"); + + quest._qactive = QUEST_ACTIVE; + quest._qlog = true; + + return StrCat(QuestsData[questId]._qlstr, " activated."); +} + +std::string DebugCmdEnableQuests() +{ + for (Quest &quest : Quests) { + if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) continue; + quest._qactive = QUEST_ACTIVE; + quest._qlog = true; + } + return StrCat("Activated all quests."); +} + +std::string DebugCmdQuestInfo(const uint8_t questId) +{ + if (questId >= MAXQUESTS) return StrCat("Quest ", questId, " does not exist!"); + const Quest &quest = Quests[questId]; + return StrCat("Quest id=", quest._qidx, " ", QuestsData[quest._qidx]._qlstr, + " active=", quest._qactive, " var1=", quest._qvar1, " var2=", quest._qvar2); +} + +std::string DebugCmdQuestsInfo() +{ + std::string ret; + for (const Quest &quest : Quests) { + StrAppend(ret, "Quest id=", quest._qidx, " ", QuestsData[quest._qidx]._qlstr, + " active=", quest._qactive, " var1=", quest._qvar1, " var2=", quest._qvar2, "\n"); + } + if (!ret.empty()) ret.pop_back(); + return ret; +} + +} // namespace + +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); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/quests.hpp b/Source/lua/modules/dev/quests.hpp new file mode 100644 index 000000000..5edb641b5 --- /dev/null +++ b/Source/lua/modules/dev/quests.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevQuestsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/panels/console.cpp b/Source/panels/console.cpp index 2a6c053a0..6d166b16b 100644 --- a/Source/panels/console.cpp +++ b/Source/panels/console.cpp @@ -1,6 +1,7 @@ #ifdef _DEBUG #include "panels/console.hpp" +#include #include #include @@ -166,18 +167,19 @@ void SendInput() void DrawAutocompleteSuggestions(const Surface &out, const std::vector &suggestions, Point position) { + const int maxInnerWidth = out.w() - TextPaddingX * 2; if (AutocompleteSuggestionsMaxWidth == -1) { int maxWidth = 0; for (const LuaAutocompleteSuggestion &suggestion : suggestions) { maxWidth = std::max(maxWidth, GetLineWidth(suggestion.displayText, AutocompleteSuggestionsTextFontSize, TextSpacing)); } - AutocompleteSuggestionsMaxWidth = maxWidth; + AutocompleteSuggestionsMaxWidth = std::min(maxWidth, maxInnerWidth); } const int outerWidth = AutocompleteSuggestionsMaxWidth + TextPaddingX * 2; if (position.x + outerWidth > out.w()) { - position.x -= AutocompleteSuggestionsMaxWidth; + position.x = out.w() - outerWidth; } const int height = static_cast(suggestions.size()) * LineHeight + TextPaddingYBottom + TextPaddingYTop; @@ -192,7 +194,10 @@ void DrawAutocompleteSuggestions(const Surface &out, const std::vector