feat: add Lua 5.1 addon system with .toc loader and /run command

Foundation for WoW-compatible addon support:

- Vendor Lua 5.1.5 source as a static library (extern/lua-5.1.5)
- TocParser: parses .toc files (## directives + file lists)
- LuaEngine: Lua 5.1 VM with sandboxed stdlib (no io/os/debug),
  WoW-compatible print() that outputs to chat, GetTime() stub
- AddonManager: scans Data/interface/AddOns/ for .toc files,
  loads .lua files on world entry, skips LoadOnDemand addons
- /run <code> slash command for inline Lua execution
- HelloWorld test addon that prints to chat on load

Integration: AddonManager initialized after asset manager, addons
loaded once on first world entry, reset on logout. XML frame
parsing is deferred to a future step.
This commit is contained in:
Kelsi 2026-03-20 11:12:07 -07:00
parent 52064eb438
commit 290e9bfbd8
115 changed files with 29035 additions and 2 deletions

View file

@ -31,6 +31,7 @@
#include "audio/footstep_manager.hpp"
#include "audio/activity_sound_manager.hpp"
#include "audio/audio_engine.hpp"
#include "addons/addon_manager.hpp"
#include <imgui.h>
#include "pipeline/m2_loader.hpp"
#include "pipeline/wmo_loader.hpp"
@ -329,6 +330,17 @@ bool Application::initialize() {
}
}
// Initialize addon system
addonManager_ = std::make_unique<addons::AddonManager>();
if (addonManager_->initialize(gameHandler.get())) {
std::string addonsDir = assetPath + "/interface/AddOns";
addonManager_->scanAddons(addonsDir);
LOG_INFO("Addon system initialized, found ", addonManager_->getAddons().size(), " addon(s)");
} else {
LOG_WARNING("Failed to initialize addon system");
addonManager_.reset();
}
} else {
LOG_WARNING("Failed to initialize asset manager - asset loading will be unavailable");
LOG_WARNING("Set WOW_DATA_PATH environment variable to your WoW Data directory");
@ -650,6 +662,7 @@ void Application::setState(AppState newState) {
// If we reuse a previously spawned instance without forcing a respawn, appearance (notably hair) can desync.
npcsSpawned = false;
playerCharacterSpawned = false;
addonsLoaded_ = false;
weaponsSheathed_ = false;
wasAutoAttacking_ = false;
loadedMapId_ = 0xFFFFFFFF;
@ -5031,6 +5044,12 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
// Only enter IN_GAME when this is the final map (no deferred entry pending).
setState(AppState::IN_GAME);
// Load addons once per session on first world entry
if (addonManager_ && !addonsLoaded_) {
addonManager_->loadAllAddons();
addonsLoaded_ = true;
}
}
void Application::buildCharSectionsCache() {