Skip to content

Commit 14bda20

Browse files
committed
Add lua events for monsters, useItem, spawnItem and helpers
1 parent c2cf695 commit 14bda20

11 files changed

Lines changed: 197 additions & 0 deletions

File tree

Source/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ set(libdevilutionx_SRCS
118118
lua/modules/dev/search.cpp
119119
lua/modules/dev/towners.cpp
120120
lua/modules/floatingnumbers.cpp
121+
lua/modules/game.cpp
121122
lua/modules/i18n.cpp
122123
lua/modules/items.cpp
123124
lua/modules/log.cpp

Source/inv.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "options.h"
3838
#include "panels/ui_panels.hpp"
3939
#include "player.h"
40+
#include "lua/lua_global.hpp"
4041
#include "plrmsg.h"
4142
#include "qol/stash.h"
4243
#include "stores.h"
@@ -2178,6 +2179,10 @@ bool UseInvItem(int cii)
21782179
return true;
21792180
}
21802181

2182+
if (LuaEventCancellable("OnItemUse", &player, item)) {
2183+
return true;
2184+
}
2185+
21812186
const int idata = ItemCAnimTbl[item->_iCurs];
21822187
if (item->_iMiscId == IMISC_BOOK)
21832188
PlaySFX(SfxID::ReadBook);

Source/lua/lua_global.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "engine/assets.hpp"
1515
#include "lua/modules/audio.hpp"
1616
#include "lua/modules/floatingnumbers.hpp"
17+
#include "lua/modules/game.hpp"
1718
#include "lua/modules/hellfire.hpp"
1819
#include "lua/modules/i18n.hpp"
1920
#include "lua/modules/items.hpp"
@@ -23,6 +24,7 @@
2324
#include "lua/modules/render.hpp"
2425
#include "lua/modules/system.hpp"
2526
#include "lua/modules/towners.hpp"
27+
#include "items.h"
2628
#include "monster.h"
2729
#include "options.h"
2830
#include "player.h"
@@ -281,6 +283,7 @@ void LuaInitialize()
281283
"devilutionx.dev", LuaDevModule(lua),
282284
#endif
283285
"devilutionx.version", PROJECT_VERSION,
286+
"devilutionx.game", LuaGameModule(lua),
284287
"devilutionx.i18n", LuaI18nModule(lua),
285288
"devilutionx.items", LuaItemModule(lua),
286289
"devilutionx.log", LuaLogModule(lua),
@@ -350,11 +353,40 @@ void LuaEvent(std::string_view name, const Monster *monster, int arg1, int arg2)
350353
CallLuaEvent(name, monster, arg1, arg2);
351354
}
352355

356+
void LuaEvent(std::string_view name, const Monster *monster, int arg1)
357+
{
358+
CallLuaEvent(name, monster, arg1);
359+
}
360+
353361
void LuaEvent(std::string_view name, const Player *player, uint32_t arg1)
354362
{
355363
CallLuaEvent(name, player, arg1);
356364
}
357365

366+
bool LuaEventCancellable(std::string_view name, const Player *player, const Item *item)
367+
{
368+
if (!CurrentLuaState.has_value()) {
369+
return false;
370+
}
371+
372+
const auto trigger = CurrentLuaState->events.traverse_get<std::optional<sol::object>>(name, "trigger");
373+
if (!trigger.has_value() || !trigger->is<sol::protected_function>()) {
374+
LogError("events.{}.trigger is not a function", name);
375+
return false;
376+
}
377+
const sol::protected_function fn = trigger->as<sol::protected_function>();
378+
const sol::protected_function_result result = fn(player, item);
379+
if (!result.valid()) {
380+
const std::string error = result.get_type() == sol::type::string
381+
? StrCat("Lua error: ", result.get<std::string>())
382+
: "Unknown Lua error";
383+
LogError(error);
384+
return false;
385+
}
386+
const sol::object retVal = result;
387+
return retVal.is<bool>() && retVal.as<bool>();
388+
}
389+
358390
sol::state &GetLuaState()
359391
{
360392
return CurrentLuaState->sol;

Source/lua/lua_global.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace devilution {
1010

11+
struct Item;
1112
struct Player;
1213
struct Monster;
1314

@@ -18,7 +19,14 @@ void LuaEvent(std::string_view name);
1819
void LuaEvent(std::string_view name, std::string_view arg);
1920
void LuaEvent(std::string_view name, const Player *player, int arg1, int arg2);
2021
void LuaEvent(std::string_view name, const Monster *monster, int arg1, int arg2);
22+
void LuaEvent(std::string_view name, const Monster *monster, int arg1);
2123
void LuaEvent(std::string_view name, const Player *player, uint32_t arg1);
24+
25+
/**
26+
* @brief Fires a cancellable Lua event.
27+
* @return true if any handler returned true (i.e. default behaviour should be cancelled).
28+
*/
29+
bool LuaEventCancellable(std::string_view name, const Player *player, const Item *item);
2230
sol::state &GetLuaState();
2331
sol::environment CreateLuaSandbox();
2432
sol::object SafeCallResult(sol::protected_function_result result, bool optional);

Source/lua/modules/game.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include "lua/modules/game.hpp"
2+
3+
#include <sol/sol.hpp>
4+
5+
#include "lua/metadoc.hpp"
6+
#include "monster.h"
7+
#include "multi.h"
8+
#include "quests.h"
9+
#include "tables/objdat.h"
10+
11+
namespace devilution {
12+
13+
sol::table LuaGameModule(sol::state_view &lua)
14+
{
15+
sol::table table = lua.create_table();
16+
17+
LuaSetDocFn(table, "prepDoEnding", "()",
18+
"Triggers the game-ending sequence (win condition). Safe to call in multiplayer.",
19+
PrepDoEnding);
20+
21+
LuaSetDocFn(table, "isQuestDone", "(questId: integer) -> boolean",
22+
"Returns true if the quest with the given ID has been completed.",
23+
[](int questId) {
24+
return Quests[questId]._qactive == QUEST_DONE;
25+
});
26+
27+
LuaSetDocFn(table, "isMultiplayer", "() -> boolean",
28+
"Returns true when running in a multiplayer session.",
29+
[]() { return gbIsMultiplayer; });
30+
31+
return table;
32+
}
33+
34+
} // namespace devilution

Source/lua/modules/game.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include <sol/sol.hpp>
4+
5+
namespace devilution {
6+
7+
sol::table LuaGameModule(sol::state_view &lua);
8+
9+
} // namespace devilution

Source/lua/modules/items.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
#include <fmt/format.h>
66
#include <sol/sol.hpp>
77

8+
#include "cursor.h"
89
#include "data/file.hpp"
10+
#include "engine/point.hpp"
911
#include "items.h"
1012
#include "lua/metadoc.hpp"
1113
#include "player.h"
@@ -465,6 +467,11 @@ void AddUniqueItemDataFromTsv(const std::string_view path, const int32_t baseMap
465467
LoadUniqueItemDatFromFile(dataFile, path, baseMappingId);
466468
}
467469

470+
void LuaSpawnQuestItem(int itemIdx, int x, int y, bool sendmsg)
471+
{
472+
SpawnQuestItem(static_cast<_item_indexes>(itemIdx), Point { x, y }, 0, SelectionRegion::Bottom, sendmsg);
473+
}
474+
468475
} // namespace
469476

470477
sol::table LuaItemModule(sol::state_view &lua)
@@ -484,6 +491,10 @@ sol::table LuaItemModule(sol::state_view &lua)
484491

485492
LuaSetDocFn(table, "addItemDataFromTsv", "(path: string, baseMappingId: number)", AddItemDataFromTsv);
486493
LuaSetDocFn(table, "addUniqueItemDataFromTsv", "(path: string, baseMappingId: number)", AddUniqueItemDataFromTsv);
494+
LuaSetDocFn(table, "spawnQuestItem",
495+
"(itemIdx: ItemIndex, x: integer, y: integer, sendmsg: boolean = true)",
496+
"Spawns a quest item at the given world coordinates. Pass sendmsg=true to sync with other clients.",
497+
LuaSpawnQuestItem);
487498

488499
// Expose enums through the module table
489500
table["ItemIndex"] = lua["ItemIndex"];

Source/lua/modules/monsters.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ void InitMonsterUserType(sol::state_view &lua)
4242
[](const Monster &monster) {
4343
return static_cast<int>(reinterpret_cast<uintptr_t>(&monster));
4444
});
45+
LuaSetDocReadonlyProperty(monsterType, "typeId", "integer",
46+
"Monster type ID matching monsters.MonsterID constants (readonly)",
47+
[](const Monster &monster) {
48+
return static_cast<int>(monster.type().type);
49+
});
50+
LuaSetDocReadonlyProperty(monsterType, "name", "string",
51+
"Monster's display name (readonly)",
52+
[](const Monster &monster) -> std::string_view {
53+
return monster.name();
54+
});
4555
}
4656

4757
} // namespace
@@ -52,6 +62,7 @@ sol::table LuaMonstersModule(sol::state_view &lua)
5262
sol::table table = lua.create_table();
5363
LuaSetDocFn(table, "addMonsterDataFromTsv", "(path: string)", AddMonsterDataFromTsv);
5464
LuaSetDocFn(table, "addUniqueMonsterDataFromTsv", "(path: string)", AddUniqueMonsterDataFromTsv);
65+
5566
return table;
5667
}
5768

Source/lua/modules/player.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "items.h"
1313
#include "lua/metadoc.hpp"
1414
#include "player.h"
15+
#include "sound_effect_enums.h"
1516

1617
namespace devilution {
1718
namespace {
@@ -111,13 +112,52 @@ void InitPlayerUserType(sol::state_view &lua)
111112
LuaSetDocReadonlyProperty(playerType, "maxMana", "number",
112113
"Maximum mana (readonly)",
113114
[](Player &player) { return player._pMaxMana >> 6; });
115+
LuaSetDocReadonlyProperty(playerType, "isMyPlayer", "boolean",
116+
"Whether this is the locally controlled player (readonly)",
117+
[](const Player &player) { return &player == MyPlayer; });
118+
LuaSetDocReadonlyProperty(playerType, "level", "integer",
119+
"Current dungeon level the player is on (readonly)",
120+
[](const Player &player) { return static_cast<int>(player.plrlevel); });
121+
LuaSetDocFn(playerType, "isOnLevel", "(level: integer) -> boolean",
122+
"Returns true if the player is on the given dungeon level",
123+
[](const Player &player, int level) {
124+
return player.isOnLevel(static_cast<uint8_t>(level));
125+
});
126+
LuaSetDocFn(playerType, "say", "(speechId: HeroSpeech) -> void",
127+
"Makes the player character say the given speech line",
128+
[](Player &player, HeroSpeech speechId) {
129+
player.Say(speechId);
130+
});
131+
}
132+
133+
void RegisterHeroSpeechEnum(sol::state_view &lua)
134+
{
135+
lua.new_enum<HeroSpeech>("HeroSpeech",
136+
{
137+
{ "ICantUseThisYet", HeroSpeech::ICantUseThisYet },
138+
{ "ThatWontWorkHere", HeroSpeech::ThatWontWorkHere },
139+
{ "ThatWontWork", HeroSpeech::ThatWontWork },
140+
{ "VengeanceIsMine", HeroSpeech::VengeanceIsMine },
141+
{ "JustWhatIWasLookingFor", HeroSpeech::JustWhatIWasLookingFor },
142+
{ "ICantCarryAnymore", HeroSpeech::ICantCarryAnymore },
143+
{ "IHaveNoRoom", HeroSpeech::IHaveNoRoom },
144+
{ "ItsTooBig", HeroSpeech::ItsTooBig },
145+
{ "ItsTooHeavy", HeroSpeech::ItsTooHeavy },
146+
{ "No", HeroSpeech::No },
147+
{ "Yes", HeroSpeech::Yes },
148+
{ "Die", HeroSpeech::Die },
149+
{ "TimeToDie", HeroSpeech::TimeToDie },
150+
{ "OhTooEasy", HeroSpeech::OhTooEasy },
151+
});
114152
}
115153
} // namespace
116154

117155
sol::table LuaPlayerModule(sol::state_view &lua)
118156
{
119157
InitPlayerUserType(lua);
158+
RegisterHeroSpeechEnum(lua);
120159
sol::table table = lua.create_table();
160+
table["HeroSpeech"] = lua["HeroSpeech"];
121161
LuaSetDocFn(table, "self", "()",
122162
"The current player",
123163
[]() {

Source/monster.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,7 @@ void ShrinkLeaderPacksize(const Monster &monster)
14991499
void MonsterDeath(Monster &monster)
15001500
{
15011501
monster.var1++;
1502+
LuaEvent("OnMonsterDeath", &monster, monster.var1);
15021503
if (monster.type().type == MT_DIABLO) {
15031504
if (monster.position.tile.x < ViewPosition.x) {
15041505
ViewPosition.x--;

0 commit comments

Comments
 (0)