mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Compare commits
No commits in common. "a90f2acd267e0cec3d2a19989d2a4979b23beca7" and "5adb5e0e9f5c26ec0ea6c34cdedb950193bbc7a2" have entirely different histories.
a90f2acd26
...
5adb5e0e9f
18 changed files with 285 additions and 4006 deletions
|
|
@ -2,8 +2,7 @@
|
|||
"Spell": {
|
||||
"ID": 0, "Attributes": 5, "IconID": 117,
|
||||
"Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1,
|
||||
"CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33,
|
||||
"DispelType": 4
|
||||
"CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33
|
||||
},
|
||||
"SpellRange": { "MaxRange": 2 },
|
||||
"ItemDisplayInfo": {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
"Spell": {
|
||||
"ID": 0, "Attributes": 5, "IconID": 124,
|
||||
"Name": 127, "Tooltip": 154, "Rank": 136, "SchoolMask": 215,
|
||||
"CastingTimeIndex": 22, "PowerType": 35, "ManaCost": 36, "RangeIndex": 40,
|
||||
"DispelType": 3
|
||||
"CastingTimeIndex": 22, "PowerType": 35, "ManaCost": 36, "RangeIndex": 40
|
||||
},
|
||||
"SpellRange": { "MaxRange": 4 },
|
||||
"ItemDisplayInfo": {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
"Spell": {
|
||||
"ID": 0, "Attributes": 5, "IconID": 117,
|
||||
"Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1,
|
||||
"CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33,
|
||||
"DispelType": 4
|
||||
"CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33
|
||||
},
|
||||
"SpellRange": { "MaxRange": 2 },
|
||||
"ItemDisplayInfo": {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
"Spell": {
|
||||
"ID": 0, "Attributes": 4, "IconID": 133,
|
||||
"Name": 136, "Tooltip": 139, "Rank": 153, "SchoolMask": 225,
|
||||
"PowerType": 14, "ManaCost": 39, "CastingTimeIndex": 47, "RangeIndex": 49,
|
||||
"DispelType": 2
|
||||
"PowerType": 14, "ManaCost": 39, "CastingTimeIndex": 47, "RangeIndex": 49
|
||||
},
|
||||
"SpellRange": { "MaxRange": 4 },
|
||||
"ItemDisplayInfo": {
|
||||
|
|
|
|||
|
|
@ -75,9 +75,6 @@ public:
|
|||
void playTargetSelect();
|
||||
void playTargetDeselect();
|
||||
|
||||
// Chat notifications
|
||||
void playWhisperReceived();
|
||||
|
||||
private:
|
||||
struct UISample {
|
||||
std::string path;
|
||||
|
|
@ -125,7 +122,6 @@ private:
|
|||
std::vector<UISample> errorSounds_;
|
||||
std::vector<UISample> selectTargetSounds_;
|
||||
std::vector<UISample> deselectTargetSounds_;
|
||||
std::vector<UISample> whisperSounds_;
|
||||
|
||||
// State tracking
|
||||
float volumeScale_ = 1.0f;
|
||||
|
|
|
|||
|
|
@ -214,9 +214,6 @@ public:
|
|||
void setMaxPower(uint32_t p) { maxPowers[powerType < 7 ? powerType : 0] = p; }
|
||||
void setMaxPowerByType(uint8_t type, uint32_t p) { if (type < 7) maxPowers[type] = p; }
|
||||
|
||||
uint32_t getPowerByType(uint8_t type) const { return type < 7 ? powers[type] : 0; }
|
||||
uint32_t getMaxPowerByType(uint8_t type) const { return type < 7 ? maxPowers[type] : 0; }
|
||||
|
||||
uint8_t getPowerType() const { return powerType; }
|
||||
void setPowerType(uint8_t t) { powerType = t; }
|
||||
|
||||
|
|
|
|||
|
|
@ -339,8 +339,7 @@ public:
|
|||
uint32_t unspentTalents = 0;
|
||||
uint8_t talentGroups = 0;
|
||||
uint8_t activeTalentGroup = 0;
|
||||
std::array<uint32_t, 19> itemEntries{}; // 0=head…18=ranged
|
||||
std::array<uint16_t, 19> enchantIds{}; // permanent enchant per slot (0 = none)
|
||||
std::array<uint32_t, 19> itemEntries{}; // 0=head…18=ranged
|
||||
};
|
||||
const InspectResult* getInspectResult() const {
|
||||
return inspectResult_.guid ? &inspectResult_ : nullptr;
|
||||
|
|
@ -353,19 +352,6 @@ public:
|
|||
uint32_t getTotalTimePlayed() const { return totalTimePlayed_; }
|
||||
uint32_t getLevelTimePlayed() const { return levelTimePlayed_; }
|
||||
|
||||
// Who results (structured, from last SMSG_WHO response)
|
||||
struct WhoEntry {
|
||||
std::string name;
|
||||
std::string guildName;
|
||||
uint32_t level = 0;
|
||||
uint32_t classId = 0;
|
||||
uint32_t raceId = 0;
|
||||
uint32_t zoneId = 0;
|
||||
};
|
||||
const std::vector<WhoEntry>& getWhoResults() const { return whoResults_; }
|
||||
uint32_t getWhoOnlineCount() const { return whoOnlineCount_; }
|
||||
std::string getWhoAreaName(uint32_t zoneId) const { return getAreaName(zoneId); }
|
||||
|
||||
// Social commands
|
||||
void addFriend(const std::string& playerName, const std::string& note = "");
|
||||
void removeFriend(const std::string& playerName);
|
||||
|
|
@ -393,28 +379,6 @@ public:
|
|||
void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF);
|
||||
const std::array<BgQueueSlot, 3>& getBgQueues() const { return bgQueues_; }
|
||||
|
||||
// BG scoreboard (MSG_PVP_LOG_DATA)
|
||||
struct BgPlayerScore {
|
||||
uint64_t guid = 0;
|
||||
std::string name;
|
||||
uint8_t team = 0; // 0=Horde, 1=Alliance
|
||||
uint32_t killingBlows = 0;
|
||||
uint32_t deaths = 0;
|
||||
uint32_t honorableKills = 0;
|
||||
uint32_t bonusHonor = 0;
|
||||
std::vector<std::pair<std::string, uint32_t>> bgStats; // BG-specific fields
|
||||
};
|
||||
struct BgScoreboardData {
|
||||
std::vector<BgPlayerScore> players;
|
||||
bool hasWinner = false;
|
||||
uint8_t winner = 0; // 0=Horde, 1=Alliance
|
||||
bool isArena = false;
|
||||
};
|
||||
void requestPvpLog();
|
||||
const BgScoreboardData* getBgScoreboard() const {
|
||||
return bgScoreboard_.players.empty() ? nullptr : &bgScoreboard_;
|
||||
}
|
||||
|
||||
// Network latency (milliseconds, updated each PONG response)
|
||||
uint32_t getLatencyMs() const { return lastLatency; }
|
||||
|
||||
|
|
@ -437,7 +401,6 @@ public:
|
|||
|
||||
// Follow/Assist
|
||||
void followTarget();
|
||||
void cancelFollow(); // Stop following current target
|
||||
void assistTarget();
|
||||
|
||||
// PvP
|
||||
|
|
@ -494,16 +457,11 @@ public:
|
|||
uint64_t getPetitionNpcGuid() const { return petitionNpcGuid_; }
|
||||
|
||||
// Ready check
|
||||
struct ReadyCheckResult {
|
||||
std::string name;
|
||||
bool ready = false;
|
||||
};
|
||||
void initiateReadyCheck();
|
||||
void respondToReadyCheck(bool ready);
|
||||
bool hasPendingReadyCheck() const { return pendingReadyCheck_; }
|
||||
void dismissReadyCheck() { pendingReadyCheck_ = false; }
|
||||
const std::string& getReadyCheckInitiator() const { return readyCheckInitiator_; }
|
||||
const std::vector<ReadyCheckResult>& getReadyCheckResults() const { return readyCheckResults_; }
|
||||
|
||||
// Duel
|
||||
void forfeitDuel();
|
||||
|
|
@ -558,19 +516,6 @@ public:
|
|||
const std::vector<CombatTextEntry>& getCombatText() const { return combatText; }
|
||||
void updateCombatText(float deltaTime);
|
||||
|
||||
// Combat log (persistent rolling history, max MAX_COMBAT_LOG entries)
|
||||
const std::deque<CombatLogEntry>& getCombatLog() const { return combatLog_; }
|
||||
void clearCombatLog() { combatLog_.clear(); }
|
||||
|
||||
// Area trigger messages (SMSG_AREA_TRIGGER_MESSAGE) — drained by UI each frame
|
||||
bool hasAreaTriggerMsg() const { return !areaTriggerMsgs_.empty(); }
|
||||
std::string popAreaTriggerMsg() {
|
||||
if (areaTriggerMsgs_.empty()) return {};
|
||||
std::string msg = areaTriggerMsgs_.front();
|
||||
areaTriggerMsgs_.pop_front();
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Threat
|
||||
struct ThreatEntry {
|
||||
uint64_t victimGuid = 0;
|
||||
|
|
@ -727,11 +672,6 @@ public:
|
|||
// Auras
|
||||
const std::vector<AuraSlot>& getPlayerAuras() const { return playerAuras; }
|
||||
const std::vector<AuraSlot>& getTargetAuras() const { return targetAuras; }
|
||||
// Per-unit aura cache (populated for party members and any unit we receive updates for)
|
||||
const std::vector<AuraSlot>* getUnitAuras(uint64_t guid) const {
|
||||
auto it = unitAurasCache_.find(guid);
|
||||
return (it != unitAurasCache_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
// Completed quests (populated from SMSG_QUERY_QUESTS_COMPLETED_RESPONSE)
|
||||
bool isQuestCompleted(uint32_t questId) const { return completedQuests_.count(questId) > 0; }
|
||||
|
|
@ -803,17 +743,6 @@ public:
|
|||
float getGameTime() const { return gameTime_; }
|
||||
float getTimeSpeed() const { return timeSpeed_; }
|
||||
|
||||
// Global Cooldown (GCD) — set when the server sends a spellId=0 cooldown entry
|
||||
float getGCDRemaining() const {
|
||||
if (gcdTotal_ <= 0.0f) return 0.0f;
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - gcdStartedAt_).count() / 1000.0f;
|
||||
float rem = gcdTotal_ - elapsed;
|
||||
return rem > 0.0f ? rem : 0.0f;
|
||||
}
|
||||
float getGCDTotal() const { return gcdTotal_; }
|
||||
bool isGCDActive() const { return getGCDRemaining() > 0.0f; }
|
||||
|
||||
// Weather state (updated by SMSG_WEATHER)
|
||||
// weatherType: 0=clear, 1=rain, 2=snow, 3=storm/fog
|
||||
uint32_t getWeatherType() const { return weatherType_; }
|
||||
|
|
@ -1106,14 +1035,6 @@ public:
|
|||
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
|
||||
void acceptDuel();
|
||||
// forfeitDuel() already declared at line ~399
|
||||
// Returns remaining duel countdown seconds, or 0 if no active countdown
|
||||
float getDuelCountdownRemaining() const {
|
||||
if (duelCountdownMs_ == 0) return 0.0f;
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - duelCountdownStartedAt_).count();
|
||||
float rem = (static_cast<float>(duelCountdownMs_) - static_cast<float>(elapsed)) / 1000.0f;
|
||||
return rem > 0.0f ? rem : 0.0f;
|
||||
}
|
||||
|
||||
// ---- Instance lockouts ----
|
||||
struct InstanceLockout {
|
||||
|
|
@ -1174,12 +1095,10 @@ public:
|
|||
uint32_t getLfgProposalId() const { return lfgProposalId_; }
|
||||
int32_t getLfgAvgWaitSec() const { return lfgAvgWaitSec_; }
|
||||
uint32_t getLfgTimeInQueueMs() const { return lfgTimeInQueueMs_; }
|
||||
uint32_t getLfgBootVotes() const { return lfgBootVotes_; }
|
||||
uint32_t getLfgBootTotal() const { return lfgBootTotal_; }
|
||||
uint32_t getLfgBootTimeLeft() const { return lfgBootTimeLeft_; }
|
||||
uint32_t getLfgBootNeeded() const { return lfgBootNeeded_; }
|
||||
const std::string& getLfgBootTargetName() const { return lfgBootTargetName_; }
|
||||
const std::string& getLfgBootReason() const { return lfgBootReason_; }
|
||||
uint32_t getLfgBootVotes() const { return lfgBootVotes_; }
|
||||
uint32_t getLfgBootTotal() const { return lfgBootTotal_; }
|
||||
uint32_t getLfgBootTimeLeft() const { return lfgBootTimeLeft_; }
|
||||
uint32_t getLfgBootNeeded() const { return lfgBootNeeded_; }
|
||||
|
||||
// ---- Arena Team Stats ----
|
||||
struct ArenaTeamStats {
|
||||
|
|
@ -1210,15 +1129,6 @@ public:
|
|||
uint32_t itemId = 0;
|
||||
std::string itemName;
|
||||
uint8_t itemQuality = 0;
|
||||
uint32_t rollCountdownMs = 60000; // Duration of roll window in ms
|
||||
std::chrono::steady_clock::time_point rollStartedAt{};
|
||||
|
||||
struct PlayerRollResult {
|
||||
std::string playerName;
|
||||
uint8_t rollNum = 0;
|
||||
uint8_t rollType = 0; // 0=need,1=greed,2=disenchant,96=pass
|
||||
};
|
||||
std::vector<PlayerRollResult> playerRolls; // live roll results from group members
|
||||
};
|
||||
bool hasPendingLootRoll() const { return pendingLootRollActive_; }
|
||||
const LootRollEntry& getPendingLootRoll() const { return pendingLootRoll_; }
|
||||
|
|
@ -1305,7 +1215,6 @@ public:
|
|||
};
|
||||
const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; }
|
||||
void abandonQuest(uint32_t questId);
|
||||
void shareQuestWithParty(uint32_t questId); // CMSG_PUSHQUESTTOPARTY
|
||||
bool requestQuestQuery(uint32_t questId, bool force = false);
|
||||
bool isQuestTracked(uint32_t questId) const { return trackedQuestIds_.count(questId) > 0; }
|
||||
void setQuestTracked(uint32_t questId, bool tracked) {
|
||||
|
|
@ -1358,29 +1267,7 @@ public:
|
|||
};
|
||||
const std::vector<FactionStandingInit>& getInitialFactions() const { return initialFactions_; }
|
||||
const std::unordered_map<uint32_t, int32_t>& getFactionStandings() const { return factionStandings_; }
|
||||
// Shaman totems (4 slots: 0=Earth, 1=Fire, 2=Water, 3=Air)
|
||||
struct TotemSlot {
|
||||
uint32_t spellId = 0;
|
||||
uint32_t durationMs = 0;
|
||||
std::chrono::steady_clock::time_point placedAt{};
|
||||
bool active() const { return spellId != 0 && remainingMs() > 0; }
|
||||
float remainingMs() const {
|
||||
if (spellId == 0 || durationMs == 0) return 0.0f;
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - placedAt).count();
|
||||
float rem = static_cast<float>(durationMs) - static_cast<float>(elapsed);
|
||||
return rem > 0.0f ? rem : 0.0f;
|
||||
}
|
||||
};
|
||||
static constexpr int NUM_TOTEM_SLOTS = 4;
|
||||
const TotemSlot& getTotemSlot(int slot) const {
|
||||
static TotemSlot empty;
|
||||
return (slot >= 0 && slot < NUM_TOTEM_SLOTS) ? activeTotemSlots_[slot] : empty;
|
||||
}
|
||||
|
||||
const std::string& getFactionNamePublic(uint32_t factionId) const;
|
||||
uint32_t getWatchedFactionId() const { return watchedFactionId_; }
|
||||
void setWatchedFactionId(uint32_t id) { watchedFactionId_ = id; }
|
||||
uint32_t getLastContactListMask() const { return lastContactListMask_; }
|
||||
uint32_t getLastContactListCount() const { return lastContactListCount_; }
|
||||
bool isServerMovementAllowed() const { return serverMovementAllowed_; }
|
||||
|
|
@ -1410,11 +1297,6 @@ public:
|
|||
void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); }
|
||||
const std::unordered_set<uint32_t>& getEarnedAchievements() const { return earnedAchievements_; }
|
||||
const std::unordered_map<uint32_t, uint64_t>& getCriteriaProgress() const { return criteriaProgress_; }
|
||||
/// Returns the WoW PackedTime earn date for an achievement, or 0 if unknown.
|
||||
uint32_t getAchievementDate(uint32_t id) const {
|
||||
auto it = achievementDates_.find(id);
|
||||
return (it != achievementDates_.end()) ? it->second : 0u;
|
||||
}
|
||||
/// Returns the name of an achievement by ID, or empty string if unknown.
|
||||
const std::string& getAchievementName(uint32_t id) const {
|
||||
auto it = achievementNameCache_.find(id);
|
||||
|
|
@ -1448,10 +1330,6 @@ public:
|
|||
using RepChangeCallback = std::function<void(const std::string& factionName, int32_t delta, int32_t standing)>;
|
||||
void setRepChangeCallback(RepChangeCallback cb) { repChangeCallback_ = std::move(cb); }
|
||||
|
||||
// Quest turn-in completion callback
|
||||
using QuestCompleteCallback = std::function<void(uint32_t questId, const std::string& questTitle)>;
|
||||
void setQuestCompleteCallback(QuestCompleteCallback cb) { questCompleteCallback_ = std::move(cb); }
|
||||
|
||||
// Mount state
|
||||
using MountCallback = std::function<void(uint32_t mountDisplayId)>; // 0 = dismount
|
||||
void setMountCallback(MountCallback cb) { mountCallback_ = std::move(cb); }
|
||||
|
|
@ -1661,8 +1539,6 @@ public:
|
|||
const std::string& getSpellName(uint32_t spellId) const;
|
||||
const std::string& getSpellRank(uint32_t spellId) const;
|
||||
const std::string& getSkillLineName(uint32_t spellId) const;
|
||||
/// Returns the DispelType for a spell (0=none,1=magic,2=curse,3=disease,4=poison,5+=other)
|
||||
uint8_t getSpellDispelType(uint32_t spellId) const;
|
||||
|
||||
struct TrainerTab {
|
||||
std::string name;
|
||||
|
|
@ -1943,7 +1819,6 @@ private:
|
|||
void handleArenaTeamEvent(network::Packet& packet);
|
||||
void handleArenaTeamStats(network::Packet& packet);
|
||||
void handleArenaError(network::Packet& packet);
|
||||
void handlePvpLogData(network::Packet& packet);
|
||||
|
||||
// ---- Bank handlers ----
|
||||
void handleShowBank(network::Packet& packet);
|
||||
|
|
@ -2190,9 +2065,6 @@ private:
|
|||
float autoAttackFacingSyncTimer_ = 0.0f; // Periodic facing sync while meleeing
|
||||
std::unordered_set<uint64_t> hostileAttackers_;
|
||||
std::vector<CombatTextEntry> combatText;
|
||||
static constexpr size_t MAX_COMBAT_LOG = 500;
|
||||
std::deque<CombatLogEntry> combatLog_;
|
||||
std::deque<std::string> areaTriggerMsgs_;
|
||||
// unitGuid → sorted threat list (descending by threat value)
|
||||
std::unordered_map<uint64_t, std::vector<ThreatEntry>> threatLists_;
|
||||
|
||||
|
|
@ -2275,7 +2147,6 @@ private:
|
|||
std::array<ActionBarSlot, ACTION_BAR_SLOTS> actionBar{};
|
||||
std::vector<AuraSlot> playerAuras;
|
||||
std::vector<AuraSlot> targetAuras;
|
||||
std::unordered_map<uint64_t, std::vector<AuraSlot>> unitAurasCache_; // per-unit aura cache
|
||||
uint64_t petGuid_ = 0;
|
||||
uint32_t petActionSlots_[10] = {}; // SMSG_PET_SPELLS action bar (10 slots)
|
||||
uint8_t petCommand_ = 1; // 0=stay,1=follow,2=attack,3=dismiss
|
||||
|
|
@ -2306,9 +2177,6 @@ private:
|
|||
// Arena team stats (indexed by team slot, updated by SMSG_ARENA_TEAM_STATS)
|
||||
std::vector<ArenaTeamStats> arenaTeamStats_;
|
||||
|
||||
// BG scoreboard (MSG_PVP_LOG_DATA)
|
||||
BgScoreboardData bgScoreboard_;
|
||||
|
||||
// Instance encounter boss units (slots 0-4 from SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT)
|
||||
std::array<uint64_t, kMaxEncounterSlots> encounterUnitGuids_ = {}; // 0 = empty slot
|
||||
|
||||
|
|
@ -2322,15 +2190,12 @@ private:
|
|||
uint32_t lfgBootTotal_ = 0; // total votes cast
|
||||
uint32_t lfgBootTimeLeft_ = 0; // seconds remaining
|
||||
uint32_t lfgBootNeeded_ = 0; // votes needed to kick
|
||||
std::string lfgBootTargetName_; // name of player being voted on
|
||||
std::string lfgBootReason_; // reason given for kick
|
||||
|
||||
// Ready check state
|
||||
bool pendingReadyCheck_ = false;
|
||||
uint32_t readyCheckReadyCount_ = 0;
|
||||
uint32_t readyCheckNotReadyCount_ = 0;
|
||||
std::string readyCheckInitiator_;
|
||||
std::vector<ReadyCheckResult> readyCheckResults_; // per-player status live during check
|
||||
|
||||
// Faction standings (factionId → absolute standing value)
|
||||
std::unordered_map<uint32_t, int32_t> factionStandings_;
|
||||
|
|
@ -2364,10 +2229,6 @@ private:
|
|||
uint32_t totalTimePlayed_ = 0;
|
||||
uint32_t levelTimePlayed_ = 0;
|
||||
|
||||
// Who results (last SMSG_WHO response)
|
||||
std::vector<WhoEntry> whoResults_;
|
||||
uint32_t whoOnlineCount_ = 0;
|
||||
|
||||
// Trade state
|
||||
TradeStatus tradeStatus_ = TradeStatus::None;
|
||||
uint64_t tradePeerGuid_= 0;
|
||||
|
|
@ -2377,16 +2238,11 @@ private:
|
|||
uint64_t myTradeGold_ = 0;
|
||||
uint64_t peerTradeGold_ = 0;
|
||||
|
||||
// Shaman totem state
|
||||
TotemSlot activeTotemSlots_[NUM_TOTEM_SLOTS];
|
||||
|
||||
// Duel state
|
||||
bool pendingDuelRequest_ = false;
|
||||
uint64_t duelChallengerGuid_= 0;
|
||||
uint64_t duelFlagGuid_ = 0;
|
||||
std::string duelChallengerName_;
|
||||
uint32_t duelCountdownMs_ = 0; // 0 = no active countdown
|
||||
std::chrono::steady_clock::time_point duelCountdownStartedAt_{};
|
||||
|
||||
// ---- Guild state ----
|
||||
std::string guildName_;
|
||||
|
|
@ -2578,7 +2434,7 @@ private:
|
|||
// Trainer
|
||||
bool trainerWindowOpen_ = false;
|
||||
TrainerListData currentTrainerList_;
|
||||
struct SpellNameEntry { std::string name; std::string rank; uint32_t schoolMask = 0; uint8_t dispelType = 0; };
|
||||
struct SpellNameEntry { std::string name; std::string rank; uint32_t schoolMask = 0; };
|
||||
std::unordered_map<uint32_t, SpellNameEntry> spellNameCache_;
|
||||
bool spellNameCacheLoaded_ = false;
|
||||
|
||||
|
|
@ -2588,8 +2444,6 @@ private:
|
|||
void loadAchievementNameCache();
|
||||
// Set of achievement IDs earned by the player (populated from SMSG_ALL_ACHIEVEMENT_DATA)
|
||||
std::unordered_set<uint32_t> earnedAchievements_;
|
||||
// Earn dates: achievementId → WoW PackedTime (from SMSG_ACHIEVEMENT_EARNED / SMSG_ALL_ACHIEVEMENT_DATA)
|
||||
std::unordered_map<uint32_t, uint32_t> achievementDates_;
|
||||
// Criteria progress: criteriaId → current value (from SMSG_CRITERIA_UPDATE)
|
||||
std::unordered_map<uint32_t, uint64_t> criteriaProgress_;
|
||||
void handleAllAchievementData(network::Packet& packet);
|
||||
|
|
@ -2664,10 +2518,6 @@ private:
|
|||
float timeSpeed_ = 0.0166f; // Time scale (default: 1 game day = 1 real hour)
|
||||
void handleLoginSetTimeSpeed(network::Packet& packet);
|
||||
|
||||
// ---- Global Cooldown (GCD) ----
|
||||
float gcdTotal_ = 0.0f;
|
||||
std::chrono::steady_clock::time_point gcdStartedAt_{};
|
||||
|
||||
// ---- Weather state (SMSG_WEATHER) ----
|
||||
uint32_t weatherType_ = 0; // 0=clear, 1=rain, 2=snow, 3=storm
|
||||
float weatherIntensity_ = 0.0f; // 0.0 to 1.0
|
||||
|
|
@ -2780,10 +2630,6 @@ private:
|
|||
|
||||
// ---- Reputation change callback ----
|
||||
RepChangeCallback repChangeCallback_;
|
||||
uint32_t watchedFactionId_ = 0; // auto-set to most recently changed faction
|
||||
|
||||
// ---- Quest completion callback ----
|
||||
QuestCompleteCallback questCompleteCallback_;
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -52,7 +51,7 @@ struct CombatTextEntry {
|
|||
enum Type : uint8_t {
|
||||
MELEE_DAMAGE, SPELL_DAMAGE, HEAL, MISS, DODGE, PARRY, BLOCK,
|
||||
CRIT_DAMAGE, CRIT_HEAL, PERIODIC_DAMAGE, PERIODIC_HEAL, ENVIRONMENTAL,
|
||||
ENERGIZE, XP_GAIN, IMMUNE, ABSORB, RESIST, PROC_TRIGGER
|
||||
ENERGIZE, XP_GAIN, IMMUNE, ABSORB, RESIST
|
||||
};
|
||||
Type type;
|
||||
int32_t amount = 0;
|
||||
|
|
@ -64,19 +63,6 @@ struct CombatTextEntry {
|
|||
bool isExpired() const { return age >= LIFETIME; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Persistent combat log entry (stored in a rolling deque, survives beyond floating-text lifetime)
|
||||
*/
|
||||
struct CombatLogEntry {
|
||||
CombatTextEntry::Type type = CombatTextEntry::MELEE_DAMAGE;
|
||||
int32_t amount = 0;
|
||||
uint32_t spellId = 0;
|
||||
bool isPlayerSource = false;
|
||||
time_t timestamp = 0; // Wall-clock time (std::time(nullptr))
|
||||
std::string sourceName; // Resolved display name of attacker/caster
|
||||
std::string targetName; // Resolved display name of victim/target
|
||||
};
|
||||
|
||||
/**
|
||||
* Spell cooldown entry received from server
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -46,32 +46,21 @@ private:
|
|||
char chatInputBuffer[512] = "";
|
||||
char whisperTargetBuffer[256] = "";
|
||||
bool chatInputActive = false;
|
||||
int selectedChatType = 0; // 0=SAY, 1=YELL, 2=PARTY, 3=GUILD, 4=WHISPER, ..., 10=CHANNEL
|
||||
int selectedChatType = 0; // 0=SAY, 1=YELL, 2=PARTY, 3=GUILD, 4=WHISPER
|
||||
int lastChatType = 0; // Track chat type changes
|
||||
int selectedChannelIdx = 0; // Index into joinedChannels_ when selectedChatType==10
|
||||
bool chatInputMoveCursorToEnd = false;
|
||||
|
||||
// Chat sent-message history (Up/Down arrow recall)
|
||||
std::vector<std::string> chatSentHistory_;
|
||||
int chatHistoryIdx_ = -1; // -1 = not browsing history
|
||||
|
||||
// Tab-completion state for slash commands
|
||||
std::string chatTabPrefix_; // prefix captured on first Tab press
|
||||
std::vector<std::string> chatTabMatches_; // matching command list
|
||||
int chatTabMatchIdx_ = -1; // active match index (-1 = inactive)
|
||||
|
||||
// Mention notification: plays a sound when the player's name appears in chat
|
||||
size_t chatMentionSeenCount_ = 0; // how many messages have been scanned for mentions
|
||||
|
||||
// Chat tabs
|
||||
int activeChatTab_ = 0;
|
||||
struct ChatTab {
|
||||
std::string name;
|
||||
uint64_t typeMask; // bitmask of ChatType values to show (64-bit: types go up to 84)
|
||||
uint32_t typeMask; // bitmask of ChatType values to show
|
||||
};
|
||||
std::vector<ChatTab> chatTabs_;
|
||||
std::vector<int> chatTabUnread_; // unread message count per tab (0 = none)
|
||||
size_t chatTabSeenCount_ = 0; // how many history messages have been processed
|
||||
void initChatTabs();
|
||||
bool shouldShowMessage(const game::MessageChatData& msg, int tabIndex) const;
|
||||
|
||||
|
|
@ -86,7 +75,6 @@ private:
|
|||
uint32_t lastPlayerHp_ = 0; // Previous frame HP for damage flash detection
|
||||
float damageFlashAlpha_ = 0.0f; // Screen edge flash intensity (fades to 0)
|
||||
bool damageFlashEnabled_ = true;
|
||||
bool lowHealthVignetteEnabled_ = true; // Persistent pulsing red vignette below 20% HP
|
||||
float levelUpFlashAlpha_ = 0.0f; // Golden level-up burst effect (fades to 0)
|
||||
uint32_t levelUpDisplayLevel_ = 0; // Level shown in level-up text
|
||||
|
||||
|
|
@ -112,29 +100,6 @@ private:
|
|||
std::vector<RepToastEntry> repToasts_;
|
||||
bool repChangeCallbackSet_ = false;
|
||||
static constexpr float kRepToastLifetime = 3.5f;
|
||||
|
||||
// Quest completion toast: slide-in when a quest is turned in
|
||||
struct QuestCompleteToastEntry { uint32_t questId = 0; std::string title; float age = 0.0f; };
|
||||
std::vector<QuestCompleteToastEntry> questCompleteToasts_;
|
||||
bool questCompleteCallbackSet_ = false;
|
||||
static constexpr float kQuestCompleteToastLifetime = 4.0f;
|
||||
|
||||
// Zone entry toast: brief banner when entering a new zone
|
||||
struct ZoneToastEntry { std::string zoneName; float age = 0.0f; };
|
||||
std::vector<ZoneToastEntry> zoneToasts_;
|
||||
|
||||
struct AreaTriggerToast { std::string text; float age = 0.0f; };
|
||||
std::vector<AreaTriggerToast> areaTriggerToasts_;
|
||||
void renderAreaTriggerToasts(float deltaTime, game::GameHandler& gameHandler);
|
||||
std::string lastKnownZone_;
|
||||
static constexpr float kZoneToastLifetime = 3.0f;
|
||||
|
||||
// Death screen: elapsed time since the death dialog first appeared
|
||||
float deathElapsed_ = 0.0f;
|
||||
bool deathTimerRunning_ = false;
|
||||
// WoW forces release after ~6 minutes; show countdown until then
|
||||
static constexpr float kForcedReleaseSec = 360.0f;
|
||||
void renderZoneToasts(float deltaTime);
|
||||
bool showPlayerInfo = false;
|
||||
bool showSocialFrame_ = false; // O key toggles social/friends list
|
||||
bool showGuildRoster_ = false;
|
||||
|
|
@ -290,7 +255,6 @@ private:
|
|||
* Render pet frame (below player frame when player has an active pet)
|
||||
*/
|
||||
void renderPetFrame(game::GameHandler& gameHandler);
|
||||
void renderTotemFrame(game::GameHandler& gameHandler);
|
||||
|
||||
/**
|
||||
* Process targeting input (Tab, Escape, click)
|
||||
|
|
@ -311,7 +275,6 @@ private:
|
|||
void renderActionBar(game::GameHandler& gameHandler);
|
||||
void renderBagBar(game::GameHandler& gameHandler);
|
||||
void renderXpBar(game::GameHandler& gameHandler);
|
||||
void renderRepBar(game::GameHandler& gameHandler);
|
||||
void renderCastBar(game::GameHandler& gameHandler);
|
||||
void renderMirrorTimers(game::GameHandler& gameHandler);
|
||||
void renderCombatText(game::GameHandler& gameHandler);
|
||||
|
|
@ -320,10 +283,8 @@ private:
|
|||
void renderBossFrames(game::GameHandler& gameHandler);
|
||||
void renderUIErrors(game::GameHandler& gameHandler, float deltaTime);
|
||||
void renderRepToasts(float deltaTime);
|
||||
void renderQuestCompleteToasts(float deltaTime);
|
||||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
||||
void renderDuelCountdown(game::GameHandler& gameHandler);
|
||||
void renderLootRollPopup(game::GameHandler& gameHandler);
|
||||
void renderTradeRequestPopup(game::GameHandler& gameHandler);
|
||||
void renderTradeWindow(game::GameHandler& gameHandler);
|
||||
|
|
@ -403,14 +364,6 @@ private:
|
|||
int bagBarPickedSlot_ = -1; // Visual drag in progress (-1 = none)
|
||||
int bagBarDragSource_ = -1; // Mouse pressed on this slot, waiting for drag or click (-1 = none)
|
||||
|
||||
// Who Results window
|
||||
bool showWhoWindow_ = false;
|
||||
void renderWhoWindow(game::GameHandler& gameHandler);
|
||||
|
||||
// Combat Log window
|
||||
bool showCombatLog_ = false;
|
||||
void renderCombatLog(game::GameHandler& gameHandler);
|
||||
|
||||
// Instance Lockouts window
|
||||
bool showInstanceLockouts_ = false;
|
||||
|
||||
|
|
@ -434,10 +387,6 @@ private:
|
|||
// Threat window
|
||||
bool showThreatWindow_ = false;
|
||||
void renderThreatWindow(game::GameHandler& gameHandler);
|
||||
|
||||
// BG scoreboard window
|
||||
bool showBgScoreboard_ = false;
|
||||
void renderBgScoreboard(game::GameHandler& gameHandler);
|
||||
uint8_t lfgRoles_ = 0x08; // default: DPS (0x02=tank, 0x04=healer, 0x08=dps)
|
||||
uint32_t lfgSelectedDungeon_ = 861; // default: random dungeon (entry 861 = Random Dungeon WotLK)
|
||||
|
||||
|
|
@ -528,9 +477,6 @@ private:
|
|||
bool showDPSMeter_ = false;
|
||||
float dpsCombatAge_ = 0.0f; // seconds in current combat (for accurate early-combat DPS)
|
||||
bool dpsWasInCombat_ = false;
|
||||
float dpsEncounterDamage_ = 0.0f; // total player damage this combat
|
||||
float dpsEncounterHeal_ = 0.0f; // total player healing this combat
|
||||
size_t dpsLogSeenCount_ = 0; // log entries already scanned
|
||||
|
||||
public:
|
||||
void triggerDing(uint32_t newLevel);
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ private:
|
|||
std::unordered_map<uint32_t, VkDescriptorSet> iconCache_;
|
||||
public:
|
||||
VkDescriptorSet getItemIcon(uint32_t displayInfoId);
|
||||
void renderItemTooltip(const game::ItemQueryResponseData& info, const game::Inventory* inventory = nullptr);
|
||||
void renderItemTooltip(const game::ItemQueryResponseData& info);
|
||||
private:
|
||||
|
||||
// Character model preview
|
||||
|
|
|
|||
|
|
@ -54,16 +54,6 @@ public:
|
|||
uint32_t getDragSpellId() const { return dragSpellId_; }
|
||||
void consumeDragSpell() { draggingSpell_ = false; dragSpellId_ = 0; dragSpellIconTex_ = VK_NULL_HANDLE; }
|
||||
|
||||
/// Returns the max range in yards for a spell (0 if self-cast, unknown, or melee).
|
||||
/// Triggers DBC load if needed. Used by the action bar for out-of-range tinting.
|
||||
uint32_t getSpellMaxRange(uint32_t spellId, pipeline::AssetManager* assetManager);
|
||||
|
||||
/// Returns the power cost and type for a spell (cost=0 if unknown/free).
|
||||
/// powerType: 0=mana, 1=rage, 2=focus, 3=energy, 6=runic power.
|
||||
/// Triggers DBC load if needed. Used by the action bar for insufficient-power tinting.
|
||||
void getSpellPowerInfo(uint32_t spellId, pipeline::AssetManager* assetManager,
|
||||
uint32_t& outCost, uint32_t& outPowerType);
|
||||
|
||||
/// Returns a WoW spell link string if the user shift-clicked a spell, then clears it.
|
||||
std::string getAndClearPendingChatLink() {
|
||||
std::string out = std::move(pendingChatSpellLink_);
|
||||
|
|
|
|||
|
|
@ -122,14 +122,6 @@ bool UiSoundManager::initialize(pipeline::AssetManager* assets) {
|
|||
deselectTargetSounds_.resize(1);
|
||||
loadSound("Sound\\Interface\\iDeselectTarget.wav", deselectTargetSounds_[0], assets);
|
||||
|
||||
// Whisper notification (falls back to iSelectTarget if the dedicated file is absent)
|
||||
whisperSounds_.resize(1);
|
||||
if (!loadSound("Sound\\Interface\\Whisper_TellMale.wav", whisperSounds_[0], assets)) {
|
||||
if (!loadSound("Sound\\Interface\\Whisper_TellFemale.wav", whisperSounds_[0], assets)) {
|
||||
whisperSounds_ = selectTargetSounds_;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("UISoundManager: Window sounds - Bag: ", (bagOpenLoaded && bagCloseLoaded) ? "YES" : "NO",
|
||||
", QuestLog: ", (questLogOpenLoaded && questLogCloseLoaded) ? "YES" : "NO",
|
||||
", CharSheet: ", (charSheetOpenLoaded && charSheetCloseLoaded) ? "YES" : "NO");
|
||||
|
|
@ -233,8 +225,5 @@ void UiSoundManager::playError() { playSound(errorSounds_); }
|
|||
void UiSoundManager::playTargetSelect() { playSound(selectTargetSounds_); }
|
||||
void UiSoundManager::playTargetDeselect() { playSound(deselectTargetSounds_); }
|
||||
|
||||
// Chat notifications
|
||||
void UiSoundManager::playWhisperReceived() { playSound(whisperSounds_); }
|
||||
|
||||
} // namespace audio
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -2028,7 +2028,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
/*uint32_t randSuffix =*/ packet.readUInt32();
|
||||
/*uint32_t randProp =*/ packet.readUInt32();
|
||||
}
|
||||
uint32_t countdown = packet.readUInt32();
|
||||
/*uint32_t countdown =*/ packet.readUInt32();
|
||||
/*uint8_t voteMask =*/ packet.readUInt8();
|
||||
// Trigger the roll popup for local player
|
||||
pendingLootRollActive_ = true;
|
||||
|
|
@ -2038,8 +2038,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
auto* info = getItemInfo(itemId);
|
||||
pendingLootRoll_.itemName = info ? info->name : std::to_string(itemId);
|
||||
pendingLootRoll_.itemQuality = info ? static_cast<uint8_t>(info->quality) : 0;
|
||||
pendingLootRoll_.rollCountdownMs = (countdown > 0 && countdown <= 120000) ? countdown : 60000;
|
||||
pendingLootRoll_.rollStartedAt = std::chrono::steady_clock::now();
|
||||
LOG_INFO("SMSG_LOOT_START_ROLL: item=", itemId, " (", pendingLootRoll_.itemName,
|
||||
") slot=", slot);
|
||||
break;
|
||||
|
|
@ -3061,11 +3059,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
uint32_t spellId = packet.readUInt32();
|
||||
LOG_DEBUG("SMSG_TOTEM_CREATED: slot=", (int)slot,
|
||||
" spellId=", spellId, " duration=", duration, "ms");
|
||||
if (slot < NUM_TOTEM_SLOTS) {
|
||||
activeTotemSlots_[slot].spellId = spellId;
|
||||
activeTotemSlots_[slot].durationMs = duration;
|
||||
activeTotemSlots_[slot].placedAt = std::chrono::steady_clock::now();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_AREA_SPIRIT_HEALER_TIME: {
|
||||
|
|
@ -3149,7 +3142,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
readyCheckReadyCount_ = 0;
|
||||
readyCheckNotReadyCount_ = 0;
|
||||
readyCheckInitiator_.clear();
|
||||
readyCheckResults_.clear();
|
||||
if (packet.getSize() - packet.getReadPos() >= 8) {
|
||||
uint64_t initiatorGuid = packet.readUInt64();
|
||||
auto entity = entityManager.getEntity(initiatorGuid);
|
||||
|
|
@ -3183,14 +3175,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
auto ent = entityManager.getEntity(respGuid);
|
||||
if (ent) rname = std::static_pointer_cast<game::Unit>(ent)->getName();
|
||||
}
|
||||
// Track per-player result for live popup display
|
||||
if (!rname.empty()) {
|
||||
bool found = false;
|
||||
for (auto& r : readyCheckResults_) {
|
||||
if (r.name == rname) { r.ready = (isReady != 0); found = true; break; }
|
||||
}
|
||||
if (!found) readyCheckResults_.push_back({ rname, isReady != 0 });
|
||||
|
||||
char rbuf[128];
|
||||
std::snprintf(rbuf, sizeof(rbuf), "%s is %s.", rname.c_str(), isReady ? "Ready" : "Not Ready");
|
||||
addSystemChatMessage(rbuf);
|
||||
|
|
@ -3206,7 +3191,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
pendingReadyCheck_ = false;
|
||||
readyCheckReadyCount_ = 0;
|
||||
readyCheckNotReadyCount_ = 0;
|
||||
readyCheckResults_.clear();
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_RAID_INSTANCE_INFO:
|
||||
|
|
@ -3228,16 +3212,9 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
case Opcode::SMSG_DUEL_INBOUNDS:
|
||||
// Re-entered the duel area; no special action needed.
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_COUNTDOWN: {
|
||||
// uint32 countdown in milliseconds (typically 3000 ms)
|
||||
if (packet.getSize() - packet.getReadPos() >= 4) {
|
||||
uint32_t ms = packet.readUInt32();
|
||||
duelCountdownMs_ = (ms > 0 && ms <= 30000) ? ms : 3000;
|
||||
duelCountdownStartedAt_ = std::chrono::steady_clock::now();
|
||||
LOG_INFO("SMSG_DUEL_COUNTDOWN: ", duelCountdownMs_, " ms");
|
||||
}
|
||||
case Opcode::SMSG_DUEL_COUNTDOWN:
|
||||
// Countdown timer — no action needed; server also sends UNIT_FIELD_FLAGS update.
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_PARTYKILLLOG: {
|
||||
// uint64 killerGuid + uint64 victimGuid
|
||||
if (packet.getSize() - packet.getReadPos() < 16) break;
|
||||
|
|
@ -3567,7 +3544,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
delta > 0 ? "increased" : "decreased",
|
||||
std::abs(delta));
|
||||
addSystemChatMessage(buf);
|
||||
watchedFactionId_ = factionId;
|
||||
if (repChangeCallback_) repChangeCallback_(name, delta, standing);
|
||||
}
|
||||
LOG_DEBUG("SMSG_SET_FACTION_STANDING: faction=", factionId, " standing=", standing);
|
||||
|
|
@ -3877,10 +3853,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
if (packet.getSize() - packet.getReadPos() >= 4) {
|
||||
/*uint32_t len =*/ packet.readUInt32();
|
||||
std::string msg = packet.readString();
|
||||
if (!msg.empty()) {
|
||||
addSystemChatMessage(msg);
|
||||
areaTriggerMsgs_.push_back(msg);
|
||||
}
|
||||
if (!msg.empty()) addSystemChatMessage(msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -4408,10 +4381,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
for (auto it = questLog_.begin(); it != questLog_.end(); ++it) {
|
||||
if (it->questId == questId) {
|
||||
// Fire toast callback before erasing
|
||||
if (questCompleteCallback_) {
|
||||
questCompleteCallback_(questId, it->title);
|
||||
}
|
||||
questLog_.erase(it);
|
||||
LOG_INFO(" Removed quest ", questId, " from quest log");
|
||||
break;
|
||||
|
|
@ -4988,7 +4957,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
handleArenaError(packet);
|
||||
break;
|
||||
case Opcode::MSG_PVP_LOG_DATA:
|
||||
handlePvpLogData(packet);
|
||||
LOG_INFO("Received MSG_PVP_LOG_DATA");
|
||||
break;
|
||||
case Opcode::MSG_INSPECT_ARENA_TEAMS:
|
||||
LOG_INFO("Received MSG_INSPECT_ARENA_TEAMS");
|
||||
|
|
@ -5207,7 +5176,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
std::vector<AuraSlot>* auraList = nullptr;
|
||||
if (auraTargetGuid == playerGuid) auraList = &playerAuras;
|
||||
else if (auraTargetGuid == targetGuid) auraList = &targetAuras;
|
||||
else if (auraTargetGuid != 0) auraList = &unitAurasCache_[auraTargetGuid];
|
||||
|
||||
if (auraList && isInit) auraList->clear();
|
||||
|
||||
|
|
@ -5591,28 +5559,9 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_SPELL_CHANCE_PROC_LOG: {
|
||||
// Format (all expansions): PackedGuid target + PackedGuid caster + uint32 spellId + ...
|
||||
if (packet.getSize() - packet.getReadPos() < 3) {
|
||||
packet.setReadPos(packet.getSize()); break;
|
||||
}
|
||||
/*uint64_t targetGuid =*/ UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 2) {
|
||||
packet.setReadPos(packet.getSize()); break;
|
||||
}
|
||||
uint64_t procCasterGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 4) {
|
||||
packet.setReadPos(packet.getSize()); break;
|
||||
}
|
||||
uint32_t procSpellId = packet.readUInt32();
|
||||
// Show a "PROC!" floating text when the player triggers the proc
|
||||
if (procCasterGuid == playerGuid && procSpellId > 0)
|
||||
addCombatText(CombatTextEntry::PROC_TRIGGER, 0, procSpellId, true);
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_SPELLINSTAKILLLOG:
|
||||
case Opcode::SMSG_SPELLLOGEXECUTE:
|
||||
case Opcode::SMSG_SPELL_CHANCE_PROC_LOG:
|
||||
case Opcode::SMSG_SPELL_CHANCE_RESIST_PUSHBACK:
|
||||
case Opcode::SMSG_SPELL_UPDATE_CHAIN_TARGETS:
|
||||
packet.setReadPos(packet.getSize());
|
||||
|
|
@ -6709,7 +6658,6 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
|
|||
actionBar = {};
|
||||
playerAuras.clear();
|
||||
targetAuras.clear();
|
||||
unitAurasCache_.clear();
|
||||
unitCastStates_.clear();
|
||||
petGuid_ = 0;
|
||||
playerXp_ = 0;
|
||||
|
|
@ -10261,15 +10209,6 @@ void GameHandler::followTarget() {
|
|||
LOG_INFO("Following target: ", targetName, " (GUID: 0x", std::hex, targetGuid, std::dec, ")");
|
||||
}
|
||||
|
||||
void GameHandler::cancelFollow() {
|
||||
if (followTargetGuid_ == 0) {
|
||||
addSystemChatMessage("You are not following anyone.");
|
||||
return;
|
||||
}
|
||||
followTargetGuid_ = 0;
|
||||
addSystemChatMessage("You stop following.");
|
||||
}
|
||||
|
||||
void GameHandler::assistTarget() {
|
||||
if (state != WorldState::IN_WORLD) {
|
||||
LOG_WARNING("Cannot assist: not in world");
|
||||
|
|
@ -10526,7 +10465,6 @@ void GameHandler::handleDuelComplete(network::Packet& packet) {
|
|||
uint8_t started = packet.readUInt8();
|
||||
// started=1: duel began, started=0: duel was cancelled before starting
|
||||
pendingDuelRequest_ = false;
|
||||
duelCountdownMs_ = 0; // clear countdown once duel is resolved
|
||||
if (!started) {
|
||||
addSystemChatMessage("The duel was cancelled.");
|
||||
}
|
||||
|
|
@ -11362,7 +11300,6 @@ void GameHandler::handleInspectResults(network::Packet& packet) {
|
|||
}
|
||||
|
||||
// Parse enchantment slot mask + enchant IDs
|
||||
std::array<uint16_t, 19> enchantIds{};
|
||||
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||
if (bytesLeft >= 4) {
|
||||
uint32_t slotMask = packet.readUInt32();
|
||||
|
|
@ -11370,7 +11307,7 @@ void GameHandler::handleInspectResults(network::Packet& packet) {
|
|||
if (slotMask & (1u << slot)) {
|
||||
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||
if (bytesLeft < 2) break;
|
||||
enchantIds[slot] = packet.readUInt16();
|
||||
packet.readUInt16(); // enchantId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11382,7 +11319,6 @@ void GameHandler::handleInspectResults(network::Packet& packet) {
|
|||
inspectResult_.unspentTalents = unspentTalents;
|
||||
inspectResult_.talentGroups = talentGroupCount;
|
||||
inspectResult_.activeTalentGroup = activeTalentGroup;
|
||||
inspectResult_.enchantIds = enchantIds;
|
||||
|
||||
// Merge any gear we already have from a prior inspect request
|
||||
auto gearIt = inspectedPlayerItemEntries_.find(guid);
|
||||
|
|
@ -12165,21 +12101,6 @@ void GameHandler::addCombatText(CombatTextEntry::Type type, int32_t amount, uint
|
|||
entry.age = 0.0f;
|
||||
entry.isPlayerSource = isPlayerSource;
|
||||
combatText.push_back(entry);
|
||||
|
||||
// Persistent combat log
|
||||
CombatLogEntry log;
|
||||
log.type = type;
|
||||
log.amount = amount;
|
||||
log.spellId = spellId;
|
||||
log.isPlayerSource = isPlayerSource;
|
||||
log.timestamp = std::time(nullptr);
|
||||
std::string pname(lookupName(playerGuid));
|
||||
std::string tname((targetGuid != 0) ? lookupName(targetGuid) : std::string());
|
||||
log.sourceName = isPlayerSource ? pname : tname;
|
||||
log.targetName = isPlayerSource ? tname : pname;
|
||||
if (combatLog_.size() >= MAX_COMBAT_LOG)
|
||||
combatLog_.pop_front();
|
||||
combatLog_.push_back(std::move(log));
|
||||
}
|
||||
|
||||
void GameHandler::updateCombatText(float deltaTime) {
|
||||
|
|
@ -13140,19 +13061,11 @@ void GameHandler::handleLfgBootProposalUpdate(network::Packet& packet) {
|
|||
lfgBootTimeLeft_ = timeLeft;
|
||||
lfgBootNeeded_ = votesNeeded;
|
||||
|
||||
// Optional: reason string and target name (null-terminated) follow the fixed fields
|
||||
if (packet.getSize() - packet.getReadPos() > 0)
|
||||
lfgBootReason_ = packet.readString();
|
||||
if (packet.getSize() - packet.getReadPos() > 0)
|
||||
lfgBootTargetName_ = packet.readString();
|
||||
|
||||
if (inProgress) {
|
||||
lfgState_ = LfgState::Boot;
|
||||
} else {
|
||||
// Boot vote ended — return to InDungeon state regardless of outcome
|
||||
lfgBootVotes_ = lfgBootTotal_ = lfgBootTimeLeft_ = lfgBootNeeded_ = 0;
|
||||
lfgBootTargetName_.clear();
|
||||
lfgBootReason_.clear();
|
||||
lfgState_ = LfgState::InDungeon;
|
||||
if (myAnswer) {
|
||||
addSystemChatMessage("Dungeon Finder: Vote kick passed — member removed.");
|
||||
|
|
@ -13162,8 +13075,7 @@ void GameHandler::handleLfgBootProposalUpdate(network::Packet& packet) {
|
|||
}
|
||||
|
||||
LOG_INFO("SMSG_LFG_BOOT_PROPOSAL_UPDATE: inProgress=", inProgress,
|
||||
" bootVotes=", bootVotes, "/", totalVotes,
|
||||
" target=", lfgBootTargetName_, " reason=", lfgBootReason_);
|
||||
" bootVotes=", bootVotes, "/", totalVotes);
|
||||
}
|
||||
|
||||
void GameHandler::handleLfgTeleportDenied(network::Packet& packet) {
|
||||
|
|
@ -13468,80 +13380,6 @@ void GameHandler::handleArenaError(network::Packet& packet) {
|
|||
LOG_INFO("Arena error: ", error, " - ", msg);
|
||||
}
|
||||
|
||||
void GameHandler::requestPvpLog() {
|
||||
if (state != WorldState::IN_WORLD || !socket) return;
|
||||
// MSG_PVP_LOG_DATA is bidirectional: client sends an empty packet to request
|
||||
network::Packet pkt(wireOpcode(Opcode::MSG_PVP_LOG_DATA));
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Requested PvP log data");
|
||||
}
|
||||
|
||||
void GameHandler::handlePvpLogData(network::Packet& packet) {
|
||||
auto remaining = [&]() { return packet.getSize() - packet.getReadPos(); };
|
||||
if (remaining() < 1) return;
|
||||
|
||||
bgScoreboard_ = BgScoreboardData{};
|
||||
bgScoreboard_.isArena = (packet.readUInt8() != 0);
|
||||
|
||||
if (bgScoreboard_.isArena) {
|
||||
// Skip arena-specific header (two teams × (rating change uint32 + name string + 5×uint32))
|
||||
// Rather than hardcoding arena parse we skip gracefully up to playerCount
|
||||
// Each arena team block: uint32 + string + uint32*5 — variable length due to string.
|
||||
// Skip by scanning for the uint32 playerCount heuristically; simply consume rest.
|
||||
packet.setReadPos(packet.getSize());
|
||||
return;
|
||||
}
|
||||
|
||||
if (remaining() < 4) return;
|
||||
uint32_t playerCount = packet.readUInt32();
|
||||
bgScoreboard_.players.reserve(playerCount);
|
||||
|
||||
for (uint32_t i = 0; i < playerCount && remaining() >= 13; ++i) {
|
||||
BgPlayerScore ps;
|
||||
ps.guid = packet.readUInt64();
|
||||
ps.team = packet.readUInt8();
|
||||
ps.killingBlows = packet.readUInt32();
|
||||
ps.honorableKills = packet.readUInt32();
|
||||
ps.deaths = packet.readUInt32();
|
||||
ps.bonusHonor = packet.readUInt32();
|
||||
|
||||
// Resolve player name from entity manager
|
||||
{
|
||||
auto ent = entityManager.getEntity(ps.guid);
|
||||
if (ent && (ent->getType() == game::ObjectType::PLAYER ||
|
||||
ent->getType() == game::ObjectType::UNIT)) {
|
||||
auto u = std::static_pointer_cast<game::Unit>(ent);
|
||||
if (!u->getName().empty()) ps.name = u->getName();
|
||||
}
|
||||
}
|
||||
|
||||
// BG-specific stat blocks: uint32 count + N × (string fieldName + uint32 value)
|
||||
if (remaining() < 4) { bgScoreboard_.players.push_back(std::move(ps)); break; }
|
||||
uint32_t statCount = packet.readUInt32();
|
||||
for (uint32_t s = 0; s < statCount && remaining() >= 5; ++s) {
|
||||
std::string fieldName;
|
||||
while (remaining() > 0) {
|
||||
char c = static_cast<char>(packet.readUInt8());
|
||||
if (c == '\0') break;
|
||||
fieldName += c;
|
||||
}
|
||||
uint32_t val = (remaining() >= 4) ? packet.readUInt32() : 0;
|
||||
ps.bgStats.emplace_back(std::move(fieldName), val);
|
||||
}
|
||||
|
||||
bgScoreboard_.players.push_back(std::move(ps));
|
||||
}
|
||||
|
||||
if (remaining() >= 1) {
|
||||
bgScoreboard_.hasWinner = (packet.readUInt8() != 0);
|
||||
if (bgScoreboard_.hasWinner && remaining() >= 1)
|
||||
bgScoreboard_.winner = packet.readUInt8();
|
||||
}
|
||||
|
||||
LOG_INFO("PvP log: ", bgScoreboard_.players.size(), " players, hasWinner=",
|
||||
bgScoreboard_.hasWinner, " winner=", (int)bgScoreboard_.winner);
|
||||
}
|
||||
|
||||
void GameHandler::handleOtherPlayerMovement(network::Packet& packet) {
|
||||
// Server relays MSG_MOVE_* for other players: packed GUID (WotLK) or full uint64 (TBC/Classic)
|
||||
const bool otherMoveTbc = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
|
|
@ -14261,10 +14099,6 @@ void GameHandler::castSpell(uint32_t spellId, uint64_t targetGuid) {
|
|||
: CastSpellPacket::build(spellId, target, ++castCount);
|
||||
socket->send(packet);
|
||||
LOG_INFO("Casting spell: ", spellId, " on 0x", std::hex, target, std::dec);
|
||||
|
||||
// Optimistically start GCD immediately on cast — server will confirm or override
|
||||
gcdTotal_ = 1.5f;
|
||||
gcdStartedAt_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void GameHandler::cancelCast() {
|
||||
|
|
@ -14623,14 +14457,6 @@ void GameHandler::handleSpellCooldown(network::Packet& packet) {
|
|||
uint32_t cooldownMs = packet.readUInt32();
|
||||
|
||||
float seconds = cooldownMs / 1000.0f;
|
||||
|
||||
// spellId=0 is the Global Cooldown marker (server sends it for GCD triggers)
|
||||
if (spellId == 0 && cooldownMs > 0 && cooldownMs <= 2000) {
|
||||
gcdTotal_ = seconds;
|
||||
gcdStartedAt_ = std::chrono::steady_clock::now();
|
||||
continue;
|
||||
}
|
||||
|
||||
spellCooldowns[spellId] = seconds;
|
||||
for (auto& slot : actionBar) {
|
||||
bool match = (slot.type == ActionBarSlot::SPELL && slot.id == spellId)
|
||||
|
|
@ -14671,10 +14497,6 @@ void GameHandler::handleAuraUpdate(network::Packet& packet, bool isAll) {
|
|||
} else if (data.guid == targetGuid) {
|
||||
auraList = &targetAuras;
|
||||
}
|
||||
// Also maintain a per-unit cache for any unit (party members, etc.)
|
||||
if (data.guid != 0 && data.guid != playerGuid && data.guid != targetGuid) {
|
||||
auraList = &unitAurasCache_[data.guid];
|
||||
}
|
||||
|
||||
if (auraList) {
|
||||
if (isAll) {
|
||||
|
|
@ -15093,34 +14915,20 @@ void GameHandler::handlePartyMemberStats(network::Packet& packet, bool isFull) {
|
|||
if (updateFlags & 0x0200) { // AURAS
|
||||
if (remaining() >= 8) {
|
||||
uint64_t auraMask = packet.readUInt64();
|
||||
// Collect aura updates for this member and store in unitAurasCache_
|
||||
// so party frame debuff dots can use them.
|
||||
std::vector<AuraSlot> newAuras;
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
if (auraMask & (uint64_t(1) << i)) {
|
||||
AuraSlot a;
|
||||
a.level = static_cast<uint8_t>(i); // use slot index
|
||||
if (isWotLK) {
|
||||
// WotLK: uint32 spellId + uint8 auraFlags
|
||||
if (remaining() < 5) break;
|
||||
a.spellId = packet.readUInt32();
|
||||
a.flags = packet.readUInt8();
|
||||
packet.readUInt32();
|
||||
packet.readUInt8();
|
||||
} else {
|
||||
// Classic/TBC: uint16 spellId only; negative auras not indicated here
|
||||
// Classic/TBC: uint16 spellId only
|
||||
if (remaining() < 2) break;
|
||||
a.spellId = packet.readUInt16();
|
||||
// Infer negative/positive from dispel type: non-zero dispel → debuff
|
||||
uint8_t dt = getSpellDispelType(a.spellId);
|
||||
if (dt > 0) a.flags = 0x80; // mark as debuff
|
||||
packet.readUInt16();
|
||||
}
|
||||
if (a.spellId != 0) newAuras.push_back(a);
|
||||
}
|
||||
}
|
||||
// Populate unitAurasCache_ for this member (merge: keep existing per-GUID data
|
||||
// only if we already have a richer source; otherwise replace with stats data)
|
||||
if (memberGuid != 0 && memberGuid != playerGuid && memberGuid != targetGuid) {
|
||||
unitAurasCache_[memberGuid] = std::move(newAuras);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updateFlags & 0x0400) { // PET_GUID
|
||||
|
|
@ -16208,28 +16016,6 @@ void GameHandler::abandonQuest(uint32_t questId) {
|
|||
gossipPois_.end());
|
||||
}
|
||||
|
||||
void GameHandler::shareQuestWithParty(uint32_t questId) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
addSystemChatMessage("Cannot share quest: not in world.");
|
||||
return;
|
||||
}
|
||||
if (!isInGroup()) {
|
||||
addSystemChatMessage("You must be in a group to share a quest.");
|
||||
return;
|
||||
}
|
||||
network::Packet pkt(wireOpcode(Opcode::CMSG_PUSHQUESTTOPARTY));
|
||||
pkt.writeUInt32(questId);
|
||||
socket->send(pkt);
|
||||
// Local feedback: find quest title
|
||||
for (const auto& q : questLog_) {
|
||||
if (q.questId == questId && !q.title.empty()) {
|
||||
addSystemChatMessage("Sharing quest: " + q.title);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addSystemChatMessage("Quest shared.");
|
||||
}
|
||||
|
||||
void GameHandler::handleQuestRequestItems(network::Packet& packet) {
|
||||
QuestRequestItemsData data;
|
||||
if (!QuestRequestItemsParser::parse(packet, data)) {
|
||||
|
|
@ -17146,14 +16932,6 @@ void GameHandler::loadSpellNameCache() {
|
|||
if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { schoolEnumField = f; hasSchoolEnum = true; }
|
||||
}
|
||||
|
||||
// DispelType field (0=none,1=magic,2=curse,3=disease,4=poison,5=stealth,…)
|
||||
uint32_t dispelField = 0xFFFFFFFF;
|
||||
bool hasDispelField = false;
|
||||
if (spellL) {
|
||||
uint32_t f = spellL->field("DispelType");
|
||||
if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { dispelField = f; hasDispelField = true; }
|
||||
}
|
||||
|
||||
uint32_t count = dbc->getRecordCount();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
uint32_t id = dbc->getUInt32(i, spellL ? (*spellL)["ID"] : 0);
|
||||
|
|
@ -17161,7 +16939,7 @@ void GameHandler::loadSpellNameCache() {
|
|||
std::string name = dbc->getString(i, spellL ? (*spellL)["Name"] : 136);
|
||||
std::string rank = dbc->getString(i, spellL ? (*spellL)["Rank"] : 153);
|
||||
if (!name.empty()) {
|
||||
SpellNameEntry entry{std::move(name), std::move(rank), 0, 0};
|
||||
SpellNameEntry entry{std::move(name), std::move(rank), 0};
|
||||
if (hasSchoolMask) {
|
||||
entry.schoolMask = dbc->getUInt32(i, schoolMaskField);
|
||||
} else if (hasSchoolEnum) {
|
||||
|
|
@ -17170,9 +16948,6 @@ void GameHandler::loadSpellNameCache() {
|
|||
uint32_t e = dbc->getUInt32(i, schoolEnumField);
|
||||
entry.schoolMask = (e < 7) ? enumToBitmask[e] : 0;
|
||||
}
|
||||
if (hasDispelField) {
|
||||
entry.dispelType = static_cast<uint8_t>(dbc->getUInt32(i, dispelField));
|
||||
}
|
||||
spellNameCache_[id] = std::move(entry);
|
||||
}
|
||||
}
|
||||
|
|
@ -17366,12 +17141,6 @@ const std::string& GameHandler::getSpellRank(uint32_t spellId) const {
|
|||
return (it != spellNameCache_.end()) ? it->second.rank : EMPTY_STRING;
|
||||
}
|
||||
|
||||
uint8_t GameHandler::getSpellDispelType(uint32_t spellId) const {
|
||||
const_cast<GameHandler*>(this)->loadSpellNameCache();
|
||||
auto it = spellNameCache_.find(spellId);
|
||||
return (it != spellNameCache_.end()) ? it->second.dispelType : 0;
|
||||
}
|
||||
|
||||
const std::string& GameHandler::getSkillLineName(uint32_t spellId) const {
|
||||
auto slIt = spellToSkillLine_.find(spellId);
|
||||
if (slIt == spellToSkillLine_.end()) return EMPTY_STRING;
|
||||
|
|
@ -18464,15 +18233,13 @@ void GameHandler::handleWho(network::Packet& packet) {
|
|||
|
||||
LOG_INFO("WHO response: ", displayCount, " players displayed, ", onlineCount, " total online");
|
||||
|
||||
// Store structured results for the who-results window
|
||||
whoResults_.clear();
|
||||
whoOnlineCount_ = onlineCount;
|
||||
|
||||
if (displayCount == 0) {
|
||||
addSystemChatMessage("No players found.");
|
||||
return;
|
||||
}
|
||||
|
||||
addSystemChatMessage(std::to_string(onlineCount) + " player(s) online:");
|
||||
|
||||
for (uint32_t i = 0; i < displayCount; ++i) {
|
||||
if (packet.getReadPos() >= packet.getSize()) break;
|
||||
std::string playerName = packet.readString();
|
||||
|
|
@ -18487,16 +18254,17 @@ void GameHandler::handleWho(network::Packet& packet) {
|
|||
if (packet.getSize() - packet.getReadPos() >= 4)
|
||||
zoneId = packet.readUInt32();
|
||||
|
||||
// Store structured entry
|
||||
WhoEntry entry;
|
||||
entry.name = playerName;
|
||||
entry.guildName = guildName;
|
||||
entry.level = level;
|
||||
entry.classId = classId;
|
||||
entry.raceId = raceId;
|
||||
entry.zoneId = zoneId;
|
||||
whoResults_.push_back(std::move(entry));
|
||||
std::string msg = " " + playerName;
|
||||
if (!guildName.empty())
|
||||
msg += " <" + guildName + ">";
|
||||
msg += " - Level " + std::to_string(level);
|
||||
if (zoneId != 0) {
|
||||
std::string zoneName = getAreaName(zoneId);
|
||||
if (!zoneName.empty())
|
||||
msg += " [" + zoneName + "]";
|
||||
}
|
||||
|
||||
addSystemChatMessage(msg);
|
||||
LOG_INFO(" ", playerName, " (", guildName, ") Lv", level, " Class:", classId,
|
||||
" Race:", raceId, " Zone:", zoneId);
|
||||
}
|
||||
|
|
@ -20153,15 +19921,12 @@ void GameHandler::handleLootRoll(network::Packet& packet) {
|
|||
pendingLootRoll_.objectGuid = objectGuid;
|
||||
pendingLootRoll_.slot = slot;
|
||||
pendingLootRoll_.itemId = itemId;
|
||||
pendingLootRoll_.playerRolls.clear();
|
||||
// Ensure item info is in cache; query if not
|
||||
queryItemInfo(itemId, 0);
|
||||
// Look up item name from cache
|
||||
auto* info = getItemInfo(itemId);
|
||||
pendingLootRoll_.itemName = info ? info->name : std::to_string(itemId);
|
||||
pendingLootRoll_.itemQuality = info ? static_cast<uint8_t>(info->quality) : 0;
|
||||
pendingLootRoll_.rollCountdownMs = 60000;
|
||||
pendingLootRoll_.rollStartedAt = std::chrono::steady_clock::now();
|
||||
LOG_INFO("SMSG_LOOT_ROLL: need/greed prompt for item=", itemId,
|
||||
" (", pendingLootRoll_.itemName, ") slot=", slot);
|
||||
return;
|
||||
|
|
@ -20178,28 +19943,6 @@ void GameHandler::handleLootRoll(network::Packet& packet) {
|
|||
}
|
||||
if (rollerName.empty()) rollerName = "Someone";
|
||||
|
||||
// Track in the live roll list while our popup is open for the same item
|
||||
if (pendingLootRollActive_ &&
|
||||
pendingLootRoll_.objectGuid == objectGuid &&
|
||||
pendingLootRoll_.slot == slot) {
|
||||
bool found = false;
|
||||
for (auto& r : pendingLootRoll_.playerRolls) {
|
||||
if (r.playerName == rollerName) {
|
||||
r.rollNum = rollNum;
|
||||
r.rollType = rollType;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LootRollEntry::PlayerRollResult prr;
|
||||
prr.playerName = rollerName;
|
||||
prr.rollNum = rollNum;
|
||||
prr.rollType = rollType;
|
||||
pendingLootRoll_.playerRolls.push_back(std::move(prr));
|
||||
}
|
||||
}
|
||||
|
||||
auto* info = getItemInfo(itemId);
|
||||
std::string iName = info ? info->name : std::to_string(itemId);
|
||||
|
||||
|
|
@ -20254,8 +19997,10 @@ void GameHandler::handleLootRollWon(network::Packet& packet) {
|
|||
winnerName.c_str(), iName.c_str(), rollName, static_cast<int>(rollNum));
|
||||
addSystemChatMessage(buf);
|
||||
|
||||
// Dismiss roll popup — roll contest is over regardless of who won
|
||||
pendingLootRollActive_ = false;
|
||||
// Clear pending roll if it was ours
|
||||
if (pendingLootRollActive_ && winnerGuid == playerGuid) {
|
||||
pendingLootRollActive_ = false;
|
||||
}
|
||||
LOG_INFO("SMSG_LOOT_ROLL_WON: winner=", winnerName, " item=", itemId,
|
||||
" roll=", rollName, "(", rollNum, ")");
|
||||
}
|
||||
|
|
@ -20312,7 +20057,7 @@ void GameHandler::handleAchievementEarned(network::Packet& packet) {
|
|||
|
||||
uint64_t guid = packet.readUInt64();
|
||||
uint32_t achievementId = packet.readUInt32();
|
||||
uint32_t earnDate = packet.readUInt32(); // WoW PackedTime bitfield
|
||||
/*uint32_t date =*/ packet.readUInt32(); // PackedTime — not displayed
|
||||
|
||||
loadAchievementNameCache();
|
||||
auto nameIt = achievementNameCache_.find(achievementId);
|
||||
|
|
@ -20331,7 +20076,6 @@ void GameHandler::handleAchievementEarned(network::Packet& packet) {
|
|||
addSystemChatMessage(buf);
|
||||
|
||||
earnedAchievements_.insert(achievementId);
|
||||
achievementDates_[achievementId] = earnDate;
|
||||
if (achievementEarnedCallback_) {
|
||||
achievementEarnedCallback_(achievementId, achName);
|
||||
}
|
||||
|
|
@ -20372,16 +20116,14 @@ void GameHandler::handleAchievementEarned(network::Packet& packet) {
|
|||
void GameHandler::handleAllAchievementData(network::Packet& packet) {
|
||||
loadAchievementNameCache();
|
||||
earnedAchievements_.clear();
|
||||
achievementDates_.clear();
|
||||
|
||||
// Parse achievement entries (id + packedDate pairs, sentinel 0xFFFFFFFF)
|
||||
while (packet.getSize() - packet.getReadPos() >= 4) {
|
||||
uint32_t id = packet.readUInt32();
|
||||
if (id == 0xFFFFFFFF) break;
|
||||
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||
uint32_t date = packet.readUInt32();
|
||||
/*uint32_t date =*/ packet.readUInt32();
|
||||
earnedAchievements_.insert(id);
|
||||
achievementDates_[id] = date;
|
||||
}
|
||||
|
||||
// Parse criteria block: id + uint64 counter + uint32 date + uint32 flags, sentinel 0xFFFFFFFF
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
#include "core/logger.hpp"
|
||||
#include <imgui.h>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
|
|
@ -1017,40 +1016,6 @@ void WorldMap::renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWi
|
|||
}
|
||||
}
|
||||
|
||||
// Hover coordinate display — show WoW coordinates under cursor
|
||||
if (currentIdx >= 0 && viewLevel != ViewLevel::WORLD) {
|
||||
auto& io = ImGui::GetIO();
|
||||
ImVec2 mp = io.MousePos;
|
||||
if (mp.x >= imgMin.x && mp.x <= imgMin.x + displayW &&
|
||||
mp.y >= imgMin.y && mp.y <= imgMin.y + displayH) {
|
||||
float mu = (mp.x - imgMin.x) / displayW;
|
||||
float mv = (mp.y - imgMin.y) / displayH;
|
||||
|
||||
const auto& zone = zones[currentIdx];
|
||||
float left = zone.locLeft, right = zone.locRight;
|
||||
float top = zone.locTop, bottom = zone.locBottom;
|
||||
if (zone.areaID == 0) {
|
||||
float l, r, t, b;
|
||||
getContinentProjectionBounds(currentIdx, l, r, t, b);
|
||||
left = l; right = r; top = t; bottom = b;
|
||||
// Undo the kVOffset applied during renderPosToMapUV for continent
|
||||
constexpr float kVOffset = -0.15f;
|
||||
mv -= kVOffset;
|
||||
}
|
||||
|
||||
float hWowX = left - mu * (left - right);
|
||||
float hWowY = top - mv * (top - bottom);
|
||||
|
||||
char coordBuf[32];
|
||||
snprintf(coordBuf, sizeof(coordBuf), "%.0f, %.0f", hWowX, hWowY);
|
||||
ImVec2 coordSz = ImGui::CalcTextSize(coordBuf);
|
||||
float cx = imgMin.x + displayW - coordSz.x - 8.0f;
|
||||
float cy = imgMin.y + displayH - coordSz.y - 8.0f;
|
||||
drawList->AddText(ImVec2(cx + 1.0f, cy + 1.0f), IM_COL32(0, 0, 0, 180), coordBuf);
|
||||
drawList->AddText(ImVec2(cx, cy), IM_COL32(220, 210, 150, 230), coordBuf);
|
||||
}
|
||||
}
|
||||
|
||||
// Continent view: clickable zone overlays
|
||||
if (viewLevel == ViewLevel::CONTINENT && continentIdx >= 0) {
|
||||
const auto& cont = zones[continentIdx];
|
||||
|
|
@ -1115,23 +1080,6 @@ void WorldMap::renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWi
|
|||
drawList->AddRect(ImVec2(sx0, sy0), ImVec2(sx1, sy1),
|
||||
IM_COL32(255, 255, 255, 30), 0.0f, 0, 1.0f);
|
||||
}
|
||||
|
||||
// Zone name label — only if the rect is large enough to fit it
|
||||
if (!z.areaName.empty()) {
|
||||
ImVec2 textSz = ImGui::CalcTextSize(z.areaName.c_str());
|
||||
float rectW = sx1 - sx0;
|
||||
float rectH = sy1 - sy0;
|
||||
if (rectW > textSz.x + 4.0f && rectH > textSz.y + 2.0f) {
|
||||
float tx = (sx0 + sx1) * 0.5f - textSz.x * 0.5f;
|
||||
float ty = (sy0 + sy1) * 0.5f - textSz.y * 0.5f;
|
||||
ImU32 labelCol = explored
|
||||
? IM_COL32(255, 230, 150, 210)
|
||||
: IM_COL32(160, 160, 160, 80);
|
||||
drawList->AddText(ImVec2(tx + 1.0f, ty + 1.0f),
|
||||
IM_COL32(0, 0, 0, 130), z.areaName.c_str());
|
||||
drawList->AddText(ImVec2(tx, ty), labelCol, z.areaName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -72,21 +72,6 @@ const game::ItemSlot* findComparableEquipped(const game::Inventory& inventory, u
|
|||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void renderCoinsText(uint32_t g, uint32_t s, uint32_t c) {
|
||||
bool any = false;
|
||||
if (g > 0) {
|
||||
ImGui::TextColored(ImVec4(1.00f, 0.82f, 0.00f, 1.0f), "%ug", g);
|
||||
any = true;
|
||||
}
|
||||
if (s > 0 || g > 0) {
|
||||
if (any) ImGui::SameLine(0, 3);
|
||||
ImGui::TextColored(ImVec4(0.80f, 0.80f, 0.80f, 1.0f), "%us", s);
|
||||
any = true;
|
||||
}
|
||||
if (any) ImGui::SameLine(0, 3);
|
||||
ImGui::TextColored(ImVec4(0.72f, 0.45f, 0.20f, 1.0f), "%uc", c);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
InventoryScreen::~InventoryScreen() {
|
||||
|
|
@ -2212,8 +2197,7 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I
|
|||
uint32_t g = item.sellPrice / 10000;
|
||||
uint32_t s = (item.sellPrice / 100) % 100;
|
||||
uint32_t c = item.sellPrice % 100;
|
||||
ImGui::TextDisabled("Sell:"); ImGui::SameLine(0, 4);
|
||||
renderCoinsText(g, s, c);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "Sell: %ug %us %uc", g, s, c);
|
||||
}
|
||||
|
||||
// Shift-hover comparison with currently equipped equivalent.
|
||||
|
|
@ -2337,7 +2321,7 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I
|
|||
// ---------------------------------------------------------------------------
|
||||
// Tooltip overload for ItemQueryResponseData (used by loot window, etc.)
|
||||
// ---------------------------------------------------------------------------
|
||||
void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info, const game::Inventory* inventory) {
|
||||
void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info) {
|
||||
ImGui::BeginTooltip();
|
||||
|
||||
ImVec4 qColor = getQualityColor(static_cast<game::ItemQuality>(info.quality));
|
||||
|
|
@ -2493,50 +2477,7 @@ void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info,
|
|||
uint32_t g = info.sellPrice / 10000;
|
||||
uint32_t s = (info.sellPrice / 100) % 100;
|
||||
uint32_t c = info.sellPrice % 100;
|
||||
ImGui::TextDisabled("Sell:"); ImGui::SameLine(0, 4);
|
||||
renderCoinsText(g, s, c);
|
||||
}
|
||||
|
||||
// Shift-hover: compare with currently equipped item
|
||||
if (inventory && ImGui::GetIO().KeyShift && info.inventoryType > 0) {
|
||||
if (const game::ItemSlot* eq = findComparableEquipped(*inventory, static_cast<uint8_t>(info.inventoryType))) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Equipped:");
|
||||
VkDescriptorSet eqIcon = getItemIcon(eq->item.displayInfoId);
|
||||
if (eqIcon) { ImGui::Image((ImTextureID)(uintptr_t)eqIcon, ImVec2(18.0f, 18.0f)); ImGui::SameLine(); }
|
||||
ImGui::TextColored(getQualityColor(eq->item.quality), "%s", eq->item.name.c_str());
|
||||
|
||||
auto showDiff = [](const char* label, float nv, float ev) {
|
||||
if (nv == 0.0f && ev == 0.0f) return;
|
||||
float diff = nv - ev;
|
||||
char buf[96];
|
||||
if (diff > 0.0f) { std::snprintf(buf, sizeof(buf), "%s: %.0f (▲%.0f)", label, nv, diff); ImGui::TextColored(ImVec4(0.0f,1.0f,0.0f,1.0f), "%s", buf); }
|
||||
else if (diff < 0.0f) { std::snprintf(buf, sizeof(buf), "%s: %.0f (▼%.0f)", label, nv, -diff); ImGui::TextColored(ImVec4(1.0f,0.3f,0.3f,1.0f), "%s", buf); }
|
||||
else { std::snprintf(buf, sizeof(buf), "%s: %.0f (=)", label, nv); ImGui::TextColored(ImVec4(0.7f,0.7f,0.7f,1.0f), "%s", buf); }
|
||||
};
|
||||
|
||||
float ilvlDiff = static_cast<float>(info.itemLevel) - static_cast<float>(eq->item.itemLevel);
|
||||
if (info.itemLevel > 0 || eq->item.itemLevel > 0) {
|
||||
char ilvlBuf[64];
|
||||
if (ilvlDiff > 0) std::snprintf(ilvlBuf, sizeof(ilvlBuf), "Item Level: %u (▲%.0f)", info.itemLevel, ilvlDiff);
|
||||
else if (ilvlDiff < 0) std::snprintf(ilvlBuf, sizeof(ilvlBuf), "Item Level: %u (▼%.0f)", info.itemLevel, -ilvlDiff);
|
||||
else std::snprintf(ilvlBuf, sizeof(ilvlBuf), "Item Level: %u (=)", info.itemLevel);
|
||||
ImVec4 ic = ilvlDiff > 0 ? ImVec4(0,1,0,1) : ilvlDiff < 0 ? ImVec4(1,0.3f,0.3f,1) : ImVec4(0.7f,0.7f,0.7f,1);
|
||||
ImGui::TextColored(ic, "%s", ilvlBuf);
|
||||
}
|
||||
|
||||
showDiff("Armor", static_cast<float>(info.armor), static_cast<float>(eq->item.armor));
|
||||
showDiff("Str", static_cast<float>(info.strength), static_cast<float>(eq->item.strength));
|
||||
showDiff("Agi", static_cast<float>(info.agility), static_cast<float>(eq->item.agility));
|
||||
showDiff("Sta", static_cast<float>(info.stamina), static_cast<float>(eq->item.stamina));
|
||||
showDiff("Int", static_cast<float>(info.intellect), static_cast<float>(eq->item.intellect));
|
||||
showDiff("Spi", static_cast<float>(info.spirit), static_cast<float>(eq->item.spirit));
|
||||
|
||||
// Hint text
|
||||
ImGui::TextDisabled("Hold Shift to compare");
|
||||
}
|
||||
} else if (info.inventoryType > 0) {
|
||||
ImGui::TextDisabled("Hold Shift to compare");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "Sell: %ug %us %uc", g, s, c);
|
||||
}
|
||||
|
||||
ImGui::EndTooltip();
|
||||
|
|
|
|||
|
|
@ -205,21 +205,6 @@ std::string cleanQuestTitleForUi(const std::string& raw, uint32_t questId) {
|
|||
if (s.size() > 72) s = s.substr(0, 72) + "...";
|
||||
return s;
|
||||
}
|
||||
|
||||
void renderCoinsText(uint32_t g, uint32_t s, uint32_t c) {
|
||||
bool any = false;
|
||||
if (g > 0) {
|
||||
ImGui::TextColored(ImVec4(1.00f, 0.82f, 0.00f, 1.0f), "%ug", g);
|
||||
any = true;
|
||||
}
|
||||
if (s > 0 || g > 0) {
|
||||
if (any) ImGui::SameLine(0, 3);
|
||||
ImGui::TextColored(ImVec4(0.80f, 0.80f, 0.80f, 1.0f), "%us", s);
|
||||
any = true;
|
||||
}
|
||||
if (any) ImGui::SameLine(0, 3);
|
||||
ImGui::TextColored(ImVec4(0.72f, 0.45f, 0.20f, 1.0f), "%uc", c);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
void QuestLogScreen::render(game::GameHandler& gameHandler, InventoryScreen& invScreen) {
|
||||
|
|
@ -377,11 +362,6 @@ void QuestLogScreen::render(game::GameHandler& gameHandler, InventoryScreen& inv
|
|||
if (ImGui::MenuItem(tracked ? "Untrack" : "Track")) {
|
||||
gameHandler.setQuestTracked(q.questId, !tracked);
|
||||
}
|
||||
if (gameHandler.isInGroup() && !q.complete) {
|
||||
if (ImGui::MenuItem("Share Quest")) {
|
||||
gameHandler.shareQuestWithParty(q.questId);
|
||||
}
|
||||
}
|
||||
if (!q.complete) {
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Abandon Quest")) {
|
||||
|
|
@ -508,7 +488,12 @@ void QuestLogScreen::render(game::GameHandler& gameHandler, InventoryScreen& inv
|
|||
uint32_t rg = static_cast<uint32_t>(sel.rewardMoney) / 10000;
|
||||
uint32_t rs = static_cast<uint32_t>(sel.rewardMoney % 10000) / 100;
|
||||
uint32_t rc = static_cast<uint32_t>(sel.rewardMoney % 100);
|
||||
renderCoinsText(rg, rs, rc);
|
||||
if (rg > 0)
|
||||
ImGui::Text("%ug %us %uc", rg, rs, rc);
|
||||
else if (rs > 0)
|
||||
ImGui::Text("%us %uc", rs, rc);
|
||||
else
|
||||
ImGui::Text("%uc", rc);
|
||||
}
|
||||
|
||||
// Guaranteed reward items
|
||||
|
|
@ -564,19 +549,12 @@ void QuestLogScreen::render(game::GameHandler& gameHandler, InventoryScreen& inv
|
|||
}
|
||||
}
|
||||
|
||||
// Track / Share / Abandon buttons
|
||||
// Track / Abandon buttons
|
||||
ImGui::Separator();
|
||||
bool isTracked = gameHandler.isQuestTracked(sel.questId);
|
||||
if (ImGui::Button(isTracked ? "Untrack" : "Track", ImVec2(100.0f, 0.0f))) {
|
||||
gameHandler.setQuestTracked(sel.questId, !isTracked);
|
||||
}
|
||||
if (gameHandler.isInGroup() && !sel.complete) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Share", ImVec2(80.0f, 0.0f))) {
|
||||
gameHandler.shareQuestWithParty(sel.questId);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Share this quest with your party");
|
||||
}
|
||||
if (!sel.complete) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Abandon Quest", ImVec2(150.0f, 0.0f))) {
|
||||
|
|
|
|||
|
|
@ -203,29 +203,6 @@ std::string SpellbookScreen::lookupSpellName(uint32_t spellId, pipeline::AssetMa
|
|||
return {};
|
||||
}
|
||||
|
||||
uint32_t SpellbookScreen::getSpellMaxRange(uint32_t spellId, pipeline::AssetManager* assetManager) {
|
||||
if (!dbcLoadAttempted) {
|
||||
loadSpellDBC(assetManager);
|
||||
}
|
||||
auto it = spellData.find(spellId);
|
||||
if (it != spellData.end()) return it->second.rangeIndex;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SpellbookScreen::getSpellPowerInfo(uint32_t spellId, pipeline::AssetManager* assetManager,
|
||||
uint32_t& outCost, uint32_t& outPowerType) {
|
||||
outCost = 0;
|
||||
outPowerType = 0;
|
||||
if (!dbcLoadAttempted) {
|
||||
loadSpellDBC(assetManager);
|
||||
}
|
||||
auto it = spellData.find(spellId);
|
||||
if (it != spellData.end()) {
|
||||
outCost = it->second.manaCost;
|
||||
outPowerType = it->second.powerType;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellbookScreen::loadSpellIconDBC(pipeline::AssetManager* assetManager) {
|
||||
if (iconDbLoaded) return;
|
||||
iconDbLoaded = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue