mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: add WoW event system for Lua addons
Implement the WoW-compatible event system that lets addons react to gameplay events in real-time: - RegisterEvent(eventName, handler) — register a Lua function for an event - UnregisterEvent(eventName, handler) — remove a handler - fireEvent() dispatches events to all registered handlers with args Currently fired events: - PLAYER_ENTERING_WORLD — after addons load and world entry completes - PLAYER_LEAVING_WORLD — before logout/disconnect Events are stored in a __WoweeEvents Lua table, dispatched via LuaEngine::fireEvent() which is called from AddonManager::fireEvent(). Error handling logs Lua errors without crashing. Updated HelloWorld addon to use RegisterEvent for world entry/exit.
This commit is contained in:
parent
7da1f6f5ca
commit
510f03fa32
6 changed files with 148 additions and 14 deletions
|
|
@ -1,18 +1,24 @@
|
|||
-- HelloWorld addon — test the WoWee addon system
|
||||
print("|cff00ff00[HelloWorld]|r Addon loaded! Lua 5.1 is working.")
|
||||
print("|cff00ff00[HelloWorld]|r GetTime() = " .. string.format("%.2f", GetTime()) .. " seconds")
|
||||
|
||||
-- Query player info (will show real data when called after world entry)
|
||||
local name = UnitName("player")
|
||||
local level = UnitLevel("player")
|
||||
local health = UnitHealth("player")
|
||||
local maxHealth = UnitHealthMax("player")
|
||||
local gold = math.floor(GetMoney() / 10000)
|
||||
-- Register for game events
|
||||
RegisterEvent("PLAYER_ENTERING_WORLD", function(event)
|
||||
local name = UnitName("player")
|
||||
local level = UnitLevel("player")
|
||||
local health = UnitHealth("player")
|
||||
local maxHealth = UnitHealthMax("player")
|
||||
local _, _, classId = UnitClass("player")
|
||||
local gold = math.floor(GetMoney() / 10000)
|
||||
|
||||
print("|cff00ff00[HelloWorld]|r Player: " .. name .. " (Level " .. level .. ")")
|
||||
if maxHealth > 0 then
|
||||
print("|cff00ff00[HelloWorld]|r Health: " .. health .. "/" .. maxHealth)
|
||||
end
|
||||
if gold > 0 then
|
||||
print("|cff00ff00[HelloWorld]|r Gold: " .. gold .. "g")
|
||||
end
|
||||
print("|cff00ff00[HelloWorld]|r Welcome, " .. name .. "! (Level " .. level .. ")")
|
||||
if maxHealth > 0 then
|
||||
print("|cff00ff00[HelloWorld]|r Health: " .. health .. "/" .. maxHealth)
|
||||
end
|
||||
if gold > 0 then
|
||||
print("|cff00ff00[HelloWorld]|r Gold: " .. gold .. "g")
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterEvent("PLAYER_LEAVING_WORLD", function(event)
|
||||
print("|cff00ff00[HelloWorld]|r Goodbye!")
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public:
|
|||
void scanAddons(const std::string& addonsPath);
|
||||
void loadAllAddons();
|
||||
bool runScript(const std::string& code);
|
||||
void fireEvent(const std::string& event, const std::vector<std::string>& args = {});
|
||||
void shutdown();
|
||||
|
||||
const std::vector<TocFile>& getAddons() const { return addons_; }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
|
|
@ -24,6 +25,11 @@ public:
|
|||
|
||||
void setGameHandler(game::GameHandler* handler);
|
||||
|
||||
// Fire a WoW event to all registered Lua handlers.
|
||||
// Extra string args are pushed as event arguments.
|
||||
void fireEvent(const std::string& eventName,
|
||||
const std::vector<std::string>& args = {});
|
||||
|
||||
lua_State* getState() { return L_; }
|
||||
bool isInitialized() const { return L_ != nullptr; }
|
||||
|
||||
|
|
@ -32,6 +38,7 @@ private:
|
|||
game::GameHandler* gameHandler_ = nullptr;
|
||||
|
||||
void registerCoreAPI();
|
||||
void registerEventAPI();
|
||||
};
|
||||
|
||||
} // namespace wowee::addons
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ bool AddonManager::runScript(const std::string& code) {
|
|||
return luaEngine_.executeString(code);
|
||||
}
|
||||
|
||||
void AddonManager::fireEvent(const std::string& event, const std::vector<std::string>& args) {
|
||||
luaEngine_.fireEvent(event, args);
|
||||
}
|
||||
|
||||
void AddonManager::shutdown() {
|
||||
addons_.clear();
|
||||
luaEngine_.shutdown();
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ bool LuaEngine::initialize() {
|
|||
}
|
||||
|
||||
registerCoreAPI();
|
||||
registerEventAPI();
|
||||
|
||||
LOG_INFO("LuaEngine: initialized (Lua 5.1)");
|
||||
return true;
|
||||
|
|
@ -298,6 +299,117 @@ void LuaEngine::registerCoreAPI() {
|
|||
}
|
||||
}
|
||||
|
||||
// ---- Event System ----
|
||||
// Lua-side: WoweeEvents table holds { ["EVENT_NAME"] = { handler1, handler2, ... } }
|
||||
// RegisterEvent("EVENT", handler) adds a handler function
|
||||
// UnregisterEvent("EVENT", handler) removes it
|
||||
|
||||
static int lua_RegisterEvent(lua_State* L) {
|
||||
const char* eventName = luaL_checkstring(L, 1);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
|
||||
// Get or create the WoweeEvents table
|
||||
lua_getglobal(L, "__WoweeEvents");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setglobal(L, "__WoweeEvents");
|
||||
}
|
||||
|
||||
// Get or create the handler list for this event
|
||||
lua_getfield(L, -1, eventName);
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -3, eventName);
|
||||
}
|
||||
|
||||
// Append the handler function to the list
|
||||
int len = static_cast<int>(lua_objlen(L, -1));
|
||||
lua_pushvalue(L, 2); // push the handler function
|
||||
lua_rawseti(L, -2, len + 1);
|
||||
|
||||
lua_pop(L, 2); // pop handler list + WoweeEvents
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lua_UnregisterEvent(lua_State* L) {
|
||||
const char* eventName = luaL_checkstring(L, 1);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
|
||||
lua_getglobal(L, "__WoweeEvents");
|
||||
if (lua_isnil(L, -1)) { lua_pop(L, 1); return 0; }
|
||||
|
||||
lua_getfield(L, -1, eventName);
|
||||
if (lua_isnil(L, -1)) { lua_pop(L, 2); return 0; }
|
||||
|
||||
// Remove matching handler from the list
|
||||
int len = static_cast<int>(lua_objlen(L, -1));
|
||||
for (int i = 1; i <= len; i++) {
|
||||
lua_rawgeti(L, -1, i);
|
||||
if (lua_rawequal(L, -1, 2)) {
|
||||
lua_pop(L, 1);
|
||||
// Shift remaining elements down
|
||||
for (int j = i; j < len; j++) {
|
||||
lua_rawgeti(L, -1, j + 1);
|
||||
lua_rawseti(L, -2, j);
|
||||
}
|
||||
lua_pushnil(L);
|
||||
lua_rawseti(L, -2, len);
|
||||
break;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LuaEngine::registerEventAPI() {
|
||||
lua_pushcfunction(L_, lua_RegisterEvent);
|
||||
lua_setglobal(L_, "RegisterEvent");
|
||||
|
||||
lua_pushcfunction(L_, lua_UnregisterEvent);
|
||||
lua_setglobal(L_, "UnregisterEvent");
|
||||
|
||||
// Create the events table
|
||||
lua_newtable(L_);
|
||||
lua_setglobal(L_, "__WoweeEvents");
|
||||
}
|
||||
|
||||
void LuaEngine::fireEvent(const std::string& eventName,
|
||||
const std::vector<std::string>& args) {
|
||||
if (!L_) return;
|
||||
|
||||
lua_getglobal(L_, "__WoweeEvents");
|
||||
if (lua_isnil(L_, -1)) { lua_pop(L_, 1); return; }
|
||||
|
||||
lua_getfield(L_, -1, eventName.c_str());
|
||||
if (lua_isnil(L_, -1)) { lua_pop(L_, 2); return; }
|
||||
|
||||
int handlerCount = static_cast<int>(lua_objlen(L_, -1));
|
||||
for (int i = 1; i <= handlerCount; i++) {
|
||||
lua_rawgeti(L_, -1, i);
|
||||
if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); continue; }
|
||||
|
||||
// Push arguments: event name first, then extra args
|
||||
lua_pushstring(L_, eventName.c_str());
|
||||
for (const auto& arg : args) {
|
||||
lua_pushstring(L_, arg.c_str());
|
||||
}
|
||||
|
||||
int nargs = 1 + static_cast<int>(args.size());
|
||||
if (lua_pcall(L_, nargs, 0, 0) != 0) {
|
||||
const char* err = lua_tostring(L_, -1);
|
||||
LOG_ERROR("LuaEngine: event '", eventName, "' handler error: ",
|
||||
err ? err : "(unknown)");
|
||||
lua_pop(L_, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L_, 2); // pop handler list + WoweeEvents
|
||||
}
|
||||
|
||||
bool LuaEngine::executeFile(const std::string& path) {
|
||||
if (!L_) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -660,6 +660,9 @@ void Application::setState(AppState newState) {
|
|||
}
|
||||
// Ensure no stale in-world player model leaks into the next login attempt.
|
||||
// If we reuse a previously spawned instance without forcing a respawn, appearance (notably hair) can desync.
|
||||
if (addonManager_ && addonsLoaded_) {
|
||||
addonManager_->fireEvent("PLAYER_LEAVING_WORLD");
|
||||
}
|
||||
npcsSpawned = false;
|
||||
playerCharacterSpawned = false;
|
||||
addonsLoaded_ = false;
|
||||
|
|
@ -5049,6 +5052,7 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
if (addonManager_ && !addonsLoaded_) {
|
||||
addonManager_->loadAllAddons();
|
||||
addonsLoaded_ = true;
|
||||
addonManager_->fireEvent("PLAYER_ENTERING_WORLD");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue