mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-26 21:13:51 +00:00
Compare commits
No commits in common. "22798d1c76f881dac6d172fa2e3af8fd10ffe7a4" and "fb7b2b53904d9aff948077faf0a862c41cf21d4b" have entirely different histories.
22798d1c76
...
fb7b2b5390
18 changed files with 51 additions and 1219 deletions
|
|
@ -27,14 +27,9 @@ public:
|
||||||
|
|
||||||
void saveAllSavedVariables();
|
void saveAllSavedVariables();
|
||||||
|
|
||||||
/// Re-initialize the Lua VM and reload all addons (used by /reload).
|
|
||||||
bool reload();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LuaEngine luaEngine_;
|
LuaEngine luaEngine_;
|
||||||
std::vector<TocFile> addons_;
|
std::vector<TocFile> addons_;
|
||||||
game::GameHandler* gameHandler_ = nullptr;
|
|
||||||
std::string addonsPath_;
|
|
||||||
|
|
||||||
bool loadAddon(const TocFile& addon);
|
bool loadAddon(const TocFile& addon);
|
||||||
std::string getSavedVariablesPath(const TocFile& addon) const;
|
std::string getSavedVariablesPath(const TocFile& addon) const;
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,6 @@ public:
|
||||||
// Chat notifications
|
// Chat notifications
|
||||||
void playWhisperReceived();
|
void playWhisperReceived();
|
||||||
|
|
||||||
// Minimap ping
|
|
||||||
void playMinimapPing();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct UISample {
|
struct UISample {
|
||||||
std::string path;
|
std::string path;
|
||||||
|
|
@ -129,7 +126,6 @@ private:
|
||||||
std::vector<UISample> selectTargetSounds_;
|
std::vector<UISample> selectTargetSounds_;
|
||||||
std::vector<UISample> deselectTargetSounds_;
|
std::vector<UISample> deselectTargetSounds_;
|
||||||
std::vector<UISample> whisperSounds_;
|
std::vector<UISample> whisperSounds_;
|
||||||
std::vector<UISample> minimapPingSounds_;
|
|
||||||
|
|
||||||
// State tracking
|
// State tracking
|
||||||
float volumeScale_ = 1.0f;
|
float volumeScale_ = 1.0f;
|
||||||
|
|
|
||||||
|
|
@ -294,14 +294,6 @@ public:
|
||||||
return spellIconPathResolver_ ? spellIconPathResolver_(spellId) : std::string{};
|
return spellIconPathResolver_ ? spellIconPathResolver_(spellId) : std::string{};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Random property/suffix name resolver: randomPropertyId -> suffix name (e.g., "of the Eagle")
|
|
||||||
// Positive IDs → ItemRandomProperties.dbc; negative IDs → ItemRandomSuffix.dbc (abs value)
|
|
||||||
using RandomPropertyNameResolver = std::function<std::string(int32_t)>;
|
|
||||||
void setRandomPropertyNameResolver(RandomPropertyNameResolver r) { randomPropertyNameResolver_ = std::move(r); }
|
|
||||||
std::string getRandomPropertyName(int32_t id) const {
|
|
||||||
return randomPropertyNameResolver_ ? randomPropertyNameResolver_(id) : std::string{};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emote animation callback: (entityGuid, animationId)
|
// Emote animation callback: (entityGuid, animationId)
|
||||||
using EmoteAnimCallback = std::function<void(uint64_t, uint32_t)>;
|
using EmoteAnimCallback = std::function<void(uint64_t, uint32_t)>;
|
||||||
void setEmoteAnimCallback(EmoteAnimCallback cb) { emoteAnimCallback_ = std::move(cb); }
|
void setEmoteAnimCallback(EmoteAnimCallback cb) { emoteAnimCallback_ = std::move(cb); }
|
||||||
|
|
@ -876,7 +868,6 @@ public:
|
||||||
|
|
||||||
// 400ms spell-queue window: next spell to cast when current finishes
|
// 400ms spell-queue window: next spell to cast when current finishes
|
||||||
uint32_t getQueuedSpellId() const { return queuedSpellId_; }
|
uint32_t getQueuedSpellId() const { return queuedSpellId_; }
|
||||||
void cancelQueuedSpell() { queuedSpellId_ = 0; queuedSpellTarget_ = 0; }
|
|
||||||
|
|
||||||
// Unit cast state (tracked per GUID for target frame + boss frames)
|
// Unit cast state (tracked per GUID for target frame + boss frames)
|
||||||
struct UnitCastState {
|
struct UnitCastState {
|
||||||
|
|
@ -1451,7 +1442,6 @@ public:
|
||||||
// roles bitmask: 0x02=tank, 0x04=healer, 0x08=dps; pass LFGDungeonEntry ID
|
// roles bitmask: 0x02=tank, 0x04=healer, 0x08=dps; pass LFGDungeonEntry ID
|
||||||
void lfgJoin(uint32_t dungeonId, uint8_t roles);
|
void lfgJoin(uint32_t dungeonId, uint8_t roles);
|
||||||
void lfgLeave();
|
void lfgLeave();
|
||||||
void lfgSetRoles(uint8_t roles);
|
|
||||||
void lfgAcceptProposal(uint32_t proposalId, bool accept);
|
void lfgAcceptProposal(uint32_t proposalId, bool accept);
|
||||||
void lfgSetBootVote(bool vote);
|
void lfgSetBootVote(bool vote);
|
||||||
void lfgTeleport(bool toLfgDungeon = true);
|
void lfgTeleport(bool toLfgDungeon = true);
|
||||||
|
|
@ -2662,7 +2652,6 @@ private:
|
||||||
AddonChatCallback addonChatCallback_;
|
AddonChatCallback addonChatCallback_;
|
||||||
AddonEventCallback addonEventCallback_;
|
AddonEventCallback addonEventCallback_;
|
||||||
SpellIconPathResolver spellIconPathResolver_;
|
SpellIconPathResolver spellIconPathResolver_;
|
||||||
RandomPropertyNameResolver randomPropertyNameResolver_;
|
|
||||||
EmoteAnimCallback emoteAnimCallback_;
|
EmoteAnimCallback emoteAnimCallback_;
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ public:
|
||||||
|
|
||||||
void setProgress(float progress) { loadProgress = progress; }
|
void setProgress(float progress) { loadProgress = progress; }
|
||||||
void setStatus(const std::string& status) { statusText = status; }
|
void setStatus(const std::string& status) { statusText = status; }
|
||||||
void setZoneName(const std::string& name) { zoneName = name; }
|
|
||||||
|
|
||||||
// Must be set before initialize() for Vulkan texture upload
|
// Must be set before initialize() for Vulkan texture upload
|
||||||
void setVkContext(VkContext* ctx) { vkCtx = ctx; }
|
void setVkContext(VkContext* ctx) { vkCtx = ctx; }
|
||||||
|
|
@ -54,7 +53,6 @@ private:
|
||||||
|
|
||||||
float loadProgress = 0.0f;
|
float loadProgress = 0.0f;
|
||||||
std::string statusText = "Loading...";
|
std::string statusText = "Loading...";
|
||||||
std::string zoneName;
|
|
||||||
|
|
||||||
int imageWidth = 0;
|
int imageWidth = 0;
|
||||||
int imageHeight = 0;
|
int imageHeight = 0;
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ public:
|
||||||
enum class Type {
|
enum class Type {
|
||||||
NONE,
|
NONE,
|
||||||
RAIN,
|
RAIN,
|
||||||
SNOW,
|
SNOW
|
||||||
STORM
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Weather();
|
Weather();
|
||||||
|
|
|
||||||
|
|
@ -388,7 +388,6 @@ private:
|
||||||
void renderBgInvitePopup(game::GameHandler& gameHandler);
|
void renderBgInvitePopup(game::GameHandler& gameHandler);
|
||||||
void renderBfMgrInvitePopup(game::GameHandler& gameHandler);
|
void renderBfMgrInvitePopup(game::GameHandler& gameHandler);
|
||||||
void renderLfgProposalPopup(game::GameHandler& gameHandler);
|
void renderLfgProposalPopup(game::GameHandler& gameHandler);
|
||||||
void renderLfgRoleCheckPopup(game::GameHandler& gameHandler);
|
|
||||||
void renderChatBubbles(game::GameHandler& gameHandler);
|
void renderChatBubbles(game::GameHandler& gameHandler);
|
||||||
void renderMailWindow(game::GameHandler& gameHandler);
|
void renderMailWindow(game::GameHandler& gameHandler);
|
||||||
void renderMailComposeWindow(game::GameHandler& gameHandler);
|
void renderMailComposeWindow(game::GameHandler& gameHandler);
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,12 @@ AddonManager::AddonManager() = default;
|
||||||
AddonManager::~AddonManager() { shutdown(); }
|
AddonManager::~AddonManager() { shutdown(); }
|
||||||
|
|
||||||
bool AddonManager::initialize(game::GameHandler* gameHandler) {
|
bool AddonManager::initialize(game::GameHandler* gameHandler) {
|
||||||
gameHandler_ = gameHandler;
|
|
||||||
if (!luaEngine_.initialize()) return false;
|
if (!luaEngine_.initialize()) return false;
|
||||||
luaEngine_.setGameHandler(gameHandler);
|
luaEngine_.setGameHandler(gameHandler);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddonManager::scanAddons(const std::string& addonsPath) {
|
void AddonManager::scanAddons(const std::string& addonsPath) {
|
||||||
addonsPath_ = addonsPath;
|
|
||||||
addons_.clear();
|
addons_.clear();
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|
@ -123,26 +121,6 @@ void AddonManager::saveAllSavedVariables() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddonManager::reload() {
|
|
||||||
LOG_INFO("AddonManager: reloading all addons...");
|
|
||||||
saveAllSavedVariables();
|
|
||||||
addons_.clear();
|
|
||||||
luaEngine_.shutdown();
|
|
||||||
|
|
||||||
if (!luaEngine_.initialize()) {
|
|
||||||
LOG_ERROR("AddonManager: failed to reinitialize Lua VM during reload");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
luaEngine_.setGameHandler(gameHandler_);
|
|
||||||
|
|
||||||
if (!addonsPath_.empty()) {
|
|
||||||
scanAddons(addonsPath_);
|
|
||||||
loadAllAddons();
|
|
||||||
}
|
|
||||||
LOG_INFO("AddonManager: reload complete");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddonManager::shutdown() {
|
void AddonManager::shutdown() {
|
||||||
saveAllSavedVariables();
|
saveAllSavedVariables();
|
||||||
addons_.clear();
|
addons_.clear();
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,15 @@ static int lua_wow_message(lua_State* L) {
|
||||||
return lua_wow_print(L);
|
return lua_wow_print(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: get player Unit from game handler
|
||||||
|
static game::Unit* getPlayerUnit(lua_State* L) {
|
||||||
|
auto* gh = getGameHandler(L);
|
||||||
|
if (!gh) return nullptr;
|
||||||
|
auto entity = gh->getEntityManager().getEntity(gh->getPlayerGuid());
|
||||||
|
if (!entity) return nullptr;
|
||||||
|
return dynamic_cast<game::Unit*>(entity.get());
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: resolve WoW unit IDs to GUID
|
// Helper: resolve WoW unit IDs to GUID
|
||||||
static uint64_t resolveUnitGuid(game::GameHandler* gh, const std::string& uid) {
|
static uint64_t resolveUnitGuid(game::GameHandler* gh, const std::string& uid) {
|
||||||
if (uid == "player") return gh->getPlayerGuid();
|
if (uid == "player") return gh->getPlayerGuid();
|
||||||
|
|
@ -379,50 +388,10 @@ static int lua_UnitAura(lua_State* L, bool wantBuff) {
|
||||||
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
|
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
|
||||||
else lua_pushnil(L); // icon texture path
|
else lua_pushnil(L); // icon texture path
|
||||||
lua_pushnumber(L, aura.charges); // count
|
lua_pushnumber(L, aura.charges); // count
|
||||||
// debuffType: resolve from Spell.dbc dispel type
|
lua_pushnil(L); // debuffType
|
||||||
{
|
|
||||||
uint8_t dt = gh->getSpellDispelType(aura.spellId);
|
|
||||||
switch (dt) {
|
|
||||||
case 1: lua_pushstring(L, "Magic"); break;
|
|
||||||
case 2: lua_pushstring(L, "Curse"); break;
|
|
||||||
case 3: lua_pushstring(L, "Disease"); break;
|
|
||||||
case 4: lua_pushstring(L, "Poison"); break;
|
|
||||||
default: lua_pushnil(L); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua_pushnumber(L, aura.maxDurationMs > 0 ? aura.maxDurationMs / 1000.0 : 0); // duration
|
lua_pushnumber(L, aura.maxDurationMs > 0 ? aura.maxDurationMs / 1000.0 : 0); // duration
|
||||||
// expirationTime: GetTime() + remaining seconds (so addons can compute countdown)
|
lua_pushnumber(L, 0); // expirationTime (would need absolute time)
|
||||||
if (aura.durationMs > 0) {
|
lua_pushnil(L); // caster
|
||||||
uint64_t auraNowMs = static_cast<uint64_t>(
|
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::steady_clock::now().time_since_epoch()).count());
|
|
||||||
int32_t remMs = aura.getRemainingMs(auraNowMs);
|
|
||||||
// GetTime epoch = steady_clock relative to engine start
|
|
||||||
static auto sStart = std::chrono::steady_clock::now();
|
|
||||||
double nowSec = std::chrono::duration<double>(
|
|
||||||
std::chrono::steady_clock::now() - sStart).count();
|
|
||||||
lua_pushnumber(L, nowSec + remMs / 1000.0);
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, 0); // permanent aura
|
|
||||||
}
|
|
||||||
// caster: return unit ID string if caster is known
|
|
||||||
if (aura.casterGuid != 0) {
|
|
||||||
if (aura.casterGuid == gh->getPlayerGuid())
|
|
||||||
lua_pushstring(L, "player");
|
|
||||||
else if (aura.casterGuid == gh->getTargetGuid())
|
|
||||||
lua_pushstring(L, "target");
|
|
||||||
else if (aura.casterGuid == gh->getFocusGuid())
|
|
||||||
lua_pushstring(L, "focus");
|
|
||||||
else if (aura.casterGuid == gh->getPetGuid())
|
|
||||||
lua_pushstring(L, "pet");
|
|
||||||
else {
|
|
||||||
char cBuf[32];
|
|
||||||
snprintf(cBuf, sizeof(cBuf), "0x%016llX", (unsigned long long)aura.casterGuid);
|
|
||||||
lua_pushstring(L, cBuf);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lua_pushnil(L);
|
|
||||||
}
|
|
||||||
lua_pushboolean(L, 0); // isStealable
|
lua_pushboolean(L, 0); // isStealable
|
||||||
lua_pushboolean(L, 0); // shouldConsolidate
|
lua_pushboolean(L, 0); // shouldConsolidate
|
||||||
lua_pushnumber(L, aura.spellId); // spellId
|
lua_pushnumber(L, aura.spellId); // spellId
|
||||||
|
|
@ -436,17 +405,6 @@ static int lua_UnitAura(lua_State* L, bool wantBuff) {
|
||||||
static int lua_UnitBuff(lua_State* L) { return lua_UnitAura(L, true); }
|
static int lua_UnitBuff(lua_State* L) { return lua_UnitAura(L, true); }
|
||||||
static int lua_UnitDebuff(lua_State* L) { return lua_UnitAura(L, false); }
|
static int lua_UnitDebuff(lua_State* L) { return lua_UnitAura(L, false); }
|
||||||
|
|
||||||
// UnitAura(unit, index, filter) — generic aura query with filter string
|
|
||||||
// filter: "HELPFUL" = buffs, "HARMFUL" = debuffs, "PLAYER" = cast by player,
|
|
||||||
// "HELPFUL|PLAYER" = buffs cast by player, etc.
|
|
||||||
static int lua_UnitAuraGeneric(lua_State* L) {
|
|
||||||
const char* filter = luaL_optstring(L, 3, "HELPFUL");
|
|
||||||
std::string f(filter ? filter : "HELPFUL");
|
|
||||||
for (char& c : f) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
|
||||||
bool wantBuff = (f.find("HARMFUL") == std::string::npos);
|
|
||||||
return lua_UnitAura(L, wantBuff);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Action API ---
|
// --- Action API ---
|
||||||
|
|
||||||
static int lua_SendChatMessage(lua_State* L) {
|
static int lua_SendChatMessage(lua_State* L) {
|
||||||
|
|
@ -534,20 +492,9 @@ static int lua_GetSpellCooldown(lua_State* L) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float cd = gh->getSpellCooldown(spellId);
|
float cd = gh->getSpellCooldown(spellId);
|
||||||
// WoW returns (start, duration, enabled) where remaining = start + duration - GetTime()
|
lua_pushnumber(L, 0); // start time (not tracked precisely, return 0)
|
||||||
// Compute start = GetTime() - elapsed, duration = total cooldown
|
lua_pushnumber(L, cd); // duration remaining
|
||||||
static auto sStart = std::chrono::steady_clock::now();
|
return 2;
|
||||||
double nowSec = std::chrono::duration<double>(
|
|
||||||
std::chrono::steady_clock::now() - sStart).count();
|
|
||||||
if (cd > 0.01f) {
|
|
||||||
lua_pushnumber(L, nowSec); // start (approximate — we don't track exact start)
|
|
||||||
lua_pushnumber(L, cd); // duration (remaining, used as total for simplicity)
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, 0); // not on cooldown
|
|
||||||
lua_pushnumber(L, 0);
|
|
||||||
}
|
|
||||||
lua_pushnumber(L, 1); // enabled (always 1 — spell is usable)
|
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lua_HasTarget(lua_State* L) {
|
static int lua_HasTarget(lua_State* L) {
|
||||||
|
|
@ -694,29 +641,6 @@ static int lua_GetCurrentMapAreaID(lua_State* L) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetZoneText() / GetRealZoneText() → current zone name
|
|
||||||
static int lua_GetZoneText(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushstring(L, ""); return 1; }
|
|
||||||
uint32_t zoneId = gh->getWorldStateZoneId();
|
|
||||||
if (zoneId != 0) {
|
|
||||||
std::string name = gh->getWhoAreaName(zoneId);
|
|
||||||
if (!name.empty()) { lua_pushstring(L, name.c_str()); return 1; }
|
|
||||||
}
|
|
||||||
lua_pushstring(L, "");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubZoneText() → subzone name (same as zone for now — server doesn't always send subzone)
|
|
||||||
static int lua_GetSubZoneText(lua_State* L) {
|
|
||||||
return lua_GetZoneText(L); // Best-effort: zone and subzone often overlap
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMinimapZoneText() → zone name displayed near minimap
|
|
||||||
static int lua_GetMinimapZoneText(lua_State* L) {
|
|
||||||
return lua_GetZoneText(L);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Player State API ---
|
// --- Player State API ---
|
||||||
// These replace the hardcoded "return false" Lua stubs with real game state.
|
// These replace the hardcoded "return false" Lua stubs with real game state.
|
||||||
|
|
||||||
|
|
@ -785,310 +709,6 @@ static int lua_GetUnitSpeed(lua_State* L) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Container/Bag API ---
|
|
||||||
// WoW bags: container 0 = backpack (16 slots), containers 1-4 = equipped bags
|
|
||||||
|
|
||||||
static int lua_GetContainerNumSlots(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
int container = static_cast<int>(luaL_checknumber(L, 1));
|
|
||||||
if (!gh) { lua_pushnumber(L, 0); return 1; }
|
|
||||||
const auto& inv = gh->getInventory();
|
|
||||||
if (container == 0) {
|
|
||||||
lua_pushnumber(L, inv.getBackpackSize());
|
|
||||||
} else if (container >= 1 && container <= 4) {
|
|
||||||
lua_pushnumber(L, inv.getBagSize(container - 1));
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, 0);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContainerItemInfo(container, slot) → texture, count, locked, quality, readable, lootable, link
|
|
||||||
static int lua_GetContainerItemInfo(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
int container = static_cast<int>(luaL_checknumber(L, 1));
|
|
||||||
int slot = static_cast<int>(luaL_checknumber(L, 2));
|
|
||||||
if (!gh) { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
const auto& inv = gh->getInventory();
|
|
||||||
const game::ItemSlot* itemSlot = nullptr;
|
|
||||||
|
|
||||||
if (container == 0 && slot >= 1 && slot <= inv.getBackpackSize()) {
|
|
||||||
itemSlot = &inv.getBackpackSlot(slot - 1); // WoW uses 1-based
|
|
||||||
} else if (container >= 1 && container <= 4) {
|
|
||||||
int bagIdx = container - 1;
|
|
||||||
int bagSize = inv.getBagSize(bagIdx);
|
|
||||||
if (slot >= 1 && slot <= bagSize)
|
|
||||||
itemSlot = &inv.getBagSlot(bagIdx, slot - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!itemSlot || itemSlot->empty()) { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
// Get item info for quality/icon
|
|
||||||
const auto* info = gh->getItemInfo(itemSlot->item.itemId);
|
|
||||||
|
|
||||||
lua_pushnil(L); // texture (icon path — would need ItemDisplayInfo icon resolver)
|
|
||||||
lua_pushnumber(L, itemSlot->item.stackCount); // count
|
|
||||||
lua_pushboolean(L, 0); // locked
|
|
||||||
lua_pushnumber(L, info ? info->quality : 0); // quality
|
|
||||||
lua_pushboolean(L, 0); // readable
|
|
||||||
lua_pushboolean(L, 0); // lootable
|
|
||||||
// Build item link with quality color
|
|
||||||
std::string name = info ? info->name : ("Item #" + std::to_string(itemSlot->item.itemId));
|
|
||||||
uint32_t q = info ? info->quality : 0;
|
|
||||||
static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"};
|
|
||||||
uint32_t qi = q < 8 ? q : 1u;
|
|
||||||
char link[256];
|
|
||||||
snprintf(link, sizeof(link), "|cff%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r",
|
|
||||||
kQH[qi], itemSlot->item.itemId, name.c_str());
|
|
||||||
lua_pushstring(L, link); // link
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContainerItemLink(container, slot) → item link string
|
|
||||||
static int lua_GetContainerItemLink(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
int container = static_cast<int>(luaL_checknumber(L, 1));
|
|
||||||
int slot = static_cast<int>(luaL_checknumber(L, 2));
|
|
||||||
if (!gh) { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
const auto& inv = gh->getInventory();
|
|
||||||
const game::ItemSlot* itemSlot = nullptr;
|
|
||||||
|
|
||||||
if (container == 0 && slot >= 1 && slot <= inv.getBackpackSize()) {
|
|
||||||
itemSlot = &inv.getBackpackSlot(slot - 1);
|
|
||||||
} else if (container >= 1 && container <= 4) {
|
|
||||||
int bagIdx = container - 1;
|
|
||||||
int bagSize = inv.getBagSize(bagIdx);
|
|
||||||
if (slot >= 1 && slot <= bagSize)
|
|
||||||
itemSlot = &inv.getBagSlot(bagIdx, slot - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!itemSlot || itemSlot->empty()) { lua_pushnil(L); return 1; }
|
|
||||||
const auto* info = gh->getItemInfo(itemSlot->item.itemId);
|
|
||||||
std::string name = info ? info->name : ("Item #" + std::to_string(itemSlot->item.itemId));
|
|
||||||
uint32_t q = info ? info->quality : 0;
|
|
||||||
char link[256];
|
|
||||||
static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"};
|
|
||||||
uint32_t qi = q < 8 ? q : 1u;
|
|
||||||
snprintf(link, sizeof(link), "|cff%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r",
|
|
||||||
kQH[qi], itemSlot->item.itemId, name.c_str());
|
|
||||||
lua_pushstring(L, link);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContainerNumFreeSlots(container) → numFreeSlots, bagType
|
|
||||||
static int lua_GetContainerNumFreeSlots(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
int container = static_cast<int>(luaL_checknumber(L, 1));
|
|
||||||
if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; }
|
|
||||||
|
|
||||||
const auto& inv = gh->getInventory();
|
|
||||||
int freeSlots = 0;
|
|
||||||
int totalSlots = 0;
|
|
||||||
|
|
||||||
if (container == 0) {
|
|
||||||
totalSlots = inv.getBackpackSize();
|
|
||||||
for (int i = 0; i < totalSlots; ++i)
|
|
||||||
if (inv.getBackpackSlot(i).empty()) ++freeSlots;
|
|
||||||
} else if (container >= 1 && container <= 4) {
|
|
||||||
totalSlots = inv.getBagSize(container - 1);
|
|
||||||
for (int i = 0; i < totalSlots; ++i)
|
|
||||||
if (inv.getBagSlot(container - 1, i).empty()) ++freeSlots;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushnumber(L, freeSlots);
|
|
||||||
lua_pushnumber(L, 0); // bagType (0 = normal)
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Equipment Slot API ---
|
|
||||||
// WoW inventory slot IDs: 1=Head,2=Neck,3=Shoulders,4=Shirt,5=Chest,
|
|
||||||
// 6=Waist,7=Legs,8=Feet,9=Wrists,10=Hands,11=Ring1,12=Ring2,
|
|
||||||
// 13=Trinket1,14=Trinket2,15=Back,16=MainHand,17=OffHand,18=Ranged,19=Tabard
|
|
||||||
|
|
||||||
static int lua_GetInventoryItemLink(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
const char* uid = luaL_optstring(L, 1, "player");
|
|
||||||
int slotId = static_cast<int>(luaL_checknumber(L, 2));
|
|
||||||
if (!gh || slotId < 1 || slotId > 19) { lua_pushnil(L); return 1; }
|
|
||||||
std::string uidStr(uid);
|
|
||||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
if (uidStr != "player") { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
const auto& inv = gh->getInventory();
|
|
||||||
const auto& slot = inv.getEquipSlot(static_cast<game::EquipSlot>(slotId - 1));
|
|
||||||
if (slot.empty()) { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
const auto* info = gh->getItemInfo(slot.item.itemId);
|
|
||||||
std::string name = info ? info->name : slot.item.name;
|
|
||||||
uint32_t q = info ? info->quality : static_cast<uint32_t>(slot.item.quality);
|
|
||||||
static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"};
|
|
||||||
uint32_t qi = q < 8 ? q : 1u;
|
|
||||||
char link[256];
|
|
||||||
snprintf(link, sizeof(link), "|cff%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r",
|
|
||||||
kQH[qi], slot.item.itemId, name.c_str());
|
|
||||||
lua_pushstring(L, link);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int lua_GetInventoryItemID(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
const char* uid = luaL_optstring(L, 1, "player");
|
|
||||||
int slotId = static_cast<int>(luaL_checknumber(L, 2));
|
|
||||||
if (!gh || slotId < 1 || slotId > 19) { lua_pushnil(L); return 1; }
|
|
||||||
std::string uidStr(uid);
|
|
||||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
if (uidStr != "player") { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
const auto& inv = gh->getInventory();
|
|
||||||
const auto& slot = inv.getEquipSlot(static_cast<game::EquipSlot>(slotId - 1));
|
|
||||||
if (slot.empty()) { lua_pushnil(L); return 1; }
|
|
||||||
lua_pushnumber(L, slot.item.itemId);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int lua_GetInventoryItemTexture(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
const char* uid = luaL_optstring(L, 1, "player");
|
|
||||||
int slotId = static_cast<int>(luaL_checknumber(L, 2));
|
|
||||||
if (!gh || slotId < 1 || slotId > 19) { lua_pushnil(L); return 1; }
|
|
||||||
std::string uidStr(uid);
|
|
||||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
if (uidStr != "player") { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
const auto& inv = gh->getInventory();
|
|
||||||
const auto& slot = inv.getEquipSlot(static_cast<game::EquipSlot>(slotId - 1));
|
|
||||||
if (slot.empty()) { lua_pushnil(L); return 1; }
|
|
||||||
// Return spell icon path for the item's on-use spell, or nil
|
|
||||||
lua_pushnil(L);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Time & XP API ---
|
|
||||||
|
|
||||||
static int lua_GetGameTime(lua_State* L) {
|
|
||||||
// Returns server game time as hours, minutes
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (gh) {
|
|
||||||
float gt = gh->getGameTime();
|
|
||||||
int hours = static_cast<int>(gt) % 24;
|
|
||||||
int mins = static_cast<int>((gt - static_cast<int>(gt)) * 60.0f);
|
|
||||||
lua_pushnumber(L, hours);
|
|
||||||
lua_pushnumber(L, mins);
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, 12);
|
|
||||||
lua_pushnumber(L, 0);
|
|
||||||
}
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int lua_GetServerTime(lua_State* L) {
|
|
||||||
lua_pushnumber(L, static_cast<double>(std::time(nullptr)));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int lua_UnitXP(lua_State* L) {
|
|
||||||
const char* uid = luaL_optstring(L, 1, "player");
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushnumber(L, 0); return 1; }
|
|
||||||
std::string u(uid);
|
|
||||||
for (char& c : u) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
if (u == "player") lua_pushnumber(L, gh->getPlayerXp());
|
|
||||||
else lua_pushnumber(L, 0);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int lua_UnitXPMax(lua_State* L) {
|
|
||||||
const char* uid = luaL_optstring(L, 1, "player");
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushnumber(L, 1); return 1; }
|
|
||||||
std::string u(uid);
|
|
||||||
for (char& c : u) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
if (u == "player") {
|
|
||||||
uint32_t nlxp = gh->getPlayerNextLevelXp();
|
|
||||||
lua_pushnumber(L, nlxp > 0 ? nlxp : 1);
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, 1);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetXPExhaustion() → rested XP pool remaining (nil if none)
|
|
||||||
static int lua_GetXPExhaustion(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushnil(L); return 1; }
|
|
||||||
uint32_t rested = gh->getPlayerRestedXp();
|
|
||||||
if (rested > 0) lua_pushnumber(L, rested);
|
|
||||||
else lua_pushnil(L);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRestState() → 1 = normal, 2 = rested
|
|
||||||
static int lua_GetRestState(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
lua_pushnumber(L, (gh && gh->isPlayerResting()) ? 2 : 1);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Quest Log API ---
|
|
||||||
|
|
||||||
static int lua_GetNumQuestLogEntries(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; }
|
|
||||||
const auto& ql = gh->getQuestLog();
|
|
||||||
lua_pushnumber(L, ql.size()); // numEntries
|
|
||||||
lua_pushnumber(L, 0); // numQuests (headers not tracked)
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQuestLogTitle(index) → title, level, suggestedGroup, isHeader, isCollapsed, isComplete, frequency, questID
|
|
||||||
static int lua_GetQuestLogTitle(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
int index = static_cast<int>(luaL_checknumber(L, 1));
|
|
||||||
if (!gh || index < 1) { lua_pushnil(L); return 1; }
|
|
||||||
const auto& ql = gh->getQuestLog();
|
|
||||||
if (index > static_cast<int>(ql.size())) { lua_pushnil(L); return 1; }
|
|
||||||
const auto& q = ql[index - 1]; // 1-based
|
|
||||||
lua_pushstring(L, q.title.c_str()); // title
|
|
||||||
lua_pushnumber(L, 0); // level (not tracked)
|
|
||||||
lua_pushnumber(L, 0); // suggestedGroup
|
|
||||||
lua_pushboolean(L, 0); // isHeader
|
|
||||||
lua_pushboolean(L, 0); // isCollapsed
|
|
||||||
lua_pushboolean(L, q.complete); // isComplete
|
|
||||||
lua_pushnumber(L, 0); // frequency
|
|
||||||
lua_pushnumber(L, q.questId); // questID
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQuestLogQuestText(index) → description, objectives
|
|
||||||
static int lua_GetQuestLogQuestText(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
int index = static_cast<int>(luaL_checknumber(L, 1));
|
|
||||||
if (!gh || index < 1) { lua_pushnil(L); return 1; }
|
|
||||||
const auto& ql = gh->getQuestLog();
|
|
||||||
if (index > static_cast<int>(ql.size())) { lua_pushnil(L); return 1; }
|
|
||||||
const auto& q = ql[index - 1];
|
|
||||||
lua_pushstring(L, ""); // description (not stored)
|
|
||||||
lua_pushstring(L, q.objectives.c_str()); // objectives
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsQuestComplete(questID) → boolean
|
|
||||||
static int lua_IsQuestComplete(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
uint32_t questId = static_cast<uint32_t>(luaL_checknumber(L, 1));
|
|
||||||
if (!gh) { lua_pushboolean(L, 0); return 1; }
|
|
||||||
for (const auto& q : gh->getQuestLog()) {
|
|
||||||
if (q.questId == questId) {
|
|
||||||
lua_pushboolean(L, q.complete);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua_pushboolean(L, 0);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Additional WoW API ---
|
// --- Additional WoW API ---
|
||||||
|
|
||||||
static int lua_UnitAffectingCombat(lua_State* L) {
|
static int lua_UnitAffectingCombat(lua_State* L) {
|
||||||
|
|
@ -1222,198 +842,6 @@ static int lua_UnitCreatureType(lua_State* L) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayerInfoByGUID(guid) → localizedClass, englishClass, localizedRace, englishRace, sex, name, realm
|
|
||||||
static int lua_GetPlayerInfoByGUID(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
const char* guidStr = luaL_checkstring(L, 1);
|
|
||||||
if (!gh || !guidStr) {
|
|
||||||
for (int i = 0; i < 7; i++) lua_pushnil(L);
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
// Parse hex GUID string "0x0000000000000001"
|
|
||||||
uint64_t guid = 0;
|
|
||||||
if (guidStr[0] == '0' && (guidStr[1] == 'x' || guidStr[1] == 'X'))
|
|
||||||
guid = strtoull(guidStr + 2, nullptr, 16);
|
|
||||||
else
|
|
||||||
guid = strtoull(guidStr, nullptr, 16);
|
|
||||||
|
|
||||||
if (guid == 0) { for (int i = 0; i < 7; i++) lua_pushnil(L); return 7; }
|
|
||||||
|
|
||||||
// Look up entity name
|
|
||||||
std::string name = gh->lookupName(guid);
|
|
||||||
if (name.empty() && guid == gh->getPlayerGuid()) {
|
|
||||||
const auto& chars = gh->getCharacters();
|
|
||||||
for (const auto& c : chars)
|
|
||||||
if (c.guid == guid) { name = c.name; break; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// For player GUID, return class/race if it's the local player
|
|
||||||
const char* className = "Unknown";
|
|
||||||
const char* raceName = "Unknown";
|
|
||||||
if (guid == gh->getPlayerGuid()) {
|
|
||||||
static const char* kClasses[] = {"","Warrior","Paladin","Hunter","Rogue","Priest",
|
|
||||||
"Death Knight","Shaman","Mage","Warlock","","Druid"};
|
|
||||||
static const char* kRaces[] = {"","Human","Orc","Dwarf","Night Elf","Undead",
|
|
||||||
"Tauren","Gnome","Troll","","Blood Elf","Draenei"};
|
|
||||||
uint8_t cid = gh->getPlayerClass();
|
|
||||||
uint8_t rid = gh->getPlayerRace();
|
|
||||||
if (cid < 12) className = kClasses[cid];
|
|
||||||
if (rid < 12) raceName = kRaces[rid];
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushstring(L, className); // 1: localizedClass
|
|
||||||
lua_pushstring(L, className); // 2: englishClass
|
|
||||||
lua_pushstring(L, raceName); // 3: localizedRace
|
|
||||||
lua_pushstring(L, raceName); // 4: englishRace
|
|
||||||
lua_pushnumber(L, 0); // 5: sex (0=unknown)
|
|
||||||
lua_pushstring(L, name.c_str()); // 6: name
|
|
||||||
lua_pushstring(L, ""); // 7: realm
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetItemLink(itemId) → "|cFFxxxxxx|Hitem:ID:...|h[Name]|h|r"
|
|
||||||
static int lua_GetItemLink(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushnil(L); return 1; }
|
|
||||||
uint32_t itemId = static_cast<uint32_t>(luaL_checknumber(L, 1));
|
|
||||||
if (itemId == 0) { lua_pushnil(L); return 1; }
|
|
||||||
const auto* info = gh->getItemInfo(itemId);
|
|
||||||
if (!info || info->name.empty()) { lua_pushnil(L); return 1; }
|
|
||||||
static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"};
|
|
||||||
uint32_t qi = info->quality < 8 ? info->quality : 1u;
|
|
||||||
char link[256];
|
|
||||||
snprintf(link, sizeof(link), "|cff%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r",
|
|
||||||
kQH[qi], itemId, info->name.c_str());
|
|
||||||
lua_pushstring(L, link);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSpellLink(spellIdOrName) → "|cFFxxxxxx|Hspell:ID|h[Name]|h|r"
|
|
||||||
static int lua_GetSpellLink(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushnil(L); return 1; }
|
|
||||||
|
|
||||||
uint32_t spellId = 0;
|
|
||||||
if (lua_isnumber(L, 1)) {
|
|
||||||
spellId = static_cast<uint32_t>(lua_tonumber(L, 1));
|
|
||||||
} else if (lua_isstring(L, 1)) {
|
|
||||||
const char* name = lua_tostring(L, 1);
|
|
||||||
if (!name || !*name) { lua_pushnil(L); return 1; }
|
|
||||||
std::string nameLow(name);
|
|
||||||
for (char& c : nameLow) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
for (uint32_t sid : gh->getKnownSpells()) {
|
|
||||||
std::string sn = gh->getSpellName(sid);
|
|
||||||
for (char& c : sn) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
if (sn == nameLow) { spellId = sid; break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (spellId == 0) { lua_pushnil(L); return 1; }
|
|
||||||
std::string name = gh->getSpellName(spellId);
|
|
||||||
if (name.empty()) { lua_pushnil(L); return 1; }
|
|
||||||
char link[256];
|
|
||||||
snprintf(link, sizeof(link), "|cff71d5ff|Hspell:%u|h[%s]|h|r", spellId, name.c_str());
|
|
||||||
lua_pushstring(L, link);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUsableSpell(spellIdOrName) → usable, noMana
|
|
||||||
static int lua_IsUsableSpell(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushboolean(L, 0); lua_pushboolean(L, 0); return 2; }
|
|
||||||
|
|
||||||
uint32_t spellId = 0;
|
|
||||||
if (lua_isnumber(L, 1)) {
|
|
||||||
spellId = static_cast<uint32_t>(lua_tonumber(L, 1));
|
|
||||||
} else if (lua_isstring(L, 1)) {
|
|
||||||
const char* name = lua_tostring(L, 1);
|
|
||||||
if (!name || !*name) { lua_pushboolean(L, 0); lua_pushboolean(L, 0); return 2; }
|
|
||||||
std::string nameLow(name);
|
|
||||||
for (char& c : nameLow) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
for (uint32_t sid : gh->getKnownSpells()) {
|
|
||||||
std::string sn = gh->getSpellName(sid);
|
|
||||||
for (char& c : sn) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
if (sn == nameLow) { spellId = sid; break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spellId == 0 || !gh->getKnownSpells().count(spellId)) {
|
|
||||||
lua_pushboolean(L, 0);
|
|
||||||
lua_pushboolean(L, 0);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if on cooldown
|
|
||||||
float cd = gh->getSpellCooldown(spellId);
|
|
||||||
bool onCooldown = (cd > 0.1f);
|
|
||||||
|
|
||||||
lua_pushboolean(L, onCooldown ? 0 : 1); // usable (not on cooldown)
|
|
||||||
lua_pushboolean(L, 0); // noMana (can't determine without spell cost data)
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInInstance() → isInstance, instanceType
|
|
||||||
static int lua_IsInInstance(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushboolean(L, 0); lua_pushstring(L, "none"); return 2; }
|
|
||||||
bool inInstance = gh->isInInstance();
|
|
||||||
lua_pushboolean(L, inInstance);
|
|
||||||
lua_pushstring(L, inInstance ? "party" : "none"); // simplified: "none", "party", "raid", "pvp", "arena"
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInstanceInfo() → name, type, difficultyIndex, difficultyName, maxPlayers, ...
|
|
||||||
static int lua_GetInstanceInfo(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) {
|
|
||||||
lua_pushstring(L, ""); lua_pushstring(L, "none"); lua_pushnumber(L, 0);
|
|
||||||
lua_pushstring(L, "Normal"); lua_pushnumber(L, 0);
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
std::string mapName = gh->getMapName(gh->getCurrentMapId());
|
|
||||||
lua_pushstring(L, mapName.c_str()); // 1: name
|
|
||||||
lua_pushstring(L, gh->isInInstance() ? "party" : "none"); // 2: instanceType
|
|
||||||
lua_pushnumber(L, gh->getInstanceDifficulty()); // 3: difficultyIndex
|
|
||||||
static const char* kDiff[] = {"Normal", "Heroic", "25 Normal", "25 Heroic"};
|
|
||||||
uint32_t diff = gh->getInstanceDifficulty();
|
|
||||||
lua_pushstring(L, (diff < 4) ? kDiff[diff] : "Normal"); // 4: difficultyName
|
|
||||||
lua_pushnumber(L, 5); // 5: maxPlayers (default 5-man)
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInstanceDifficulty() → difficulty (1=normal, 2=heroic, 3=25normal, 4=25heroic)
|
|
||||||
static int lua_GetInstanceDifficulty(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
lua_pushnumber(L, gh ? (gh->getInstanceDifficulty() + 1) : 1); // WoW returns 1-based
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnitClassification(unit) → "normal", "elite", "rareelite", "worldboss", "rare"
|
|
||||||
static int lua_UnitClassification(lua_State* L) {
|
|
||||||
auto* gh = getGameHandler(L);
|
|
||||||
if (!gh) { lua_pushstring(L, "normal"); return 1; }
|
|
||||||
const char* uid = luaL_optstring(L, 1, "target");
|
|
||||||
std::string uidStr(uid);
|
|
||||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
uint64_t guid = resolveUnitGuid(gh, uidStr);
|
|
||||||
if (guid == 0) { lua_pushstring(L, "normal"); return 1; }
|
|
||||||
auto entity = gh->getEntityManager().getEntity(guid);
|
|
||||||
if (!entity || entity->getType() == game::ObjectType::PLAYER) {
|
|
||||||
lua_pushstring(L, "normal");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
auto unit = std::dynamic_pointer_cast<game::Unit>(entity);
|
|
||||||
if (!unit) { lua_pushstring(L, "normal"); return 1; }
|
|
||||||
int rank = gh->getCreatureRank(unit->getEntry());
|
|
||||||
switch (rank) {
|
|
||||||
case 1: lua_pushstring(L, "elite"); break;
|
|
||||||
case 2: lua_pushstring(L, "rareelite"); break;
|
|
||||||
case 3: lua_pushstring(L, "worldboss"); break;
|
|
||||||
case 4: lua_pushstring(L, "rare"); break;
|
|
||||||
default: lua_pushstring(L, "normal"); break;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Frame System ---
|
// --- Frame System ---
|
||||||
// Minimal WoW-compatible frame objects with RegisterEvent/SetScript/GetScript.
|
// Minimal WoW-compatible frame objects with RegisterEvent/SetScript/GetScript.
|
||||||
// Frames are Lua tables with a metatable that provides methods.
|
// Frames are Lua tables with a metatable that provides methods.
|
||||||
|
|
@ -1747,7 +1175,6 @@ void LuaEngine::registerCoreAPI() {
|
||||||
{"InCombatLockdown", lua_InCombatLockdown},
|
{"InCombatLockdown", lua_InCombatLockdown},
|
||||||
{"UnitBuff", lua_UnitBuff},
|
{"UnitBuff", lua_UnitBuff},
|
||||||
{"UnitDebuff", lua_UnitDebuff},
|
{"UnitDebuff", lua_UnitDebuff},
|
||||||
{"UnitAura", lua_UnitAuraGeneric},
|
|
||||||
{"GetNumAddOns", lua_GetNumAddOns},
|
{"GetNumAddOns", lua_GetNumAddOns},
|
||||||
{"GetAddOnInfo", lua_GetAddOnInfo},
|
{"GetAddOnInfo", lua_GetAddOnInfo},
|
||||||
{"GetSpellInfo", lua_GetSpellInfo},
|
{"GetSpellInfo", lua_GetSpellInfo},
|
||||||
|
|
@ -1756,10 +1183,6 @@ void LuaEngine::registerCoreAPI() {
|
||||||
{"GetLocale", lua_GetLocale},
|
{"GetLocale", lua_GetLocale},
|
||||||
{"GetBuildInfo", lua_GetBuildInfo},
|
{"GetBuildInfo", lua_GetBuildInfo},
|
||||||
{"GetCurrentMapAreaID", lua_GetCurrentMapAreaID},
|
{"GetCurrentMapAreaID", lua_GetCurrentMapAreaID},
|
||||||
{"GetZoneText", lua_GetZoneText},
|
|
||||||
{"GetRealZoneText", lua_GetZoneText},
|
|
||||||
{"GetSubZoneText", lua_GetSubZoneText},
|
|
||||||
{"GetMinimapZoneText", lua_GetMinimapZoneText},
|
|
||||||
// Player state (replaces hardcoded stubs)
|
// Player state (replaces hardcoded stubs)
|
||||||
{"IsMounted", lua_IsMounted},
|
{"IsMounted", lua_IsMounted},
|
||||||
{"IsFlying", lua_IsFlying},
|
{"IsFlying", lua_IsFlying},
|
||||||
|
|
@ -1778,35 +1201,6 @@ void LuaEngine::registerCoreAPI() {
|
||||||
{"UnitIsFriend", lua_UnitIsFriend},
|
{"UnitIsFriend", lua_UnitIsFriend},
|
||||||
{"UnitIsEnemy", lua_UnitIsEnemy},
|
{"UnitIsEnemy", lua_UnitIsEnemy},
|
||||||
{"UnitCreatureType", lua_UnitCreatureType},
|
{"UnitCreatureType", lua_UnitCreatureType},
|
||||||
{"UnitClassification", lua_UnitClassification},
|
|
||||||
{"GetPlayerInfoByGUID", lua_GetPlayerInfoByGUID},
|
|
||||||
{"GetItemLink", lua_GetItemLink},
|
|
||||||
{"GetSpellLink", lua_GetSpellLink},
|
|
||||||
{"IsUsableSpell", lua_IsUsableSpell},
|
|
||||||
{"IsInInstance", lua_IsInInstance},
|
|
||||||
{"GetInstanceInfo", lua_GetInstanceInfo},
|
|
||||||
{"GetInstanceDifficulty", lua_GetInstanceDifficulty},
|
|
||||||
// Container/bag API
|
|
||||||
{"GetContainerNumSlots", lua_GetContainerNumSlots},
|
|
||||||
{"GetContainerItemInfo", lua_GetContainerItemInfo},
|
|
||||||
{"GetContainerItemLink", lua_GetContainerItemLink},
|
|
||||||
{"GetContainerNumFreeSlots", lua_GetContainerNumFreeSlots},
|
|
||||||
// Equipment slot API
|
|
||||||
{"GetInventoryItemLink", lua_GetInventoryItemLink},
|
|
||||||
{"GetInventoryItemID", lua_GetInventoryItemID},
|
|
||||||
{"GetInventoryItemTexture", lua_GetInventoryItemTexture},
|
|
||||||
// Time/XP API
|
|
||||||
{"GetGameTime", lua_GetGameTime},
|
|
||||||
{"GetServerTime", lua_GetServerTime},
|
|
||||||
{"UnitXP", lua_UnitXP},
|
|
||||||
{"UnitXPMax", lua_UnitXPMax},
|
|
||||||
{"GetXPExhaustion", lua_GetXPExhaustion},
|
|
||||||
{"GetRestState", lua_GetRestState},
|
|
||||||
// Quest log API
|
|
||||||
{"GetNumQuestLogEntries", lua_GetNumQuestLogEntries},
|
|
||||||
{"GetQuestLogTitle", lua_GetQuestLogTitle},
|
|
||||||
{"GetQuestLogQuestText", lua_GetQuestLogQuestText},
|
|
||||||
{"IsQuestComplete", lua_IsQuestComplete},
|
|
||||||
// Utilities
|
// Utilities
|
||||||
{"strsplit", lua_strsplit},
|
{"strsplit", lua_strsplit},
|
||||||
{"strtrim", lua_strtrim},
|
{"strtrim", lua_strtrim},
|
||||||
|
|
@ -1988,20 +1382,6 @@ void LuaEngine::registerCoreAPI() {
|
||||||
" SHAMAN={r=0.0,g=0.44,b=0.87}, MAGE={r=0.41,g=0.80,b=0.94},\n"
|
" SHAMAN={r=0.0,g=0.44,b=0.87}, MAGE={r=0.41,g=0.80,b=0.94},\n"
|
||||||
" WARLOCK={r=0.58,g=0.51,b=0.79}, DRUID={r=1.0,g=0.49,b=0.04},\n"
|
" WARLOCK={r=0.58,g=0.51,b=0.79}, DRUID={r=1.0,g=0.49,b=0.04},\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
// Money formatting utility
|
|
||||||
"function GetCoinTextureString(copper)\n"
|
|
||||||
" if not copper or copper == 0 then return '0c' end\n"
|
|
||||||
" copper = math.floor(copper)\n"
|
|
||||||
" local g = math.floor(copper / 10000)\n"
|
|
||||||
" local s = math.floor(math.fmod(copper, 10000) / 100)\n"
|
|
||||||
" local c = math.fmod(copper, 100)\n"
|
|
||||||
" local r = ''\n"
|
|
||||||
" if g > 0 then r = r .. g .. 'g ' end\n"
|
|
||||||
" if s > 0 then r = r .. s .. 's ' end\n"
|
|
||||||
" if c > 0 or r == '' then r = r .. c .. 'c' end\n"
|
|
||||||
" return r\n"
|
|
||||||
"end\n"
|
|
||||||
"GetCoinText = GetCoinTextureString\n"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,12 +130,6 @@ bool UiSoundManager::initialize(pipeline::AssetManager* assets) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimap ping sound
|
|
||||||
minimapPingSounds_.resize(1);
|
|
||||||
if (!loadSound("Sound\\Interface\\MapPing.wav", minimapPingSounds_[0], assets)) {
|
|
||||||
minimapPingSounds_ = selectTargetSounds_; // fallback to target select sound
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("UISoundManager: Window sounds - Bag: ", (bagOpenLoaded && bagCloseLoaded) ? "YES" : "NO",
|
LOG_INFO("UISoundManager: Window sounds - Bag: ", (bagOpenLoaded && bagCloseLoaded) ? "YES" : "NO",
|
||||||
", QuestLog: ", (questLogOpenLoaded && questLogCloseLoaded) ? "YES" : "NO",
|
", QuestLog: ", (questLogOpenLoaded && questLogCloseLoaded) ? "YES" : "NO",
|
||||||
", CharSheet: ", (charSheetOpenLoaded && charSheetCloseLoaded) ? "YES" : "NO");
|
", CharSheet: ", (charSheetOpenLoaded && charSheetCloseLoaded) ? "YES" : "NO");
|
||||||
|
|
@ -242,8 +236,5 @@ void UiSoundManager::playTargetDeselect() { playSound(deselectTargetSounds_); }
|
||||||
// Chat notifications
|
// Chat notifications
|
||||||
void UiSoundManager::playWhisperReceived() { playSound(whisperSounds_); }
|
void UiSoundManager::playWhisperReceived() { playSound(whisperSounds_); }
|
||||||
|
|
||||||
// Minimap ping
|
|
||||||
void UiSoundManager::playMinimapPing() { playSound(minimapPingSounds_); }
|
|
||||||
|
|
||||||
} // namespace audio
|
} // namespace audio
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -413,38 +413,6 @@ bool Application::initialize() {
|
||||||
return pit->second;
|
return pit->second;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Wire random property/suffix name resolver for item display
|
|
||||||
{
|
|
||||||
auto propNames = std::make_shared<std::unordered_map<int32_t, std::string>>();
|
|
||||||
auto propLoaded = std::make_shared<bool>(false);
|
|
||||||
auto* amPtr = assetManager.get();
|
|
||||||
gameHandler->setRandomPropertyNameResolver([propNames, propLoaded, amPtr](int32_t id) -> std::string {
|
|
||||||
if (!amPtr || id == 0) return {};
|
|
||||||
if (!*propLoaded) {
|
|
||||||
*propLoaded = true;
|
|
||||||
// ItemRandomProperties.dbc: ID=0, Name=4 (string)
|
|
||||||
if (auto dbc = amPtr->loadDBC("ItemRandomProperties.dbc"); dbc && dbc->isLoaded()) {
|
|
||||||
uint32_t nameField = (dbc->getFieldCount() > 4) ? 4 : 1;
|
|
||||||
for (uint32_t r = 0; r < dbc->getRecordCount(); ++r) {
|
|
||||||
int32_t rid = static_cast<int32_t>(dbc->getUInt32(r, 0));
|
|
||||||
std::string name = dbc->getString(r, nameField);
|
|
||||||
if (!name.empty() && rid > 0) (*propNames)[rid] = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ItemRandomSuffix.dbc: ID=0, Name=4 (string) — stored as negative IDs
|
|
||||||
if (auto dbc = amPtr->loadDBC("ItemRandomSuffix.dbc"); dbc && dbc->isLoaded()) {
|
|
||||||
uint32_t nameField = (dbc->getFieldCount() > 4) ? 4 : 1;
|
|
||||||
for (uint32_t r = 0; r < dbc->getRecordCount(); ++r) {
|
|
||||||
int32_t rid = static_cast<int32_t>(dbc->getUInt32(r, 0));
|
|
||||||
std::string name = dbc->getString(r, nameField);
|
|
||||||
if (!name.empty() && rid > 0) (*propNames)[-rid] = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto it = propNames->find(id);
|
|
||||||
return (it != propNames->end()) ? it->second : std::string{};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
LOG_INFO("Addon system initialized, found ", addonManager_->getAddons().size(), " addon(s)");
|
LOG_INFO("Addon system initialized, found ", addonManager_->getAddons().size(), " addon(s)");
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING("Failed to initialize addon system");
|
LOG_WARNING("Failed to initialize addon system");
|
||||||
|
|
@ -678,15 +646,6 @@ void Application::run() {
|
||||||
LOG_ERROR("GPU device lost — exiting application");
|
LOG_ERROR("GPU device lost — exiting application");
|
||||||
window->setShouldClose(true);
|
window->setShouldClose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Soft frame rate cap when vsync is off to prevent 100% CPU usage.
|
|
||||||
// Target ~240 FPS max (~4.2ms per frame); vsync handles its own pacing.
|
|
||||||
if (!window->isVsyncEnabled() && deltaTime < 0.004f) {
|
|
||||||
float sleepMs = (0.004f - deltaTime) * 1000.0f;
|
|
||||||
if (sleepMs > 0.5f)
|
|
||||||
std::this_thread::sleep_for(std::chrono::microseconds(
|
|
||||||
static_cast<int64_t>(sleepMs * 900.0f))); // 90% of target to account for sleep overshoot
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
watchdogRunning.store(false, std::memory_order_release);
|
watchdogRunning.store(false, std::memory_order_release);
|
||||||
|
|
@ -4327,15 +4286,6 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
||||||
window->swapBuffers();
|
window->swapBuffers();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set zone name on loading screen from Map.dbc
|
|
||||||
if (gameHandler) {
|
|
||||||
std::string mapDisplayName = gameHandler->getMapName(mapId);
|
|
||||||
if (!mapDisplayName.empty())
|
|
||||||
loadingScreen.setZoneName(mapDisplayName);
|
|
||||||
else
|
|
||||||
loadingScreen.setZoneName("Loading...");
|
|
||||||
}
|
|
||||||
|
|
||||||
showProgress("Entering world...", 0.0f);
|
showProgress("Entering world...", 0.0f);
|
||||||
|
|
||||||
// --- Clean up previous map's state on map change ---
|
// --- Clean up previous map's state on map change ---
|
||||||
|
|
|
||||||
|
|
@ -784,22 +784,7 @@ void GameHandler::disconnect() {
|
||||||
wardenLoadedModule_.reset();
|
wardenLoadedModule_.reset();
|
||||||
pendingIncomingPackets_.clear();
|
pendingIncomingPackets_.clear();
|
||||||
pendingUpdateObjectWork_.clear();
|
pendingUpdateObjectWork_.clear();
|
||||||
// Fire despawn callbacks so the renderer releases M2/character model resources.
|
// Clear entity state so reconnect sees fresh CREATE_OBJECT for all visible objects.
|
||||||
for (const auto& [guid, entity] : entityManager.getEntities()) {
|
|
||||||
if (guid == playerGuid) continue;
|
|
||||||
if (entity->getType() == ObjectType::UNIT && creatureDespawnCallback_)
|
|
||||||
creatureDespawnCallback_(guid);
|
|
||||||
else if (entity->getType() == ObjectType::PLAYER && playerDespawnCallback_)
|
|
||||||
playerDespawnCallback_(guid);
|
|
||||||
else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_)
|
|
||||||
gameObjectDespawnCallback_(guid);
|
|
||||||
}
|
|
||||||
otherPlayerVisibleItemEntries_.clear();
|
|
||||||
otherPlayerVisibleDirty_.clear();
|
|
||||||
otherPlayerMoveTimeMs_.clear();
|
|
||||||
unitCastStates_.clear();
|
|
||||||
unitAurasCache_.clear();
|
|
||||||
combatText.clear();
|
|
||||||
entityManager.clear();
|
entityManager.clear();
|
||||||
setState(WorldState::DISCONNECTED);
|
setState(WorldState::DISCONNECTED);
|
||||||
LOG_INFO("Disconnected from world server");
|
LOG_INFO("Disconnected from world server");
|
||||||
|
|
@ -1978,7 +1963,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
/*uint32_t itemSlot =*/ packet.readUInt32();
|
/*uint32_t itemSlot =*/ packet.readUInt32();
|
||||||
uint32_t itemId = packet.readUInt32();
|
uint32_t itemId = packet.readUInt32();
|
||||||
/*uint32_t suffixFactor =*/ packet.readUInt32();
|
/*uint32_t suffixFactor =*/ packet.readUInt32();
|
||||||
int32_t randomProp = static_cast<int32_t>(packet.readUInt32());
|
/*int32_t randomProp =*/ static_cast<int32_t>(packet.readUInt32());
|
||||||
uint32_t count = packet.readUInt32();
|
uint32_t count = packet.readUInt32();
|
||||||
/*uint32_t totalCount =*/ packet.readUInt32();
|
/*uint32_t totalCount =*/ packet.readUInt32();
|
||||||
|
|
||||||
|
|
@ -1987,11 +1972,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
if (const ItemQueryResponseData* info = getItemInfo(itemId)) {
|
if (const ItemQueryResponseData* info = getItemInfo(itemId)) {
|
||||||
// Item info already cached — emit immediately.
|
// Item info already cached — emit immediately.
|
||||||
std::string itemName = info->name.empty() ? ("item #" + std::to_string(itemId)) : info->name;
|
std::string itemName = info->name.empty() ? ("item #" + std::to_string(itemId)) : info->name;
|
||||||
// Append random suffix name (e.g., "of the Eagle") if present
|
|
||||||
if (randomProp != 0) {
|
|
||||||
std::string suffix = getRandomPropertyName(randomProp);
|
|
||||||
if (!suffix.empty()) itemName += " " + suffix;
|
|
||||||
}
|
|
||||||
uint32_t quality = info->quality;
|
uint32_t quality = info->quality;
|
||||||
std::string link = buildItemLink(itemId, quality, itemName);
|
std::string link = buildItemLink(itemId, quality, itemName);
|
||||||
std::string msg = "Received: " + link;
|
std::string msg = "Received: " + link;
|
||||||
|
|
@ -2002,9 +1982,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
sfx->playLootItem();
|
sfx->playLootItem();
|
||||||
}
|
}
|
||||||
if (itemLootCallback_) itemLootCallback_(itemId, count, quality, itemName);
|
if (itemLootCallback_) itemLootCallback_(itemId, count, quality, itemName);
|
||||||
// Fire CHAT_MSG_LOOT for loot tracking addons
|
|
||||||
if (addonEventCallback_)
|
|
||||||
addonEventCallback_("CHAT_MSG_LOOT", {msg, "", std::to_string(itemId), std::to_string(count)});
|
|
||||||
} else {
|
} else {
|
||||||
// Item info not yet cached; defer until SMSG_ITEM_QUERY_SINGLE_RESPONSE.
|
// Item info not yet cached; defer until SMSG_ITEM_QUERY_SINGLE_RESPONSE.
|
||||||
pendingItemPushNotifs_.push_back({itemId, count});
|
pendingItemPushNotifs_.push_back({itemId, count});
|
||||||
|
|
@ -2304,8 +2281,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
: ("Spell cast failed (error " + std::to_string(castResult) + ")");
|
: ("Spell cast failed (error " + std::to_string(castResult) + ")");
|
||||||
addUIError(errMsg);
|
addUIError(errMsg);
|
||||||
if (spellCastFailedCallback_) spellCastFailedCallback_(castResultSpellId);
|
if (spellCastFailedCallback_) spellCastFailedCallback_(castResultSpellId);
|
||||||
if (addonEventCallback_)
|
|
||||||
addonEventCallback_("UNIT_SPELLCAST_FAILED", {"player", std::to_string(castResultSpellId)});
|
|
||||||
MessageChatData msg;
|
MessageChatData msg;
|
||||||
msg.type = ChatType::SYSTEM;
|
msg.type = ChatType::SYSTEM;
|
||||||
msg.language = ChatLanguage::UNIVERSAL;
|
msg.language = ChatLanguage::UNIVERSAL;
|
||||||
|
|
@ -2375,10 +2350,9 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
/*uint32_t mapId =*/ packet.readUInt32();
|
/*uint32_t mapId =*/ packet.readUInt32();
|
||||||
uint32_t slot = packet.readUInt32();
|
uint32_t slot = packet.readUInt32();
|
||||||
uint32_t itemId = packet.readUInt32();
|
uint32_t itemId = packet.readUInt32();
|
||||||
int32_t rollRandProp = 0;
|
|
||||||
if (isWotLK) {
|
if (isWotLK) {
|
||||||
/*uint32_t randSuffix =*/ packet.readUInt32();
|
/*uint32_t randSuffix =*/ packet.readUInt32();
|
||||||
rollRandProp = static_cast<int32_t>(packet.readUInt32());
|
/*uint32_t randProp =*/ packet.readUInt32();
|
||||||
}
|
}
|
||||||
uint32_t countdown = packet.readUInt32();
|
uint32_t countdown = packet.readUInt32();
|
||||||
uint8_t voteMask = packet.readUInt8();
|
uint8_t voteMask = packet.readUInt8();
|
||||||
|
|
@ -2388,14 +2362,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
pendingLootRoll_.slot = slot;
|
pendingLootRoll_.slot = slot;
|
||||||
pendingLootRoll_.itemId = itemId;
|
pendingLootRoll_.itemId = itemId;
|
||||||
// Ensure item info is queried so the roll popup can show the name/icon.
|
// Ensure item info is queried so the roll popup can show the name/icon.
|
||||||
|
// The popup re-reads getItemInfo() live, so the name will populate once
|
||||||
|
// SMSG_ITEM_QUERY_SINGLE_RESPONSE arrives (usually within ~100 ms).
|
||||||
queryItemInfo(itemId, 0);
|
queryItemInfo(itemId, 0);
|
||||||
auto* info = getItemInfo(itemId);
|
auto* info = getItemInfo(itemId);
|
||||||
std::string rollItemName = info ? info->name : std::to_string(itemId);
|
pendingLootRoll_.itemName = info ? info->name : std::to_string(itemId);
|
||||||
if (rollRandProp != 0) {
|
|
||||||
std::string suffix = getRandomPropertyName(rollRandProp);
|
|
||||||
if (!suffix.empty()) rollItemName += " " + suffix;
|
|
||||||
}
|
|
||||||
pendingLootRoll_.itemName = rollItemName;
|
|
||||||
pendingLootRoll_.itemQuality = info ? static_cast<uint8_t>(info->quality) : 0;
|
pendingLootRoll_.itemQuality = info ? static_cast<uint8_t>(info->quality) : 0;
|
||||||
pendingLootRoll_.rollCountdownMs = (countdown > 0 && countdown <= 120000) ? countdown : 60000;
|
pendingLootRoll_.rollCountdownMs = (countdown > 0 && countdown <= 120000) ? countdown : 60000;
|
||||||
pendingLootRoll_.voteMask = voteMask;
|
pendingLootRoll_.voteMask = voteMask;
|
||||||
|
|
@ -2551,8 +2522,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
poi.icon = icon;
|
poi.icon = icon;
|
||||||
poi.data = data;
|
poi.data = data;
|
||||||
poi.name = std::move(name);
|
poi.name = std::move(name);
|
||||||
// Cap POI count to prevent unbounded growth from rapid gossip queries
|
|
||||||
if (gossipPois_.size() >= 200) gossipPois_.erase(gossipPois_.begin());
|
|
||||||
gossipPois_.push_back(std::move(poi));
|
gossipPois_.push_back(std::move(poi));
|
||||||
LOG_DEBUG("SMSG_GOSSIP_POI: x=", poiX, " y=", poiY, " icon=", icon);
|
LOG_DEBUG("SMSG_GOSSIP_POI: x=", poiX, " y=", poiY, " icon=", icon);
|
||||||
break;
|
break;
|
||||||
|
|
@ -3412,15 +3381,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fire UNIT_SPELLCAST_INTERRUPTED for Lua addons
|
|
||||||
if (addonEventCallback_) {
|
|
||||||
std::string unitId;
|
|
||||||
if (failGuid == playerGuid || failGuid == 0) unitId = "player";
|
|
||||||
else if (failGuid == targetGuid) unitId = "target";
|
|
||||||
else if (failGuid == focusGuid) unitId = "focus";
|
|
||||||
if (!unitId.empty())
|
|
||||||
addonEventCallback_("UNIT_SPELLCAST_INTERRUPTED", {unitId});
|
|
||||||
}
|
|
||||||
if (failGuid == playerGuid || failGuid == 0) {
|
if (failGuid == playerGuid || failGuid == 0) {
|
||||||
// Player's own cast failed — clear gather-node loot target so the
|
// Player's own cast failed — clear gather-node loot target so the
|
||||||
// next timed cast doesn't try to loot a stale interrupted gather node.
|
// next timed cast doesn't try to loot a stale interrupted gather node.
|
||||||
|
|
@ -3558,13 +3518,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
ping.wowY = pingX; // canonical WoW Y = west = server's posX
|
ping.wowY = pingX; // canonical WoW Y = west = server's posX
|
||||||
ping.age = 0.0f;
|
ping.age = 0.0f;
|
||||||
minimapPings_.push_back(ping);
|
minimapPings_.push_back(ping);
|
||||||
// Play ping sound for other players' pings (not our own)
|
|
||||||
if (senderGuid != playerGuid) {
|
|
||||||
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
|
||||||
sfx->playMinimapPing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Opcode::SMSG_ZONE_UNDER_ATTACK: {
|
case Opcode::SMSG_ZONE_UNDER_ATTACK: {
|
||||||
|
|
@ -4192,8 +4145,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
addSystemChatMessage(buf);
|
addSystemChatMessage(buf);
|
||||||
watchedFactionId_ = factionId;
|
watchedFactionId_ = factionId;
|
||||||
if (repChangeCallback_) repChangeCallback_(name, delta, standing);
|
if (repChangeCallback_) repChangeCallback_(name, delta, standing);
|
||||||
if (addonEventCallback_)
|
|
||||||
addonEventCallback_("UPDATE_FACTION", {});
|
|
||||||
}
|
}
|
||||||
LOG_DEBUG("SMSG_SET_FACTION_STANDING: faction=", factionId, " standing=", standing);
|
LOG_DEBUG("SMSG_SET_FACTION_STANDING: faction=", factionId, " standing=", standing);
|
||||||
}
|
}
|
||||||
|
|
@ -4592,7 +4543,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_INFO("SMSG_ACTION_BUTTONS: populated action bar from server");
|
LOG_INFO("SMSG_ACTION_BUTTONS: populated action bar from server");
|
||||||
if (addonEventCallback_) addonEventCallback_("ACTIONBAR_SLOT_CHANGED", {});
|
|
||||||
packet.setReadPos(packet.getSize());
|
packet.setReadPos(packet.getSize());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -5030,14 +4980,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
uint64_t progress = packet.readUInt64();
|
uint64_t progress = packet.readUInt64();
|
||||||
packet.readUInt32(); // elapsedTime
|
packet.readUInt32(); // elapsedTime
|
||||||
packet.readUInt32(); // creationTime
|
packet.readUInt32(); // creationTime
|
||||||
uint64_t oldProgress = 0;
|
|
||||||
auto cpit = criteriaProgress_.find(criteriaId);
|
|
||||||
if (cpit != criteriaProgress_.end()) oldProgress = cpit->second;
|
|
||||||
criteriaProgress_[criteriaId] = progress;
|
criteriaProgress_[criteriaId] = progress;
|
||||||
LOG_DEBUG("SMSG_CRITERIA_UPDATE: id=", criteriaId, " progress=", progress);
|
LOG_DEBUG("SMSG_CRITERIA_UPDATE: id=", criteriaId, " progress=", progress);
|
||||||
// Fire addon event for achievement tracking addons
|
|
||||||
if (addonEventCallback_ && progress != oldProgress)
|
|
||||||
addonEventCallback_("CRITERIA_UPDATE", {std::to_string(criteriaId), std::to_string(progress)});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -5121,27 +5065,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
case Opcode::SMSG_ENCHANTMENTLOG: {
|
case Opcode::SMSG_ENCHANTMENTLOG: {
|
||||||
// uint64 targetGuid + uint64 casterGuid + uint32 spellId + uint32 displayId + uint32 animType
|
// uint64 targetGuid + uint64 casterGuid + uint32 spellId + uint32 displayId + uint32 animType
|
||||||
if (packet.getSize() - packet.getReadPos() >= 28) {
|
if (packet.getSize() - packet.getReadPos() >= 28) {
|
||||||
uint64_t enchTargetGuid = packet.readUInt64();
|
/*uint64_t targetGuid =*/ packet.readUInt64();
|
||||||
uint64_t enchCasterGuid = packet.readUInt64();
|
/*uint64_t casterGuid =*/ packet.readUInt64();
|
||||||
uint32_t enchSpellId = packet.readUInt32();
|
uint32_t spellId = packet.readUInt32();
|
||||||
/*uint32_t displayId =*/ packet.readUInt32();
|
/*uint32_t displayId =*/ packet.readUInt32();
|
||||||
/*uint32_t animType =*/ packet.readUInt32();
|
/*uint32_t animType =*/ packet.readUInt32();
|
||||||
LOG_DEBUG("SMSG_ENCHANTMENTLOG: spellId=", enchSpellId);
|
LOG_DEBUG("SMSG_ENCHANTMENTLOG: spellId=", spellId);
|
||||||
// Show enchant message if the player is involved
|
|
||||||
if (enchTargetGuid == playerGuid || enchCasterGuid == playerGuid) {
|
|
||||||
const std::string& enchName = getSpellName(enchSpellId);
|
|
||||||
std::string casterName = lookupName(enchCasterGuid);
|
|
||||||
if (!enchName.empty()) {
|
|
||||||
std::string msg;
|
|
||||||
if (enchCasterGuid == playerGuid)
|
|
||||||
msg = "You enchant with " + enchName + ".";
|
|
||||||
else if (!casterName.empty())
|
|
||||||
msg = casterName + " enchants your item with " + enchName + ".";
|
|
||||||
else
|
|
||||||
msg = "Your item has been enchanted with " + enchName + ".";
|
|
||||||
addSystemChatMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -5334,7 +5263,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (addonEventCallback_) addonEventCallback_("QUEST_LOG_UPDATE", {});
|
|
||||||
// Re-query all nearby quest giver NPCs so markers refresh
|
// Re-query all nearby quest giver NPCs so markers refresh
|
||||||
if (socket) {
|
if (socket) {
|
||||||
for (const auto& [guid, entity] : entityManager.getEntities()) {
|
for (const auto& [guid, entity] : entityManager.getEntities()) {
|
||||||
|
|
@ -6209,23 +6137,16 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
handleAuctionCommandResult(packet);
|
handleAuctionCommandResult(packet);
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_AUCTION_OWNER_NOTIFICATION: {
|
case Opcode::SMSG_AUCTION_OWNER_NOTIFICATION: {
|
||||||
// auctionId(u32) + action(u32) + error(u32) + itemEntry(u32) + randomPropertyId(u32) + ...
|
// auctionId(u32) + action(u32) + error(u32) + itemEntry(u32) + ...
|
||||||
// action: 0=sold/won, 1=expired, 2=bid placed on your auction
|
// action: 0=sold/won, 1=expired, 2=bid placed on your auction
|
||||||
if (packet.getSize() - packet.getReadPos() >= 16) {
|
if (packet.getSize() - packet.getReadPos() >= 16) {
|
||||||
/*uint32_t auctionId =*/ packet.readUInt32();
|
/*uint32_t auctionId =*/ packet.readUInt32();
|
||||||
uint32_t action = packet.readUInt32();
|
uint32_t action = packet.readUInt32();
|
||||||
/*uint32_t error =*/ packet.readUInt32();
|
/*uint32_t error =*/ packet.readUInt32();
|
||||||
uint32_t itemEntry = packet.readUInt32();
|
uint32_t itemEntry = packet.readUInt32();
|
||||||
int32_t ownerRandProp = 0;
|
|
||||||
if (packet.getSize() - packet.getReadPos() >= 4)
|
|
||||||
ownerRandProp = static_cast<int32_t>(packet.readUInt32());
|
|
||||||
ensureItemInfo(itemEntry);
|
ensureItemInfo(itemEntry);
|
||||||
auto* info = getItemInfo(itemEntry);
|
auto* info = getItemInfo(itemEntry);
|
||||||
std::string rawName = info && !info->name.empty() ? info->name : ("Item #" + std::to_string(itemEntry));
|
std::string rawName = info && !info->name.empty() ? info->name : ("Item #" + std::to_string(itemEntry));
|
||||||
if (ownerRandProp != 0) {
|
|
||||||
std::string suffix = getRandomPropertyName(ownerRandProp);
|
|
||||||
if (!suffix.empty()) rawName += " " + suffix;
|
|
||||||
}
|
|
||||||
uint32_t aucQuality = info ? info->quality : 1u;
|
uint32_t aucQuality = info ? info->quality : 1u;
|
||||||
std::string itemLink = buildItemLink(itemEntry, aucQuality, rawName);
|
std::string itemLink = buildItemLink(itemEntry, aucQuality, rawName);
|
||||||
if (action == 1)
|
if (action == 1)
|
||||||
|
|
@ -6239,21 +6160,14 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Opcode::SMSG_AUCTION_BIDDER_NOTIFICATION: {
|
case Opcode::SMSG_AUCTION_BIDDER_NOTIFICATION: {
|
||||||
// auctionHouseId(u32) + auctionId(u32) + bidderGuid(u64) + bidAmount(u32) + outbidAmount(u32) + itemEntry(u32) + randomPropertyId(u32)
|
// auctionId(u32) + itemEntry(u32) + ...
|
||||||
if (packet.getSize() - packet.getReadPos() >= 8) {
|
if (packet.getSize() - packet.getReadPos() >= 8) {
|
||||||
/*uint32_t auctionId =*/ packet.readUInt32();
|
uint32_t auctionId = packet.readUInt32();
|
||||||
uint32_t itemEntry = packet.readUInt32();
|
uint32_t itemEntry = packet.readUInt32();
|
||||||
int32_t bidRandProp = 0;
|
(void)auctionId;
|
||||||
// Try to read randomPropertyId if enough data remains
|
|
||||||
if (packet.getSize() - packet.getReadPos() >= 4)
|
|
||||||
bidRandProp = static_cast<int32_t>(packet.readUInt32());
|
|
||||||
ensureItemInfo(itemEntry);
|
ensureItemInfo(itemEntry);
|
||||||
auto* info = getItemInfo(itemEntry);
|
auto* info = getItemInfo(itemEntry);
|
||||||
std::string rawName2 = info && !info->name.empty() ? info->name : ("Item #" + std::to_string(itemEntry));
|
std::string rawName2 = info && !info->name.empty() ? info->name : ("Item #" + std::to_string(itemEntry));
|
||||||
if (bidRandProp != 0) {
|
|
||||||
std::string suffix = getRandomPropertyName(bidRandProp);
|
|
||||||
if (!suffix.empty()) rawName2 += " " + suffix;
|
|
||||||
}
|
|
||||||
uint32_t bidQuality = info ? info->quality : 1u;
|
uint32_t bidQuality = info ? info->quality : 1u;
|
||||||
std::string bidLink = buildItemLink(itemEntry, bidQuality, rawName2);
|
std::string bidLink = buildItemLink(itemEntry, bidQuality, rawName2);
|
||||||
addSystemChatMessage("You have been outbid on " + bidLink + ".");
|
addSystemChatMessage("You have been outbid on " + bidLink + ".");
|
||||||
|
|
@ -6266,14 +6180,10 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
if (packet.getSize() - packet.getReadPos() >= 12) {
|
if (packet.getSize() - packet.getReadPos() >= 12) {
|
||||||
/*uint32_t auctionId =*/ packet.readUInt32();
|
/*uint32_t auctionId =*/ packet.readUInt32();
|
||||||
uint32_t itemEntry = packet.readUInt32();
|
uint32_t itemEntry = packet.readUInt32();
|
||||||
int32_t itemRandom = static_cast<int32_t>(packet.readUInt32());
|
/*uint32_t itemRandom =*/ packet.readUInt32();
|
||||||
ensureItemInfo(itemEntry);
|
ensureItemInfo(itemEntry);
|
||||||
auto* info = getItemInfo(itemEntry);
|
auto* info = getItemInfo(itemEntry);
|
||||||
std::string rawName3 = info && !info->name.empty() ? info->name : ("Item #" + std::to_string(itemEntry));
|
std::string rawName3 = info && !info->name.empty() ? info->name : ("Item #" + std::to_string(itemEntry));
|
||||||
if (itemRandom != 0) {
|
|
||||||
std::string suffix = getRandomPropertyName(itemRandom);
|
|
||||||
if (!suffix.empty()) rawName3 += " " + suffix;
|
|
||||||
}
|
|
||||||
uint32_t remQuality = info ? info->quality : 1u;
|
uint32_t remQuality = info ? info->quality : 1u;
|
||||||
std::string remLink = buildItemLink(itemEntry, remQuality, rawName3);
|
std::string remLink = buildItemLink(itemEntry, remQuality, rawName3);
|
||||||
addSystemChatMessage("Your auction of " + remLink + " has expired.");
|
addSystemChatMessage("Your auction of " + remLink + " has expired.");
|
||||||
|
|
@ -7392,15 +7302,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
LOG_DEBUG("MSG_CHANNEL_START: caster=0x", std::hex, chanCaster, std::dec,
|
LOG_DEBUG("MSG_CHANNEL_START: caster=0x", std::hex, chanCaster, std::dec,
|
||||||
" spell=", chanSpellId, " total=", chanTotalMs, "ms");
|
" spell=", chanSpellId, " total=", chanTotalMs, "ms");
|
||||||
// Fire UNIT_SPELLCAST_CHANNEL_START for Lua addons
|
|
||||||
if (addonEventCallback_) {
|
|
||||||
std::string unitId;
|
|
||||||
if (chanCaster == playerGuid) unitId = "player";
|
|
||||||
else if (chanCaster == targetGuid) unitId = "target";
|
|
||||||
else if (chanCaster == focusGuid) unitId = "focus";
|
|
||||||
if (!unitId.empty())
|
|
||||||
addonEventCallback_("UNIT_SPELLCAST_CHANNEL_START", {unitId, std::to_string(chanSpellId)});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -7428,15 +7329,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
LOG_DEBUG("MSG_CHANNEL_UPDATE: caster=0x", std::hex, chanCaster2, std::dec,
|
LOG_DEBUG("MSG_CHANNEL_UPDATE: caster=0x", std::hex, chanCaster2, std::dec,
|
||||||
" remaining=", chanRemainMs, "ms");
|
" remaining=", chanRemainMs, "ms");
|
||||||
// Fire UNIT_SPELLCAST_CHANNEL_STOP when channel ends
|
|
||||||
if (chanRemainMs == 0 && addonEventCallback_) {
|
|
||||||
std::string unitId;
|
|
||||||
if (chanCaster2 == playerGuid) unitId = "player";
|
|
||||||
else if (chanCaster2 == targetGuid) unitId = "target";
|
|
||||||
else if (chanCaster2 == focusGuid) unitId = "focus";
|
|
||||||
if (!unitId.empty())
|
|
||||||
addonEventCallback_("UNIT_SPELLCAST_CHANNEL_STOP", {unitId});
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12682,8 +12574,6 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
||||||
if (addonEventCallback_) addonEventCallback_("PLAYER_ALIVE", {});
|
if (addonEventCallback_) addonEventCallback_("PLAYER_ALIVE", {});
|
||||||
if (ghostStateCallback_) ghostStateCallback_(false);
|
if (ghostStateCallback_) ghostStateCallback_(false);
|
||||||
}
|
}
|
||||||
if (addonEventCallback_)
|
|
||||||
addonEventCallback_("PLAYER_FLAGS_CHANGED", {});
|
|
||||||
}
|
}
|
||||||
else if (ufMeleeAPV != 0xFFFF && key == ufMeleeAPV) { playerMeleeAP_ = static_cast<int32_t>(val); }
|
else if (ufMeleeAPV != 0xFFFF && key == ufMeleeAPV) { playerMeleeAP_ = static_cast<int32_t>(val); }
|
||||||
else if (ufRangedAPV != 0xFFFF && key == ufRangedAPV) { playerRangedAP_ = static_cast<int32_t>(val); }
|
else if (ufRangedAPV != 0xFFFF && key == ufRangedAPV) { playerRangedAP_ = static_cast<int32_t>(val); }
|
||||||
|
|
@ -12714,11 +12604,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
||||||
// Do not auto-create quests from VALUES quest-log slot fields for the
|
// Do not auto-create quests from VALUES quest-log slot fields for the
|
||||||
// same reason as CREATE_OBJECT2 above (can be misaligned per realm).
|
// same reason as CREATE_OBJECT2 above (can be misaligned per realm).
|
||||||
if (applyInventoryFields(block.fields)) slotsChanged = true;
|
if (applyInventoryFields(block.fields)) slotsChanged = true;
|
||||||
if (slotsChanged) {
|
if (slotsChanged) rebuildOnlineInventory();
|
||||||
rebuildOnlineInventory();
|
|
||||||
if (addonEventCallback_)
|
|
||||||
addonEventCallback_("PLAYER_EQUIPMENT_CHANGED", {});
|
|
||||||
}
|
|
||||||
extractSkillFields(lastPlayerFields_);
|
extractSkillFields(lastPlayerFields_);
|
||||||
extractExploredZoneFields(lastPlayerFields_);
|
extractExploredZoneFields(lastPlayerFields_);
|
||||||
applyQuestStateFromFields(lastPlayerFields_);
|
applyQuestStateFromFields(lastPlayerFields_);
|
||||||
|
|
@ -12823,10 +12709,6 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
||||||
}
|
}
|
||||||
if (inventoryChanged) {
|
if (inventoryChanged) {
|
||||||
rebuildOnlineInventory();
|
rebuildOnlineInventory();
|
||||||
if (addonEventCallback_) {
|
|
||||||
addonEventCallback_("BAG_UPDATE", {});
|
|
||||||
addonEventCallback_("UNIT_INVENTORY_CHANGED", {"player"});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (block.hasMovement && entity->getType() == ObjectType::GAMEOBJECT) {
|
if (block.hasMovement && entity->getType() == ObjectType::GAMEOBJECT) {
|
||||||
|
|
@ -13084,21 +12966,8 @@ void GameHandler::handleDestroyObject(network::Packet& packet) {
|
||||||
// Clean up quest giver status
|
// Clean up quest giver status
|
||||||
npcQuestStatus_.erase(data.guid);
|
npcQuestStatus_.erase(data.guid);
|
||||||
|
|
||||||
// Remove combat text entries referencing the destroyed entity so floating
|
|
||||||
// damage numbers don't linger after the source/target despawns.
|
|
||||||
combatText.erase(
|
|
||||||
std::remove_if(combatText.begin(), combatText.end(),
|
|
||||||
[&data](const CombatTextEntry& e) {
|
|
||||||
return e.dstGuid == data.guid;
|
|
||||||
}),
|
|
||||||
combatText.end());
|
|
||||||
|
|
||||||
// Clean up unit cast state (cast bar) for the destroyed unit
|
|
||||||
unitCastStates_.erase(data.guid);
|
|
||||||
// Clean up cached auras
|
|
||||||
unitAurasCache_.erase(data.guid);
|
|
||||||
|
|
||||||
tabCycleStale = true;
|
tabCycleStale = true;
|
||||||
|
// Entity count logging disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::sendChatMessage(ChatType type, const std::string& message, const std::string& target) {
|
void GameHandler::sendChatMessage(ChatType type, const std::string& message, const std::string& target) {
|
||||||
|
|
@ -17274,17 +17143,6 @@ void GameHandler::lfgLeave() {
|
||||||
LOG_INFO("Sent CMSG_LFG_LEAVE");
|
LOG_INFO("Sent CMSG_LFG_LEAVE");
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::lfgSetRoles(uint8_t roles) {
|
|
||||||
if (state != WorldState::IN_WORLD || !socket) return;
|
|
||||||
const uint32_t wire = wireOpcode(Opcode::CMSG_LFG_SET_ROLES);
|
|
||||||
if (wire == 0xFFFF) return;
|
|
||||||
|
|
||||||
network::Packet pkt(static_cast<uint16_t>(wire));
|
|
||||||
pkt.writeUInt8(roles);
|
|
||||||
socket->send(pkt);
|
|
||||||
LOG_INFO("Sent CMSG_LFG_SET_ROLES: roles=", static_cast<int>(roles));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameHandler::lfgAcceptProposal(uint32_t proposalId, bool accept) {
|
void GameHandler::lfgAcceptProposal(uint32_t proposalId, bool accept) {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
|
|
||||||
|
|
@ -19576,10 +19434,7 @@ void GameHandler::handleSupercededSpell(network::Packet& packet) {
|
||||||
LOG_DEBUG("Action bar slot upgraded: spell ", oldSpellId, " -> ", newSpellId);
|
LOG_DEBUG("Action bar slot upgraded: spell ", oldSpellId, " -> ", newSpellId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (barChanged) {
|
if (barChanged) saveCharacterConfig();
|
||||||
saveCharacterConfig();
|
|
||||||
if (addonEventCallback_) addonEventCallback_("ACTIONBAR_SLOT_CHANGED", {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show "Upgraded to X" only when the new spell wasn't already announced by the
|
// Show "Upgraded to X" only when the new spell wasn't already announced by the
|
||||||
// trainer-buy handler. For non-trainer supersedes (e.g. quest rewards), the new
|
// trainer-buy handler. For non-trainer supersedes (e.g. quest rewards), the new
|
||||||
|
|
@ -19846,7 +19701,6 @@ void GameHandler::handleGroupList(network::Packet& packet) {
|
||||||
const bool hasRoles = isActiveExpansion("wotlk");
|
const bool hasRoles = isActiveExpansion("wotlk");
|
||||||
// Snapshot state before reset so we can detect transitions.
|
// Snapshot state before reset so we can detect transitions.
|
||||||
const uint32_t prevCount = partyData.memberCount;
|
const uint32_t prevCount = partyData.memberCount;
|
||||||
const uint8_t prevLootMethod = partyData.lootMethod;
|
|
||||||
const bool wasInGroup = !partyData.isEmpty();
|
const bool wasInGroup = !partyData.isEmpty();
|
||||||
// Reset before parsing — SMSG_GROUP_LIST is a full replacement, not a delta.
|
// Reset before parsing — SMSG_GROUP_LIST is a full replacement, not a delta.
|
||||||
// Without this, repeated GROUP_LIST packets push duplicate members.
|
// Without this, repeated GROUP_LIST packets push duplicate members.
|
||||||
|
|
@ -19863,14 +19717,6 @@ void GameHandler::handleGroupList(network::Packet& packet) {
|
||||||
} else if (nowInGroup && partyData.memberCount != prevCount) {
|
} else if (nowInGroup && partyData.memberCount != prevCount) {
|
||||||
LOG_INFO("Group updated: ", partyData.memberCount, " members");
|
LOG_INFO("Group updated: ", partyData.memberCount, " members");
|
||||||
}
|
}
|
||||||
// Loot method change notification
|
|
||||||
if (wasInGroup && nowInGroup && partyData.lootMethod != prevLootMethod) {
|
|
||||||
static const char* kLootMethods[] = {
|
|
||||||
"Free for All", "Round Robin", "Master Looter", "Group Loot", "Need Before Greed"
|
|
||||||
};
|
|
||||||
const char* methodName = (partyData.lootMethod < 5) ? kLootMethods[partyData.lootMethod] : "Unknown";
|
|
||||||
addSystemChatMessage(std::string("Loot method changed to ") + methodName + ".");
|
|
||||||
}
|
|
||||||
// Fire GROUP_ROSTER_UPDATE / PARTY_MEMBERS_CHANGED for Lua addons
|
// Fire GROUP_ROSTER_UPDATE / PARTY_MEMBERS_CHANGED for Lua addons
|
||||||
if (addonEventCallback_) {
|
if (addonEventCallback_) {
|
||||||
addonEventCallback_("GROUP_ROSTER_UPDATE", {});
|
addonEventCallback_("GROUP_ROSTER_UPDATE", {});
|
||||||
|
|
@ -21069,7 +20915,6 @@ void GameHandler::handleQuestPoiQueryResponse(network::Packet& packet) {
|
||||||
poi.name = questTitle.empty() ? "Quest objective" : questTitle;
|
poi.name = questTitle.empty() ? "Quest objective" : questTitle;
|
||||||
LOG_DEBUG("Quest POI: questId=", questId, " mapId=", mapId,
|
LOG_DEBUG("Quest POI: questId=", questId, " mapId=", mapId,
|
||||||
" centroid=(", poi.x, ",", poi.y, ") title=", poi.name);
|
" centroid=(", poi.x, ",", poi.y, ") title=", poi.name);
|
||||||
if (gossipPois_.size() >= 200) gossipPois_.erase(gossipPois_.begin());
|
|
||||||
gossipPois_.push_back(std::move(poi));
|
gossipPois_.push_back(std::move(poi));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -21131,10 +20976,6 @@ void GameHandler::addQuestToLocalLogIfMissing(uint32_t questId, const std::strin
|
||||||
entry.title = title.empty() ? ("Quest #" + std::to_string(questId)) : title;
|
entry.title = title.empty() ? ("Quest #" + std::to_string(questId)) : title;
|
||||||
entry.objectives = objectives;
|
entry.objectives = objectives;
|
||||||
questLog_.push_back(std::move(entry));
|
questLog_.push_back(std::move(entry));
|
||||||
if (addonEventCallback_) {
|
|
||||||
addonEventCallback_("QUEST_ACCEPTED", {std::to_string(questId)});
|
|
||||||
addonEventCallback_("QUEST_LOG_UPDATE", {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameHandler::resyncQuestLogFromServerSlots(bool forceQueryMetadata) {
|
bool GameHandler::resyncQuestLogFromServerSlots(bool forceQueryMetadata) {
|
||||||
|
|
@ -21643,7 +21484,6 @@ void GameHandler::openVendor(uint64_t npcGuid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::closeVendor() {
|
void GameHandler::closeVendor() {
|
||||||
bool wasOpen = vendorWindowOpen;
|
|
||||||
vendorWindowOpen = false;
|
vendorWindowOpen = false;
|
||||||
currentVendorItems = ListInventoryData{};
|
currentVendorItems = ListInventoryData{};
|
||||||
buybackItems_.clear();
|
buybackItems_.clear();
|
||||||
|
|
@ -21652,7 +21492,6 @@ void GameHandler::closeVendor() {
|
||||||
pendingBuybackWireSlot_ = 0;
|
pendingBuybackWireSlot_ = 0;
|
||||||
pendingBuyItemId_ = 0;
|
pendingBuyItemId_ = 0;
|
||||||
pendingBuyItemSlot_ = 0;
|
pendingBuyItemSlot_ = 0;
|
||||||
if (wasOpen && addonEventCallback_) addonEventCallback_("MERCHANT_CLOSED", {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::buyItem(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count) {
|
void GameHandler::buyItem(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count) {
|
||||||
|
|
@ -22374,7 +22213,6 @@ void GameHandler::handleListInventory(network::Packet& packet) {
|
||||||
currentVendorItems.canRepair = savedCanRepair;
|
currentVendorItems.canRepair = savedCanRepair;
|
||||||
vendorWindowOpen = true;
|
vendorWindowOpen = true;
|
||||||
gossipWindowOpen = false; // Close gossip if vendor opens
|
gossipWindowOpen = false; // Close gossip if vendor opens
|
||||||
if (addonEventCallback_) addonEventCallback_("MERCHANT_SHOW", {});
|
|
||||||
|
|
||||||
// Auto-sell grey items if enabled
|
// Auto-sell grey items if enabled
|
||||||
if (autoSellGrey_ && currentVendorItems.vendorGuid != 0) {
|
if (autoSellGrey_ && currentVendorItems.vendorGuid != 0) {
|
||||||
|
|
@ -22917,24 +22755,11 @@ void GameHandler::handleXpGain(network::Packet& packet) {
|
||||||
// but we can show combat text for XP gains
|
// but we can show combat text for XP gains
|
||||||
addCombatText(CombatTextEntry::XP_GAIN, static_cast<int32_t>(data.totalXp), 0, true);
|
addCombatText(CombatTextEntry::XP_GAIN, static_cast<int32_t>(data.totalXp), 0, true);
|
||||||
|
|
||||||
// Build XP message with source creature name when available
|
std::string msg = "You gain " + std::to_string(data.totalXp) + " experience.";
|
||||||
std::string msg;
|
|
||||||
if (data.victimGuid != 0 && data.type == 0) {
|
|
||||||
// Kill XP — resolve creature name
|
|
||||||
std::string victimName = lookupName(data.victimGuid);
|
|
||||||
if (!victimName.empty())
|
|
||||||
msg = victimName + " dies, you gain " + std::to_string(data.totalXp) + " experience.";
|
|
||||||
else
|
|
||||||
msg = "You gain " + std::to_string(data.totalXp) + " experience.";
|
|
||||||
} else {
|
|
||||||
msg = "You gain " + std::to_string(data.totalXp) + " experience.";
|
|
||||||
}
|
|
||||||
if (data.groupBonus > 0) {
|
if (data.groupBonus > 0) {
|
||||||
msg += " (+" + std::to_string(data.groupBonus) + " group bonus)";
|
msg += " (+" + std::to_string(data.groupBonus) + " group bonus)";
|
||||||
}
|
}
|
||||||
addSystemChatMessage(msg);
|
addSystemChatMessage(msg);
|
||||||
if (addonEventCallback_)
|
|
||||||
addonEventCallback_("CHAT_MSG_COMBAT_XP_GAIN", {msg, std::to_string(data.totalXp)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22949,8 +22774,6 @@ void GameHandler::addMoneyCopper(uint32_t amount) {
|
||||||
msg += std::to_string(silver) + "s ";
|
msg += std::to_string(silver) + "s ";
|
||||||
msg += std::to_string(copper) + "c.";
|
msg += std::to_string(copper) + "c.";
|
||||||
addSystemChatMessage(msg);
|
addSystemChatMessage(msg);
|
||||||
if (addonEventCallback_)
|
|
||||||
addonEventCallback_("CHAT_MSG_MONEY", {msg});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::addSystemChatMessage(const std::string& message) {
|
void GameHandler::addSystemChatMessage(const std::string& message) {
|
||||||
|
|
@ -23131,24 +22954,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
|
||||||
mountCallback_(0);
|
mountCallback_(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoke despawn callbacks for all entities before clearing, so the renderer
|
// Clear world state for the new map
|
||||||
// can release M2 instances, character models, and associated resources.
|
|
||||||
for (const auto& [guid, entity] : entityManager.getEntities()) {
|
|
||||||
if (guid == playerGuid) continue; // skip self
|
|
||||||
if (entity->getType() == ObjectType::UNIT && creatureDespawnCallback_) {
|
|
||||||
creatureDespawnCallback_(guid);
|
|
||||||
} else if (entity->getType() == ObjectType::PLAYER && playerDespawnCallback_) {
|
|
||||||
playerDespawnCallback_(guid);
|
|
||||||
} else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) {
|
|
||||||
gameObjectDespawnCallback_(guid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
otherPlayerVisibleItemEntries_.clear();
|
|
||||||
otherPlayerVisibleDirty_.clear();
|
|
||||||
otherPlayerMoveTimeMs_.clear();
|
|
||||||
unitCastStates_.clear();
|
|
||||||
unitAurasCache_.clear();
|
|
||||||
combatText.clear();
|
|
||||||
entityManager.clear();
|
entityManager.clear();
|
||||||
hostileAttackers_.clear();
|
hostileAttackers_.clear();
|
||||||
worldStates_.clear();
|
worldStates_.clear();
|
||||||
|
|
@ -24449,19 +24255,7 @@ void GameHandler::extractSkillFields(const std::map<uint16_t, uint32_t>& fields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skillsChanged = (newSkills.size() != playerSkills_.size());
|
|
||||||
if (!skillsChanged) {
|
|
||||||
for (const auto& [id, sk] : newSkills) {
|
|
||||||
auto it = playerSkills_.find(id);
|
|
||||||
if (it == playerSkills_.end() || it->second.value != sk.value) {
|
|
||||||
skillsChanged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
playerSkills_ = std::move(newSkills);
|
playerSkills_ = std::move(newSkills);
|
||||||
if (skillsChanged && addonEventCallback_)
|
|
||||||
addonEventCallback_("SKILL_LINES_CHANGED", {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::extractExploredZoneFields(const std::map<uint16_t, uint32_t>& fields) {
|
void GameHandler::extractExploredZoneFields(const std::map<uint16_t, uint32_t>& fields) {
|
||||||
|
|
@ -24793,13 +24587,11 @@ void GameHandler::updateAttachedTransportChildren(float /*deltaTime*/) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void GameHandler::closeMailbox() {
|
void GameHandler::closeMailbox() {
|
||||||
bool wasOpen = mailboxOpen_;
|
|
||||||
mailboxOpen_ = false;
|
mailboxOpen_ = false;
|
||||||
mailboxGuid_ = 0;
|
mailboxGuid_ = 0;
|
||||||
mailInbox_.clear();
|
mailInbox_.clear();
|
||||||
selectedMailIndex_ = -1;
|
selectedMailIndex_ = -1;
|
||||||
showMailCompose_ = false;
|
showMailCompose_ = false;
|
||||||
if (wasOpen && addonEventCallback_) addonEventCallback_("MAIL_CLOSED", {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::refreshMailList() {
|
void GameHandler::refreshMailList() {
|
||||||
|
|
@ -24976,7 +24768,6 @@ void GameHandler::handleShowMailbox(network::Packet& packet) {
|
||||||
hasNewMail_ = false;
|
hasNewMail_ = false;
|
||||||
selectedMailIndex_ = -1;
|
selectedMailIndex_ = -1;
|
||||||
showMailCompose_ = false;
|
showMailCompose_ = false;
|
||||||
if (addonEventCallback_) addonEventCallback_("MAIL_SHOW", {});
|
|
||||||
// Request inbox contents
|
// Request inbox contents
|
||||||
refreshMailList();
|
refreshMailList();
|
||||||
}
|
}
|
||||||
|
|
@ -25135,10 +24926,8 @@ void GameHandler::openBank(uint64_t guid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::closeBank() {
|
void GameHandler::closeBank() {
|
||||||
bool wasOpen = bankOpen_;
|
|
||||||
bankOpen_ = false;
|
bankOpen_ = false;
|
||||||
bankerGuid_ = 0;
|
bankerGuid_ = 0;
|
||||||
if (wasOpen && addonEventCallback_) addonEventCallback_("BANKFRAME_CLOSED", {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::buyBankSlot() {
|
void GameHandler::buyBankSlot() {
|
||||||
|
|
@ -25169,7 +24958,6 @@ void GameHandler::handleShowBank(network::Packet& packet) {
|
||||||
bankerGuid_ = packet.readUInt64();
|
bankerGuid_ = packet.readUInt64();
|
||||||
bankOpen_ = true;
|
bankOpen_ = true;
|
||||||
gossipWindowOpen = false; // Close gossip when bank opens
|
gossipWindowOpen = false; // Close gossip when bank opens
|
||||||
if (addonEventCallback_) addonEventCallback_("BANKFRAME_OPENED", {});
|
|
||||||
// Bank items are already tracked via update fields (bank slot GUIDs)
|
// Bank items are already tracked via update fields (bank slot GUIDs)
|
||||||
// Trigger rebuild to populate bank slots in inventory
|
// Trigger rebuild to populate bank slots in inventory
|
||||||
rebuildOnlineInventory();
|
rebuildOnlineInventory();
|
||||||
|
|
@ -25288,10 +25076,8 @@ void GameHandler::openAuctionHouse(uint64_t guid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::closeAuctionHouse() {
|
void GameHandler::closeAuctionHouse() {
|
||||||
bool wasOpen = auctionOpen_;
|
|
||||||
auctionOpen_ = false;
|
auctionOpen_ = false;
|
||||||
auctioneerGuid_ = 0;
|
auctioneerGuid_ = 0;
|
||||||
if (wasOpen && addonEventCallback_) addonEventCallback_("AUCTION_HOUSE_CLOSED", {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::auctionSearch(const std::string& name, uint8_t levelMin, uint8_t levelMax,
|
void GameHandler::auctionSearch(const std::string& name, uint8_t levelMin, uint8_t levelMax,
|
||||||
|
|
@ -25372,7 +25158,6 @@ void GameHandler::handleAuctionHello(network::Packet& packet) {
|
||||||
auctionHouseId_ = data.auctionHouseId;
|
auctionHouseId_ = data.auctionHouseId;
|
||||||
auctionOpen_ = true;
|
auctionOpen_ = true;
|
||||||
gossipWindowOpen = false; // Close gossip when auction house opens
|
gossipWindowOpen = false; // Close gossip when auction house opens
|
||||||
if (addonEventCallback_) addonEventCallback_("AUCTION_HOUSE_SHOW", {});
|
|
||||||
auctionActiveTab_ = 0;
|
auctionActiveTab_ = 0;
|
||||||
auctionBrowseResults_ = AuctionListResult{};
|
auctionBrowseResults_ = AuctionListResult{};
|
||||||
auctionOwnerResults_ = AuctionListResult{};
|
auctionOwnerResults_ = AuctionListResult{};
|
||||||
|
|
@ -25913,10 +25698,9 @@ void GameHandler::handleLootRollWon(network::Packet& packet) {
|
||||||
/*uint32_t slot =*/ packet.readUInt32();
|
/*uint32_t slot =*/ packet.readUInt32();
|
||||||
uint64_t winnerGuid = packet.readUInt64();
|
uint64_t winnerGuid = packet.readUInt64();
|
||||||
uint32_t itemId = packet.readUInt32();
|
uint32_t itemId = packet.readUInt32();
|
||||||
int32_t wonRandProp = 0;
|
|
||||||
if (isWotLK) {
|
if (isWotLK) {
|
||||||
/*uint32_t randSuffix =*/ packet.readUInt32();
|
/*uint32_t randSuffix =*/ packet.readUInt32();
|
||||||
wonRandProp = static_cast<int32_t>(packet.readUInt32());
|
/*uint32_t randProp =*/ packet.readUInt32();
|
||||||
}
|
}
|
||||||
uint8_t rollNum = packet.readUInt8();
|
uint8_t rollNum = packet.readUInt8();
|
||||||
uint8_t rollType = packet.readUInt8();
|
uint8_t rollType = packet.readUInt8();
|
||||||
|
|
@ -25935,10 +25719,6 @@ void GameHandler::handleLootRollWon(network::Packet& packet) {
|
||||||
|
|
||||||
auto* info = getItemInfo(itemId);
|
auto* info = getItemInfo(itemId);
|
||||||
std::string iName = info && !info->name.empty() ? info->name : std::to_string(itemId);
|
std::string iName = info && !info->name.empty() ? info->name : std::to_string(itemId);
|
||||||
if (wonRandProp != 0) {
|
|
||||||
std::string suffix = getRandomPropertyName(wonRandProp);
|
|
||||||
if (!suffix.empty()) iName += " " + suffix;
|
|
||||||
}
|
|
||||||
uint32_t wonItemQuality = info ? info->quality : 1u;
|
uint32_t wonItemQuality = info ? info->quality : 1u;
|
||||||
std::string wonItemLink = buildItemLink(itemId, wonItemQuality, iName);
|
std::string wonItemLink = buildItemLink(itemId, wonItemQuality, iName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -668,7 +668,7 @@ void WorldSocket::tryParsePackets() {
|
||||||
closeSocketNoJoin();
|
closeSocketNoJoin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
constexpr uint16_t kMaxWorldPacketSize = 0x8000; // 32KB — allows large guild rosters, auction lists
|
constexpr uint16_t kMaxWorldPacketSize = 0x4000;
|
||||||
if (size > kMaxWorldPacketSize) {
|
if (size > kMaxWorldPacketSize) {
|
||||||
LOG_ERROR("World packet framing desync: oversized packet size=", size,
|
LOG_ERROR("World packet framing desync: oversized packet size=", size,
|
||||||
" rawHdr=", std::hex,
|
" rawHdr=", std::hex,
|
||||||
|
|
|
||||||
|
|
@ -313,12 +313,8 @@ void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sun height attenuation — flare weakens when sun is near horizon (sunrise/sunset)
|
|
||||||
float sunHeight = sunDir.z; // z = up in render space; 0 = horizon, 1 = zenith
|
|
||||||
float heightFactor = glm::smoothstep(-0.05f, 0.25f, sunHeight);
|
|
||||||
|
|
||||||
// Atmospheric attenuation — fog, clouds, and weather reduce lens flare
|
// Atmospheric attenuation — fog, clouds, and weather reduce lens flare
|
||||||
float atmosphericFactor = heightFactor;
|
float atmosphericFactor = 1.0f;
|
||||||
atmosphericFactor *= (1.0f - glm::clamp(fogDensity * 0.8f, 0.0f, 0.9f)); // Heavy fog nearly kills flare
|
atmosphericFactor *= (1.0f - glm::clamp(fogDensity * 0.8f, 0.0f, 0.9f)); // Heavy fog nearly kills flare
|
||||||
atmosphericFactor *= (1.0f - glm::clamp(cloudDensity * 0.6f, 0.0f, 0.7f)); // Clouds attenuate
|
atmosphericFactor *= (1.0f - glm::clamp(cloudDensity * 0.6f, 0.0f, 0.7f)); // Clouds attenuate
|
||||||
atmosphericFactor *= (1.0f - glm::clamp(weatherIntensity * 0.9f, 0.0f, 0.95f)); // Rain/snow heavily attenuates
|
atmosphericFactor *= (1.0f - glm::clamp(weatherIntensity * 0.9f, 0.0f, 0.95f)); // Rain/snow heavily attenuates
|
||||||
|
|
@ -343,9 +339,6 @@ void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec
|
||||||
VkDeviceSize offset = 0;
|
VkDeviceSize offset = 0;
|
||||||
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
|
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
|
||||||
|
|
||||||
// Warm tint at sunrise/sunset — shift flare color toward orange/amber when sun is low
|
|
||||||
float warmTint = 1.0f - glm::smoothstep(0.05f, 0.35f, sunHeight);
|
|
||||||
|
|
||||||
// Render each flare element
|
// Render each flare element
|
||||||
for (const auto& element : flareElements) {
|
for (const auto& element : flareElements) {
|
||||||
// Calculate position along sun-to-center axis
|
// Calculate position along sun-to-center axis
|
||||||
|
|
@ -354,19 +347,12 @@ void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec
|
||||||
// Apply visibility, intensity, and atmospheric attenuation
|
// Apply visibility, intensity, and atmospheric attenuation
|
||||||
float brightness = element.brightness * visibility * intensityMultiplier * atmosphericFactor;
|
float brightness = element.brightness * visibility * intensityMultiplier * atmosphericFactor;
|
||||||
|
|
||||||
// Apply warm sunset/sunrise color shift
|
|
||||||
glm::vec3 tintedColor = element.color;
|
|
||||||
if (warmTint > 0.01f) {
|
|
||||||
glm::vec3 warmColor(1.0f, 0.6f, 0.25f); // amber/orange
|
|
||||||
tintedColor = glm::mix(tintedColor, warmColor, warmTint * 0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set push constants
|
// Set push constants
|
||||||
FlarePushConstants push{};
|
FlarePushConstants push{};
|
||||||
push.position = position;
|
push.position = position;
|
||||||
push.size = element.size;
|
push.size = element.size;
|
||||||
push.aspectRatio = aspectRatio;
|
push.aspectRatio = aspectRatio;
|
||||||
push.colorBrightness = glm::vec4(tintedColor, brightness);
|
push.colorBrightness = glm::vec4(element.color, brightness);
|
||||||
|
|
||||||
vkCmdPushConstants(cmd, pipelineLayout,
|
vkCmdPushConstants(cmd, pipelineLayout,
|
||||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
|
|
||||||
|
|
@ -261,20 +261,6 @@ void LoadingScreen::renderOverlay() {
|
||||||
ImVec2(0, 0), ImVec2(screenW, screenH));
|
ImVec2(0, 0), ImVec2(screenW, screenH));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zone name header
|
|
||||||
if (!zoneName.empty()) {
|
|
||||||
ImFont* font = ImGui::GetFont();
|
|
||||||
float zoneTextSize = 24.0f;
|
|
||||||
ImVec2 zoneSize = font->CalcTextSizeA(zoneTextSize, FLT_MAX, 0.0f, zoneName.c_str());
|
|
||||||
float zoneX = (screenW - zoneSize.x) * 0.5f;
|
|
||||||
float zoneY = screenH * 0.06f - 44.0f;
|
|
||||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
||||||
dl->AddText(font, zoneTextSize, ImVec2(zoneX + 2.0f, zoneY + 2.0f),
|
|
||||||
IM_COL32(0, 0, 0, 200), zoneName.c_str());
|
|
||||||
dl->AddText(font, zoneTextSize, ImVec2(zoneX, zoneY),
|
|
||||||
IM_COL32(255, 220, 120, 255), zoneName.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress bar
|
// Progress bar
|
||||||
{
|
{
|
||||||
const float barWidthFrac = 0.6f;
|
const float barWidthFrac = 0.6f;
|
||||||
|
|
@ -346,22 +332,6 @@ void LoadingScreen::render() {
|
||||||
ImVec2(0, 0), ImVec2(screenW, screenH));
|
ImVec2(0, 0), ImVec2(screenW, screenH));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zone name header (large text centered above progress bar)
|
|
||||||
if (!zoneName.empty()) {
|
|
||||||
ImFont* font = ImGui::GetFont();
|
|
||||||
float zoneTextSize = 24.0f;
|
|
||||||
ImVec2 zoneSize = font->CalcTextSizeA(zoneTextSize, FLT_MAX, 0.0f, zoneName.c_str());
|
|
||||||
float zoneX = (screenW - zoneSize.x) * 0.5f;
|
|
||||||
float zoneY = screenH * 0.06f - 44.0f; // above percentage text
|
|
||||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
||||||
// Drop shadow
|
|
||||||
dl->AddText(font, zoneTextSize, ImVec2(zoneX + 2.0f, zoneY + 2.0f),
|
|
||||||
IM_COL32(0, 0, 0, 200), zoneName.c_str());
|
|
||||||
// Gold text
|
|
||||||
dl->AddText(font, zoneTextSize, ImVec2(zoneX, zoneY),
|
|
||||||
IM_COL32(255, 220, 120, 255), zoneName.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress bar (top of screen)
|
// Progress bar (top of screen)
|
||||||
{
|
{
|
||||||
const float barWidthFrac = 0.6f;
|
const float barWidthFrac = 0.6f;
|
||||||
|
|
|
||||||
|
|
@ -3192,7 +3192,7 @@ void Renderer::update(float deltaTime) {
|
||||||
// Server-driven weather (SMSG_WEATHER) — authoritative
|
// Server-driven weather (SMSG_WEATHER) — authoritative
|
||||||
if (wType == 1) weather->setWeatherType(Weather::Type::RAIN);
|
if (wType == 1) weather->setWeatherType(Weather::Type::RAIN);
|
||||||
else if (wType == 2) weather->setWeatherType(Weather::Type::SNOW);
|
else if (wType == 2) weather->setWeatherType(Weather::Type::SNOW);
|
||||||
else if (wType == 3) weather->setWeatherType(Weather::Type::STORM);
|
else if (wType == 3) weather->setWeatherType(Weather::Type::RAIN); // thunderstorm — use rain particles
|
||||||
else weather->setWeatherType(Weather::Type::NONE);
|
else weather->setWeatherType(Weather::Type::NONE);
|
||||||
weather->setIntensity(wInt);
|
weather->setIntensity(wInt);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -135,14 +135,6 @@ void SkySystem::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
||||||
|
|
||||||
// --- Clouds (DBC-driven colors + sun lighting) ---
|
// --- Clouds (DBC-driven colors + sun lighting) ---
|
||||||
if (clouds_) {
|
if (clouds_) {
|
||||||
// Sync cloud density with weather/DBC-driven cloud coverage.
|
|
||||||
// Active weather (rain/snow/storm) increases cloud density for visual consistency.
|
|
||||||
float effectiveDensity = params.cloudDensity;
|
|
||||||
if (params.weatherIntensity > 0.05f) {
|
|
||||||
float weatherBoost = params.weatherIntensity * 0.4f; // storms add up to 0.4 density
|
|
||||||
effectiveDensity = glm::min(1.0f, effectiveDensity + weatherBoost);
|
|
||||||
}
|
|
||||||
clouds_->setDensity(effectiveDensity);
|
|
||||||
clouds_->render(cmd, perFrameSet, params);
|
clouds_->render(cmd, perFrameSet, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,10 +198,6 @@ void Weather::update(const Camera& camera, float deltaTime) {
|
||||||
if (weatherType == Type::RAIN) {
|
if (weatherType == Type::RAIN) {
|
||||||
p.velocity = glm::vec3(0.0f, -50.0f, 0.0f); // Fast downward
|
p.velocity = glm::vec3(0.0f, -50.0f, 0.0f); // Fast downward
|
||||||
p.maxLifetime = 5.0f;
|
p.maxLifetime = 5.0f;
|
||||||
} else if (weatherType == Type::STORM) {
|
|
||||||
// Storm: faster, angled rain with wind
|
|
||||||
p.velocity = glm::vec3(15.0f, -70.0f, 8.0f);
|
|
||||||
p.maxLifetime = 3.5f;
|
|
||||||
} else { // SNOW
|
} else { // SNOW
|
||||||
p.velocity = glm::vec3(0.0f, -5.0f, 0.0f); // Slow downward
|
p.velocity = glm::vec3(0.0f, -5.0f, 0.0f); // Slow downward
|
||||||
p.maxLifetime = 10.0f;
|
p.maxLifetime = 10.0f;
|
||||||
|
|
@ -249,12 +245,6 @@ void Weather::updateParticle(Particle& particle, const Camera& camera, float del
|
||||||
particle.velocity.x = windX;
|
particle.velocity.x = windX;
|
||||||
particle.velocity.z = windZ;
|
particle.velocity.z = windZ;
|
||||||
}
|
}
|
||||||
// Storm: gusty, turbulent wind with varying direction
|
|
||||||
if (weatherType == Type::STORM) {
|
|
||||||
float gust = std::sin(particle.lifetime * 1.5f + particle.position.x * 0.1f) * 5.0f;
|
|
||||||
particle.velocity.x = 15.0f + gust;
|
|
||||||
particle.velocity.z = 8.0f + std::cos(particle.lifetime * 2.0f) * 3.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update position
|
// Update position
|
||||||
particle.position += particle.velocity * deltaTime;
|
particle.position += particle.velocity * deltaTime;
|
||||||
|
|
@ -285,9 +275,6 @@ void Weather::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet) {
|
||||||
if (weatherType == Type::RAIN) {
|
if (weatherType == Type::RAIN) {
|
||||||
push.particleSize = 3.0f;
|
push.particleSize = 3.0f;
|
||||||
push.particleColor = glm::vec4(0.7f, 0.8f, 0.9f, 0.6f);
|
push.particleColor = glm::vec4(0.7f, 0.8f, 0.9f, 0.6f);
|
||||||
} else if (weatherType == Type::STORM) {
|
|
||||||
push.particleSize = 3.5f;
|
|
||||||
push.particleColor = glm::vec4(0.6f, 0.65f, 0.75f, 0.7f); // Darker, more opaque
|
|
||||||
} else { // SNOW
|
} else { // SNOW
|
||||||
push.particleSize = 8.0f;
|
push.particleSize = 8.0f;
|
||||||
push.particleColor = glm::vec4(1.0f, 1.0f, 1.0f, 0.9f);
|
push.particleColor = glm::vec4(1.0f, 1.0f, 1.0f, 0.9f);
|
||||||
|
|
|
||||||
|
|
@ -720,7 +720,6 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderBgInvitePopup(gameHandler);
|
renderBgInvitePopup(gameHandler);
|
||||||
renderBfMgrInvitePopup(gameHandler);
|
renderBfMgrInvitePopup(gameHandler);
|
||||||
renderLfgProposalPopup(gameHandler);
|
renderLfgProposalPopup(gameHandler);
|
||||||
renderLfgRoleCheckPopup(gameHandler);
|
|
||||||
renderGuildRoster(gameHandler);
|
renderGuildRoster(gameHandler);
|
||||||
renderSocialFrame(gameHandler);
|
renderSocialFrame(gameHandler);
|
||||||
renderBuffBar(gameHandler);
|
renderBuffBar(gameHandler);
|
||||||
|
|
@ -2617,32 +2616,24 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
||||||
|
|
||||||
static const std::vector<std::string> kCmds = {
|
static const std::vector<std::string> kCmds = {
|
||||||
"/afk", "/assist", "/away",
|
"/afk", "/assist", "/away",
|
||||||
"/cancelaura", "/cancelform", "/cancellogout", "/cancelshapeshift",
|
"/cancelaura", "/cancelform", "/cancelshapeshift",
|
||||||
"/cast", "/castsequence", "/chathelp", "/clear", "/clearfocus",
|
"/cast", "/castsequence", "/chathelp", "/clear", "/clearfocus", "/cleartarget",
|
||||||
"/clearmainassist", "/clearmaintank", "/cleartarget", "/cloak",
|
"/combatlog", "/dance", "/dismount", "/dnd", "/do", "/duel",
|
||||||
"/combatlog", "/dance", "/dismount", "/dnd", "/do", "/duel", "/dump",
|
|
||||||
"/e", "/emote", "/equip", "/equipset",
|
"/e", "/emote", "/equip", "/equipset",
|
||||||
"/focus", "/follow", "/forfeit", "/friend",
|
"/focus", "/follow", "/forfeit", "/friend",
|
||||||
"/g", "/gdemote", "/ginvite", "/gkick", "/gleader", "/gmotd",
|
"/g", "/ginvite", "/gmticket", "/grouploot", "/guild", "/guildinfo",
|
||||||
"/gmticket", "/gpromote", "/gquit", "/grouploot", "/groster",
|
|
||||||
"/guild", "/guildinfo",
|
|
||||||
"/helm", "/help",
|
"/helm", "/help",
|
||||||
"/i", "/ignore", "/inspect", "/instance", "/invite",
|
"/i", "/ignore", "/inspect", "/instance", "/invite",
|
||||||
"/j", "/join", "/kick", "/kneel",
|
"/j", "/join", "/kick", "/kneel",
|
||||||
"/l", "/leave", "/leaveparty", "/loc", "/local", "/logout",
|
"/l", "/leave", "/loc", "/local", "/logout",
|
||||||
"/macrohelp", "/mainassist", "/maintank", "/mark", "/me",
|
"/macrohelp", "/mark", "/me",
|
||||||
"/notready",
|
|
||||||
"/p", "/party", "/petaggressive", "/petattack", "/petdefensive",
|
"/p", "/party", "/petaggressive", "/petattack", "/petdefensive",
|
||||||
"/petdismiss", "/petfollow", "/pethalt", "/petpassive", "/petstay",
|
"/petdismiss", "/petfollow", "/pethalt", "/petpassive", "/petstay",
|
||||||
"/played", "/pvp",
|
"/played", "/pvp",
|
||||||
"/r", "/raid", "/raidinfo", "/raidwarning", "/random", "/ready",
|
"/r", "/raid", "/raidinfo", "/raidwarning", "/random", "/reply", "/roll", "/run",
|
||||||
"/readycheck", "/reload", "/reloadui", "/removefriend",
|
"/s", "/say", "/screenshot", "/setloot", "/shout", "/sit", "/stand",
|
||||||
"/reply", "/rl", "/roll", "/run",
|
|
||||||
"/s", "/say", "/score", "/screenshot", "/script", "/setloot",
|
|
||||||
"/shout", "/sit", "/stand",
|
|
||||||
"/startattack", "/stopattack", "/stopcasting", "/stopfollow", "/stopmacro",
|
"/startattack", "/stopattack", "/stopcasting", "/stopfollow", "/stopmacro",
|
||||||
"/t", "/target", "/targetenemy", "/targetfriend", "/targetlast",
|
"/t", "/target", "/threat", "/time", "/trade",
|
||||||
"/threat", "/ticket", "/time", "/trade",
|
|
||||||
"/unignore", "/uninvite", "/unstuck", "/use",
|
"/unignore", "/uninvite", "/unstuck", "/use",
|
||||||
"/w", "/whisper", "/who", "/wts", "/wtb",
|
"/w", "/whisper", "/who", "/wts", "/wtb",
|
||||||
"/y", "/yell", "/zone"
|
"/y", "/yell", "/zone"
|
||||||
|
|
@ -6016,30 +6007,6 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /dump <expression> — evaluate Lua expression and print result
|
|
||||||
if ((cmdLower == "dump" || cmdLower == "print") && spacePos != std::string::npos) {
|
|
||||||
std::string expr = command.substr(spacePos + 1);
|
|
||||||
auto* am = core::Application::getInstance().getAddonManager();
|
|
||||||
if (am && am->isInitialized()) {
|
|
||||||
// Wrap expression in print(tostring(...)) to display the value
|
|
||||||
std::string wrapped = "local __v = " + expr +
|
|
||||||
"; if type(__v) == 'table' then "
|
|
||||||
" local parts = {} "
|
|
||||||
" for k,v in pairs(__v) do parts[#parts+1] = tostring(k)..'='..tostring(v) end "
|
|
||||||
" print('{' .. table.concat(parts, ', ') .. '}') "
|
|
||||||
"else print(tostring(__v)) end";
|
|
||||||
am->runScript(wrapped);
|
|
||||||
} else {
|
|
||||||
game::MessageChatData errMsg;
|
|
||||||
errMsg.type = game::ChatType::SYSTEM;
|
|
||||||
errMsg.language = game::ChatLanguage::UNIVERSAL;
|
|
||||||
errMsg.message = "Addon system not initialized.";
|
|
||||||
gameHandler.addLocalChatMessage(errMsg);
|
|
||||||
}
|
|
||||||
chatInputBuffer[0] = '\0';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check addon slash commands (SlashCmdList) before built-in commands
|
// Check addon slash commands (SlashCmdList) before built-in commands
|
||||||
{
|
{
|
||||||
auto* am = core::Application::getInstance().getAddonManager();
|
auto* am = core::Application::getInstance().getAddonManager();
|
||||||
|
|
@ -6067,30 +6034,6 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /reload or /reloadui — reload all addons (save variables, re-init Lua, re-scan .toc files)
|
|
||||||
if (cmdLower == "reload" || cmdLower == "reloadui" || cmdLower == "rl") {
|
|
||||||
auto* am = core::Application::getInstance().getAddonManager();
|
|
||||||
if (am) {
|
|
||||||
am->reload();
|
|
||||||
am->fireEvent("VARIABLES_LOADED");
|
|
||||||
am->fireEvent("PLAYER_LOGIN");
|
|
||||||
am->fireEvent("PLAYER_ENTERING_WORLD");
|
|
||||||
game::MessageChatData rlMsg;
|
|
||||||
rlMsg.type = game::ChatType::SYSTEM;
|
|
||||||
rlMsg.language = game::ChatLanguage::UNIVERSAL;
|
|
||||||
rlMsg.message = "Interface reloaded.";
|
|
||||||
gameHandler.addLocalChatMessage(rlMsg);
|
|
||||||
} else {
|
|
||||||
game::MessageChatData rlMsg;
|
|
||||||
rlMsg.type = game::ChatType::SYSTEM;
|
|
||||||
rlMsg.language = game::ChatLanguage::UNIVERSAL;
|
|
||||||
rlMsg.message = "Addon system not available.";
|
|
||||||
gameHandler.addLocalChatMessage(rlMsg);
|
|
||||||
}
|
|
||||||
chatInputBuffer[0] = '\0';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /stopmacro [conditions]
|
// /stopmacro [conditions]
|
||||||
// Halts execution of the current macro (remaining lines are skipped).
|
// Halts execution of the current macro (remaining lines are skipped).
|
||||||
// With a condition block, only stops if the conditions evaluate to true.
|
// With a condition block, only stops if the conditions evaluate to true.
|
||||||
|
|
@ -7140,12 +7083,6 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cmdLower == "cancelqueuedspell" || cmdLower == "stopspellqueue") {
|
|
||||||
gameHandler.cancelQueuedSpell();
|
|
||||||
chatInputBuffer[0] = '\0';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /equipset [name] — equip a saved equipment set by name (partial match, case-insensitive)
|
// /equipset [name] — equip a saved equipment set by name (partial match, case-insensitive)
|
||||||
// /equipset — list available sets in chat
|
// /equipset — list available sets in chat
|
||||||
if (cmdLower == "equipset") {
|
if (cmdLower == "equipset") {
|
||||||
|
|
@ -9475,7 +9412,7 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (totalCount > 0) {
|
if (totalCount > 0) {
|
||||||
char countStr[16];
|
char countStr[8];
|
||||||
snprintf(countStr, sizeof(countStr), "%d", totalCount);
|
snprintf(countStr, sizeof(countStr), "%d", totalCount);
|
||||||
ImVec2 btnMax = ImGui::GetItemRectMax();
|
ImVec2 btnMax = ImGui::GetItemRectMax();
|
||||||
ImVec2 tsz = ImGui::CalcTextSize(countStr);
|
ImVec2 tsz = ImGui::CalcTextSize(countStr);
|
||||||
|
|
@ -14264,71 +14201,6 @@ void GameScreen::renderLfgProposalPopup(game::GameHandler& gameHandler) {
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScreen::renderLfgRoleCheckPopup(game::GameHandler& gameHandler) {
|
|
||||||
using LfgState = game::GameHandler::LfgState;
|
|
||||||
if (gameHandler.getLfgState() != LfgState::RoleCheck) return;
|
|
||||||
|
|
||||||
auto* window = core::Application::getInstance().getWindow();
|
|
||||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
||||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(screenW / 2.0f - 160.0f, screenH / 2.0f - 80.0f), ImGuiCond_Always);
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(320.0f, 0.0f), ImGuiCond_Always);
|
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.18f, 0.96f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.3f, 0.5f, 0.9f, 1.0f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(0.1f, 0.1f, 0.3f, 1.0f));
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
|
||||||
|
|
||||||
const ImGuiWindowFlags flags =
|
|
||||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse;
|
|
||||||
|
|
||||||
if (ImGui::Begin("Role Check##LfgRoleCheck", nullptr, flags)) {
|
|
||||||
ImGui::TextColored(ImVec4(0.4f, 0.7f, 1.0f, 1.0f), "Confirm your role:");
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
// Role checkboxes
|
|
||||||
bool isTank = (lfgRoles_ & 0x02) != 0;
|
|
||||||
bool isHealer = (lfgRoles_ & 0x04) != 0;
|
|
||||||
bool isDps = (lfgRoles_ & 0x08) != 0;
|
|
||||||
|
|
||||||
if (ImGui::Checkbox("Tank", &isTank)) lfgRoles_ = (lfgRoles_ & ~0x02) | (isTank ? 0x02 : 0);
|
|
||||||
ImGui::SameLine(120.0f);
|
|
||||||
if (ImGui::Checkbox("Healer", &isHealer)) lfgRoles_ = (lfgRoles_ & ~0x04) | (isHealer ? 0x04 : 0);
|
|
||||||
ImGui::SameLine(220.0f);
|
|
||||||
if (ImGui::Checkbox("DPS", &isDps)) lfgRoles_ = (lfgRoles_ & ~0x08) | (isDps ? 0x08 : 0);
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
bool hasRole = (lfgRoles_ & 0x0E) != 0;
|
|
||||||
if (!hasRole) ImGui::BeginDisabled();
|
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.4f, 0.15f, 1.0f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
|
|
||||||
if (ImGui::Button("Accept", ImVec2(140.0f, 28.0f))) {
|
|
||||||
gameHandler.lfgSetRoles(lfgRoles_);
|
|
||||||
}
|
|
||||||
ImGui::PopStyleColor(2);
|
|
||||||
|
|
||||||
if (!hasRole) ImGui::EndDisabled();
|
|
||||||
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.15f, 0.15f, 1.0f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.6f, 0.2f, 0.2f, 1.0f));
|
|
||||||
if (ImGui::Button("Leave Queue", ImVec2(140.0f, 28.0f))) {
|
|
||||||
gameHandler.lfgLeave();
|
|
||||||
}
|
|
||||||
ImGui::PopStyleColor(2);
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
ImGui::PopStyleVar();
|
|
||||||
ImGui::PopStyleColor(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameScreen::renderGuildRoster(game::GameHandler& gameHandler) {
|
void GameScreen::renderGuildRoster(game::GameHandler& gameHandler) {
|
||||||
// Guild Roster toggle (customizable keybind)
|
// Guild Roster toggle (customizable keybind)
|
||||||
if (!chatInputActive && !ImGui::GetIO().WantTextInput &&
|
if (!chatInputActive && !ImGui::GetIO().WantTextInput &&
|
||||||
|
|
@ -22305,12 +22177,6 @@ void GameScreen::renderAuctionHouseWindow(game::GameHandler& gameHandler) {
|
||||||
const auto& auction = results.auctions[i];
|
const auto& auction = results.auctions[i];
|
||||||
auto* info = gameHandler.getItemInfo(auction.itemEntry);
|
auto* info = gameHandler.getItemInfo(auction.itemEntry);
|
||||||
std::string name = info ? info->name : ("Item #" + std::to_string(auction.itemEntry));
|
std::string name = info ? info->name : ("Item #" + std::to_string(auction.itemEntry));
|
||||||
// Append random suffix name (e.g., "of the Eagle") if present
|
|
||||||
if (auction.randomPropertyId != 0) {
|
|
||||||
std::string suffix = gameHandler.getRandomPropertyName(
|
|
||||||
static_cast<int32_t>(auction.randomPropertyId));
|
|
||||||
if (!suffix.empty()) name += " " + suffix;
|
|
||||||
}
|
|
||||||
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
||||||
ImVec4 qc = InventoryScreen::getQualityColor(quality);
|
ImVec4 qc = InventoryScreen::getQualityColor(quality);
|
||||||
|
|
||||||
|
|
@ -22504,11 +22370,6 @@ void GameScreen::renderAuctionHouseWindow(game::GameHandler& gameHandler) {
|
||||||
const auto& a = results.auctions[bi];
|
const auto& a = results.auctions[bi];
|
||||||
auto* info = gameHandler.getItemInfo(a.itemEntry);
|
auto* info = gameHandler.getItemInfo(a.itemEntry);
|
||||||
std::string name = info ? info->name : ("Item #" + std::to_string(a.itemEntry));
|
std::string name = info ? info->name : ("Item #" + std::to_string(a.itemEntry));
|
||||||
if (a.randomPropertyId != 0) {
|
|
||||||
std::string suffix = gameHandler.getRandomPropertyName(
|
|
||||||
static_cast<int32_t>(a.randomPropertyId));
|
|
||||||
if (!suffix.empty()) name += " " + suffix;
|
|
||||||
}
|
|
||||||
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
||||||
ImVec4 bqc = InventoryScreen::getQualityColor(quality);
|
ImVec4 bqc = InventoryScreen::getQualityColor(quality);
|
||||||
|
|
||||||
|
|
@ -22521,15 +22382,6 @@ void GameScreen::renderAuctionHouseWindow(game::GameHandler& gameHandler) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// High bidder indicator
|
|
||||||
bool isHighBidder = (a.bidderGuid != 0 && a.bidderGuid == gameHandler.getPlayerGuid());
|
|
||||||
if (isHighBidder) {
|
|
||||||
ImGui::TextColored(ImVec4(0.2f, 0.9f, 0.2f, 1.0f), "[Winning]");
|
|
||||||
ImGui::SameLine();
|
|
||||||
} else if (a.bidderGuid != 0) {
|
|
||||||
ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f), "[Outbid]");
|
|
||||||
ImGui::SameLine();
|
|
||||||
}
|
|
||||||
ImGui::TextColored(bqc, "%s", name.c_str());
|
ImGui::TextColored(bqc, "%s", name.c_str());
|
||||||
// Tooltip and shift-click
|
// Tooltip and shift-click
|
||||||
if (ImGui::IsItemHovered() && info && info->valid)
|
if (ImGui::IsItemHovered() && info && info->valid)
|
||||||
|
|
@ -22593,11 +22445,6 @@ void GameScreen::renderAuctionHouseWindow(game::GameHandler& gameHandler) {
|
||||||
const auto& a = results.auctions[i];
|
const auto& a = results.auctions[i];
|
||||||
auto* info = gameHandler.getItemInfo(a.itemEntry);
|
auto* info = gameHandler.getItemInfo(a.itemEntry);
|
||||||
std::string name = info ? info->name : ("Item #" + std::to_string(a.itemEntry));
|
std::string name = info ? info->name : ("Item #" + std::to_string(a.itemEntry));
|
||||||
if (a.randomPropertyId != 0) {
|
|
||||||
std::string suffix = gameHandler.getRandomPropertyName(
|
|
||||||
static_cast<int32_t>(a.randomPropertyId));
|
|
||||||
if (!suffix.empty()) name += " " + suffix;
|
|
||||||
}
|
|
||||||
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
|
|
@ -22610,11 +22457,6 @@ void GameScreen::renderAuctionHouseWindow(game::GameHandler& gameHandler) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bid activity indicator for seller
|
|
||||||
if (a.bidderGuid != 0) {
|
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.2f, 1.0f), "[Bid]");
|
|
||||||
ImGui::SameLine();
|
|
||||||
}
|
|
||||||
ImGui::TextColored(oqc, "%s", name.c_str());
|
ImGui::TextColored(oqc, "%s", name.c_str());
|
||||||
if (ImGui::IsItemHovered() && info && info->valid)
|
if (ImGui::IsItemHovered() && info && info->valid)
|
||||||
inventoryScreen.renderItemTooltip(*info);
|
inventoryScreen.renderItemTooltip(*info);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue