Browse Source

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())
```
pull/6795/head
Gleb Mazovetskiy 2 years ago
parent
commit
a2b94cc03c
  1. 16
      Source/CMakeLists.txt
  2. 16
      Source/control.cpp
  3. 1002
      Source/debug.cpp
  4. 37
      Source/debug.h
  5. 5
      Source/diablo.cpp
  6. 30
      Source/items.cpp
  7. 1
      Source/lua/autocomplete.cpp
  8. 2
      Source/lua/metadoc.hpp
  9. 194
      Source/lua/modules/dev.cpp
  10. 133
      Source/lua/modules/dev/display.cpp
  11. 10
      Source/lua/modules/dev/display.hpp
  12. 73
      Source/lua/modules/dev/items.cpp
  13. 10
      Source/lua/modules/dev/items.hpp
  14. 124
      Source/lua/modules/dev/level.cpp
  15. 10
      Source/lua/modules/dev/level.hpp
  16. 41
      Source/lua/modules/dev/level/map.cpp
  17. 10
      Source/lua/modules/dev/level/map.hpp
  18. 82
      Source/lua/modules/dev/level/warp.cpp
  19. 10
      Source/lua/modules/dev/level/warp.hpp
  20. 175
      Source/lua/modules/dev/monsters.cpp
  21. 10
      Source/lua/modules/dev/monsters.hpp
  22. 109
      Source/lua/modules/dev/player.cpp
  23. 11
      Source/lua/modules/dev/player.hpp
  24. 101
      Source/lua/modules/dev/player/gold.cpp
  25. 10
      Source/lua/modules/dev/player/gold.hpp
  26. 39
      Source/lua/modules/dev/player/spells.cpp
  27. 10
      Source/lua/modules/dev/player/spells.hpp
  28. 100
      Source/lua/modules/dev/player/stats.cpp
  29. 10
      Source/lua/modules/dev/player/stats.hpp
  30. 22
      Source/lua/modules/dev/quests.cpp
  31. 56
      Source/lua/modules/dev/search.cpp
  32. 10
      Source/lua/modules/dev/search.hpp
  33. 93
      Source/lua/modules/dev/towners.cpp
  34. 10
      Source/lua/modules/dev/towners.hpp
  35. 72
      Source/panels/console.cpp
  36. 2
      Source/panels/console.hpp
  37. 8
      Source/towners.cpp
  38. 2
      Source/towners.h

16
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

16
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);

1002
Source/debug.cpp

File diff suppressed because it is too large Load Diff

37
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

5
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();
}

30
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<int32_t> 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<size_t>(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

1
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<std::optional<sol::object>>("_G");
if (fallback.has_value() && fallback->get_type() == sol::type::table) {

2
Source/lua/metadoc.hpp

@ -19,7 +19,7 @@ inline std::string LuaDocstringKey(std::string_view key)
}
template <typename T>
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<T>(value);
table[LuaSignatureKey(key)] = signature;

194
Source/lua/modules/dev.cpp

@ -3,193 +3,29 @@
#include <sol/sol.hpp>
#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<uint8_t>(SpellID::Firebolt); i < MAX_SPELLS; i++) {
if (GetSpellBookLevel(static_cast<SpellID>(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;
}

133
Source/lua/modules/dev/display.cpp

@ -0,0 +1,133 @@
#ifdef _DEBUG
#include "lua/modules/dev/display.hpp"
#include <array>
#include <optional>
#include <string>
#include <sol/sol.hpp>
#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<bool> on)
{
DebugGrid = on.value_or(!DebugGrid);
return StrCat("Tile grid highlighting: ", DebugGrid ? "On" : "Off");
}
std::string DebugCmdVision(std::optional<bool> on)
{
DebugVision = on.value_or(!DebugVision);
return StrCat("Vision highlighting: ", DebugVision ? "On" : "Off");
}
std::string DebugCmdPath(std::optional<bool> on)
{
DebugPath = on.value_or(!DebugPath);
return StrCat("Path highlighting: ", DebugPath ? "On" : "Off");
}
std::string DebugCmdFullbright(std::optional<bool> on)
{
ToggleLighting();
return StrCat("Fullbright: ", DisableLighting ? "On" : "Off");
}
std::string DebugCmdShowTileData(std::optional<std::string_view> dataType)
{
static const std::array<std::string_view, 21> 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 &param : DataTypes) {
index++;
if (*dataType != param)
continue;
found = true;
auto newGridText = static_cast<DebugGridTextItem>(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<bool> on)
{
DebugScrollViewEnabled = on.value_or(!DebugScrollViewEnabled);
if (!DebugScrollViewEnabled)
InitMultiView();
return StrCat("Scroll view: ", DebugScrollViewEnabled ? "On" : "Off");
}
std::string DebugCmdToggleFPS(std::optional<bool> 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

10
Source/lua/modules/dev/display.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevDisplayModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

73
Source/lua/modules/dev/items.cpp

@ -0,0 +1,73 @@
#ifdef _DEBUG
#include "lua/modules/dev/items.hpp"
#include <string>
#include <sol/sol.hpp>
#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

10
Source/lua/modules/dev/items.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevItemsModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

124
Source/lua/modules/dev/level.cpp

@ -0,0 +1,124 @@
#ifdef _DEBUG
#include "lua/modules/dev/level.hpp"
#include <cstdio>
#include <optional>
#include <string>
#include <sol/sol.hpp>
#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<int> 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

10
Source/lua/modules/dev/level.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevLevelModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

41
Source/lua/modules/dev/level/map.cpp

@ -0,0 +1,41 @@
#ifdef _DEBUG
#include "lua/modules/dev/level/map.hpp"
#include <string>
#include <sol/sol.hpp>
#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

10
Source/lua/modules/dev/level/map.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevLevelMapModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

82
Source/lua/modules/dev/level/warp.cpp

@ -0,0 +1,82 @@
#ifdef _DEBUG
#include "lua/modules/dev/level/warp.hpp"
#include <cstdint>
#include <string>
#include <string_view>
#include <sol/sol.hpp>
#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<dungeon_type>(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

10
Source/lua/modules/dev/level/warp.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevLevelWarpModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

175
Source/lua/modules/dev/monsters.cpp

@ -0,0 +1,175 @@
#ifdef _DEBUG
#include "lua/modules/dev/monsters.hpp"
#include <optional>
#include <string>
#include <sol/sol.hpp>
#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<unsigned> 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<UniqueMonsterType>(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<std::string> {
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<size_t>(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<unsigned> 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<std::string> {
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

10
Source/lua/modules/dev/monsters.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevMonstersModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

109
Source/lua/modules/dev/player.cpp

@ -0,0 +1,109 @@
#ifdef _DEBUG
#include "lua/modules/dev/player.hpp"
#include <cstdint>
#include <string>
#include <sol/sol.hpp>
#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<bool> on)
{
DebugGodMode = on.value_or(!DebugGodMode);
return StrCat("God mode: ", DebugGodMode ? "On" : "Off");
}
std::string DebugCmdPlayerInfo(std::optional<uint8_t> 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

11
Source/lua/modules/dev/player.hpp

@ -0,0 +1,11 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevPlayerModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

101
Source/lua/modules/dev/player/gold.cpp

@ -0,0 +1,101 @@
#ifdef _DEBUG
#include "lua/modules/dev/player/gold.hpp"
#include <cstdint>
#include <optional>
#include <string>
#include <sol/sol.hpp>
#include "items.h"
#include "lua/metadoc.hpp"
#include "player.h"
namespace devilution {
namespace {
std::string DebugCmdGiveGoldCheat(std::optional<int> 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<int> 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

10
Source/lua/modules/dev/player/gold.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevPlayerGoldModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

39
Source/lua/modules/dev/player/spells.cpp

@ -0,0 +1,39 @@
#ifdef _DEBUG
#include "lua/modules/dev/player/spells.hpp"
#include <cstdint>
#include <string>
#include <sol/sol.hpp>
#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<uint8_t>(SpellID::Firebolt); i < MAX_SPELLS; i++) {
if (GetSpellBookLevel(static_cast<SpellID>(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

10
Source/lua/modules/dev/player/spells.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevPlayerSpellsModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

100
Source/lua/modules/dev/player/stats.cpp

@ -0,0 +1,100 @@
#ifdef _DEBUG
#include "lua/modules/dev/player/stats.hpp"
#include <string>
#include <sol/sol.hpp>
#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<int> 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

10
Source/lua/modules/dev/player/stats.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevPlayerStatsModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

22
Source/lua/modules/dev/quests.cpp

@ -1,8 +1,12 @@
#ifdef _DEBUG
#include "lua/modules/dev/quests.hpp"
#include <cstdint>
#include <string>
#include <sol/sol.hpp>
#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;
}

56
Source/lua/modules/dev/search.cpp

@ -0,0 +1,56 @@
#ifdef _DEBUG
#include "lua/modules/dev/quests.hpp"
#include <string>
#include <sol/sol.hpp>
#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

10
Source/lua/modules/dev/search.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevSearchModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

93
Source/lua/modules/dev/towners.cpp

@ -0,0 +1,93 @@
#ifdef _DEBUG
#include "lua/modules/dev/towners.hpp"
#include <string>
#include <sol/sol.hpp>
#include "lua/metadoc.hpp"
#include "player.h"
#include "spells.h"
#include "towners.h"
#include "utils/str_cat.hpp"
namespace devilution {
namespace {
std::unordered_map<std::string_view, _talker_id> 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<int>(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

10
Source/lua/modules/dev/towners.hpp

@ -0,0 +1,10 @@
#pragma once
#ifdef _DEBUG
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaDevTownersModule(sol::state_view &lua);
} // namespace devilution
#endif // _DEBUG

72
Source/panels/console.cpp

@ -93,6 +93,7 @@ struct ConsoleLine {
};
std::vector<ConsoleLine> 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<int>(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<std::string, std::string> 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<std::string, std::string> 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) });

2
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);

8
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;
}

2
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];

Loading…
Cancel
Save