feat: display Lua addon errors as in-game UI errors

Previously Lua addon errors only logged to the log file. Now they
display as red UI error text to the player (same as spell errors and
game warnings), helping addon developers debug issues in real-time.

Add LuaErrorCallback to LuaEngine, fire it from event handler and
frame OnEvent pcall error paths. Wire the callback to GameHandler's
addUIError in application.cpp.
This commit is contained in:
Kelsi 2026-03-21 06:00:06 -07:00
parent 64c0c75bbf
commit 19b8d31da2
3 changed files with 17 additions and 3 deletions

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -47,9 +48,14 @@ public:
lua_State* getState() { return L_; } lua_State* getState() { return L_; }
bool isInitialized() const { return L_ != nullptr; } bool isInitialized() const { return L_ != nullptr; }
// Optional callback for Lua errors (displayed as UI errors to the player)
using LuaErrorCallback = std::function<void(const std::string&)>;
void setLuaErrorCallback(LuaErrorCallback cb) { luaErrorCallback_ = std::move(cb); }
private: private:
lua_State* L_ = nullptr; lua_State* L_ = nullptr;
game::GameHandler* gameHandler_ = nullptr; game::GameHandler* gameHandler_ = nullptr;
LuaErrorCallback luaErrorCallback_;
void registerCoreAPI(); void registerCoreAPI();
void registerEventAPI(); void registerEventAPI();

View file

@ -3820,8 +3820,9 @@ void LuaEngine::fireEvent(const std::string& eventName,
int nargs = 1 + static_cast<int>(args.size()); int nargs = 1 + static_cast<int>(args.size());
if (lua_pcall(L_, nargs, 0, 0) != 0) { if (lua_pcall(L_, nargs, 0, 0) != 0) {
const char* err = lua_tostring(L_, -1); const char* err = lua_tostring(L_, -1);
LOG_ERROR("LuaEngine: event '", eventName, "' handler error: ", std::string errStr = err ? err : "(unknown)";
err ? err : "(unknown)"); LOG_ERROR("LuaEngine: event '", eventName, "' handler error: ", errStr);
if (luaErrorCallback_) luaErrorCallback_(errStr);
lua_pop(L_, 1); lua_pop(L_, 1);
} }
} }
@ -3847,7 +3848,10 @@ void LuaEngine::fireEvent(const std::string& eventName,
for (const auto& arg : args) lua_pushstring(L_, arg.c_str()); for (const auto& arg : args) lua_pushstring(L_, arg.c_str());
int nargs = 2 + static_cast<int>(args.size()); int nargs = 2 + static_cast<int>(args.size());
if (lua_pcall(L_, nargs, 0, 0) != 0) { if (lua_pcall(L_, nargs, 0, 0) != 0) {
LOG_ERROR("LuaEngine: frame OnEvent error: ", lua_tostring(L_, -1)); const char* ferr = lua_tostring(L_, -1);
std::string ferrStr = ferr ? ferr : "(unknown)";
LOG_ERROR("LuaEngine: frame OnEvent error: ", ferrStr);
if (luaErrorCallback_) luaErrorCallback_(ferrStr);
lua_pop(L_, 1); lua_pop(L_, 1);
} }
} else { } else {

View file

@ -335,6 +335,10 @@ bool Application::initialize() {
if (addonManager_->initialize(gameHandler.get())) { if (addonManager_->initialize(gameHandler.get())) {
std::string addonsDir = assetPath + "/interface/AddOns"; std::string addonsDir = assetPath + "/interface/AddOns";
addonManager_->scanAddons(addonsDir); addonManager_->scanAddons(addonsDir);
// Wire Lua errors to UI error display
addonManager_->getLuaEngine()->setLuaErrorCallback([gh = gameHandler.get()](const std::string& err) {
if (gh) gh->addUIError(err);
});
// Wire chat messages to addon event dispatch // Wire chat messages to addon event dispatch
gameHandler->setAddonChatCallback([this](const game::MessageChatData& msg) { gameHandler->setAddonChatCallback([this](const game::MessageChatData& msg) {
if (!addonManager_ || !addonsLoaded_) return; if (!addonManager_ || !addonsLoaded_) return;