2026-02-02 12:24:50 -08:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
|
|
#include "game/world_packets.hpp"
|
|
|
|
|
|
#include "game/character.hpp"
|
2026-02-12 22:56:36 -08:00
|
|
|
|
#include "game/opcode_table.hpp"
|
|
|
|
|
|
#include "game/update_field_table.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
|
#include "game/inventory.hpp"
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
#include "game/spell_defines.hpp"
|
|
|
|
|
|
#include "game/group_defines.hpp"
|
2026-02-08 00:59:40 -08:00
|
|
|
|
#include <glm/glm.hpp>
|
2026-02-02 12:24:50 -08:00
|
|
|
|
#include <memory>
|
|
|
|
|
|
#include <string>
|
|
|
|
|
|
#include <vector>
|
2026-02-04 11:31:08 -08:00
|
|
|
|
#include <deque>
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
#include <array>
|
2026-02-02 12:24:50 -08:00
|
|
|
|
#include <functional>
|
|
|
|
|
|
#include <cstdint>
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
#include <unordered_set>
|
2026-02-07 14:21:50 -08:00
|
|
|
|
#include <map>
|
2026-02-12 00:04:53 -08:00
|
|
|
|
#include <optional>
|
2026-02-12 00:45:24 -08:00
|
|
|
|
#include <algorithm>
|
2026-02-20 02:19:17 -08:00
|
|
|
|
#include <chrono>
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-10 21:29:10 -08:00
|
|
|
|
namespace wowee::game {
|
|
|
|
|
|
class TransportManager;
|
2026-02-12 02:09:15 -08:00
|
|
|
|
class WardenCrypto;
|
2026-02-14 02:00:15 -08:00
|
|
|
|
class WardenMemory;
|
2026-02-14 19:20:32 -08:00
|
|
|
|
class WardenModule;
|
2026-02-12 02:43:20 -08:00
|
|
|
|
class WardenModuleManager;
|
2026-02-12 22:56:36 -08:00
|
|
|
|
class PacketParsers;
|
2026-02-10 21:29:10 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
namespace wowee {
|
|
|
|
|
|
namespace network { class WorldSocket; class Packet; }
|
|
|
|
|
|
|
|
|
|
|
|
namespace game {
|
|
|
|
|
|
|
2026-02-07 14:21:50 -08:00
|
|
|
|
struct PlayerSkill {
|
|
|
|
|
|
uint32_t skillId = 0;
|
|
|
|
|
|
uint16_t value = 0;
|
|
|
|
|
|
uint16_t maxValue = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-06 20:10:10 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Quest giver status values (WoW 3.3.5a)
|
|
|
|
|
|
*/
|
|
|
|
|
|
enum class QuestGiverStatus : uint8_t {
|
|
|
|
|
|
NONE = 0,
|
|
|
|
|
|
UNAVAILABLE = 1,
|
|
|
|
|
|
INCOMPLETE = 5, // ? (gray)
|
|
|
|
|
|
REWARD_REP = 6,
|
|
|
|
|
|
AVAILABLE_LOW = 7, // ! (gray, low-level)
|
|
|
|
|
|
AVAILABLE = 8, // ! (yellow)
|
|
|
|
|
|
REWARD = 10 // ? (yellow)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-10 05:46:03 -07:00
|
|
|
|
/**
|
|
|
|
|
|
* A single contact list entry (friend, ignore, or mute).
|
|
|
|
|
|
*/
|
|
|
|
|
|
struct ContactEntry {
|
|
|
|
|
|
uint64_t guid = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
std::string note;
|
|
|
|
|
|
uint32_t flags = 0; // 0x1=friend, 0x2=ignore, 0x4=mute
|
|
|
|
|
|
uint8_t status = 0; // 0=offline, 1=online, 2=AFK, 3=DND
|
|
|
|
|
|
uint32_t areaId = 0;
|
|
|
|
|
|
uint32_t level = 0;
|
|
|
|
|
|
uint32_t classId = 0;
|
|
|
|
|
|
|
|
|
|
|
|
bool isFriend() const { return (flags & 0x1) != 0; }
|
|
|
|
|
|
bool isIgnored() const { return (flags & 0x2) != 0; }
|
|
|
|
|
|
bool isOnline() const { return status != 0; }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* World connection state
|
|
|
|
|
|
*/
|
|
|
|
|
|
enum class WorldState {
|
|
|
|
|
|
DISCONNECTED, // Not connected
|
|
|
|
|
|
CONNECTING, // TCP connection in progress
|
|
|
|
|
|
CONNECTED, // Connected, waiting for challenge
|
|
|
|
|
|
CHALLENGE_RECEIVED, // Received SMSG_AUTH_CHALLENGE
|
|
|
|
|
|
AUTH_SENT, // Sent CMSG_AUTH_SESSION, encryption initialized
|
|
|
|
|
|
AUTHENTICATED, // Received SMSG_AUTH_RESPONSE success
|
|
|
|
|
|
READY, // Ready for character/world operations
|
|
|
|
|
|
CHAR_LIST_REQUESTED, // CMSG_CHAR_ENUM sent
|
|
|
|
|
|
CHAR_LIST_RECEIVED, // SMSG_CHAR_ENUM received
|
|
|
|
|
|
ENTERING_WORLD, // CMSG_PLAYER_LOGIN sent
|
|
|
|
|
|
IN_WORLD, // In game world
|
|
|
|
|
|
FAILED // Connection or authentication failed
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* World connection callbacks
|
|
|
|
|
|
*/
|
|
|
|
|
|
using WorldConnectSuccessCallback = std::function<void()>;
|
|
|
|
|
|
using WorldConnectFailureCallback = std::function<void(const std::string& reason)>;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GameHandler - Manages world server connection and game protocol
|
|
|
|
|
|
*
|
|
|
|
|
|
* Handles:
|
|
|
|
|
|
* - Connection to world server
|
|
|
|
|
|
* - Authentication with session key from auth server
|
|
|
|
|
|
* - RC4 header encryption
|
|
|
|
|
|
* - Character enumeration
|
|
|
|
|
|
* - World entry
|
|
|
|
|
|
* - Game packets
|
|
|
|
|
|
*/
|
|
|
|
|
|
class GameHandler {
|
|
|
|
|
|
public:
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
// Talent data structures (must be public for use in templates)
|
|
|
|
|
|
struct TalentEntry {
|
|
|
|
|
|
uint32_t talentId = 0;
|
|
|
|
|
|
uint32_t tabId = 0; // Which talent tree
|
|
|
|
|
|
uint8_t row = 0; // Tier (0-10)
|
|
|
|
|
|
uint8_t column = 0; // Column (0-3)
|
|
|
|
|
|
uint32_t rankSpells[5] = {}; // Spell IDs for ranks 1-5
|
|
|
|
|
|
uint32_t prereqTalent[3] = {}; // Required talents
|
|
|
|
|
|
uint8_t prereqRank[3] = {}; // Required ranks
|
|
|
|
|
|
uint8_t maxRank = 0; // Number of ranks (1-5)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct TalentTabEntry {
|
|
|
|
|
|
uint32_t tabId = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
uint32_t classMask = 0; // Which classes can use this tab
|
|
|
|
|
|
uint8_t orderIndex = 0; // Display order (0-2)
|
|
|
|
|
|
std::string backgroundFile; // Texture path
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
GameHandler();
|
|
|
|
|
|
~GameHandler();
|
|
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
|
/** Access the active opcode table (wire ↔ logical mapping). */
|
|
|
|
|
|
const OpcodeTable& getOpcodeTable() const { return opcodeTable_; }
|
|
|
|
|
|
OpcodeTable& getOpcodeTable() { return opcodeTable_; }
|
|
|
|
|
|
const UpdateFieldTable& getUpdateFieldTable() const { return updateFieldTable_; }
|
|
|
|
|
|
UpdateFieldTable& getUpdateFieldTable() { return updateFieldTable_; }
|
|
|
|
|
|
PacketParsers* getPacketParsers() { return packetParsers_.get(); }
|
|
|
|
|
|
void setPacketParsers(std::unique_ptr<PacketParsers> parsers);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Connect to world server
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param host World server hostname/IP
|
|
|
|
|
|
* @param port World server port (default 8085)
|
|
|
|
|
|
* @param sessionKey 40-byte session key from auth server
|
|
|
|
|
|
* @param accountName Account name (will be uppercased)
|
|
|
|
|
|
* @param build Client build number (default 12340 for 3.3.5a)
|
|
|
|
|
|
* @return true if connection initiated
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool connect(const std::string& host,
|
|
|
|
|
|
uint16_t port,
|
|
|
|
|
|
const std::vector<uint8_t>& sessionKey,
|
|
|
|
|
|
const std::string& accountName,
|
2026-02-13 01:51:49 -08:00
|
|
|
|
uint32_t build = 12340,
|
|
|
|
|
|
uint32_t realmId = 0);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Disconnect from world server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void disconnect();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Check if connected to world server
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool isConnected() const;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get current connection state
|
|
|
|
|
|
*/
|
|
|
|
|
|
WorldState getState() const { return state; }
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Request character list from server
|
|
|
|
|
|
* Must be called when state is READY or AUTHENTICATED
|
|
|
|
|
|
*/
|
|
|
|
|
|
void requestCharacterList();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get list of characters (available after CHAR_LIST_RECEIVED state)
|
|
|
|
|
|
*/
|
|
|
|
|
|
const std::vector<Character>& getCharacters() const { return characters; }
|
|
|
|
|
|
|
2026-02-05 14:13:48 -08:00
|
|
|
|
void createCharacter(const CharCreateData& data);
|
2026-02-06 03:24:46 -08:00
|
|
|
|
void deleteCharacter(uint64_t characterGuid);
|
2026-02-05 14:13:48 -08:00
|
|
|
|
|
|
|
|
|
|
using CharCreateCallback = std::function<void(bool success, const std::string& message)>;
|
|
|
|
|
|
void setCharCreateCallback(CharCreateCallback cb) { charCreateCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-06 03:24:46 -08:00
|
|
|
|
using CharDeleteCallback = std::function<void(bool success)>;
|
|
|
|
|
|
void setCharDeleteCallback(CharDeleteCallback cb) { charDeleteCallback_ = std::move(cb); }
|
2026-02-06 18:34:45 -08:00
|
|
|
|
uint8_t getLastCharDeleteResult() const { return lastCharDeleteResult_; }
|
2026-02-06 03:24:46 -08:00
|
|
|
|
|
2026-02-17 13:59:29 -08:00
|
|
|
|
using CharLoginFailCallback = std::function<void(const std::string& reason)>;
|
|
|
|
|
|
void setCharLoginFailCallback(CharLoginFailCallback cb) { charLoginFailCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Select and log in with a character
|
|
|
|
|
|
* @param characterGuid GUID of character to log in with
|
|
|
|
|
|
*/
|
|
|
|
|
|
void selectCharacter(uint64_t characterGuid);
|
2026-02-05 14:35:12 -08:00
|
|
|
|
void setActiveCharacterGuid(uint64_t guid) { activeCharacterGuid_ = guid; }
|
|
|
|
|
|
uint64_t getActiveCharacterGuid() const { return activeCharacterGuid_; }
|
|
|
|
|
|
const Character* getActiveCharacter() const;
|
|
|
|
|
|
const Character* getFirstCharacter() const;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get current player movement info
|
|
|
|
|
|
*/
|
|
|
|
|
|
const MovementInfo& getMovementInfo() const { return movementInfo; }
|
2026-02-08 03:24:12 -08:00
|
|
|
|
uint32_t getCurrentMapId() const { return currentMapId_; }
|
2026-02-08 03:39:02 -08:00
|
|
|
|
bool getHomeBind(uint32_t& mapId, glm::vec3& pos) const {
|
|
|
|
|
|
if (!hasHomeBind_) return false;
|
|
|
|
|
|
mapId = homeBindMapId_;
|
|
|
|
|
|
pos = homeBindPos_;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Send a movement packet
|
2026-02-20 02:50:59 -08:00
|
|
|
|
* @param opcode Movement opcode (MSG_MOVE_START_FORWARD, etc.)
|
2026-02-02 12:24:50 -08:00
|
|
|
|
*/
|
|
|
|
|
|
void sendMovement(Opcode opcode);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Update player position
|
|
|
|
|
|
* @param x X coordinate
|
|
|
|
|
|
* @param y Y coordinate
|
|
|
|
|
|
* @param z Z coordinate
|
|
|
|
|
|
*/
|
|
|
|
|
|
void setPosition(float x, float y, float z);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Update player orientation
|
|
|
|
|
|
* @param orientation Facing direction in radians
|
|
|
|
|
|
*/
|
|
|
|
|
|
void setOrientation(float orientation);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get entity manager (for accessing entities in view)
|
|
|
|
|
|
*/
|
|
|
|
|
|
EntityManager& getEntityManager() { return entityManager; }
|
|
|
|
|
|
const EntityManager& getEntityManager() const { return entityManager; }
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Send a chat message
|
|
|
|
|
|
* @param type Chat type (SAY, YELL, WHISPER, etc.)
|
|
|
|
|
|
* @param message Message text
|
|
|
|
|
|
* @param target Target name (for whispers, empty otherwise)
|
|
|
|
|
|
*/
|
|
|
|
|
|
void sendChatMessage(ChatType type, const std::string& message, const std::string& target = "");
|
2026-02-14 14:30:09 -08:00
|
|
|
|
void sendTextEmote(uint32_t textEmoteId, uint64_t targetGuid = 0);
|
|
|
|
|
|
void joinChannel(const std::string& channelName, const std::string& password = "");
|
|
|
|
|
|
void leaveChannel(const std::string& channelName);
|
|
|
|
|
|
const std::vector<std::string>& getJoinedChannels() const { return joinedChannels_; }
|
|
|
|
|
|
std::string getChannelByIndex(int index) const;
|
2026-02-14 18:27:59 -08:00
|
|
|
|
int getChannelIndex(const std::string& channelName) const;
|
|
|
|
|
|
|
|
|
|
|
|
// Chat auto-join settings (set by UI before autoJoinDefaultChannels)
|
|
|
|
|
|
struct ChatAutoJoin {
|
|
|
|
|
|
bool general = true;
|
|
|
|
|
|
bool trade = true;
|
|
|
|
|
|
bool localDefense = true;
|
|
|
|
|
|
bool lfg = true;
|
2026-02-16 21:16:25 -08:00
|
|
|
|
bool local = true;
|
2026-02-14 18:27:59 -08:00
|
|
|
|
};
|
|
|
|
|
|
ChatAutoJoin chatAutoJoin;
|
2026-02-14 14:30:09 -08:00
|
|
|
|
|
|
|
|
|
|
// Chat bubble callback: (senderGuid, message, isYell)
|
|
|
|
|
|
using ChatBubbleCallback = std::function<void(uint64_t, const std::string&, bool)>;
|
|
|
|
|
|
void setChatBubbleCallback(ChatBubbleCallback cb) { chatBubbleCallback_ = std::move(cb); }
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-14 15:11:43 -08:00
|
|
|
|
// Emote animation callback: (entityGuid, animationId)
|
|
|
|
|
|
using EmoteAnimCallback = std::function<void(uint64_t, uint32_t)>;
|
|
|
|
|
|
void setEmoteAnimCallback(EmoteAnimCallback cb) { emoteAnimCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Get chat history (recent messages)
|
|
|
|
|
|
* @param maxMessages Maximum number of messages to return (0 = all)
|
|
|
|
|
|
* @return Vector of chat messages
|
|
|
|
|
|
*/
|
2026-02-04 11:31:08 -08:00
|
|
|
|
const std::deque<MessageChatData>& getChatHistory() const { return chatHistory; }
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Add a locally-generated chat message (e.g., emote feedback)
|
|
|
|
|
|
*/
|
|
|
|
|
|
void addLocalChatMessage(const MessageChatData& msg);
|
|
|
|
|
|
|
2026-02-05 14:01:26 -08:00
|
|
|
|
// Money (copper)
|
|
|
|
|
|
uint64_t getMoneyCopper() const { return playerMoneyCopper_; }
|
|
|
|
|
|
|
2026-02-19 17:45:09 -08:00
|
|
|
|
// Server-authoritative armor (UNIT_FIELD_RESISTANCES[0])
|
|
|
|
|
|
int32_t getArmorRating() const { return playerArmorRating_; }
|
|
|
|
|
|
|
2026-03-12 12:24:15 -07:00
|
|
|
|
// Server-authoritative elemental resistances (UNIT_FIELD_RESISTANCES[1-6]).
|
|
|
|
|
|
// school: 1=Holy, 2=Fire, 3=Nature, 4=Frost, 5=Shadow, 6=Arcane. Returns 0 if not received.
|
|
|
|
|
|
int32_t getResistance(int school) const {
|
|
|
|
|
|
if (school < 1 || school > 6) return 0;
|
|
|
|
|
|
return playerResistances_[school - 1];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 23:08:15 -07:00
|
|
|
|
// Server-authoritative primary stats (UNIT_FIELD_STAT0-4: STR, AGI, STA, INT, SPI).
|
|
|
|
|
|
// Returns -1 if the server hasn't sent the value yet.
|
|
|
|
|
|
int32_t getPlayerStat(int idx) const {
|
|
|
|
|
|
if (idx < 0 || idx > 4) return -1;
|
|
|
|
|
|
return playerStats_[idx];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Inventory
|
|
|
|
|
|
Inventory& getInventory() { return inventory; }
|
|
|
|
|
|
const Inventory& getInventory() const { return inventory; }
|
2026-02-06 03:13:42 -08:00
|
|
|
|
bool consumeOnlineEquipmentDirty() { bool d = onlineEquipDirty_; onlineEquipDirty_ = false; return d; }
|
2026-03-04 19:47:01 -08:00
|
|
|
|
void resetEquipmentDirtyTracking() { lastEquipDisplayIds_ = {}; onlineEquipDirty_ = true; }
|
2026-02-12 14:55:27 -08:00
|
|
|
|
void unequipToBackpack(EquipSlot equipSlot);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Targeting
|
|
|
|
|
|
void setTarget(uint64_t guid);
|
|
|
|
|
|
void clearTarget();
|
|
|
|
|
|
uint64_t getTargetGuid() const { return targetGuid; }
|
|
|
|
|
|
std::shared_ptr<Entity> getTarget() const;
|
|
|
|
|
|
bool hasTarget() const { return targetGuid != 0; }
|
|
|
|
|
|
void tabTarget(float playerX, float playerY, float playerZ);
|
|
|
|
|
|
|
2026-02-07 13:44:36 -08:00
|
|
|
|
// Focus targeting
|
|
|
|
|
|
void setFocus(uint64_t guid);
|
|
|
|
|
|
void clearFocus();
|
|
|
|
|
|
uint64_t getFocusGuid() const { return focusGuid; }
|
|
|
|
|
|
std::shared_ptr<Entity> getFocus() const;
|
|
|
|
|
|
bool hasFocus() const { return focusGuid != 0; }
|
|
|
|
|
|
|
|
|
|
|
|
// Advanced targeting
|
|
|
|
|
|
void targetLastTarget();
|
|
|
|
|
|
void targetEnemy(bool reverse = false);
|
|
|
|
|
|
void targetFriend(bool reverse = false);
|
|
|
|
|
|
|
2026-02-07 12:37:13 -08:00
|
|
|
|
// Inspection
|
|
|
|
|
|
void inspectTarget();
|
|
|
|
|
|
|
2026-03-12 21:27:02 -07:00
|
|
|
|
struct InspectArenaTeam {
|
|
|
|
|
|
uint32_t teamId = 0;
|
|
|
|
|
|
uint8_t type = 0; // bracket size: 2, 3, or 5
|
|
|
|
|
|
uint32_t weekGames = 0;
|
|
|
|
|
|
uint32_t weekWins = 0;
|
|
|
|
|
|
uint32_t seasonGames = 0;
|
|
|
|
|
|
uint32_t seasonWins = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
uint32_t personalRating = 0;
|
|
|
|
|
|
};
|
2026-03-12 02:52:40 -07:00
|
|
|
|
struct InspectResult {
|
|
|
|
|
|
uint64_t guid = 0;
|
|
|
|
|
|
std::string playerName;
|
|
|
|
|
|
uint32_t totalTalents = 0;
|
|
|
|
|
|
uint32_t unspentTalents = 0;
|
|
|
|
|
|
uint8_t talentGroups = 0;
|
|
|
|
|
|
uint8_t activeTalentGroup = 0;
|
2026-03-12 07:37:29 -07:00
|
|
|
|
std::array<uint32_t, 19> itemEntries{}; // 0=head…18=ranged
|
|
|
|
|
|
std::array<uint16_t, 19> enchantIds{}; // permanent enchant per slot (0 = none)
|
2026-03-12 21:27:02 -07:00
|
|
|
|
std::vector<InspectArenaTeam> arenaTeams; // from MSG_INSPECT_ARENA_TEAMS (WotLK)
|
2026-03-12 02:52:40 -07:00
|
|
|
|
};
|
|
|
|
|
|
const InspectResult* getInspectResult() const {
|
|
|
|
|
|
return inspectResult_.guid ? &inspectResult_ : nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:43:32 -08:00
|
|
|
|
// Server info commands
|
|
|
|
|
|
void queryServerTime();
|
|
|
|
|
|
void requestPlayedTime();
|
|
|
|
|
|
void queryWho(const std::string& playerName = "");
|
2026-03-12 03:09:52 -07:00
|
|
|
|
uint32_t getTotalTimePlayed() const { return totalTimePlayed_; }
|
|
|
|
|
|
uint32_t getLevelTimePlayed() const { return levelTimePlayed_; }
|
2026-02-07 12:43:32 -08:00
|
|
|
|
|
2026-03-12 10:41:18 -07:00
|
|
|
|
// 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); }
|
|
|
|
|
|
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
// Social commands
|
|
|
|
|
|
void addFriend(const std::string& playerName, const std::string& note = "");
|
|
|
|
|
|
void removeFriend(const std::string& playerName);
|
|
|
|
|
|
void setFriendNote(const std::string& playerName, const std::string& note);
|
2026-02-07 12:58:11 -08:00
|
|
|
|
void addIgnore(const std::string& playerName);
|
|
|
|
|
|
void removeIgnore(const std::string& playerName);
|
2026-03-12 01:31:44 -07:00
|
|
|
|
const std::unordered_map<std::string, uint64_t>& getIgnoreCache() const { return ignoreCache; }
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
|
|
|
|
|
|
// Random roll
|
|
|
|
|
|
void randomRoll(uint32_t minRoll = 1, uint32_t maxRoll = 100);
|
|
|
|
|
|
|
2026-03-10 21:12:28 -07:00
|
|
|
|
// Battleground queue slot (public so UI can read invite details)
|
|
|
|
|
|
struct BgQueueSlot {
|
|
|
|
|
|
uint32_t queueSlot = 0;
|
|
|
|
|
|
uint32_t bgTypeId = 0;
|
|
|
|
|
|
uint8_t arenaType = 0;
|
|
|
|
|
|
uint32_t statusId = 0; // 0=none, 1=wait_queue, 2=wait_join, 3=in_progress
|
|
|
|
|
|
uint32_t inviteTimeout = 80;
|
|
|
|
|
|
std::chrono::steady_clock::time_point inviteReceivedTime{};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-12 21:54:48 -07:00
|
|
|
|
// Available BG list (populated by SMSG_BATTLEFIELD_LIST)
|
|
|
|
|
|
struct AvailableBgInfo {
|
|
|
|
|
|
uint32_t bgTypeId = 0;
|
|
|
|
|
|
bool isRegistered = false;
|
|
|
|
|
|
bool isHoliday = false;
|
|
|
|
|
|
uint32_t minLevel = 0;
|
|
|
|
|
|
uint32_t maxLevel = 0;
|
|
|
|
|
|
std::vector<uint32_t> instanceIds;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-26 17:56:11 -08:00
|
|
|
|
// Battleground
|
|
|
|
|
|
bool hasPendingBgInvite() const;
|
|
|
|
|
|
void acceptBattlefield(uint32_t queueSlot = 0xFFFFFFFF);
|
2026-03-10 21:12:28 -07:00
|
|
|
|
void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF);
|
|
|
|
|
|
const std::array<BgQueueSlot, 3>& getBgQueues() const { return bgQueues_; }
|
2026-03-12 21:54:48 -07:00
|
|
|
|
const std::vector<AvailableBgInfo>& getAvailableBgs() const { return availableBgs_; }
|
2026-02-26 17:56:11 -08:00
|
|
|
|
|
2026-03-12 12:02:59 -07:00
|
|
|
|
// 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
|
|
|
|
|
|
};
|
2026-03-12 23:46:38 -07:00
|
|
|
|
struct ArenaTeamScore {
|
|
|
|
|
|
std::string teamName;
|
|
|
|
|
|
uint32_t ratingChange = 0; // signed delta packed as uint32
|
|
|
|
|
|
uint32_t newRating = 0;
|
|
|
|
|
|
};
|
2026-03-12 12:02:59 -07:00
|
|
|
|
struct BgScoreboardData {
|
|
|
|
|
|
std::vector<BgPlayerScore> players;
|
|
|
|
|
|
bool hasWinner = false;
|
|
|
|
|
|
uint8_t winner = 0; // 0=Horde, 1=Alliance
|
|
|
|
|
|
bool isArena = false;
|
2026-03-12 23:46:38 -07:00
|
|
|
|
// Arena-only fields (valid when isArena=true)
|
|
|
|
|
|
ArenaTeamScore arenaTeams[2]; // team 0 = first, team 1 = second
|
2026-03-12 12:02:59 -07:00
|
|
|
|
};
|
|
|
|
|
|
void requestPvpLog();
|
|
|
|
|
|
const BgScoreboardData* getBgScoreboard() const {
|
|
|
|
|
|
return bgScoreboard_.players.empty() ? nullptr : &bgScoreboard_;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 21:19:42 -07:00
|
|
|
|
// Network latency (milliseconds, updated each PONG response)
|
|
|
|
|
|
uint32_t getLatencyMs() const { return lastLatency; }
|
|
|
|
|
|
|
2026-02-07 12:58:11 -08:00
|
|
|
|
// Logout commands
|
|
|
|
|
|
void requestLogout();
|
|
|
|
|
|
void cancelLogout();
|
|
|
|
|
|
|
|
|
|
|
|
// Stand state
|
|
|
|
|
|
void setStandState(uint8_t state); // 0=stand, 1=sit, 2=sit_chair, 3=sleep, 4=sit_low_chair, 5=sit_medium_chair, 6=sit_high_chair, 7=dead, 8=kneel, 9=submerged
|
Implement SMSG_STANDSTATE_UPDATE and SMSG_ITEM_PUSH_RESULT handlers
SMSG_STANDSTATE_UPDATE:
- Parse uint8 stand state from server confirmation packet
- Store in standState_ member (0=stand, 7=dead, 8=kneel, etc.)
- Expose getStandState(), isSitting(), isDead(), isKneeling() accessors
SMSG_ITEM_PUSH_RESULT:
- Parse full WotLK 3.3.5a payload: guid, received, created, showInChat,
bagSlot, itemSlot, itemId, suffixFactor, randomPropertyId, count, totalCount
- Show "Received: <name> x<count>" chat notification when showInChat=1
- Queue item info lookup via queryItemInfo so name resolves asap
2026-03-09 12:58:52 -07:00
|
|
|
|
uint8_t getStandState() const { return standState_; }
|
|
|
|
|
|
bool isSitting() const { return standState_ >= 1 && standState_ <= 6; }
|
|
|
|
|
|
bool isDead() const { return standState_ == 7; }
|
|
|
|
|
|
bool isKneeling() const { return standState_ == 8; }
|
2026-02-07 12:58:11 -08:00
|
|
|
|
|
2026-02-07 13:03:21 -08:00
|
|
|
|
// Display toggles
|
|
|
|
|
|
void toggleHelm();
|
|
|
|
|
|
void toggleCloak();
|
2026-03-12 00:13:48 -07:00
|
|
|
|
bool isHelmVisible() const { return helmVisible_; }
|
|
|
|
|
|
bool isCloakVisible() const { return cloakVisible_; }
|
2026-02-07 13:03:21 -08:00
|
|
|
|
|
|
|
|
|
|
// Follow/Assist
|
|
|
|
|
|
void followTarget();
|
2026-03-12 09:36:14 -07:00
|
|
|
|
void cancelFollow(); // Stop following current target
|
2026-02-07 13:03:21 -08:00
|
|
|
|
void assistTarget();
|
|
|
|
|
|
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
// PvP
|
|
|
|
|
|
void togglePvp();
|
|
|
|
|
|
|
2026-03-11 23:00:03 -07:00
|
|
|
|
// Minimap ping (Ctrl+click on minimap; wowX/wowY in canonical WoW coords)
|
|
|
|
|
|
void sendMinimapPing(float wowX, float wowY);
|
|
|
|
|
|
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
// Guild commands
|
|
|
|
|
|
void requestGuildInfo();
|
|
|
|
|
|
void requestGuildRoster();
|
|
|
|
|
|
void setGuildMotd(const std::string& motd);
|
|
|
|
|
|
void promoteGuildMember(const std::string& playerName);
|
|
|
|
|
|
void demoteGuildMember(const std::string& playerName);
|
|
|
|
|
|
void leaveGuild();
|
|
|
|
|
|
void inviteToGuild(const std::string& playerName);
|
2026-02-13 21:39:48 -08:00
|
|
|
|
void kickGuildMember(const std::string& playerName);
|
2026-02-16 20:16:14 -08:00
|
|
|
|
void disbandGuild();
|
|
|
|
|
|
void setGuildLeader(const std::string& name);
|
|
|
|
|
|
void setGuildPublicNote(const std::string& name, const std::string& note);
|
|
|
|
|
|
void setGuildOfficerNote(const std::string& name, const std::string& note);
|
2026-02-13 21:39:48 -08:00
|
|
|
|
void acceptGuildInvite();
|
|
|
|
|
|
void declineGuildInvite();
|
2026-03-12 02:31:12 -07:00
|
|
|
|
|
|
|
|
|
|
// GM Ticket
|
|
|
|
|
|
void submitGmTicket(const std::string& text);
|
|
|
|
|
|
void deleteGmTicket();
|
feat: parse SMSG_GMTICKET_GETTICKET/SYSTEMSTATUS and SMSG_SPELLINSTAKILLLOG
Previously SMSG_GMTICKET_GETTICKET and SMSG_GMTICKET_SYSTEMSTATUS were
silently consumed. Now both are fully parsed:
- SMSG_GMTICKET_GETTICKET decodes all four status codes (no ticket,
open ticket, closed, suspended), extracts ticket text, age and
server-estimated wait time, and stores them on GameHandler.
- SMSG_GMTICKET_SYSTEMSTATUS shows a chat message when GM support
goes offline/online.
- Added requestGmTicket() (sends CMSG_GMTICKET_GETTICKET) called
automatically when the GM Ticket UI window is opened, so the player
sees their existing open ticket text and wait time on first open.
- GM Ticket UI window now shows current-ticket status bar, estimated
wait time, and hides the Delete button when no ticket is active.
Also implements SMSG_SPELLINSTAKILLLOG (previously silently consumed):
parses caster/victim/spellId for all expansions and emits combat text
when the local player is involved in an instant-kill spell event (e.g.
Execute, Obliterate).
2026-03-12 22:14:46 -07:00
|
|
|
|
void requestGmTicket(); ///< Send CMSG_GMTICKET_GETTICKET to query open ticket
|
|
|
|
|
|
|
|
|
|
|
|
// GM ticket status accessors
|
|
|
|
|
|
bool hasActiveGmTicket() const { return gmTicketActive_; }
|
|
|
|
|
|
const std::string& getGmTicketText() const { return gmTicketText_; }
|
|
|
|
|
|
bool isGmSupportAvailable() const { return gmSupportAvailable_; }
|
|
|
|
|
|
float getGmTicketWaitHours() const { return gmTicketWaitHours_; }
|
2026-03-12 22:25:46 -07:00
|
|
|
|
|
|
|
|
|
|
// Battlefield Manager (Wintergrasp)
|
|
|
|
|
|
bool hasBfMgrInvite() const { return bfMgrInvitePending_; }
|
|
|
|
|
|
bool isInBfMgrZone() const { return bfMgrActive_; }
|
|
|
|
|
|
uint32_t getBfMgrZoneId() const { return bfMgrZoneId_; }
|
|
|
|
|
|
void acceptBfMgrInvite();
|
|
|
|
|
|
void declineBfMgrInvite();
|
|
|
|
|
|
|
|
|
|
|
|
// WotLK Calendar
|
|
|
|
|
|
uint32_t getCalendarPendingInvites() const { return calendarPendingInvites_; }
|
|
|
|
|
|
void requestCalendar(); ///< Send CMSG_CALENDAR_GET_CALENDAR to the server
|
2026-02-13 21:39:48 -08:00
|
|
|
|
void queryGuildInfo(uint32_t guildId);
|
2026-02-25 14:44:44 -08:00
|
|
|
|
void createGuild(const std::string& guildName);
|
|
|
|
|
|
void addGuildRank(const std::string& rankName);
|
|
|
|
|
|
void deleteGuildRank();
|
|
|
|
|
|
void requestPetitionShowlist(uint64_t npcGuid);
|
|
|
|
|
|
void buyPetition(uint64_t npcGuid, const std::string& guildName);
|
2026-02-13 21:39:48 -08:00
|
|
|
|
|
|
|
|
|
|
// Guild state accessors
|
2026-02-14 15:05:18 -08:00
|
|
|
|
bool isInGuild() const {
|
|
|
|
|
|
if (!guildName_.empty()) return true;
|
|
|
|
|
|
const Character* ch = getActiveCharacter();
|
|
|
|
|
|
return ch && ch->hasGuild();
|
|
|
|
|
|
}
|
2026-02-13 21:39:48 -08:00
|
|
|
|
const std::string& getGuildName() const { return guildName_; }
|
|
|
|
|
|
const GuildRosterData& getGuildRoster() const { return guildRoster_; }
|
|
|
|
|
|
bool hasGuildRoster() const { return hasGuildRoster_; }
|
2026-02-16 20:16:14 -08:00
|
|
|
|
const std::vector<std::string>& getGuildRankNames() const { return guildRankNames_; }
|
2026-02-13 21:39:48 -08:00
|
|
|
|
bool hasPendingGuildInvite() const { return pendingGuildInvite_; }
|
|
|
|
|
|
const std::string& getPendingGuildInviterName() const { return pendingGuildInviterName_; }
|
|
|
|
|
|
const std::string& getPendingGuildInviteGuildName() const { return pendingGuildInviteGuildName_; }
|
2026-02-25 14:44:44 -08:00
|
|
|
|
const GuildInfoData& getGuildInfoData() const { return guildInfoData_; }
|
|
|
|
|
|
const GuildQueryResponseData& getGuildQueryData() const { return guildQueryData_; }
|
|
|
|
|
|
bool hasGuildInfoData() const { return guildInfoData_.isValid(); }
|
|
|
|
|
|
bool hasPetitionShowlist() const { return showPetitionDialog_; }
|
|
|
|
|
|
void clearPetitionDialog() { showPetitionDialog_ = false; }
|
|
|
|
|
|
uint32_t getPetitionCost() const { return petitionCost_; }
|
|
|
|
|
|
uint64_t getPetitionNpcGuid() const { return petitionNpcGuid_; }
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
|
|
|
|
|
|
// Ready check
|
2026-03-12 09:07:37 -07:00
|
|
|
|
struct ReadyCheckResult {
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
bool ready = false;
|
|
|
|
|
|
};
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
void initiateReadyCheck();
|
|
|
|
|
|
void respondToReadyCheck(bool ready);
|
2026-03-09 14:48:30 -07:00
|
|
|
|
bool hasPendingReadyCheck() const { return pendingReadyCheck_; }
|
|
|
|
|
|
void dismissReadyCheck() { pendingReadyCheck_ = false; }
|
|
|
|
|
|
const std::string& getReadyCheckInitiator() const { return readyCheckInitiator_; }
|
2026-03-12 09:07:37 -07:00
|
|
|
|
const std::vector<ReadyCheckResult>& getReadyCheckResults() const { return readyCheckResults_; }
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
|
|
|
|
|
|
// Duel
|
|
|
|
|
|
void forfeitDuel();
|
|
|
|
|
|
|
2026-02-07 13:17:01 -08:00
|
|
|
|
// AFK/DND status
|
|
|
|
|
|
void toggleAfk(const std::string& message = "");
|
|
|
|
|
|
void toggleDnd(const std::string& message = "");
|
2026-03-11 23:21:27 -07:00
|
|
|
|
bool isAfk() const { return afkStatus_; }
|
|
|
|
|
|
bool isDnd() const { return dndStatus_; }
|
2026-02-07 13:17:01 -08:00
|
|
|
|
void replyToLastWhisper(const std::string& message);
|
|
|
|
|
|
std::string getLastWhisperSender() const { return lastWhisperSender_; }
|
|
|
|
|
|
void setLastWhisperSender(const std::string& name) { lastWhisperSender_ = name; }
|
|
|
|
|
|
|
2026-02-07 13:28:46 -08:00
|
|
|
|
// Party/Raid management
|
|
|
|
|
|
void uninvitePlayer(const std::string& playerName);
|
|
|
|
|
|
void leaveParty();
|
|
|
|
|
|
void setMainTank(uint64_t targetGuid);
|
|
|
|
|
|
void setMainAssist(uint64_t targetGuid);
|
|
|
|
|
|
void clearMainTank();
|
|
|
|
|
|
void clearMainAssist();
|
|
|
|
|
|
void requestRaidInfo();
|
|
|
|
|
|
|
2026-02-07 13:36:50 -08:00
|
|
|
|
// Combat and Trade
|
|
|
|
|
|
void proposeDuel(uint64_t targetGuid);
|
|
|
|
|
|
void initiateTrade(uint64_t targetGuid);
|
|
|
|
|
|
void stopCasting();
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 1: Name queries ----
|
|
|
|
|
|
void queryPlayerName(uint64_t guid);
|
|
|
|
|
|
void queryCreatureInfo(uint32_t entry, uint64_t guid);
|
2026-02-08 00:59:40 -08:00
|
|
|
|
void queryGameObjectInfo(uint32_t entry, uint64_t guid);
|
2026-02-14 20:20:43 -08:00
|
|
|
|
const GameObjectQueryResponseData* getCachedGameObjectInfo(uint32_t entry) const {
|
|
|
|
|
|
auto it = gameObjectInfoCache_.find(entry);
|
|
|
|
|
|
return (it != gameObjectInfoCache_.end()) ? &it->second : nullptr;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
std::string getCachedPlayerName(uint64_t guid) const;
|
|
|
|
|
|
std::string getCachedCreatureName(uint32_t entry) const;
|
2026-03-12 14:14:25 -07:00
|
|
|
|
// Returns the creature subname/title (e.g. "<Warchief of the Horde>"), empty if not cached
|
|
|
|
|
|
std::string getCachedCreatureSubName(uint32_t entry) const {
|
|
|
|
|
|
auto it = creatureInfoCache.find(entry);
|
|
|
|
|
|
return (it != creatureInfoCache.end()) ? it->second.subName : "";
|
|
|
|
|
|
}
|
2026-03-12 14:13:09 -07:00
|
|
|
|
// Returns the creature rank (0=Normal,1=Elite,2=RareElite,3=Boss,4=Rare)
|
|
|
|
|
|
// or -1 if not cached yet
|
|
|
|
|
|
int getCreatureRank(uint32_t entry) const {
|
|
|
|
|
|
auto it = creatureInfoCache.find(entry);
|
|
|
|
|
|
return (it != creatureInfoCache.end()) ? static_cast<int>(it->second.rank) : -1;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
// ---- Phase 2: Combat ----
|
|
|
|
|
|
void startAutoAttack(uint64_t targetGuid);
|
|
|
|
|
|
void stopAutoAttack();
|
|
|
|
|
|
bool isAutoAttacking() const { return autoAttacking; }
|
2026-02-20 03:38:12 -08:00
|
|
|
|
bool hasAutoAttackIntent() const { return autoAttackRequested_; }
|
|
|
|
|
|
bool isInCombat() const { return autoAttacking || !hostileAttackers_.empty(); }
|
|
|
|
|
|
bool isInCombatWith(uint64_t guid) const {
|
|
|
|
|
|
return guid != 0 &&
|
|
|
|
|
|
((autoAttacking && autoAttackTarget == guid) ||
|
|
|
|
|
|
(hostileAttackers_.count(guid) > 0));
|
|
|
|
|
|
}
|
|
|
|
|
|
uint64_t getAutoAttackTargetGuid() const { return autoAttackTarget; }
|
2026-02-06 18:34:45 -08:00
|
|
|
|
bool isAggressiveTowardPlayer(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
|
2026-03-12 20:05:36 -07:00
|
|
|
|
// Timestamp (ms since epoch) of the most recent player melee auto-attack.
|
|
|
|
|
|
// Zero if no swing has occurred this session.
|
|
|
|
|
|
uint64_t getLastMeleeSwingMs() const { return lastMeleeSwingMs_; }
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
const std::vector<CombatTextEntry>& getCombatText() const { return combatText; }
|
|
|
|
|
|
void updateCombatText(float deltaTime);
|
|
|
|
|
|
|
feat: add persistent combat log window (/combatlog or /cl)
Stores up to 500 combat events in a rolling deque alongside the existing
floating combat text. Events are populated via the existing addCombatText()
call site, resolving attacker/target names from the entity manager and
player name cache at event time.
- CombatLogEntry struct in spell_defines.hpp (type, amount, spellId,
isPlayerSource, timestamp, sourceName, targetName)
- getCombatLog() / clearCombatLog() accessors on GameHandler
- renderCombatLog() in GameScreen: scrollable two-column table (Time +
Event), color-coded by event category, with Damage/Healing/Misc filter
checkboxes, auto-scroll toggle, and Clear button
- /combatlog (/cl) chat command toggles the window
2026-03-12 11:00:10 -07:00
|
|
|
|
// Combat log (persistent rolling history, max MAX_COMBAT_LOG entries)
|
|
|
|
|
|
const std::deque<CombatLogEntry>& getCombatLog() const { return combatLog_; }
|
|
|
|
|
|
void clearCombatLog() { combatLog_.clear(); }
|
|
|
|
|
|
|
2026-03-12 11:06:40 -07:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 02:59:09 -07:00
|
|
|
|
// Threat
|
|
|
|
|
|
struct ThreatEntry {
|
|
|
|
|
|
uint64_t victimGuid = 0;
|
|
|
|
|
|
uint32_t threat = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
// Returns the current threat list for a given unit GUID (from last SMSG_THREAT_UPDATE)
|
|
|
|
|
|
const std::vector<ThreatEntry>* getThreatList(uint64_t unitGuid) const {
|
|
|
|
|
|
auto it = threatLists_.find(unitGuid);
|
|
|
|
|
|
return (it != threatLists_.end()) ? &it->second : nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Returns the threat list for the player's current target, or nullptr
|
|
|
|
|
|
const std::vector<ThreatEntry>* getTargetThreatList() const {
|
|
|
|
|
|
return targetGuid ? getThreatList(targetGuid) : nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 3: Spells ----
|
|
|
|
|
|
void castSpell(uint32_t spellId, uint64_t targetGuid = 0);
|
|
|
|
|
|
void cancelCast();
|
|
|
|
|
|
void cancelAura(uint32_t spellId);
|
2026-02-26 10:41:29 -08:00
|
|
|
|
void dismissPet();
|
2026-03-12 19:42:31 -07:00
|
|
|
|
void renamePet(const std::string& newName);
|
2026-02-26 10:41:29 -08:00
|
|
|
|
bool hasPet() const { return petGuid_ != 0; }
|
|
|
|
|
|
uint64_t getPetGuid() const { return petGuid_; }
|
2026-03-09 22:53:09 -07:00
|
|
|
|
|
|
|
|
|
|
// ---- Pet state (populated by SMSG_PET_SPELLS / SMSG_PET_MODE) ----
|
|
|
|
|
|
// 10 action bar slots; each entry is a packed uint32:
|
|
|
|
|
|
// bits 0-23 = spell ID (or 0 for empty)
|
|
|
|
|
|
// bits 24-31 = action type (0x00=cast, 0xC0=autocast on, 0x40=autocast off)
|
|
|
|
|
|
static constexpr int PET_ACTION_BAR_SLOTS = 10;
|
|
|
|
|
|
uint32_t getPetActionSlot(int idx) const {
|
|
|
|
|
|
if (idx < 0 || idx >= PET_ACTION_BAR_SLOTS) return 0;
|
|
|
|
|
|
return petActionSlots_[idx];
|
|
|
|
|
|
}
|
|
|
|
|
|
// Pet command/react state from SMSG_PET_MODE or SMSG_PET_SPELLS
|
|
|
|
|
|
uint8_t getPetCommand() const { return petCommand_; } // 0=stay,1=follow,2=attack,3=dismiss
|
|
|
|
|
|
uint8_t getPetReact() const { return petReact_; } // 0=passive,1=defensive,2=aggressive
|
|
|
|
|
|
// Spells the pet knows (from SMSG_PET_SPELLS spell list)
|
|
|
|
|
|
const std::vector<uint32_t>& getPetSpells() const { return petSpellList_; }
|
|
|
|
|
|
// Pet autocast set (spellIds that have autocast enabled)
|
|
|
|
|
|
bool isPetSpellAutocast(uint32_t spellId) const {
|
|
|
|
|
|
return petAutocastSpells_.count(spellId) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Send CMSG_PET_ACTION to issue a pet command
|
|
|
|
|
|
void sendPetAction(uint32_t action, uint64_t targetGuid = 0);
|
2026-02-17 15:13:54 -08:00
|
|
|
|
const std::unordered_set<uint32_t>& getKnownSpells() const { return knownSpells; }
|
2026-03-09 20:23:38 -07:00
|
|
|
|
|
feat: implement pet stable system (MSG_LIST_STABLED_PETS, CMSG_STABLE_PET, CMSG_UNSTABLE_PET)
- Parse MSG_LIST_STABLED_PETS (SMSG): populate StabledPet list with
petNumber, entry, level, name, displayId, and active status
- Detect stable master via gossip option text/keyword matching and
auto-send MSG_LIST_STABLED_PETS request to open the stable UI
- Refresh list automatically after SMSG_STABLE_RESULT to reflect state
- New packet builders: ListStabledPetsPacket, StablePetPacket, UnstablePetPacket
- New public API: requestStabledPetList(), stablePet(slot), unstablePet(petNumber)
- Stable window UI: shows active/stabled pets with store/retrieve buttons,
slot count, refresh, and close; opens when server sends pet list
- Clear stable state on world logout/disconnect
2026-03-12 19:15:52 -07:00
|
|
|
|
// ---- Pet Stable ----
|
|
|
|
|
|
struct StabledPet {
|
|
|
|
|
|
uint32_t petNumber = 0; // server-side pet number (used for unstable/swap)
|
|
|
|
|
|
uint32_t entry = 0; // creature entry ID
|
|
|
|
|
|
uint32_t level = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
uint32_t displayId = 0;
|
|
|
|
|
|
bool isActive = false; // true = currently summoned/active slot
|
|
|
|
|
|
};
|
|
|
|
|
|
bool isStableWindowOpen() const { return stableWindowOpen_; }
|
|
|
|
|
|
void closeStableWindow() { stableWindowOpen_ = false; }
|
|
|
|
|
|
uint64_t getStableMasterGuid() const { return stableMasterGuid_; }
|
|
|
|
|
|
uint8_t getStableSlots() const { return stableNumSlots_; }
|
|
|
|
|
|
const std::vector<StabledPet>& getStabledPets() const { return stabledPets_; }
|
|
|
|
|
|
void requestStabledPetList(); // CMSG MSG_LIST_STABLED_PETS
|
|
|
|
|
|
void stablePet(uint8_t slot); // CMSG_STABLE_PET (store active pet in slot)
|
|
|
|
|
|
void unstablePet(uint32_t petNumber); // CMSG_UNSTABLE_PET (retrieve to active)
|
|
|
|
|
|
|
2026-03-09 20:23:38 -07:00
|
|
|
|
// Player proficiency bitmasks (from SMSG_SET_PROFICIENCY)
|
|
|
|
|
|
// itemClass 2 = Weapon (subClassMask bits: 0=Axe1H,1=Axe2H,2=Bow,3=Gun,4=Mace1H,5=Mace2H,6=Polearm,7=Sword1H,8=Sword2H,10=Staff,13=Fist,14=Misc,15=Dagger,16=Thrown,17=Crossbow,18=Wand,19=Fishing)
|
|
|
|
|
|
// itemClass 4 = Armor (subClassMask bits: 1=Cloth,2=Leather,3=Mail,4=Plate,6=Shield)
|
|
|
|
|
|
uint32_t getWeaponProficiency() const { return weaponProficiency_; }
|
|
|
|
|
|
uint32_t getArmorProficiency() const { return armorProficiency_; }
|
|
|
|
|
|
bool canUseWeaponSubclass(uint32_t subClass) const { return (weaponProficiency_ >> subClass) & 1u; }
|
|
|
|
|
|
bool canUseArmorSubclass(uint32_t subClass) const { return (armorProficiency_ >> subClass) & 1u; }
|
|
|
|
|
|
|
2026-03-09 20:36:20 -07:00
|
|
|
|
// Minimap pings from party members
|
|
|
|
|
|
struct MinimapPing {
|
|
|
|
|
|
uint64_t senderGuid = 0;
|
|
|
|
|
|
float wowX = 0.0f; // canonical WoW X (north)
|
|
|
|
|
|
float wowY = 0.0f; // canonical WoW Y (west)
|
|
|
|
|
|
float age = 0.0f; // seconds since received
|
|
|
|
|
|
static constexpr float LIFETIME = 5.0f;
|
|
|
|
|
|
bool isExpired() const { return age >= LIFETIME; }
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::vector<MinimapPing>& getMinimapPings() const { return minimapPings_; }
|
|
|
|
|
|
void tickMinimapPings(float dt) {
|
|
|
|
|
|
for (auto& p : minimapPings_) p.age += dt;
|
|
|
|
|
|
minimapPings_.erase(
|
|
|
|
|
|
std::remove_if(minimapPings_.begin(), minimapPings_.end(),
|
|
|
|
|
|
[](const MinimapPing& p){ return p.isExpired(); }),
|
|
|
|
|
|
minimapPings_.end());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
bool isCasting() const { return casting; }
|
2026-03-12 00:43:29 -07:00
|
|
|
|
bool isChanneling() const { return casting && castIsChannel; }
|
2026-02-19 03:31:49 -08:00
|
|
|
|
bool isGameObjectInteractionCasting() const {
|
|
|
|
|
|
return casting && currentCastSpellId == 0 && pendingGameObjectInteractGuid_ != 0;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint32_t getCurrentCastSpellId() const { return currentCastSpellId; }
|
|
|
|
|
|
float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; }
|
|
|
|
|
|
float getCastTimeRemaining() const { return castTimeRemaining; }
|
|
|
|
|
|
|
2026-03-09 23:13:30 -07:00
|
|
|
|
// Unit cast state (tracked per GUID for target frame + boss frames)
|
|
|
|
|
|
struct UnitCastState {
|
|
|
|
|
|
bool casting = false;
|
|
|
|
|
|
uint32_t spellId = 0;
|
|
|
|
|
|
float timeRemaining = 0.0f;
|
|
|
|
|
|
float timeTotal = 0.0f;
|
|
|
|
|
|
};
|
|
|
|
|
|
// Returns cast state for any unit by GUID (empty/non-casting if not found)
|
|
|
|
|
|
const UnitCastState* getUnitCastState(uint64_t guid) const {
|
|
|
|
|
|
auto it = unitCastStates_.find(guid);
|
|
|
|
|
|
return (it != unitCastStates_.end() && it->second.casting) ? &it->second : nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Convenience helpers for the current target
|
|
|
|
|
|
bool isTargetCasting() const { return getUnitCastState(targetGuid) != nullptr; }
|
|
|
|
|
|
uint32_t getTargetCastSpellId() const {
|
|
|
|
|
|
auto* s = getUnitCastState(targetGuid);
|
|
|
|
|
|
return s ? s->spellId : 0;
|
|
|
|
|
|
}
|
2026-03-09 23:06:40 -07:00
|
|
|
|
float getTargetCastProgress() const {
|
2026-03-09 23:13:30 -07:00
|
|
|
|
auto* s = getUnitCastState(targetGuid);
|
|
|
|
|
|
return (s && s->timeTotal > 0.0f)
|
|
|
|
|
|
? (s->timeTotal - s->timeRemaining) / s->timeTotal : 0.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
float getTargetCastTimeRemaining() const {
|
|
|
|
|
|
auto* s = getUnitCastState(targetGuid);
|
|
|
|
|
|
return s ? s->timeRemaining : 0.0f;
|
2026-03-09 23:06:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
// Talents
|
|
|
|
|
|
uint8_t getActiveTalentSpec() const { return activeTalentSpec_; }
|
|
|
|
|
|
uint8_t getUnspentTalentPoints() const { return unspentTalentPoints_[activeTalentSpec_]; }
|
|
|
|
|
|
uint8_t getUnspentTalentPoints(uint8_t spec) const { return spec < 2 ? unspentTalentPoints_[spec] : 0; }
|
|
|
|
|
|
const std::unordered_map<uint32_t, uint8_t>& getLearnedTalents() const { return learnedTalents_[activeTalentSpec_]; }
|
|
|
|
|
|
const std::unordered_map<uint32_t, uint8_t>& getLearnedTalents(uint8_t spec) const {
|
|
|
|
|
|
static std::unordered_map<uint32_t, uint8_t> empty;
|
|
|
|
|
|
return spec < 2 ? learnedTalents_[spec] : empty;
|
|
|
|
|
|
}
|
2026-03-12 17:39:35 -07:00
|
|
|
|
|
|
|
|
|
|
// Glyphs (WotLK): up to 6 glyph slots per spec (3 major + 3 minor)
|
|
|
|
|
|
static constexpr uint8_t MAX_GLYPH_SLOTS = 6;
|
|
|
|
|
|
const std::array<uint16_t, MAX_GLYPH_SLOTS>& getGlyphs() const { return learnedGlyphs_[activeTalentSpec_]; }
|
|
|
|
|
|
const std::array<uint16_t, MAX_GLYPH_SLOTS>& getGlyphs(uint8_t spec) const {
|
|
|
|
|
|
static std::array<uint16_t, MAX_GLYPH_SLOTS> empty{};
|
|
|
|
|
|
return spec < 2 ? learnedGlyphs_[spec] : empty;
|
|
|
|
|
|
}
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
uint8_t getTalentRank(uint32_t talentId) const {
|
|
|
|
|
|
auto it = learnedTalents_[activeTalentSpec_].find(talentId);
|
|
|
|
|
|
return (it != learnedTalents_[activeTalentSpec_].end()) ? it->second : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
void learnTalent(uint32_t talentId, uint32_t requestedRank);
|
|
|
|
|
|
void switchTalentSpec(uint8_t newSpec);
|
|
|
|
|
|
|
|
|
|
|
|
// Talent DBC access
|
|
|
|
|
|
const TalentEntry* getTalentEntry(uint32_t talentId) const {
|
|
|
|
|
|
auto it = talentCache_.find(talentId);
|
|
|
|
|
|
return (it != talentCache_.end()) ? &it->second : nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
const TalentTabEntry* getTalentTabEntry(uint32_t tabId) const {
|
|
|
|
|
|
auto it = talentTabCache_.find(tabId);
|
|
|
|
|
|
return (it != talentTabCache_.end()) ? &it->second : nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
const std::unordered_map<uint32_t, TalentEntry>& getAllTalents() const { return talentCache_; }
|
|
|
|
|
|
const std::unordered_map<uint32_t, TalentTabEntry>& getAllTalentTabs() const { return talentTabCache_; }
|
|
|
|
|
|
void loadTalentDbc();
|
|
|
|
|
|
|
2026-03-10 15:56:41 -07:00
|
|
|
|
// Action bar — 4 bars × 12 slots = 48 total
|
|
|
|
|
|
// Bar 0 (slots 0-11): main bottom bar (1-0, -, =)
|
|
|
|
|
|
// Bar 1 (slots 12-23): second bar above main (Shift+1 ... Shift+=)
|
|
|
|
|
|
// Bar 2 (slots 24-35): right side vertical bar
|
|
|
|
|
|
// Bar 3 (slots 36-47): left side vertical bar
|
2026-03-10 06:04:43 -07:00
|
|
|
|
static constexpr int SLOTS_PER_BAR = 12;
|
2026-03-10 15:56:41 -07:00
|
|
|
|
static constexpr int ACTION_BARS = 4;
|
|
|
|
|
|
static constexpr int ACTION_BAR_SLOTS = SLOTS_PER_BAR * ACTION_BARS; // 48
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
std::array<ActionBarSlot, ACTION_BAR_SLOTS>& getActionBar() { return actionBar; }
|
|
|
|
|
|
const std::array<ActionBarSlot, ACTION_BAR_SLOTS>& getActionBar() const { return actionBar; }
|
|
|
|
|
|
void setActionBarSlot(int slot, ActionBarSlot::Type type, uint32_t id);
|
|
|
|
|
|
|
Fix vendor buying, improve character select, parallelize WMO culling, and optimize collision
- Fix CMSG_BUY_ITEM count field from uint8 to uint32 (server silently dropped undersized packets)
- Character select screen: remember last selected character, two-column layout with details panel, double-click to enter world, responsive window sizing
- Fix stale character data between logins by replacing static init flag with per-character GUID tracking
- Parallelize WMO visibility culling across worker threads (same pattern as M2 renderer)
- Optimize WMO collision queries with world-space group bounds early rejection in getFloorHeight, checkWallCollision, isInsideWMO, and raycastBoundingBoxes
- Reduce camera ground samples from 5 to 3 movement-aligned probes
- Add WMO interior lighting, unlit materials, vertex color multiply, and alpha blending support
2026-02-07 15:29:19 -08:00
|
|
|
|
void saveCharacterConfig();
|
|
|
|
|
|
void loadCharacterConfig();
|
|
|
|
|
|
static std::string getCharacterConfigDir();
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// Auras
|
|
|
|
|
|
const std::vector<AuraSlot>& getPlayerAuras() const { return playerAuras; }
|
|
|
|
|
|
const std::vector<AuraSlot>& getTargetAuras() const { return targetAuras; }
|
2026-03-12 11:44:30 -07:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-03-09 15:32:11 -07:00
|
|
|
|
// Completed quests (populated from SMSG_QUERY_QUESTS_COMPLETED_RESPONSE)
|
|
|
|
|
|
bool isQuestCompleted(uint32_t questId) const { return completedQuests_.count(questId) > 0; }
|
|
|
|
|
|
const std::unordered_set<uint32_t>& getCompletedQuests() const { return completedQuests_; }
|
|
|
|
|
|
|
2026-02-07 00:00:06 -08:00
|
|
|
|
// NPC death callback (for animations)
|
|
|
|
|
|
using NpcDeathCallback = std::function<void(uint64_t guid)>;
|
|
|
|
|
|
void setNpcDeathCallback(NpcDeathCallback cb) { npcDeathCallback_ = std::move(cb); }
|
|
|
|
|
|
|
Add comprehensive NPC voice system with interaction and combat sounds
Implements full NPC voice interaction system supporting 6 different sound categories
for all playable races/genders. System loads ~450+ voice clips from MPQ archives.
Voice Categories:
- Greeting: Play on NPC right-click interaction
- Farewell: Play when closing gossip/dialog windows
- Vendor: Play when opening merchant/vendor windows
- Pissed: Play after clicking NPC 5+ times (spam protection)
- Aggro: Play when NPC enters combat with player
- Flee: Play when NPC is fleeing (ready for low-health triggers)
Features:
- Race/gender detection from NPC display IDs via CreatureDisplayInfoExtra.dbc
- Intelligent click tracking for pissed sounds
- Combat sounds use player character vocal files for humanoid NPCs
- Cooldown system prevents voice spam (2s default, combat sounds bypass)
- Generic fallback voices for unsupported NPC types
- 3D positional audio support
Voice Support:
- All playable races: Human, Dwarf, Gnome, Night Elf, Orc, Tauren, Troll, Undead
- Male and female variants for each race
- StandardNPC sounds for social interactions
- Character vocal sounds for combat
Technical Changes:
- Refactored NpcVoiceManager to support multiple sound categories
- Added callbacks: NpcFarewell, NpcVendor, NpcAggro
- Extended voice loading to parse both StandardNPC and Character vocal paths
- Integrated with GameHandler for gossip, vendor, and combat events
- Added detailed voice detection logging for debugging
Also includes:
- Sound manifest files added to docs/ for reference
- Blacksmith hammer pitch increased to 1.6x (was 1.4x)
- Blacksmith volume reduced 30% to 0.25 (was 0.35)
2026-02-09 16:03:51 -08:00
|
|
|
|
using NpcAggroCallback = std::function<void(uint64_t guid, const glm::vec3& position)>;
|
|
|
|
|
|
void setNpcAggroCallback(NpcAggroCallback cb) { npcAggroCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-07 00:00:06 -08:00
|
|
|
|
// NPC respawn callback (health 0 → >0, resets animation to idle)
|
|
|
|
|
|
using NpcRespawnCallback = std::function<void(uint64_t guid)>;
|
|
|
|
|
|
void setNpcRespawnCallback(NpcRespawnCallback cb) { npcRespawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-10 09:46:46 -07:00
|
|
|
|
// Stand state animation callback — fired when SMSG_STANDSTATE_UPDATE confirms a new state
|
|
|
|
|
|
// standState: 0=stand, 1-6=sit variants, 7=dead, 8=kneel
|
|
|
|
|
|
using StandStateCallback = std::function<void(uint8_t standState)>;
|
|
|
|
|
|
void setStandStateCallback(StandStateCallback cb) { standStateCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-10 09:57:24 -07:00
|
|
|
|
// Ghost state callback — fired when player enters or leaves ghost (spirit) form
|
|
|
|
|
|
using GhostStateCallback = std::function<void(bool isGhost)>;
|
|
|
|
|
|
void setGhostStateCallback(GhostStateCallback cb) { ghostStateCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-05 14:01:26 -08:00
|
|
|
|
// Melee swing callback (for driving animation/SFX)
|
|
|
|
|
|
using MeleeSwingCallback = std::function<void()>;
|
|
|
|
|
|
void setMeleeSwingCallback(MeleeSwingCallback cb) { meleeSwingCallback_ = std::move(cb); }
|
2026-02-05 12:01:03 -08:00
|
|
|
|
|
2026-03-10 09:42:17 -07:00
|
|
|
|
// Spell cast animation callbacks — true=start cast/channel, false=finish/cancel
|
|
|
|
|
|
// guid: caster (may be player or another unit), isChannel: channel vs regular cast
|
|
|
|
|
|
using SpellCastAnimCallback = std::function<void(uint64_t guid, bool start, bool isChannel)>;
|
|
|
|
|
|
void setSpellCastAnimCallback(SpellCastAnimCallback cb) { spellCastAnimCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-10 10:55:23 -07:00
|
|
|
|
// Unit animation hint: signal jump (animId=38) for other players/NPCs
|
2026-03-10 10:30:50 -07:00
|
|
|
|
using UnitAnimHintCallback = std::function<void(uint64_t guid, uint32_t animId)>;
|
|
|
|
|
|
void setUnitAnimHintCallback(UnitAnimHintCallback cb) { unitAnimHintCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-10 10:55:23 -07:00
|
|
|
|
// Unit move-flags callback: fired on every MSG_MOVE_* for other players with the raw flags field.
|
|
|
|
|
|
// Drives Walk(4) vs Run(5) selection and swim state initialization from heartbeat packets.
|
|
|
|
|
|
using UnitMoveFlagsCallback = std::function<void(uint64_t guid, uint32_t moveFlags)>;
|
|
|
|
|
|
void setUnitMoveFlagsCallback(UnitMoveFlagsCallback cb) { unitMoveFlagsCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-07 00:00:06 -08:00
|
|
|
|
// NPC swing callback (plays attack animation on NPC)
|
|
|
|
|
|
using NpcSwingCallback = std::function<void(uint64_t guid)>;
|
|
|
|
|
|
void setNpcSwingCallback(NpcSwingCallback cb) { npcSwingCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-09 01:29:44 -08:00
|
|
|
|
// NPC greeting callback (plays voice line when NPC is clicked)
|
|
|
|
|
|
using NpcGreetingCallback = std::function<void(uint64_t guid, const glm::vec3& position)>;
|
|
|
|
|
|
void setNpcGreetingCallback(NpcGreetingCallback cb) { npcGreetingCallback_ = std::move(cb); }
|
|
|
|
|
|
|
Add comprehensive NPC voice system with interaction and combat sounds
Implements full NPC voice interaction system supporting 6 different sound categories
for all playable races/genders. System loads ~450+ voice clips from MPQ archives.
Voice Categories:
- Greeting: Play on NPC right-click interaction
- Farewell: Play when closing gossip/dialog windows
- Vendor: Play when opening merchant/vendor windows
- Pissed: Play after clicking NPC 5+ times (spam protection)
- Aggro: Play when NPC enters combat with player
- Flee: Play when NPC is fleeing (ready for low-health triggers)
Features:
- Race/gender detection from NPC display IDs via CreatureDisplayInfoExtra.dbc
- Intelligent click tracking for pissed sounds
- Combat sounds use player character vocal files for humanoid NPCs
- Cooldown system prevents voice spam (2s default, combat sounds bypass)
- Generic fallback voices for unsupported NPC types
- 3D positional audio support
Voice Support:
- All playable races: Human, Dwarf, Gnome, Night Elf, Orc, Tauren, Troll, Undead
- Male and female variants for each race
- StandardNPC sounds for social interactions
- Character vocal sounds for combat
Technical Changes:
- Refactored NpcVoiceManager to support multiple sound categories
- Added callbacks: NpcFarewell, NpcVendor, NpcAggro
- Extended voice loading to parse both StandardNPC and Character vocal paths
- Integrated with GameHandler for gossip, vendor, and combat events
- Added detailed voice detection logging for debugging
Also includes:
- Sound manifest files added to docs/ for reference
- Blacksmith hammer pitch increased to 1.6x (was 1.4x)
- Blacksmith volume reduced 30% to 0.25 (was 0.35)
2026-02-09 16:03:51 -08:00
|
|
|
|
using NpcFarewellCallback = std::function<void(uint64_t guid, const glm::vec3& position)>;
|
|
|
|
|
|
void setNpcFarewellCallback(NpcFarewellCallback cb) { npcFarewellCallback_ = std::move(cb); }
|
|
|
|
|
|
|
|
|
|
|
|
using NpcVendorCallback = std::function<void(uint64_t guid, const glm::vec3& position)>;
|
|
|
|
|
|
void setNpcVendorCallback(NpcVendorCallback cb) { npcVendorCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-07 00:00:06 -08:00
|
|
|
|
// XP tracking
|
2026-02-05 12:07:58 -08:00
|
|
|
|
uint32_t getPlayerXp() const { return playerXp_; }
|
|
|
|
|
|
uint32_t getPlayerNextLevelXp() const { return playerNextLevelXp_; }
|
2026-03-10 07:35:30 -07:00
|
|
|
|
uint32_t getPlayerRestedXp() const { return playerRestedXp_; }
|
|
|
|
|
|
bool isPlayerResting() const { return isResting_; }
|
2026-02-06 23:52:16 -08:00
|
|
|
|
uint32_t getPlayerLevel() const { return serverPlayerLevel_; }
|
2026-02-11 18:25:04 -08:00
|
|
|
|
const std::vector<uint32_t>& getPlayerExploredZoneMasks() const { return playerExploredZones_; }
|
|
|
|
|
|
bool hasPlayerExploredZoneMasks() const { return hasPlayerExploredZones_; }
|
2026-02-06 16:53:09 -08:00
|
|
|
|
static uint32_t killXp(uint32_t playerLevel, uint32_t victimLevel);
|
2026-02-05 12:07:58 -08:00
|
|
|
|
|
2026-02-10 19:30:45 -08:00
|
|
|
|
// Server time (for deterministic moon phases, etc.)
|
|
|
|
|
|
float getGameTime() const { return gameTime_; }
|
|
|
|
|
|
float getTimeSpeed() const { return timeSpeed_; }
|
|
|
|
|
|
|
2026-03-12 05:38:13 -07:00
|
|
|
|
// 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; }
|
|
|
|
|
|
|
2026-02-17 17:59:41 -08:00
|
|
|
|
// Weather state (updated by SMSG_WEATHER)
|
|
|
|
|
|
// weatherType: 0=clear, 1=rain, 2=snow, 3=storm/fog
|
|
|
|
|
|
uint32_t getWeatherType() const { return weatherType_; }
|
|
|
|
|
|
float getWeatherIntensity() const { return weatherIntensity_; }
|
|
|
|
|
|
bool isRaining() const { return weatherType_ == 1 && weatherIntensity_ > 0.05f; }
|
|
|
|
|
|
bool isSnowing() const { return weatherType_ == 2 && weatherIntensity_ > 0.05f; }
|
2026-03-09 14:57:46 -07:00
|
|
|
|
uint32_t getOverrideLightId() const { return overrideLightId_; }
|
|
|
|
|
|
uint32_t getOverrideLightTransMs() const { return overrideLightTransMs_; }
|
2026-02-17 17:59:41 -08:00
|
|
|
|
|
2026-02-07 14:21:50 -08:00
|
|
|
|
// Player skills
|
|
|
|
|
|
const std::map<uint32_t, PlayerSkill>& getPlayerSkills() const { return playerSkills_; }
|
|
|
|
|
|
const std::string& getSkillName(uint32_t skillId) const;
|
|
|
|
|
|
uint32_t getSkillCategory(uint32_t skillId) const;
|
|
|
|
|
|
|
2026-02-05 21:28:21 -08:00
|
|
|
|
// World entry callback (online mode - triggered when entering world)
|
2026-03-10 08:35:36 -07:00
|
|
|
|
// Parameters: mapId, x, y, z (canonical WoW coords), isInitialEntry=true on first login or reconnect
|
|
|
|
|
|
using WorldEntryCallback = std::function<void(uint32_t mapId, float x, float y, float z, bool isInitialEntry)>;
|
2026-02-05 21:28:21 -08:00
|
|
|
|
void setWorldEntryCallback(WorldEntryCallback cb) { worldEntryCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-10 12:28:11 -07:00
|
|
|
|
// Knockback callback: called when server sends SMSG_MOVE_KNOCK_BACK for the player.
|
|
|
|
|
|
// Parameters: vcos, vsin (render-space direction), hspeed, vspeed (raw from packet).
|
|
|
|
|
|
using KnockBackCallback = std::function<void(float vcos, float vsin, float hspeed, float vspeed)>;
|
|
|
|
|
|
void setKnockBackCallback(KnockBackCallback cb) { knockBackCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-12 19:37:53 -07:00
|
|
|
|
// Camera shake callback: called when server sends SMSG_CAMERA_SHAKE.
|
|
|
|
|
|
// Parameters: magnitude (world units), frequency (Hz), duration (seconds).
|
|
|
|
|
|
using CameraShakeCallback = std::function<void(float magnitude, float frequency, float duration)>;
|
|
|
|
|
|
void setCameraShakeCallback(CameraShakeCallback cb) { cameraShakeCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-07 16:59:20 -08:00
|
|
|
|
// Unstuck callback (resets player Z to floor height)
|
|
|
|
|
|
using UnstuckCallback = std::function<void()>;
|
|
|
|
|
|
void setUnstuckCallback(UnstuckCallback cb) { unstuckCallback_ = std::move(cb); }
|
|
|
|
|
|
void unstuck();
|
2026-02-08 03:24:12 -08:00
|
|
|
|
void setUnstuckGyCallback(UnstuckCallback cb) { unstuckGyCallback_ = std::move(cb); }
|
|
|
|
|
|
void unstuckGy();
|
2026-03-07 22:03:28 -08:00
|
|
|
|
void setUnstuckHearthCallback(UnstuckCallback cb) { unstuckHearthCallback_ = std::move(cb); }
|
|
|
|
|
|
void unstuckHearth();
|
2026-02-08 03:32:00 -08:00
|
|
|
|
using BindPointCallback = std::function<void(uint32_t mapId, float x, float y, float z)>;
|
|
|
|
|
|
void setBindPointCallback(BindPointCallback cb) { bindPointCallback_ = std::move(cb); }
|
2026-02-07 16:59:20 -08:00
|
|
|
|
|
2026-03-09 21:57:42 -07:00
|
|
|
|
// Called when the player starts casting Hearthstone so terrain at the bind
|
|
|
|
|
|
// point can be pre-loaded during the cast time.
|
|
|
|
|
|
// Parameters: mapId and canonical (x, y, z) of the bind location.
|
|
|
|
|
|
using HearthstonePreloadCallback = std::function<void(uint32_t mapId, float x, float y, float z)>;
|
|
|
|
|
|
void setHearthstonePreloadCallback(HearthstonePreloadCallback cb) { hearthstonePreloadCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Creature spawn callback (online mode - triggered when creature enters view)
|
feat: propagate OBJECT_FIELD_SCALE_X through creature and GO spawn pipeline
Reads OBJECT_FIELD_SCALE_X (field 4, cross-expansion) from CREATE_OBJECT
update fields and passes it through the full creature and game object spawn
chain: game_handler callbacks → pending spawn structs → async load results
→ createInstance() calls. This gives boss giants, gnomes, children, and
other non-unit-scale NPCs correct visual size, and ensures scaled GOs
(e.g. large treasure chests, oversized plants) render at the server-specified
scale rather than always at 1.0.
- Added OBJECT_FIELD_SCALE_X to UF enum and all expansion update_fields.json
- Added float scale to CreatureSpawnCallback and GameObjectSpawnCallback
- Propagated scale through PendingCreatureSpawn, PreparedCreatureModel,
PendingGameObjectSpawn, PreparedGameObjectWMO
- Used scale in charRenderer/m2Renderer/wmoRenderer createInstance() calls
- Sanity-clamped raw float to [0.01, 100.0] range before use
2026-03-10 22:45:47 -07:00
|
|
|
|
// Parameters: guid, displayId, x, y, z (canonical), orientation, scale (OBJECT_FIELD_SCALE_X)
|
|
|
|
|
|
using CreatureSpawnCallback = std::function<void(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation, float scale)>;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
void setCreatureSpawnCallback(CreatureSpawnCallback cb) { creatureSpawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
|
|
|
|
|
// Creature despawn callback (online mode - triggered when creature leaves view)
|
|
|
|
|
|
using CreatureDespawnCallback = std::function<void(uint64_t guid)>;
|
|
|
|
|
|
void setCreatureDespawnCallback(CreatureDespawnCallback cb) { creatureDespawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-13 19:40:54 -08:00
|
|
|
|
// Player spawn callback (online mode - triggered when a player enters view).
|
|
|
|
|
|
// Players need appearance data so the renderer can build the right body/hair textures.
|
|
|
|
|
|
using PlayerSpawnCallback = std::function<void(uint64_t guid,
|
|
|
|
|
|
uint32_t displayId,
|
|
|
|
|
|
uint8_t raceId,
|
|
|
|
|
|
uint8_t genderId,
|
|
|
|
|
|
uint32_t appearanceBytes,
|
|
|
|
|
|
uint8_t facialFeatures,
|
|
|
|
|
|
float x, float y, float z, float orientation)>;
|
|
|
|
|
|
void setPlayerSpawnCallback(PlayerSpawnCallback cb) { playerSpawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
|
|
|
|
|
using PlayerDespawnCallback = std::function<void(uint64_t guid)>;
|
|
|
|
|
|
void setPlayerDespawnCallback(PlayerDespawnCallback cb) { playerDespawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-13 20:10:19 -08:00
|
|
|
|
// Online player equipment visuals callback.
|
|
|
|
|
|
// Sends a best-effort view of equipped items for players in view using ItemDisplayInfo IDs.
|
|
|
|
|
|
// Arrays are indexed by EquipSlot (0..18). Values are 0 when unknown/unavailable.
|
|
|
|
|
|
using PlayerEquipmentCallback = std::function<void(uint64_t guid,
|
|
|
|
|
|
const std::array<uint32_t, 19>& displayInfoIds,
|
|
|
|
|
|
const std::array<uint8_t, 19>& inventoryTypes)>;
|
|
|
|
|
|
void setPlayerEquipmentCallback(PlayerEquipmentCallback cb) { playerEquipmentCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-07 19:44:03 -08:00
|
|
|
|
// GameObject spawn callback (online mode - triggered when gameobject enters view)
|
feat: propagate OBJECT_FIELD_SCALE_X through creature and GO spawn pipeline
Reads OBJECT_FIELD_SCALE_X (field 4, cross-expansion) from CREATE_OBJECT
update fields and passes it through the full creature and game object spawn
chain: game_handler callbacks → pending spawn structs → async load results
→ createInstance() calls. This gives boss giants, gnomes, children, and
other non-unit-scale NPCs correct visual size, and ensures scaled GOs
(e.g. large treasure chests, oversized plants) render at the server-specified
scale rather than always at 1.0.
- Added OBJECT_FIELD_SCALE_X to UF enum and all expansion update_fields.json
- Added float scale to CreatureSpawnCallback and GameObjectSpawnCallback
- Propagated scale through PendingCreatureSpawn, PreparedCreatureModel,
PendingGameObjectSpawn, PreparedGameObjectWMO
- Used scale in charRenderer/m2Renderer/wmoRenderer createInstance() calls
- Sanity-clamped raw float to [0.01, 100.0] range before use
2026-03-10 22:45:47 -07:00
|
|
|
|
// Parameters: guid, entry, displayId, x, y, z (canonical), orientation, scale (OBJECT_FIELD_SCALE_X)
|
|
|
|
|
|
using GameObjectSpawnCallback = std::function<void(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale)>;
|
2026-02-07 19:44:03 -08:00
|
|
|
|
void setGameObjectSpawnCallback(GameObjectSpawnCallback cb) { gameObjectSpawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-12 00:04:53 -08:00
|
|
|
|
// GameObject move callback (online mode - triggered when gameobject position updates)
|
|
|
|
|
|
// Parameters: guid, x, y, z (canonical), orientation
|
|
|
|
|
|
using GameObjectMoveCallback = std::function<void(uint64_t guid, float x, float y, float z, float orientation)>;
|
|
|
|
|
|
void setGameObjectMoveCallback(GameObjectMoveCallback cb) { gameObjectMoveCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-07 19:44:03 -08:00
|
|
|
|
// GameObject despawn callback (online mode - triggered when gameobject leaves view)
|
|
|
|
|
|
using GameObjectDespawnCallback = std::function<void(uint64_t guid)>;
|
|
|
|
|
|
void setGameObjectDespawnCallback(GameObjectDespawnCallback cb) { gameObjectDespawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-23 05:39:02 -08:00
|
|
|
|
using GameObjectCustomAnimCallback = std::function<void(uint64_t guid, uint32_t animId)>;
|
|
|
|
|
|
void setGameObjectCustomAnimCallback(GameObjectCustomAnimCallback cb) { gameObjectCustomAnimCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-06 14:24:38 -08:00
|
|
|
|
// Faction hostility map (populated from FactionTemplate.dbc by Application)
|
|
|
|
|
|
void setFactionHostileMap(std::unordered_map<uint32_t, bool> map) { factionHostileMap_ = std::move(map); }
|
|
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
|
// Creature move callback (online mode - triggered by SMSG_MONSTER_MOVE)
|
|
|
|
|
|
// Parameters: guid, x, y, z (canonical), duration_ms (0 = instant)
|
|
|
|
|
|
using CreatureMoveCallback = std::function<void(uint64_t guid, float x, float y, float z, uint32_t durationMs)>;
|
|
|
|
|
|
void setCreatureMoveCallback(CreatureMoveCallback cb) { creatureMoveCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-08 00:59:40 -08:00
|
|
|
|
// Transport move callback (online mode - triggered when transport position updates)
|
|
|
|
|
|
// Parameters: guid, x, y, z (canonical), orientation
|
|
|
|
|
|
using TransportMoveCallback = std::function<void(uint64_t guid, float x, float y, float z, float orientation)>;
|
|
|
|
|
|
void setTransportMoveCallback(TransportMoveCallback cb) { transportMoveCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-11 00:54:38 -08:00
|
|
|
|
// Transport spawn callback (online mode - triggered when transport GameObject is first detected)
|
|
|
|
|
|
// Parameters: guid, entry, displayId, x, y, z (canonical), orientation
|
|
|
|
|
|
using TransportSpawnCallback = std::function<void(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation)>;
|
|
|
|
|
|
void setTransportSpawnCallback(TransportSpawnCallback cb) { transportSpawnCallback_ = std::move(cb); }
|
|
|
|
|
|
|
|
|
|
|
|
// Notify that a transport has been spawned (called after WMO instance creation)
|
|
|
|
|
|
void notifyTransportSpawned(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation) {
|
|
|
|
|
|
if (transportSpawnCallback_) {
|
|
|
|
|
|
transportSpawnCallback_(guid, entry, displayId, x, y, z, orientation);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 00:59:40 -08:00
|
|
|
|
// Transport state for player-on-transport
|
|
|
|
|
|
bool isOnTransport() const { return playerTransportGuid_ != 0; }
|
|
|
|
|
|
uint64_t getPlayerTransportGuid() const { return playerTransportGuid_; }
|
|
|
|
|
|
glm::vec3 getPlayerTransportOffset() const { return playerTransportOffset_; }
|
2026-02-11 00:54:38 -08:00
|
|
|
|
|
|
|
|
|
|
// Check if a GUID is a known transport
|
|
|
|
|
|
bool isTransportGuid(uint64_t guid) const { return transportGuids_.count(guid) > 0; }
|
2026-02-12 00:04:53 -08:00
|
|
|
|
bool hasServerTransportUpdate(uint64_t guid) const { return serverUpdatedTransportGuids_.count(guid) > 0; }
|
2026-02-10 21:29:10 -08:00
|
|
|
|
glm::vec3 getComposedWorldPosition(); // Compose transport transform * local offset
|
|
|
|
|
|
TransportManager* getTransportManager() { return transportManager_.get(); }
|
|
|
|
|
|
void setPlayerOnTransport(uint64_t transportGuid, const glm::vec3& localOffset) {
|
2026-03-11 16:06:36 -07:00
|
|
|
|
// Validate transport is registered before attaching player
|
|
|
|
|
|
// (defer if transport not yet registered to prevent desyncs)
|
|
|
|
|
|
if (transportGuid != 0 && !isTransportGuid(transportGuid)) {
|
|
|
|
|
|
return; // Transport not yet registered; skip attachment
|
|
|
|
|
|
}
|
2026-02-10 21:29:10 -08:00
|
|
|
|
playerTransportGuid_ = transportGuid;
|
|
|
|
|
|
playerTransportOffset_ = localOffset;
|
2026-02-12 00:45:24 -08:00
|
|
|
|
playerTransportStickyGuid_ = transportGuid;
|
|
|
|
|
|
playerTransportStickyTimer_ = 8.0f;
|
|
|
|
|
|
movementInfo.transportGuid = transportGuid;
|
2026-02-10 21:29:10 -08:00
|
|
|
|
}
|
2026-03-06 23:01:11 -08:00
|
|
|
|
void setPlayerTransportOffset(const glm::vec3& offset) {
|
|
|
|
|
|
playerTransportOffset_ = offset;
|
|
|
|
|
|
}
|
2026-02-10 21:29:10 -08:00
|
|
|
|
void clearPlayerTransport() {
|
2026-02-12 00:45:24 -08:00
|
|
|
|
if (playerTransportGuid_ != 0) {
|
|
|
|
|
|
playerTransportStickyGuid_ = playerTransportGuid_;
|
|
|
|
|
|
playerTransportStickyTimer_ = std::max(playerTransportStickyTimer_, 1.5f);
|
|
|
|
|
|
}
|
2026-02-10 21:29:10 -08:00
|
|
|
|
playerTransportGuid_ = 0;
|
|
|
|
|
|
playerTransportOffset_ = glm::vec3(0.0f);
|
2026-02-12 00:45:24 -08:00
|
|
|
|
movementInfo.transportGuid = 0;
|
2026-02-10 21:29:10 -08:00
|
|
|
|
}
|
2026-02-08 00:59:40 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// Cooldowns
|
|
|
|
|
|
float getSpellCooldown(uint32_t spellId) const;
|
2026-03-12 15:25:07 -07:00
|
|
|
|
const std::unordered_map<uint32_t, float>& getSpellCooldowns() const { return spellCooldowns; }
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
// Player GUID
|
|
|
|
|
|
uint64_t getPlayerGuid() const { return playerGuid; }
|
2026-03-10 15:18:00 -07:00
|
|
|
|
|
|
|
|
|
|
// Look up a display name for any guid: checks playerNameCache then entity manager.
|
|
|
|
|
|
// Returns empty string if unknown. Used by chat display to resolve names at render time.
|
|
|
|
|
|
const std::string& lookupName(uint64_t guid) const {
|
|
|
|
|
|
static const std::string kEmpty;
|
|
|
|
|
|
auto it = playerNameCache.find(guid);
|
|
|
|
|
|
if (it != playerNameCache.end()) return it->second;
|
|
|
|
|
|
auto entity = entityManager.getEntity(guid);
|
|
|
|
|
|
if (entity) {
|
|
|
|
|
|
if (auto* unit = dynamic_cast<const Unit*>(entity.get())) {
|
|
|
|
|
|
if (!unit->getName().empty()) return unit->getName();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return kEmpty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
uint8_t getPlayerClass() const {
|
|
|
|
|
|
const Character* ch = getActiveCharacter();
|
|
|
|
|
|
return ch ? static_cast<uint8_t>(ch->characterClass) : 0;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
void setPlayerGuid(uint64_t guid) { playerGuid = guid; }
|
|
|
|
|
|
|
2026-02-06 17:27:20 -08:00
|
|
|
|
// Player death state
|
|
|
|
|
|
bool isPlayerDead() const { return playerDead_; }
|
2026-02-07 23:12:24 -08:00
|
|
|
|
bool isPlayerGhost() const { return releasedSpirit_; }
|
|
|
|
|
|
bool showDeathDialog() const { return playerDead_ && !releasedSpirit_; }
|
|
|
|
|
|
bool showResurrectDialog() const { return resurrectRequestPending_; }
|
2026-03-09 22:27:24 -07:00
|
|
|
|
const std::string& getResurrectCasterName() const { return resurrectCasterName_; }
|
2026-03-10 12:53:05 -07:00
|
|
|
|
bool showTalentWipeConfirmDialog() const { return talentWipePending_; }
|
|
|
|
|
|
uint32_t getTalentWipeCost() const { return talentWipeCost_; }
|
|
|
|
|
|
void confirmTalentWipe();
|
|
|
|
|
|
void cancelTalentWipe() { talentWipePending_ = false; }
|
2026-03-09 22:31:56 -07:00
|
|
|
|
/** True when ghost is within 40 yards of corpse position (same map). */
|
|
|
|
|
|
bool canReclaimCorpse() const;
|
2026-03-11 22:41:26 -07:00
|
|
|
|
/** Distance (yards) from ghost to corpse, or -1 if no corpse data. */
|
|
|
|
|
|
float getCorpseDistance() const {
|
|
|
|
|
|
if (corpseMapId_ == 0 || currentMapId_ != corpseMapId_) return -1.0f;
|
2026-03-13 00:59:43 -07:00
|
|
|
|
// movementInfo is canonical (x=north=server_y, y=west=server_x);
|
|
|
|
|
|
// corpse coords are raw server (x=west, y=north) — swap to compare.
|
|
|
|
|
|
float dx = movementInfo.x - corpseY_;
|
|
|
|
|
|
float dy = movementInfo.y - corpseX_;
|
2026-03-11 22:41:26 -07:00
|
|
|
|
float dz = movementInfo.z - corpseZ_;
|
|
|
|
|
|
return std::sqrt(dx*dx + dy*dy + dz*dz);
|
|
|
|
|
|
}
|
2026-03-11 23:19:48 -07:00
|
|
|
|
/** Corpse position in canonical WoW coords (X=north, Y=west).
|
|
|
|
|
|
* Returns false if no corpse data or on a different map. */
|
|
|
|
|
|
bool getCorpseCanonicalPos(float& outX, float& outY) const {
|
|
|
|
|
|
if (corpseMapId_ == 0 || currentMapId_ != corpseMapId_) return false;
|
|
|
|
|
|
outX = corpseY_; // server Y = canonical X (north)
|
|
|
|
|
|
outY = corpseX_; // server X = canonical Y (west)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-09 22:31:56 -07:00
|
|
|
|
/** Send CMSG_RECLAIM_CORPSE; noop if not a ghost or not near corpse. */
|
|
|
|
|
|
void reclaimCorpse();
|
2026-02-06 17:27:20 -08:00
|
|
|
|
void releaseSpirit();
|
2026-02-07 23:12:24 -08:00
|
|
|
|
void acceptResurrect();
|
|
|
|
|
|
void declineResurrect();
|
2026-02-06 17:27:20 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 4: Group ----
|
|
|
|
|
|
void inviteToGroup(const std::string& playerName);
|
|
|
|
|
|
void acceptGroupInvite();
|
|
|
|
|
|
void declineGroupInvite();
|
|
|
|
|
|
void leaveGroup();
|
|
|
|
|
|
bool isInGroup() const { return !partyData.isEmpty(); }
|
|
|
|
|
|
const GroupListData& getPartyData() const { return partyData; }
|
2026-03-10 05:46:03 -07:00
|
|
|
|
const std::vector<ContactEntry>& getContacts() const { return contacts_; }
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
|
|
|
|
|
|
const std::string& getPendingInviterName() const { return pendingInviterName; }
|
|
|
|
|
|
|
2026-03-09 14:15:59 -07:00
|
|
|
|
// ---- Item text (books / readable items) ----
|
|
|
|
|
|
bool isItemTextOpen() const { return itemTextOpen_; }
|
|
|
|
|
|
const std::string& getItemText() const { return itemText_; }
|
|
|
|
|
|
void closeItemText() { itemTextOpen_ = false; }
|
|
|
|
|
|
void queryItemText(uint64_t itemGuid);
|
|
|
|
|
|
|
2026-03-09 14:14:15 -07:00
|
|
|
|
// ---- Shared Quest ----
|
|
|
|
|
|
bool hasPendingSharedQuest() const { return pendingSharedQuest_; }
|
|
|
|
|
|
uint32_t getSharedQuestId() const { return sharedQuestId_; }
|
|
|
|
|
|
const std::string& getSharedQuestTitle() const { return sharedQuestTitle_; }
|
|
|
|
|
|
const std::string& getSharedQuestSharerName() const { return sharedQuestSharerName_; }
|
|
|
|
|
|
void acceptSharedQuest();
|
|
|
|
|
|
void declineSharedQuest();
|
|
|
|
|
|
|
2026-03-09 14:07:50 -07:00
|
|
|
|
// ---- Summon ----
|
|
|
|
|
|
bool hasPendingSummonRequest() const { return pendingSummonRequest_; }
|
|
|
|
|
|
const std::string& getSummonerName() const { return summonerName_; }
|
|
|
|
|
|
float getSummonTimeoutSec() const { return summonTimeoutSec_; }
|
|
|
|
|
|
void acceptSummon();
|
|
|
|
|
|
void declineSummon();
|
2026-03-09 14:08:49 -07:00
|
|
|
|
void tickSummonTimeout(float dt) {
|
|
|
|
|
|
if (!pendingSummonRequest_) return;
|
|
|
|
|
|
summonTimeoutSec_ -= dt;
|
|
|
|
|
|
if (summonTimeoutSec_ <= 0.0f) {
|
|
|
|
|
|
pendingSummonRequest_ = false;
|
|
|
|
|
|
summonTimeoutSec_ = 0.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-09 14:07:50 -07:00
|
|
|
|
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
// ---- Trade ----
|
|
|
|
|
|
enum class TradeStatus : uint8_t {
|
|
|
|
|
|
None = 0, PendingIncoming, Open, Accepted, Complete
|
|
|
|
|
|
};
|
2026-03-11 00:44:07 -07:00
|
|
|
|
|
|
|
|
|
|
static constexpr int TRADE_SLOT_COUNT = 6; // WoW has 6 normal trade slots + slot 6 for non-trade item
|
|
|
|
|
|
|
|
|
|
|
|
struct TradeSlot {
|
|
|
|
|
|
uint32_t itemId = 0;
|
|
|
|
|
|
uint32_t displayId = 0;
|
|
|
|
|
|
uint32_t stackCount = 0;
|
|
|
|
|
|
uint64_t itemGuid = 0;
|
|
|
|
|
|
uint8_t bag = 0xFF; // 0xFF = not set
|
|
|
|
|
|
uint8_t bagSlot = 0xFF;
|
|
|
|
|
|
bool occupied = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
TradeStatus getTradeStatus() const { return tradeStatus_; }
|
|
|
|
|
|
bool hasPendingTradeRequest() const { return tradeStatus_ == TradeStatus::PendingIncoming; }
|
2026-03-11 00:44:07 -07:00
|
|
|
|
bool isTradeOpen() const { return tradeStatus_ == TradeStatus::Open || tradeStatus_ == TradeStatus::Accepted; }
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
const std::string& getTradePeerName() const { return tradePeerName_; }
|
2026-03-11 00:44:07 -07:00
|
|
|
|
|
|
|
|
|
|
// My trade slots (what I'm offering)
|
|
|
|
|
|
const std::array<TradeSlot, TRADE_SLOT_COUNT>& getMyTradeSlots() const { return myTradeSlots_; }
|
|
|
|
|
|
// Peer's trade slots (what they're offering)
|
|
|
|
|
|
const std::array<TradeSlot, TRADE_SLOT_COUNT>& getPeerTradeSlots() const { return peerTradeSlots_; }
|
|
|
|
|
|
uint64_t getMyTradeGold() const { return myTradeGold_; }
|
|
|
|
|
|
uint64_t getPeerTradeGold() const { return peerTradeGold_; }
|
|
|
|
|
|
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
void acceptTradeRequest(); // respond to incoming SMSG_TRADE_STATUS(1) with CMSG_BEGIN_TRADE
|
|
|
|
|
|
void declineTradeRequest(); // respond with CMSG_CANCEL_TRADE
|
|
|
|
|
|
void acceptTrade(); // lock in offer: CMSG_ACCEPT_TRADE
|
|
|
|
|
|
void cancelTrade(); // CMSG_CANCEL_TRADE
|
2026-03-11 00:44:07 -07:00
|
|
|
|
void setTradeItem(uint8_t tradeSlot, uint8_t bag, uint8_t bagSlot);
|
|
|
|
|
|
void clearTradeItem(uint8_t tradeSlot);
|
|
|
|
|
|
void setTradeGold(uint64_t copper);
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
|
2026-03-09 13:58:02 -07:00
|
|
|
|
// ---- Duel ----
|
|
|
|
|
|
bool hasPendingDuelRequest() const { return pendingDuelRequest_; }
|
|
|
|
|
|
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
|
|
|
|
|
|
void acceptDuel();
|
|
|
|
|
|
// forfeitDuel() already declared at line ~399
|
2026-03-12 05:06:14 -07:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2026-03-09 13:58:02 -07:00
|
|
|
|
|
2026-03-09 13:36:23 -07:00
|
|
|
|
// ---- Instance lockouts ----
|
|
|
|
|
|
struct InstanceLockout {
|
|
|
|
|
|
uint32_t mapId = 0;
|
|
|
|
|
|
uint32_t difficulty = 0; // 0=normal,1=heroic/10man,2=25man,3=25man heroic
|
|
|
|
|
|
uint64_t resetTime = 0; // Unix timestamp of instance reset
|
|
|
|
|
|
bool locked = false;
|
|
|
|
|
|
bool extended = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::vector<InstanceLockout>& getInstanceLockouts() const { return instanceLockouts_; }
|
2026-03-09 20:05:09 -07:00
|
|
|
|
|
|
|
|
|
|
// Boss encounter unit tracking (SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT)
|
|
|
|
|
|
static constexpr uint32_t kMaxEncounterSlots = 5;
|
2026-03-09 19:54:32 -07:00
|
|
|
|
// Returns boss unit guid for the given encounter slot (0 if none)
|
|
|
|
|
|
uint64_t getEncounterUnitGuid(uint32_t slot) const {
|
|
|
|
|
|
return (slot < kMaxEncounterSlots) ? encounterUnitGuids_[slot] : 0;
|
|
|
|
|
|
}
|
2026-03-09 13:36:23 -07:00
|
|
|
|
|
2026-03-10 06:10:29 -07:00
|
|
|
|
// Raid target markers (MSG_RAID_TARGET_UPDATE)
|
|
|
|
|
|
// Icon indices 0-7: Star, Circle, Diamond, Triangle, Moon, Square, Cross, Skull
|
|
|
|
|
|
static constexpr uint32_t kRaidMarkCount = 8;
|
|
|
|
|
|
// Returns the GUID marked with the given icon (0 = no mark)
|
|
|
|
|
|
uint64_t getRaidMarkGuid(uint32_t icon) const {
|
|
|
|
|
|
return (icon < kRaidMarkCount) ? raidTargetGuids_[icon] : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Returns the raid mark icon for a given guid (0xFF = no mark)
|
|
|
|
|
|
uint8_t getEntityRaidMark(uint64_t guid) const {
|
|
|
|
|
|
if (guid == 0) return 0xFF;
|
|
|
|
|
|
for (uint32_t i = 0; i < kRaidMarkCount; ++i)
|
|
|
|
|
|
if (raidTargetGuids_[i] == guid) return static_cast<uint8_t>(i);
|
|
|
|
|
|
return 0xFF;
|
|
|
|
|
|
}
|
2026-03-12 00:39:56 -07:00
|
|
|
|
// Set or clear a raid mark on a guid (icon 0-7, or 0xFF to clear)
|
|
|
|
|
|
void setRaidMark(uint64_t guid, uint8_t icon);
|
2026-03-10 06:10:29 -07:00
|
|
|
|
|
2026-03-09 13:30:23 -07:00
|
|
|
|
// ---- LFG / Dungeon Finder ----
|
|
|
|
|
|
enum class LfgState : uint8_t {
|
|
|
|
|
|
None = 0,
|
|
|
|
|
|
RoleCheck = 1,
|
|
|
|
|
|
Queued = 2,
|
|
|
|
|
|
Proposal = 3,
|
|
|
|
|
|
Boot = 4,
|
|
|
|
|
|
InDungeon = 5,
|
|
|
|
|
|
FinishedDungeon= 6,
|
|
|
|
|
|
RaidBrowser = 7,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// roles bitmask: 0x02=tank, 0x04=healer, 0x08=dps; pass LFGDungeonEntry ID
|
|
|
|
|
|
void lfgJoin(uint32_t dungeonId, uint8_t roles);
|
|
|
|
|
|
void lfgLeave();
|
|
|
|
|
|
void lfgAcceptProposal(uint32_t proposalId, bool accept);
|
2026-03-10 12:08:58 -07:00
|
|
|
|
void lfgSetBootVote(bool vote);
|
2026-03-09 13:30:23 -07:00
|
|
|
|
void lfgTeleport(bool toLfgDungeon = true);
|
|
|
|
|
|
LfgState getLfgState() const { return lfgState_; }
|
|
|
|
|
|
bool isLfgQueued() const { return lfgState_ == LfgState::Queued; }
|
|
|
|
|
|
bool isLfgInDungeon() const { return lfgState_ == LfgState::InDungeon; }
|
2026-03-09 13:47:07 -07:00
|
|
|
|
uint32_t getLfgDungeonId() const { return lfgDungeonId_; }
|
|
|
|
|
|
uint32_t getLfgProposalId() const { return lfgProposalId_; }
|
|
|
|
|
|
int32_t getLfgAvgWaitSec() const { return lfgAvgWaitSec_; }
|
|
|
|
|
|
uint32_t getLfgTimeInQueueMs() const { return lfgTimeInQueueMs_; }
|
2026-03-12 09:09:41 -07:00
|
|
|
|
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_; }
|
2026-03-09 13:30:23 -07:00
|
|
|
|
|
2026-03-12 02:35:29 -07:00
|
|
|
|
// ---- Arena Team Stats ----
|
|
|
|
|
|
struct ArenaTeamStats {
|
|
|
|
|
|
uint32_t teamId = 0;
|
|
|
|
|
|
uint32_t rating = 0;
|
|
|
|
|
|
uint32_t weekGames = 0;
|
|
|
|
|
|
uint32_t weekWins = 0;
|
|
|
|
|
|
uint32_t seasonGames = 0;
|
|
|
|
|
|
uint32_t seasonWins = 0;
|
|
|
|
|
|
uint32_t rank = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::vector<ArenaTeamStats>& getArenaTeamStats() const { return arenaTeamStats_; }
|
|
|
|
|
|
|
2026-03-12 21:01:51 -07:00
|
|
|
|
// ---- Arena Team Roster ----
|
|
|
|
|
|
struct ArenaTeamMember {
|
|
|
|
|
|
uint64_t guid = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
bool online = false;
|
|
|
|
|
|
uint32_t weekGames = 0;
|
|
|
|
|
|
uint32_t weekWins = 0;
|
|
|
|
|
|
uint32_t seasonGames = 0;
|
|
|
|
|
|
uint32_t seasonWins = 0;
|
|
|
|
|
|
uint32_t personalRating = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
struct ArenaTeamRoster {
|
|
|
|
|
|
uint32_t teamId = 0;
|
|
|
|
|
|
std::vector<ArenaTeamMember> members;
|
|
|
|
|
|
};
|
|
|
|
|
|
// Returns roster for the given teamId, or nullptr if not yet received
|
|
|
|
|
|
const ArenaTeamRoster* getArenaTeamRoster(uint32_t teamId) const {
|
|
|
|
|
|
for (const auto& r : arenaTeamRosters_) {
|
|
|
|
|
|
if (r.teamId == teamId) return &r;
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 5: Loot ----
|
|
|
|
|
|
void lootTarget(uint64_t guid);
|
|
|
|
|
|
void lootItem(uint8_t slotIndex);
|
|
|
|
|
|
void closeLoot();
|
2026-02-06 18:34:45 -08:00
|
|
|
|
void activateSpiritHealer(uint64_t npcGuid);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
bool isLootWindowOpen() const { return lootWindowOpen; }
|
|
|
|
|
|
const LootResponseData& getCurrentLoot() const { return currentLoot; }
|
2026-02-17 16:31:00 -08:00
|
|
|
|
void setAutoLoot(bool enabled) { autoLoot_ = enabled; }
|
|
|
|
|
|
bool isAutoLoot() const { return autoLoot_; }
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-03-12 17:58:24 -07:00
|
|
|
|
// Master loot candidates (from SMSG_LOOT_MASTER_LIST)
|
|
|
|
|
|
const std::vector<uint64_t>& getMasterLootCandidates() const { return masterLootCandidates_; }
|
|
|
|
|
|
bool hasMasterLootCandidates() const { return !masterLootCandidates_.empty(); }
|
|
|
|
|
|
void lootMasterGive(uint8_t lootSlot, uint64_t targetGuid);
|
|
|
|
|
|
|
2026-03-09 14:01:27 -07:00
|
|
|
|
// Group loot roll
|
|
|
|
|
|
struct LootRollEntry {
|
|
|
|
|
|
uint64_t objectGuid = 0;
|
|
|
|
|
|
uint32_t slot = 0;
|
|
|
|
|
|
uint32_t itemId = 0;
|
|
|
|
|
|
std::string itemName;
|
|
|
|
|
|
uint8_t itemQuality = 0;
|
2026-03-12 04:57:36 -07:00
|
|
|
|
uint32_t rollCountdownMs = 60000; // Duration of roll window in ms
|
|
|
|
|
|
std::chrono::steady_clock::time_point rollStartedAt{};
|
2026-03-12 08:59:38 -07:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-03-09 14:01:27 -07:00
|
|
|
|
};
|
|
|
|
|
|
bool hasPendingLootRoll() const { return pendingLootRollActive_; }
|
|
|
|
|
|
const LootRollEntry& getPendingLootRoll() const { return pendingLootRoll_; }
|
|
|
|
|
|
void sendLootRoll(uint64_t objectGuid, uint32_t slot, uint8_t rollType);
|
|
|
|
|
|
// rollType: 0=need, 1=greed, 2=disenchant, 96=pass
|
|
|
|
|
|
|
2026-03-12 17:48:08 -07:00
|
|
|
|
// Equipment Sets (WotLK): saved gear loadouts
|
|
|
|
|
|
struct EquipmentSetInfo {
|
|
|
|
|
|
uint64_t setGuid = 0;
|
|
|
|
|
|
uint32_t setId = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
std::string iconName;
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::vector<EquipmentSetInfo>& getEquipmentSets() const { return equipmentSetInfo_; }
|
|
|
|
|
|
void useEquipmentSet(uint32_t setId);
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// NPC Gossip
|
|
|
|
|
|
void interactWithNpc(uint64_t guid);
|
2026-02-07 19:44:03 -08:00
|
|
|
|
void interactWithGameObject(uint64_t guid);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
void selectGossipOption(uint32_t optionId);
|
2026-02-06 11:45:35 -08:00
|
|
|
|
void selectGossipQuest(uint32_t questId);
|
2026-02-06 11:59:51 -08:00
|
|
|
|
void acceptQuest();
|
|
|
|
|
|
void declineQuest();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
void closeGossip();
|
|
|
|
|
|
bool isGossipWindowOpen() const { return gossipWindowOpen; }
|
|
|
|
|
|
const GossipMessageData& getCurrentGossip() const { return currentGossip; }
|
2026-03-11 17:27:23 -07:00
|
|
|
|
bool isQuestDetailsOpen() {
|
|
|
|
|
|
// Check if delayed opening timer has expired
|
|
|
|
|
|
if (questDetailsOpen) return true;
|
|
|
|
|
|
if (questDetailsOpenTime != std::chrono::steady_clock::time_point{}) {
|
|
|
|
|
|
if (std::chrono::steady_clock::now() >= questDetailsOpenTime) {
|
|
|
|
|
|
questDetailsOpen = true;
|
|
|
|
|
|
questDetailsOpenTime = std::chrono::steady_clock::time_point{};
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-06 11:59:51 -08:00
|
|
|
|
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails; }
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-03-09 14:38:45 -07:00
|
|
|
|
// Gossip / quest map POI markers (SMSG_GOSSIP_POI)
|
|
|
|
|
|
struct GossipPoi {
|
|
|
|
|
|
float x = 0.0f; // WoW canonical X (north)
|
|
|
|
|
|
float y = 0.0f; // WoW canonical Y (west)
|
|
|
|
|
|
uint32_t icon = 0; // POI icon type
|
|
|
|
|
|
uint32_t data = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::vector<GossipPoi>& getGossipPois() const { return gossipPois_; }
|
|
|
|
|
|
void clearGossipPois() { gossipPois_.clear(); }
|
|
|
|
|
|
|
2026-02-06 21:50:15 -08:00
|
|
|
|
// Quest turn-in
|
|
|
|
|
|
bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; }
|
|
|
|
|
|
const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; }
|
|
|
|
|
|
void completeQuest(); // Send CMSG_QUESTGIVER_COMPLETE_QUEST
|
|
|
|
|
|
void closeQuestRequestItems();
|
|
|
|
|
|
|
2026-03-11 19:11:02 -07:00
|
|
|
|
bool isQuestOfferRewardOpen() const { return questOfferRewardOpen_; }
|
2026-02-06 21:50:15 -08:00
|
|
|
|
const QuestOfferRewardData& getQuestOfferReward() const { return currentQuestOfferReward_; }
|
|
|
|
|
|
void chooseQuestReward(uint32_t rewardIndex); // Send CMSG_QUESTGIVER_CHOOSE_REWARD
|
|
|
|
|
|
void closeQuestOfferReward();
|
|
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
|
// Quest log
|
|
|
|
|
|
struct QuestLogEntry {
|
|
|
|
|
|
uint32_t questId = 0;
|
|
|
|
|
|
std::string title;
|
|
|
|
|
|
std::string objectives;
|
|
|
|
|
|
bool complete = false;
|
2026-03-10 23:52:18 -07:00
|
|
|
|
// Objective kill counts: npcOrGoEntry -> (current, required)
|
2026-02-10 01:24:37 -08:00
|
|
|
|
std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> killCounts;
|
2026-02-18 04:06:14 -08:00
|
|
|
|
// Quest item progress: itemId -> current count
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> itemCounts;
|
2026-02-19 00:56:24 -08:00
|
|
|
|
// Server-authoritative quest item requirements from REQUEST_ITEMS
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> requiredItemCounts;
|
2026-03-10 23:52:18 -07:00
|
|
|
|
// Structured kill objectives parsed from SMSG_QUEST_QUERY_RESPONSE.
|
|
|
|
|
|
// Index 0-3 map to the server's objective slot order (packed into update fields).
|
|
|
|
|
|
// npcOrGoId != 0 => entity objective (kill NPC or interact with GO).
|
|
|
|
|
|
struct KillObjective {
|
|
|
|
|
|
int32_t npcOrGoId = 0; // negative = game-object entry
|
|
|
|
|
|
uint32_t required = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
std::array<KillObjective, 4> killObjectives{}; // zeroed by default
|
|
|
|
|
|
// Required item objectives parsed from SMSG_QUEST_QUERY_RESPONSE.
|
|
|
|
|
|
// itemId != 0 => collect items of that type.
|
|
|
|
|
|
struct ItemObjective {
|
|
|
|
|
|
uint32_t itemId = 0;
|
|
|
|
|
|
uint32_t required = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
std::array<ItemObjective, 6> itemObjectives{}; // zeroed by default
|
2026-03-11 22:30:16 -07:00
|
|
|
|
// Reward data parsed from SMSG_QUEST_QUERY_RESPONSE
|
|
|
|
|
|
int32_t rewardMoney = 0; // copper; positive=reward, negative=cost
|
|
|
|
|
|
std::array<QuestRewardItem, 4> rewardItems{}; // guaranteed reward items
|
|
|
|
|
|
std::array<QuestRewardItem, 6> rewardChoiceItems{}; // player picks one of these
|
2026-02-06 13:47:03 -08:00
|
|
|
|
};
|
|
|
|
|
|
const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; }
|
|
|
|
|
|
void abandonQuest(uint32_t questId);
|
2026-03-12 09:56:38 -07:00
|
|
|
|
void shareQuestWithParty(uint32_t questId); // CMSG_PUSHQUESTTOPARTY
|
2026-02-19 00:30:21 -08:00
|
|
|
|
bool requestQuestQuery(uint32_t questId, bool force = false);
|
2026-03-10 05:18:45 -07:00
|
|
|
|
bool isQuestTracked(uint32_t questId) const { return trackedQuestIds_.count(questId) > 0; }
|
|
|
|
|
|
void setQuestTracked(uint32_t questId, bool tracked) {
|
|
|
|
|
|
if (tracked) trackedQuestIds_.insert(questId);
|
|
|
|
|
|
else trackedQuestIds_.erase(questId);
|
|
|
|
|
|
}
|
|
|
|
|
|
const std::unordered_set<uint32_t>& getTrackedQuestIds() const { return trackedQuestIds_; }
|
2026-02-19 00:56:24 -08:00
|
|
|
|
bool isQuestQueryPending(uint32_t questId) const {
|
|
|
|
|
|
return pendingQuestQueryIds_.count(questId) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
void clearQuestQueryPending(uint32_t questId) { pendingQuestQueryIds_.erase(questId); }
|
2026-02-18 23:30:38 -08:00
|
|
|
|
const std::unordered_map<uint32_t, uint32_t>& getWorldStates() const { return worldStates_; }
|
|
|
|
|
|
std::optional<uint32_t> getWorldState(uint32_t key) const {
|
|
|
|
|
|
auto it = worldStates_.find(key);
|
|
|
|
|
|
if (it == worldStates_.end()) return std::nullopt;
|
|
|
|
|
|
return it->second;
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t getWorldStateMapId() const { return worldStateMapId_; }
|
|
|
|
|
|
uint32_t getWorldStateZoneId() const { return worldStateZoneId_; }
|
|
|
|
|
|
|
2026-03-09 14:30:48 -07:00
|
|
|
|
// Mirror timers (0=fatigue, 1=breath, 2=feigndeath)
|
|
|
|
|
|
struct MirrorTimer {
|
|
|
|
|
|
int32_t value = 0;
|
|
|
|
|
|
int32_t maxValue = 0;
|
|
|
|
|
|
int32_t scale = 0; // +1 = counting up, -1 = counting down
|
|
|
|
|
|
bool paused = false;
|
|
|
|
|
|
bool active = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
const MirrorTimer& getMirrorTimer(int type) const {
|
|
|
|
|
|
static MirrorTimer empty;
|
|
|
|
|
|
return (type >= 0 && type < 3) ? mirrorTimers_[type] : empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Combo points
|
|
|
|
|
|
uint8_t getComboPoints() const { return comboPoints_; }
|
|
|
|
|
|
uint64_t getComboTarget() const { return comboTarget_; }
|
|
|
|
|
|
|
2026-03-09 18:28:03 -07:00
|
|
|
|
// Death Knight rune state (6 runes: 0-1=Blood, 2-3=Unholy, 4-5=Frost; may become Death=3)
|
|
|
|
|
|
enum class RuneType : uint8_t { Blood = 0, Unholy = 1, Frost = 2, Death = 3 };
|
|
|
|
|
|
struct RuneSlot {
|
|
|
|
|
|
RuneType type = RuneType::Blood;
|
|
|
|
|
|
bool ready = true; // Server-confirmed ready state
|
|
|
|
|
|
float readyFraction = 1.0f; // 0.0=depleted → 1.0=full (from server sync)
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::array<RuneSlot, 6>& getPlayerRunes() const { return playerRunes_; }
|
|
|
|
|
|
|
2026-03-12 23:59:38 -07:00
|
|
|
|
// Talent-driven spell modifiers (SMSG_SET_FLAT_SPELL_MODIFIER / SMSG_SET_PCT_SPELL_MODIFIER)
|
|
|
|
|
|
// SpellModOp matches WotLK SpellModOp enum (server-side).
|
|
|
|
|
|
enum class SpellModOp : uint8_t {
|
|
|
|
|
|
Damage = 0,
|
|
|
|
|
|
Duration = 1,
|
|
|
|
|
|
Threat = 2,
|
|
|
|
|
|
Effect1 = 3,
|
|
|
|
|
|
Charges = 4,
|
|
|
|
|
|
Range = 5,
|
|
|
|
|
|
Radius = 6,
|
|
|
|
|
|
CritChance = 7,
|
|
|
|
|
|
AllEffects = 8,
|
|
|
|
|
|
NotLoseCastingTime = 9,
|
|
|
|
|
|
CastingTime = 10,
|
|
|
|
|
|
Cooldown = 11,
|
|
|
|
|
|
Effect2 = 12,
|
|
|
|
|
|
IgnoreArmor = 13,
|
|
|
|
|
|
Cost = 14,
|
|
|
|
|
|
CritDamageBonus = 15,
|
|
|
|
|
|
ResistMissChance = 16,
|
|
|
|
|
|
JumpTargets = 17,
|
|
|
|
|
|
ChanceOfSuccess = 18,
|
|
|
|
|
|
ActivationTime = 19,
|
|
|
|
|
|
Efficiency = 20,
|
|
|
|
|
|
MultipleValue = 21,
|
|
|
|
|
|
ResistDispelChance = 22,
|
|
|
|
|
|
Effect3 = 23,
|
|
|
|
|
|
BonusMultiplier = 24,
|
|
|
|
|
|
ProcPerMinute = 25,
|
|
|
|
|
|
ValueMultiplier = 26,
|
|
|
|
|
|
ResistPushback = 27,
|
|
|
|
|
|
MechanicDuration = 28,
|
|
|
|
|
|
StartCooldown = 29,
|
|
|
|
|
|
PeriodicBonus = 30,
|
|
|
|
|
|
AttackPower = 31,
|
|
|
|
|
|
};
|
|
|
|
|
|
static constexpr int SPELL_MOD_OP_COUNT = 32;
|
|
|
|
|
|
|
|
|
|
|
|
// Key: (SpellModOp, groupIndex) — value: accumulated flat or pct modifier
|
|
|
|
|
|
// pct values are stored in integer percent (e.g. -20 means -20% reduction).
|
|
|
|
|
|
struct SpellModKey {
|
|
|
|
|
|
SpellModOp op;
|
|
|
|
|
|
uint8_t group;
|
|
|
|
|
|
bool operator==(const SpellModKey& o) const {
|
|
|
|
|
|
return op == o.op && group == o.group;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
struct SpellModKeyHash {
|
|
|
|
|
|
std::size_t operator()(const SpellModKey& k) const {
|
|
|
|
|
|
return std::hash<uint32_t>()(
|
|
|
|
|
|
(static_cast<uint32_t>(static_cast<uint8_t>(k.op)) << 8) | k.group);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Returns the sum of all flat modifiers for a given op across all groups.
|
|
|
|
|
|
// (Callers that need per-group resolution can use getSpellFlatMods() directly.)
|
|
|
|
|
|
int32_t getSpellFlatMod(SpellModOp op) const {
|
|
|
|
|
|
int32_t total = 0;
|
|
|
|
|
|
for (const auto& [k, v] : spellFlatMods_)
|
|
|
|
|
|
if (k.op == op) total += v;
|
|
|
|
|
|
return total;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Returns the sum of all pct modifiers for a given op across all groups (in %).
|
|
|
|
|
|
int32_t getSpellPctMod(SpellModOp op) const {
|
|
|
|
|
|
int32_t total = 0;
|
|
|
|
|
|
for (const auto& [k, v] : spellPctMods_)
|
|
|
|
|
|
if (k.op == op) total += v;
|
|
|
|
|
|
return total;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convenience: apply flat+pct modifier to a base value.
|
|
|
|
|
|
// result = (base + flatMod) * (1.0 + pctMod/100.0), clamped to >= 0.
|
|
|
|
|
|
static int32_t applySpellMod(int32_t base, int32_t flat, int32_t pct) {
|
|
|
|
|
|
int64_t v = static_cast<int64_t>(base) + flat;
|
|
|
|
|
|
if (pct != 0) v = v + (v * pct + 50) / 100; // round half-up
|
|
|
|
|
|
return static_cast<int32_t>(v < 0 ? 0 : v);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-18 23:30:38 -08:00
|
|
|
|
struct FactionStandingInit {
|
|
|
|
|
|
uint8_t flags = 0;
|
|
|
|
|
|
int32_t standing = 0;
|
|
|
|
|
|
};
|
2026-03-12 23:30:44 -07:00
|
|
|
|
// Faction flag bitmask constants (from Faction.dbc ReputationFlags / SMSG_INITIALIZE_FACTIONS)
|
|
|
|
|
|
static constexpr uint8_t FACTION_FLAG_VISIBLE = 0x01; // shown in reputation list
|
|
|
|
|
|
static constexpr uint8_t FACTION_FLAG_AT_WAR = 0x02; // player is at war
|
|
|
|
|
|
static constexpr uint8_t FACTION_FLAG_HIDDEN = 0x04; // never shown
|
|
|
|
|
|
static constexpr uint8_t FACTION_FLAG_INVISIBLE_FORCED = 0x08;
|
|
|
|
|
|
static constexpr uint8_t FACTION_FLAG_PEACE_FORCED = 0x10;
|
|
|
|
|
|
|
2026-02-18 23:30:38 -08:00
|
|
|
|
const std::vector<FactionStandingInit>& getInitialFactions() const { return initialFactions_; }
|
2026-03-09 14:48:30 -07:00
|
|
|
|
const std::unordered_map<uint32_t, int32_t>& getFactionStandings() const { return factionStandings_; }
|
2026-03-12 23:30:44 -07:00
|
|
|
|
|
|
|
|
|
|
// Returns true if the player has "at war" toggled for the faction at repListId
|
|
|
|
|
|
bool isFactionAtWar(uint32_t repListId) const {
|
|
|
|
|
|
if (repListId >= initialFactions_.size()) return false;
|
|
|
|
|
|
return (initialFactions_[repListId].flags & FACTION_FLAG_AT_WAR) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Returns true if the faction is visible in the reputation list
|
|
|
|
|
|
bool isFactionVisible(uint32_t repListId) const {
|
|
|
|
|
|
if (repListId >= initialFactions_.size()) return false;
|
|
|
|
|
|
const uint8_t f = initialFactions_[repListId].flags;
|
|
|
|
|
|
if (f & FACTION_FLAG_HIDDEN) return false;
|
|
|
|
|
|
if (f & FACTION_FLAG_INVISIBLE_FORCED) return false;
|
|
|
|
|
|
return (f & FACTION_FLAG_VISIBLE) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Returns the faction ID for a given repListId (0 if unknown)
|
|
|
|
|
|
uint32_t getFactionIdByRepListId(uint32_t repListId) const;
|
|
|
|
|
|
// Returns the repListId for a given faction ID (0xFFFFFFFF if not found)
|
|
|
|
|
|
uint32_t getRepListIdByFactionId(uint32_t factionId) const;
|
2026-03-12 05:16:43 -07:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 14:52:13 -07:00
|
|
|
|
const std::string& getFactionNamePublic(uint32_t factionId) const;
|
2026-03-12 05:03:03 -07:00
|
|
|
|
uint32_t getWatchedFactionId() const { return watchedFactionId_; }
|
|
|
|
|
|
void setWatchedFactionId(uint32_t id) { watchedFactionId_ = id; }
|
2026-02-18 23:30:38 -08:00
|
|
|
|
uint32_t getLastContactListMask() const { return lastContactListMask_; }
|
|
|
|
|
|
uint32_t getLastContactListCount() const { return lastContactListCount_; }
|
|
|
|
|
|
bool isServerMovementAllowed() const { return serverMovementAllowed_; }
|
2026-02-06 13:47:03 -08:00
|
|
|
|
|
2026-02-06 20:10:10 -08:00
|
|
|
|
// Quest giver status (! and ? markers)
|
|
|
|
|
|
QuestGiverStatus getQuestGiverStatus(uint64_t guid) const {
|
|
|
|
|
|
auto it = npcQuestStatus_.find(guid);
|
|
|
|
|
|
return (it != npcQuestStatus_.end()) ? it->second : QuestGiverStatus::NONE;
|
|
|
|
|
|
}
|
|
|
|
|
|
const std::unordered_map<uint64_t, QuestGiverStatus>& getNpcQuestStatuses() const { return npcQuestStatus_; }
|
|
|
|
|
|
|
2026-02-19 21:13:13 -08:00
|
|
|
|
// Charge callback — fires when player casts a charge spell toward target
|
|
|
|
|
|
// Parameters: targetGuid, targetX, targetY, targetZ (canonical WoW coordinates)
|
|
|
|
|
|
using ChargeCallback = std::function<void(uint64_t targetGuid, float x, float y, float z)>;
|
|
|
|
|
|
void setChargeCallback(ChargeCallback cb) { chargeCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-17 17:23:42 -08:00
|
|
|
|
// Level-up callback — fires when the player gains a level (newLevel > 1)
|
|
|
|
|
|
using LevelUpCallback = std::function<void(uint32_t newLevel)>;
|
|
|
|
|
|
void setLevelUpCallback(LevelUpCallback cb) { levelUpCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-12 17:54:49 -07:00
|
|
|
|
// Stat deltas from the last SMSG_LEVELUP_INFO (valid until next level-up)
|
|
|
|
|
|
struct LevelUpDeltas {
|
|
|
|
|
|
uint32_t hp = 0;
|
|
|
|
|
|
uint32_t mana = 0;
|
|
|
|
|
|
uint32_t str = 0, agi = 0, sta = 0, intel = 0, spi = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
const LevelUpDeltas& getLastLevelUpDeltas() const { return lastLevelUpDeltas_; }
|
|
|
|
|
|
|
2026-03-12 18:15:51 -07:00
|
|
|
|
// Temporary weapon enchant timers (from SMSG_ITEM_ENCHANT_TIME_UPDATE)
|
|
|
|
|
|
// Slot: 0=main-hand, 1=off-hand, 2=ranged. Value: expire time (steady_clock ms).
|
|
|
|
|
|
struct TempEnchantTimer {
|
|
|
|
|
|
uint32_t slot = 0;
|
|
|
|
|
|
uint64_t expireMs = 0; // std::chrono::steady_clock ms timestamp when it expires
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::vector<TempEnchantTimer>& getTempEnchantTimers() const { return tempEnchantTimers_; }
|
|
|
|
|
|
// Returns remaining ms for a given slot, or 0 if absent/expired.
|
|
|
|
|
|
uint32_t getTempEnchantRemainingMs(uint32_t slot) const;
|
|
|
|
|
|
static constexpr const char* kTempEnchantSlotNames[] = { "Main Hand", "Off Hand", "Ranged" };
|
|
|
|
|
|
|
2026-03-12 18:21:50 -07:00
|
|
|
|
// ---- Readable text (books / scrolls / notes) ----
|
|
|
|
|
|
// Populated by handlePageTextQueryResponse(); multi-page items chain via nextPageId.
|
|
|
|
|
|
struct BookPage { uint32_t pageId = 0; std::string text; };
|
|
|
|
|
|
const std::vector<BookPage>& getBookPages() const { return bookPages_; }
|
|
|
|
|
|
bool hasBookOpen() const { return !bookPages_.empty(); }
|
|
|
|
|
|
void clearBook() { bookPages_.clear(); }
|
|
|
|
|
|
|
2026-02-19 20:36:25 -08:00
|
|
|
|
// Other player level-up callback — fires when another player gains a level
|
|
|
|
|
|
using OtherPlayerLevelUpCallback = std::function<void(uint64_t guid, uint32_t newLevel)>;
|
|
|
|
|
|
void setOtherPlayerLevelUpCallback(OtherPlayerLevelUpCallback cb) { otherPlayerLevelUpCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-09 13:53:42 -07:00
|
|
|
|
// Achievement earned callback — fires when SMSG_ACHIEVEMENT_EARNED is received
|
2026-03-10 20:53:21 -07:00
|
|
|
|
using AchievementEarnedCallback = std::function<void(uint32_t achievementId, const std::string& name)>;
|
2026-03-09 13:53:42 -07:00
|
|
|
|
void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); }
|
2026-03-10 20:53:21 -07:00
|
|
|
|
const std::unordered_set<uint32_t>& getEarnedAchievements() const { return earnedAchievements_; }
|
2026-03-12 15:42:55 -07:00
|
|
|
|
|
2026-03-12 20:23:36 -07:00
|
|
|
|
// Title system — earned title bits and the currently displayed title
|
|
|
|
|
|
const std::unordered_set<uint32_t>& getKnownTitleBits() const { return knownTitleBits_; }
|
|
|
|
|
|
int32_t getChosenTitleBit() const { return chosenTitleBit_; }
|
|
|
|
|
|
/// Returns the formatted title string for a given bit (replaces %s with player name), or empty.
|
|
|
|
|
|
std::string getFormattedTitle(uint32_t bit) const;
|
|
|
|
|
|
/// Send CMSG_SET_TITLE to activate a title (bit >= 0) or clear it (bit = -1).
|
|
|
|
|
|
void sendSetTitle(int32_t bit);
|
|
|
|
|
|
|
2026-03-12 15:42:55 -07:00
|
|
|
|
// Area discovery callback — fires when SMSG_EXPLORATION_EXPERIENCE is received
|
|
|
|
|
|
using AreaDiscoveryCallback = std::function<void(const std::string& areaName, uint32_t xpGained)>;
|
|
|
|
|
|
void setAreaDiscoveryCallback(AreaDiscoveryCallback cb) { areaDiscoveryCallback_ = std::move(cb); }
|
2026-03-12 15:57:09 -07:00
|
|
|
|
|
|
|
|
|
|
// Quest objective progress callback — fires on SMSG_QUESTUPDATE_ADD_KILL / ADD_ITEM
|
|
|
|
|
|
// questTitle: name of the quest; objectiveName: creature/item name; current/required counts
|
|
|
|
|
|
using QuestProgressCallback = std::function<void(const std::string& questTitle,
|
|
|
|
|
|
const std::string& objectiveName,
|
|
|
|
|
|
uint32_t current, uint32_t required)>;
|
|
|
|
|
|
void setQuestProgressCallback(QuestProgressCallback cb) { questProgressCallback_ = std::move(cb); }
|
2026-03-12 03:03:02 -07:00
|
|
|
|
const std::unordered_map<uint32_t, uint64_t>& getCriteriaProgress() const { return criteriaProgress_; }
|
2026-03-12 07:22:36 -07:00
|
|
|
|
/// 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;
|
|
|
|
|
|
}
|
2026-03-11 22:07:44 -07:00
|
|
|
|
/// 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);
|
|
|
|
|
|
if (it != achievementNameCache_.end()) return it->second;
|
|
|
|
|
|
static const std::string kEmpty;
|
|
|
|
|
|
return kEmpty;
|
|
|
|
|
|
}
|
2026-03-12 12:49:38 -07:00
|
|
|
|
/// Returns the description of an achievement by ID, or empty string if unknown.
|
|
|
|
|
|
const std::string& getAchievementDescription(uint32_t id) const {
|
|
|
|
|
|
auto it = achievementDescCache_.find(id);
|
|
|
|
|
|
if (it != achievementDescCache_.end()) return it->second;
|
|
|
|
|
|
static const std::string kEmpty;
|
|
|
|
|
|
return kEmpty;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// Returns the point value of an achievement by ID, or 0 if unknown.
|
|
|
|
|
|
uint32_t getAchievementPoints(uint32_t id) const {
|
|
|
|
|
|
auto it = achievementPointsCache_.find(id);
|
|
|
|
|
|
return (it != achievementPointsCache_.end()) ? it->second : 0u;
|
|
|
|
|
|
}
|
2026-03-12 23:23:02 -07:00
|
|
|
|
/// Returns the set of achievement IDs earned by an inspected player (via SMSG_RESPOND_INSPECT_ACHIEVEMENTS).
|
|
|
|
|
|
/// Returns nullptr if no inspect data is available for the given GUID.
|
|
|
|
|
|
const std::unordered_set<uint32_t>* getInspectedPlayerAchievements(uint64_t guid) const {
|
|
|
|
|
|
auto it = inspectedPlayerAchievements_.find(guid);
|
|
|
|
|
|
return (it != inspectedPlayerAchievements_.end()) ? &it->second : nullptr;
|
|
|
|
|
|
}
|
2026-03-09 13:53:42 -07:00
|
|
|
|
|
2026-03-09 15:46:19 -07:00
|
|
|
|
// Server-triggered music callback — fires when SMSG_PLAY_MUSIC is received.
|
|
|
|
|
|
// The soundId corresponds to a SoundEntries.dbc record. The receiver is
|
|
|
|
|
|
// responsible for looking up the file path and forwarding to MusicManager.
|
|
|
|
|
|
using PlayMusicCallback = std::function<void(uint32_t soundId)>;
|
|
|
|
|
|
void setPlayMusicCallback(PlayMusicCallback cb) { playMusicCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-09 16:11:19 -07:00
|
|
|
|
// Server-triggered 2-D sound effect callback — fires when SMSG_PLAY_SOUND is received.
|
|
|
|
|
|
// The soundId corresponds to a SoundEntries.dbc record.
|
|
|
|
|
|
using PlaySoundCallback = std::function<void(uint32_t soundId)>;
|
|
|
|
|
|
void setPlaySoundCallback(PlaySoundCallback cb) { playSoundCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-09 16:16:39 -07:00
|
|
|
|
// Server-triggered 3-D positional sound callback — fires for SMSG_PLAY_OBJECT_SOUND and
|
|
|
|
|
|
// SMSG_PLAY_SPELL_IMPACT. Includes sourceGuid so the receiver can look up world position.
|
|
|
|
|
|
using PlayPositionalSoundCallback = std::function<void(uint32_t soundId, uint64_t sourceGuid)>;
|
|
|
|
|
|
void setPlayPositionalSoundCallback(PlayPositionalSoundCallback cb) { playPositionalSoundCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-12 01:15:11 -07:00
|
|
|
|
// UI error frame: prominent on-screen error messages (spell can't be cast, etc.)
|
|
|
|
|
|
using UIErrorCallback = std::function<void(const std::string& msg)>;
|
|
|
|
|
|
void setUIErrorCallback(UIErrorCallback cb) { uiErrorCallback_ = std::move(cb); }
|
|
|
|
|
|
void addUIError(const std::string& msg) { if (uiErrorCallback_) uiErrorCallback_(msg); }
|
|
|
|
|
|
|
2026-03-12 01:51:18 -07:00
|
|
|
|
// Reputation change toast: factionName, delta, new standing
|
|
|
|
|
|
using RepChangeCallback = std::function<void(const std::string& factionName, int32_t delta, int32_t standing)>;
|
|
|
|
|
|
void setRepChangeCallback(RepChangeCallback cb) { repChangeCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-12 16:19:25 -07:00
|
|
|
|
// PvP honor credit callback (honorable kill or BG reward)
|
|
|
|
|
|
using PvpHonorCallback = std::function<void(uint32_t honorAmount, uint64_t victimGuid, uint32_t victimRank)>;
|
|
|
|
|
|
void setPvpHonorCallback(PvpHonorCallback cb) { pvpHonorCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-12 16:24:11 -07:00
|
|
|
|
// Item looted / received callback (SMSG_ITEM_PUSH_RESULT when showInChat is set)
|
|
|
|
|
|
using ItemLootCallback = std::function<void(uint32_t itemId, uint32_t count, uint32_t quality, const std::string& name)>;
|
|
|
|
|
|
void setItemLootCallback(ItemLootCallback cb) { itemLootCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-12 04:53:03 -07:00
|
|
|
|
// 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); }
|
|
|
|
|
|
|
2026-02-07 17:59:40 -08:00
|
|
|
|
// Mount state
|
|
|
|
|
|
using MountCallback = std::function<void(uint32_t mountDisplayId)>; // 0 = dismount
|
|
|
|
|
|
void setMountCallback(MountCallback cb) { mountCallback_ = std::move(cb); }
|
2026-02-08 21:32:38 -08:00
|
|
|
|
|
|
|
|
|
|
// Taxi terrain precaching callback
|
|
|
|
|
|
using TaxiPrecacheCallback = std::function<void(const std::vector<glm::vec3>&)>;
|
|
|
|
|
|
void setTaxiPrecacheCallback(TaxiPrecacheCallback cb) { taxiPrecacheCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-08 22:05:38 -08:00
|
|
|
|
// Taxi orientation callback (for mount rotation: yaw, pitch, roll in radians)
|
|
|
|
|
|
using TaxiOrientationCallback = std::function<void(float yaw, float pitch, float roll)>;
|
2026-02-08 22:00:33 -08:00
|
|
|
|
void setTaxiOrientationCallback(TaxiOrientationCallback cb) { taxiOrientationCallback_ = std::move(cb); }
|
|
|
|
|
|
|
|
|
|
|
|
// Callback for when taxi flight is about to start (after mounting delay, before movement begins)
|
|
|
|
|
|
using TaxiFlightStartCallback = std::function<void()>;
|
|
|
|
|
|
void setTaxiFlightStartCallback(TaxiFlightStartCallback cb) { taxiFlightStartCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-03-12 21:24:42 -07:00
|
|
|
|
// Callback fired when server sends SMSG_OPEN_LFG_DUNGEON_FINDER (open dungeon finder UI)
|
|
|
|
|
|
using OpenLfgCallback = std::function<void()>;
|
|
|
|
|
|
void setOpenLfgCallback(OpenLfgCallback cb) { openLfgCallback_ = std::move(cb); }
|
|
|
|
|
|
|
2026-02-07 17:59:40 -08:00
|
|
|
|
bool isMounted() const { return currentMountDisplayId_ != 0; }
|
2026-02-07 21:00:05 -08:00
|
|
|
|
bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
|
2026-02-07 17:59:40 -08:00
|
|
|
|
float getServerRunSpeed() const { return serverRunSpeed_; }
|
2026-03-10 13:11:50 -07:00
|
|
|
|
float getServerWalkSpeed() const { return serverWalkSpeed_; }
|
|
|
|
|
|
float getServerSwimSpeed() const { return serverSwimSpeed_; }
|
2026-03-10 13:51:47 -07:00
|
|
|
|
float getServerSwimBackSpeed() const { return serverSwimBackSpeed_; }
|
2026-03-10 13:25:10 -07:00
|
|
|
|
float getServerFlightSpeed() const { return serverFlightSpeed_; }
|
2026-03-10 14:05:50 -07:00
|
|
|
|
float getServerFlightBackSpeed() const { return serverFlightBackSpeed_; }
|
2026-03-10 13:28:53 -07:00
|
|
|
|
float getServerRunBackSpeed() const { return serverRunBackSpeed_; }
|
2026-03-10 14:18:25 -07:00
|
|
|
|
float getServerTurnRate() const { return serverTurnRate_; }
|
2026-03-10 13:01:44 -07:00
|
|
|
|
bool isPlayerRooted() const {
|
|
|
|
|
|
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::ROOT)) != 0;
|
|
|
|
|
|
}
|
2026-03-10 13:07:34 -07:00
|
|
|
|
bool isGravityDisabled() const {
|
|
|
|
|
|
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::LEVITATING)) != 0;
|
|
|
|
|
|
}
|
2026-03-10 13:14:52 -07:00
|
|
|
|
bool isFeatherFalling() const {
|
|
|
|
|
|
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::FEATHER_FALL)) != 0;
|
|
|
|
|
|
}
|
2026-03-10 13:18:04 -07:00
|
|
|
|
bool isWaterWalking() const {
|
|
|
|
|
|
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::WATER_WALK)) != 0;
|
|
|
|
|
|
}
|
physics: implement player-controlled flying mount physics
When CAN_FLY + FLYING movement flags are both set (flying mounts, Druid
Flight Form), the CameraController now uses 3D pitch-following movement
instead of ground physics:
- Forward/back follows the camera's 3D look direction (ascend when
looking up, descend when looking down)
- Space = ascend vertically, X (while mounted) = descend
- No gravity, no grounding, no jump coyote time
- Fall-damage checks suppressed (grounded=true)
Also wire up all remaining server movement state flags to CameraController:
- Feather Fall: cap terminal velocity at -2 m/s
- Water Walk: clamp to water surface, skip swim entry
- Flying: 3D movement with no gravity
All states synced each frame from GameHandler via isPlayerFlying(),
isFeatherFalling(), isWaterWalking(), isGravityDisabled().
2026-03-10 13:23:38 -07:00
|
|
|
|
bool isPlayerFlying() const {
|
|
|
|
|
|
const uint32_t flyMask = static_cast<uint32_t>(MovementFlags::CAN_FLY) |
|
|
|
|
|
|
static_cast<uint32_t>(MovementFlags::FLYING);
|
|
|
|
|
|
return (movementInfo.flags & flyMask) == flyMask;
|
|
|
|
|
|
}
|
2026-03-10 13:39:23 -07:00
|
|
|
|
bool isHovering() const {
|
|
|
|
|
|
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::HOVER)) != 0;
|
|
|
|
|
|
}
|
2026-03-10 14:46:17 -07:00
|
|
|
|
bool isSwimming() const {
|
|
|
|
|
|
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::SWIMMING)) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Set the character pitch angle (radians) for movement packets (flight / swimming).
|
|
|
|
|
|
// Positive = nose up, negative = nose down.
|
|
|
|
|
|
void setMovementPitch(float radians) { movementInfo.pitch = radians; }
|
2026-02-07 17:59:40 -08:00
|
|
|
|
void dismount();
|
|
|
|
|
|
|
2026-02-07 16:59:20 -08:00
|
|
|
|
// Taxi / Flight Paths
|
|
|
|
|
|
bool isTaxiWindowOpen() const { return taxiWindowOpen_; }
|
|
|
|
|
|
void closeTaxi();
|
|
|
|
|
|
void activateTaxi(uint32_t destNodeId);
|
|
|
|
|
|
bool isOnTaxiFlight() const { return onTaxiFlight_; }
|
2026-02-11 19:28:15 -08:00
|
|
|
|
bool isTaxiMountActive() const { return taxiMountActive_; }
|
2026-02-11 21:14:35 -08:00
|
|
|
|
bool isTaxiActivationPending() const { return taxiActivatePending_; }
|
|
|
|
|
|
void forceClearTaxiAndMovementState();
|
2026-02-07 16:59:20 -08:00
|
|
|
|
const ShowTaxiNodesData& getTaxiData() const { return currentTaxiData_; }
|
|
|
|
|
|
uint32_t getTaxiCurrentNode() const { return currentTaxiData_.nearestNode; }
|
|
|
|
|
|
|
|
|
|
|
|
struct TaxiNode {
|
|
|
|
|
|
uint32_t id = 0;
|
|
|
|
|
|
uint32_t mapId = 0;
|
|
|
|
|
|
float x = 0, y = 0, z = 0;
|
|
|
|
|
|
std::string name;
|
2026-02-08 03:05:38 -08:00
|
|
|
|
uint32_t mountDisplayIdAlliance = 0;
|
|
|
|
|
|
uint32_t mountDisplayIdHorde = 0;
|
2026-02-07 16:59:20 -08:00
|
|
|
|
};
|
|
|
|
|
|
struct TaxiPathEdge {
|
|
|
|
|
|
uint32_t pathId = 0;
|
|
|
|
|
|
uint32_t fromNode = 0, toNode = 0;
|
|
|
|
|
|
uint32_t cost = 0;
|
|
|
|
|
|
};
|
2026-02-08 21:32:38 -08:00
|
|
|
|
struct TaxiPathNode {
|
|
|
|
|
|
uint32_t id = 0;
|
|
|
|
|
|
uint32_t pathId = 0;
|
|
|
|
|
|
uint32_t nodeIndex = 0;
|
|
|
|
|
|
uint32_t mapId = 0;
|
|
|
|
|
|
float x = 0, y = 0, z = 0;
|
|
|
|
|
|
};
|
2026-02-07 16:59:20 -08:00
|
|
|
|
const std::unordered_map<uint32_t, TaxiNode>& getTaxiNodes() const { return taxiNodes_; }
|
2026-02-07 19:04:15 -08:00
|
|
|
|
uint32_t getTaxiCostTo(uint32_t destNodeId) const;
|
2026-03-09 19:30:18 -07:00
|
|
|
|
bool taxiNpcHasRoutes(uint64_t guid) const {
|
|
|
|
|
|
auto it = taxiNpcHasRoutes_.find(guid);
|
|
|
|
|
|
return it != taxiNpcHasRoutes_.end() && it->second;
|
|
|
|
|
|
}
|
2026-02-07 16:59:20 -08:00
|
|
|
|
|
2026-03-12 17:25:00 -07:00
|
|
|
|
// Vehicle (WotLK)
|
|
|
|
|
|
bool isInVehicle() const { return vehicleId_ != 0; }
|
|
|
|
|
|
uint32_t getVehicleId() const { return vehicleId_; }
|
|
|
|
|
|
void sendRequestVehicleExit();
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// Vendor
|
|
|
|
|
|
void openVendor(uint64_t npcGuid);
|
2026-02-06 11:59:51 -08:00
|
|
|
|
void closeVendor();
|
Fix vendor buying, improve character select, parallelize WMO culling, and optimize collision
- Fix CMSG_BUY_ITEM count field from uint8 to uint32 (server silently dropped undersized packets)
- Character select screen: remember last selected character, two-column layout with details panel, double-click to enter world, responsive window sizing
- Fix stale character data between logins by replacing static init flag with per-character GUID tracking
- Parallelize WMO visibility culling across worker threads (same pattern as M2 renderer)
- Optimize WMO collision queries with world-space group bounds early rejection in getFloorHeight, checkWallCollision, isInsideWMO, and raycastBoundingBoxes
- Reduce camera ground samples from 5 to 3 movement-aligned probes
- Add WMO interior lighting, unlit materials, vertex color multiply, and alpha blending support
2026-02-07 15:29:19 -08:00
|
|
|
|
void buyItem(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count);
|
2026-02-06 19:50:22 -08:00
|
|
|
|
void sellItem(uint64_t vendorGuid, uint64_t itemGuid, uint32_t count);
|
2026-02-06 13:47:03 -08:00
|
|
|
|
void sellItemBySlot(int backpackIndex);
|
2026-02-17 01:00:04 -08:00
|
|
|
|
void sellItemInBag(int bagIndex, int slotIndex);
|
2026-02-19 05:28:13 -08:00
|
|
|
|
struct BuybackItem {
|
|
|
|
|
|
uint64_t itemGuid = 0;
|
|
|
|
|
|
ItemDef item;
|
|
|
|
|
|
uint32_t count = 1;
|
|
|
|
|
|
};
|
|
|
|
|
|
void buyBackItem(uint32_t buybackSlot);
|
2026-03-10 16:21:09 -07:00
|
|
|
|
void repairItem(uint64_t vendorGuid, uint64_t itemGuid);
|
|
|
|
|
|
void repairAll(uint64_t vendorGuid, bool useGuildBank = false);
|
2026-02-19 05:28:13 -08:00
|
|
|
|
const std::deque<BuybackItem>& getBuybackItems() const { return buybackItems_; }
|
2026-02-06 18:34:45 -08:00
|
|
|
|
void autoEquipItemBySlot(int backpackIndex);
|
2026-02-17 01:00:04 -08:00
|
|
|
|
void autoEquipItemInBag(int bagIndex, int slotIndex);
|
2026-02-06 18:34:45 -08:00
|
|
|
|
void useItemBySlot(int backpackIndex);
|
2026-02-17 01:00:04 -08:00
|
|
|
|
void useItemInBag(int bagIndex, int slotIndex);
|
2026-02-19 06:34:06 -08:00
|
|
|
|
void destroyItem(uint8_t bag, uint8_t slot, uint8_t count = 1);
|
2026-02-17 01:00:04 -08:00
|
|
|
|
void swapContainerItems(uint8_t srcBag, uint8_t srcSlot, uint8_t dstBag, uint8_t dstSlot);
|
2026-02-19 22:34:22 -08:00
|
|
|
|
void swapBagSlots(int srcBagIndex, int dstBagIndex);
|
2026-02-06 19:17:35 -08:00
|
|
|
|
void useItemById(uint32_t itemId);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
bool isVendorWindowOpen() const { return vendorWindowOpen; }
|
|
|
|
|
|
const ListInventoryData& getVendorItems() const { return currentVendorItems; }
|
2026-03-10 16:21:09 -07:00
|
|
|
|
void setVendorCanRepair(bool v) { currentVendorItems.canRepair = v; }
|
2026-02-08 14:33:39 -08:00
|
|
|
|
|
2026-02-15 14:00:41 -08:00
|
|
|
|
// Mail
|
|
|
|
|
|
bool isMailboxOpen() const { return mailboxOpen_; }
|
|
|
|
|
|
const std::vector<MailMessage>& getMailInbox() const { return mailInbox_; }
|
|
|
|
|
|
int getSelectedMailIndex() const { return selectedMailIndex_; }
|
|
|
|
|
|
void setSelectedMailIndex(int idx) { selectedMailIndex_ = idx; }
|
|
|
|
|
|
bool isMailComposeOpen() const { return showMailCompose_; }
|
2026-02-25 14:11:09 -08:00
|
|
|
|
void openMailCompose() { showMailCompose_ = true; clearMailAttachments(); }
|
|
|
|
|
|
void closeMailCompose() { showMailCompose_ = false; clearMailAttachments(); }
|
2026-02-16 18:46:44 -08:00
|
|
|
|
bool hasNewMail() const { return hasNewMail_; }
|
2026-02-15 14:00:41 -08:00
|
|
|
|
void closeMailbox();
|
|
|
|
|
|
void sendMail(const std::string& recipient, const std::string& subject,
|
|
|
|
|
|
const std::string& body, uint32_t money, uint32_t cod = 0);
|
2026-02-25 14:11:09 -08:00
|
|
|
|
|
|
|
|
|
|
// Mail attachments (max 12 per WotLK)
|
|
|
|
|
|
static constexpr int MAIL_MAX_ATTACHMENTS = 12;
|
|
|
|
|
|
struct MailAttachSlot {
|
|
|
|
|
|
uint64_t itemGuid = 0;
|
|
|
|
|
|
game::ItemDef item;
|
|
|
|
|
|
uint8_t srcBag = 0xFF; // source container for return
|
|
|
|
|
|
uint8_t srcSlot = 0;
|
|
|
|
|
|
bool occupied() const { return itemGuid != 0; }
|
|
|
|
|
|
};
|
|
|
|
|
|
bool attachItemFromBackpack(int backpackIndex);
|
|
|
|
|
|
bool attachItemFromBag(int bagIndex, int slotIndex);
|
|
|
|
|
|
bool detachMailAttachment(int attachIndex);
|
|
|
|
|
|
void clearMailAttachments();
|
|
|
|
|
|
const std::array<MailAttachSlot, 12>& getMailAttachments() const { return mailAttachments_; }
|
|
|
|
|
|
int getMailAttachmentCount() const;
|
2026-02-15 14:00:41 -08:00
|
|
|
|
void mailTakeMoney(uint32_t mailId);
|
|
|
|
|
|
void mailTakeItem(uint32_t mailId, uint32_t itemIndex);
|
|
|
|
|
|
void mailDelete(uint32_t mailId);
|
|
|
|
|
|
void mailMarkAsRead(uint32_t mailId);
|
|
|
|
|
|
void refreshMailList();
|
|
|
|
|
|
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
// Bank
|
|
|
|
|
|
void openBank(uint64_t guid);
|
|
|
|
|
|
void closeBank();
|
|
|
|
|
|
void buyBankSlot();
|
|
|
|
|
|
void depositItem(uint8_t srcBag, uint8_t srcSlot);
|
|
|
|
|
|
void withdrawItem(uint8_t srcBag, uint8_t srcSlot);
|
|
|
|
|
|
bool isBankOpen() const { return bankOpen_; }
|
|
|
|
|
|
uint64_t getBankerGuid() const { return bankerGuid_; }
|
2026-02-26 11:12:34 -08:00
|
|
|
|
int getEffectiveBankSlots() const { return effectiveBankSlots_; }
|
|
|
|
|
|
int getEffectiveBankBagSlots() const { return effectiveBankBagSlots_; }
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
|
|
|
|
|
|
// Guild Bank
|
|
|
|
|
|
void openGuildBank(uint64_t guid);
|
|
|
|
|
|
void closeGuildBank();
|
|
|
|
|
|
void queryGuildBankTab(uint8_t tabId);
|
|
|
|
|
|
void buyGuildBankTab();
|
|
|
|
|
|
void depositGuildBankMoney(uint32_t amount);
|
|
|
|
|
|
void withdrawGuildBankMoney(uint32_t amount);
|
|
|
|
|
|
void guildBankWithdrawItem(uint8_t tabId, uint8_t bankSlot, uint8_t destBag, uint8_t destSlot);
|
|
|
|
|
|
void guildBankDepositItem(uint8_t tabId, uint8_t bankSlot, uint8_t srcBag, uint8_t srcSlot);
|
|
|
|
|
|
bool isGuildBankOpen() const { return guildBankOpen_; }
|
|
|
|
|
|
const GuildBankData& getGuildBankData() const { return guildBankData_; }
|
|
|
|
|
|
uint8_t getGuildBankActiveTab() const { return guildBankActiveTab_; }
|
|
|
|
|
|
void setGuildBankActiveTab(uint8_t tab) { guildBankActiveTab_ = tab; }
|
|
|
|
|
|
|
|
|
|
|
|
// Auction House
|
|
|
|
|
|
void openAuctionHouse(uint64_t guid);
|
|
|
|
|
|
void closeAuctionHouse();
|
|
|
|
|
|
void auctionSearch(const std::string& name, uint8_t levelMin, uint8_t levelMax,
|
|
|
|
|
|
uint32_t quality, uint32_t itemClass, uint32_t itemSubClass,
|
|
|
|
|
|
uint32_t invTypeMask, uint8_t usableOnly, uint32_t offset = 0);
|
|
|
|
|
|
void auctionSellItem(uint64_t itemGuid, uint32_t stackCount, uint32_t bid,
|
|
|
|
|
|
uint32_t buyout, uint32_t duration);
|
|
|
|
|
|
void auctionPlaceBid(uint32_t auctionId, uint32_t amount);
|
|
|
|
|
|
void auctionBuyout(uint32_t auctionId, uint32_t buyoutPrice);
|
|
|
|
|
|
void auctionCancelItem(uint32_t auctionId);
|
|
|
|
|
|
void auctionListOwnerItems(uint32_t offset = 0);
|
|
|
|
|
|
void auctionListBidderItems(uint32_t offset = 0);
|
|
|
|
|
|
bool isAuctionHouseOpen() const { return auctionOpen_; }
|
|
|
|
|
|
uint64_t getAuctioneerGuid() const { return auctioneerGuid_; }
|
|
|
|
|
|
const AuctionListResult& getAuctionBrowseResults() const { return auctionBrowseResults_; }
|
|
|
|
|
|
const AuctionListResult& getAuctionOwnerResults() const { return auctionOwnerResults_; }
|
|
|
|
|
|
const AuctionListResult& getAuctionBidderResults() const { return auctionBidderResults_; }
|
|
|
|
|
|
int getAuctionActiveTab() const { return auctionActiveTab_; }
|
|
|
|
|
|
void setAuctionActiveTab(int tab) { auctionActiveTab_ = tab; }
|
|
|
|
|
|
float getAuctionSearchDelay() const { return auctionSearchDelayTimer_; }
|
|
|
|
|
|
|
2026-02-08 14:33:39 -08:00
|
|
|
|
// Trainer
|
|
|
|
|
|
bool isTrainerWindowOpen() const { return trainerWindowOpen_; }
|
|
|
|
|
|
const TrainerListData& getTrainerSpells() const { return currentTrainerList_; }
|
|
|
|
|
|
void trainSpell(uint32_t spellId);
|
|
|
|
|
|
void closeTrainer();
|
|
|
|
|
|
const std::string& getSpellName(uint32_t spellId) const;
|
|
|
|
|
|
const std::string& getSpellRank(uint32_t spellId) const;
|
2026-03-12 13:08:41 -07:00
|
|
|
|
/// Returns the tooltip/description text from Spell.dbc (empty if unknown or has no text).
|
|
|
|
|
|
const std::string& getSpellDescription(uint32_t spellId) const;
|
2026-02-08 14:46:01 -08:00
|
|
|
|
const std::string& getSkillLineName(uint32_t spellId) const;
|
2026-03-12 06:55:16 -07:00
|
|
|
|
/// 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;
|
2026-02-08 14:46:01 -08:00
|
|
|
|
|
|
|
|
|
|
struct TrainerTab {
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
std::vector<const TrainerSpell*> spells;
|
|
|
|
|
|
};
|
|
|
|
|
|
const std::vector<TrainerTab>& getTrainerTabs() const { return trainerTabs_; }
|
2026-02-06 11:59:51 -08:00
|
|
|
|
const ItemQueryResponseData* getItemInfo(uint32_t itemId) const {
|
|
|
|
|
|
auto it = itemInfoCache_.find(itemId);
|
|
|
|
|
|
return (it != itemInfoCache_.end()) ? &it->second : nullptr;
|
|
|
|
|
|
}
|
2026-02-14 15:58:54 -08:00
|
|
|
|
// Request item info from server if not already cached/pending
|
|
|
|
|
|
void ensureItemInfo(uint32_t entry) {
|
|
|
|
|
|
if (entry == 0 || itemInfoCache_.count(entry) || pendingItemQueries_.count(entry)) return;
|
|
|
|
|
|
queryItemInfo(entry, 0);
|
|
|
|
|
|
}
|
2026-02-06 13:47:03 -08:00
|
|
|
|
uint64_t getBackpackItemGuid(int index) const {
|
|
|
|
|
|
if (index < 0 || index >= static_cast<int>(backpackSlotGuids_.size())) return 0;
|
|
|
|
|
|
return backpackSlotGuids_[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
uint64_t getVendorGuid() const { return currentVendorItems.vendorGuid; }
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Set callbacks
|
|
|
|
|
|
*/
|
|
|
|
|
|
void setOnSuccess(WorldConnectSuccessCallback callback) { onSuccess = callback; }
|
|
|
|
|
|
void setOnFailure(WorldConnectFailureCallback callback) { onFailure = callback; }
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Update - call regularly (e.g., each frame)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param deltaTime Time since last update in seconds
|
|
|
|
|
|
*/
|
|
|
|
|
|
void update(float deltaTime);
|
|
|
|
|
|
|
2026-02-14 19:27:35 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Reset DBC-backed caches so they reload from new expansion data.
|
|
|
|
|
|
* Called by Application when the expansion profile changes.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void resetDbcCaches();
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
private:
|
2026-02-07 20:51:53 -08:00
|
|
|
|
void autoTargetAttacker(uint64_t attackerGuid);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle incoming packet from world server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handlePacket(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_AUTH_CHALLENGE from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleAuthChallenge(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_AUTH_RESPONSE from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleAuthResponse(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_CHAR_ENUM from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleCharEnum(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-17 13:59:29 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_CHARACTER_LOGIN_FAILED from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleCharLoginFailed(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_LOGIN_VERIFY_WORLD from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleLoginVerifyWorld(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-12 02:09:15 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_CLIENTCACHE_VERSION from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleClientCacheVersion(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_TUTORIAL_FLAGS from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleTutorialFlags(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_WARDEN_DATA gate packet from server.
|
|
|
|
|
|
* We do not implement anti-cheat exchange for third-party realms.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleWardenData(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_ACCOUNT_DATA_TIMES from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleAccountDataTimes(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_MOTD from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleMotd(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-20 00:28:51 -08:00
|
|
|
|
/** Handle SMSG_NOTIFICATION (vanilla/classic server notification string) */
|
|
|
|
|
|
void handleNotification(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_PONG from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handlePong(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_UPDATE_OBJECT from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleUpdateObject(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_COMPRESSED_UPDATE_OBJECT from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleCompressedUpdateObject(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_DESTROY_OBJECT from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleDestroyObject(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Handle SMSG_MESSAGECHAT from server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void handleMessageChat(network::Packet& packet);
|
2026-02-14 14:30:09 -08:00
|
|
|
|
void handleTextEmote(network::Packet& packet);
|
|
|
|
|
|
void handleChannelNotify(network::Packet& packet);
|
|
|
|
|
|
void autoJoinDefaultChannels();
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 1 handlers ----
|
|
|
|
|
|
void handleNameQueryResponse(network::Packet& packet);
|
|
|
|
|
|
void handleCreatureQueryResponse(network::Packet& packet);
|
2026-02-08 00:59:40 -08:00
|
|
|
|
void handleGameObjectQueryResponse(network::Packet& packet);
|
2026-02-20 23:31:30 -08:00
|
|
|
|
void handleGameObjectPageText(network::Packet& packet);
|
|
|
|
|
|
void handlePageTextQueryResponse(network::Packet& packet);
|
2026-02-06 03:11:43 -08:00
|
|
|
|
void handleItemQueryResponse(network::Packet& packet);
|
2026-02-13 20:26:55 -08:00
|
|
|
|
void handleInspectResults(network::Packet& packet);
|
2026-02-06 03:11:43 -08:00
|
|
|
|
void queryItemInfo(uint32_t entry, uint64_t guid);
|
|
|
|
|
|
void rebuildOnlineInventory();
|
2026-02-13 20:10:19 -08:00
|
|
|
|
void maybeDetectVisibleItemLayout();
|
|
|
|
|
|
void updateOtherPlayerVisibleItems(uint64_t guid, const std::map<uint16_t, uint32_t>& fields);
|
|
|
|
|
|
void emitOtherPlayerEquipment(uint64_t guid);
|
|
|
|
|
|
void emitAllOtherPlayerEquipment();
|
2026-02-06 18:34:45 -08:00
|
|
|
|
void detectInventorySlotBases(const std::map<uint16_t, uint32_t>& fields);
|
|
|
|
|
|
bool applyInventoryFields(const std::map<uint16_t, uint32_t>& fields);
|
2026-02-13 22:14:34 -08:00
|
|
|
|
void extractContainerFields(uint64_t containerGuid, const std::map<uint16_t, uint32_t>& fields);
|
2026-02-06 18:52:28 -08:00
|
|
|
|
uint64_t resolveOnlineItemGuid(uint32_t itemId) const;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
// ---- Phase 2 handlers ----
|
|
|
|
|
|
void handleAttackStart(network::Packet& packet);
|
|
|
|
|
|
void handleAttackStop(network::Packet& packet);
|
|
|
|
|
|
void handleAttackerStateUpdate(network::Packet& packet);
|
|
|
|
|
|
void handleSpellDamageLog(network::Packet& packet);
|
|
|
|
|
|
void handleSpellHealLog(network::Packet& packet);
|
|
|
|
|
|
|
2026-03-09 15:23:02 -07:00
|
|
|
|
// ---- Equipment set handler ----
|
|
|
|
|
|
void handleEquipmentSetList(network::Packet& packet);
|
|
|
|
|
|
void handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs);
|
|
|
|
|
|
void handleSetForcedReactions(network::Packet& packet);
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 3 handlers ----
|
|
|
|
|
|
void handleInitialSpells(network::Packet& packet);
|
|
|
|
|
|
void handleCastFailed(network::Packet& packet);
|
|
|
|
|
|
void handleSpellStart(network::Packet& packet);
|
|
|
|
|
|
void handleSpellGo(network::Packet& packet);
|
|
|
|
|
|
void handleSpellCooldown(network::Packet& packet);
|
|
|
|
|
|
void handleCooldownEvent(network::Packet& packet);
|
2026-03-09 13:53:42 -07:00
|
|
|
|
void handleAchievementEarned(network::Packet& packet);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
void handleAuraUpdate(network::Packet& packet, bool isAll);
|
|
|
|
|
|
void handleLearnedSpell(network::Packet& packet);
|
2026-02-10 01:24:37 -08:00
|
|
|
|
void handleSupercededSpell(network::Packet& packet);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
void handleRemovedSpell(network::Packet& packet);
|
2026-02-10 01:24:37 -08:00
|
|
|
|
void handleUnlearnSpells(network::Packet& packet);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
// ---- Talent handlers ----
|
|
|
|
|
|
void handleTalentsInfo(network::Packet& packet);
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 4 handlers ----
|
|
|
|
|
|
void handleGroupInvite(network::Packet& packet);
|
|
|
|
|
|
void handleGroupDecline(network::Packet& packet);
|
|
|
|
|
|
void handleGroupList(network::Packet& packet);
|
|
|
|
|
|
void handleGroupUninvite(network::Packet& packet);
|
|
|
|
|
|
void handlePartyCommandResult(network::Packet& packet);
|
2026-02-26 10:25:55 -08:00
|
|
|
|
void handlePartyMemberStats(network::Packet& packet, bool isFull);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
// ---- Guild handlers ----
|
|
|
|
|
|
void handleGuildInfo(network::Packet& packet);
|
|
|
|
|
|
void handleGuildRoster(network::Packet& packet);
|
|
|
|
|
|
void handleGuildQueryResponse(network::Packet& packet);
|
|
|
|
|
|
void handleGuildEvent(network::Packet& packet);
|
|
|
|
|
|
void handleGuildInvite(network::Packet& packet);
|
|
|
|
|
|
void handleGuildCommandResult(network::Packet& packet);
|
2026-02-25 14:44:44 -08:00
|
|
|
|
void handlePetitionShowlist(network::Packet& packet);
|
2026-02-26 10:41:29 -08:00
|
|
|
|
void handlePetSpells(network::Packet& packet);
|
2026-02-25 14:44:44 -08:00
|
|
|
|
void handleTurnInPetitionResults(network::Packet& packet);
|
2026-02-13 21:39:48 -08:00
|
|
|
|
|
2026-02-05 14:13:48 -08:00
|
|
|
|
// ---- Character creation handler ----
|
|
|
|
|
|
void handleCharCreateResponse(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-05 12:07:58 -08:00
|
|
|
|
// ---- XP handler ----
|
|
|
|
|
|
void handleXpGain(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
|
// ---- Creature movement handler ----
|
|
|
|
|
|
void handleMonsterMove(network::Packet& packet);
|
2026-02-18 03:15:25 -08:00
|
|
|
|
void handleCompressedMoves(network::Packet& packet);
|
2026-02-11 00:54:38 -08:00
|
|
|
|
void handleMonsterMoveTransport(network::Packet& packet);
|
2026-02-06 13:47:03 -08:00
|
|
|
|
|
2026-02-13 18:59:09 -08:00
|
|
|
|
// ---- Other player movement (MSG_MOVE_* from server) ----
|
|
|
|
|
|
void handleOtherPlayerMovement(network::Packet& packet);
|
2026-03-13 03:13:29 -07:00
|
|
|
|
void handleMoveSetSpeed(network::Packet& packet);
|
2026-02-13 18:59:09 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 5 handlers ----
|
|
|
|
|
|
void handleLootResponse(network::Packet& packet);
|
|
|
|
|
|
void handleLootReleaseResponse(network::Packet& packet);
|
|
|
|
|
|
void handleLootRemoved(network::Packet& packet);
|
|
|
|
|
|
void handleGossipMessage(network::Packet& packet);
|
2026-02-19 02:53:44 -08:00
|
|
|
|
void handleQuestgiverQuestList(network::Packet& packet);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
void handleGossipComplete(network::Packet& packet);
|
2026-03-11 00:18:23 -07:00
|
|
|
|
void handleQuestPoiQueryResponse(network::Packet& packet);
|
2026-02-06 11:59:51 -08:00
|
|
|
|
void handleQuestDetails(network::Packet& packet);
|
2026-02-06 21:50:15 -08:00
|
|
|
|
void handleQuestRequestItems(network::Packet& packet);
|
|
|
|
|
|
void handleQuestOfferReward(network::Packet& packet);
|
2026-02-20 17:14:13 -08:00
|
|
|
|
void clearPendingQuestAccept(uint32_t questId);
|
|
|
|
|
|
void triggerQuestAcceptResync(uint32_t questId, uint64_t npcGuid, const char* reason);
|
|
|
|
|
|
bool hasQuestInLog(uint32_t questId) const;
|
2026-02-20 23:20:02 -08:00
|
|
|
|
int findQuestLogSlotIndexFromServer(uint32_t questId) const;
|
2026-02-20 17:14:13 -08:00
|
|
|
|
void addQuestToLocalLogIfMissing(uint32_t questId, const std::string& title, const std::string& objectives);
|
|
|
|
|
|
bool resyncQuestLogFromServerSlots(bool forceQueryMetadata);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
void handleListInventory(network::Packet& packet);
|
2026-02-05 14:01:26 -08:00
|
|
|
|
void addMoneyCopper(uint32_t amount);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-02-07 16:59:20 -08:00
|
|
|
|
// ---- Teleport handler ----
|
|
|
|
|
|
void handleTeleportAck(network::Packet& packet);
|
2026-02-08 03:05:38 -08:00
|
|
|
|
void handleNewWorld(network::Packet& packet);
|
2026-02-07 16:59:20 -08:00
|
|
|
|
|
Add missing movement ACK responses to avoid server stalls
Implement generic handlers for force speed changes (walk, run back,
swim, swim back, flight, flight back, turn rate, pitch rate),
movement flag toggles (CAN_FLY, HOVER, feather fall, water walk),
and knockback ACKs. Fix SMSG_TIME_SYNC_REQ to respond with
CMSG_TIME_SYNC_RESP instead of silently dropping.
2026-02-26 03:02:51 -08:00
|
|
|
|
// ---- Movement ACK handlers ----
|
2026-02-07 17:59:40 -08:00
|
|
|
|
void handleForceRunSpeedChange(network::Packet& packet);
|
Add missing movement ACK responses to avoid server stalls
Implement generic handlers for force speed changes (walk, run back,
swim, swim back, flight, flight back, turn rate, pitch rate),
movement flag toggles (CAN_FLY, HOVER, feather fall, water walk),
and knockback ACKs. Fix SMSG_TIME_SYNC_REQ to respond with
CMSG_TIME_SYNC_RESP instead of silently dropping.
2026-02-26 03:02:51 -08:00
|
|
|
|
void handleForceSpeedChange(network::Packet& packet, const char* name, Opcode ackOpcode, float* speedStorage);
|
2026-02-20 03:14:48 -08:00
|
|
|
|
void handleForceMoveRootState(network::Packet& packet, bool rooted);
|
Add missing movement ACK responses to avoid server stalls
Implement generic handlers for force speed changes (walk, run back,
swim, swim back, flight, flight back, turn rate, pitch rate),
movement flag toggles (CAN_FLY, HOVER, feather fall, water walk),
and knockback ACKs. Fix SMSG_TIME_SYNC_REQ to respond with
CMSG_TIME_SYNC_RESP instead of silently dropping.
2026-02-26 03:02:51 -08:00
|
|
|
|
void handleForceMoveFlagChange(network::Packet& packet, const char* name, Opcode ackOpcode, uint32_t flag, bool set);
|
2026-03-10 11:40:46 -07:00
|
|
|
|
void handleMoveSetCollisionHeight(network::Packet& packet);
|
Add missing movement ACK responses to avoid server stalls
Implement generic handlers for force speed changes (walk, run back,
swim, swim back, flight, flight back, turn rate, pitch rate),
movement flag toggles (CAN_FLY, HOVER, feather fall, water walk),
and knockback ACKs. Fix SMSG_TIME_SYNC_REQ to respond with
CMSG_TIME_SYNC_RESP instead of silently dropping.
2026-02-26 03:02:51 -08:00
|
|
|
|
void handleMoveKnockBack(network::Packet& packet);
|
2026-02-07 17:59:40 -08:00
|
|
|
|
|
2026-02-26 17:56:11 -08:00
|
|
|
|
// ---- Area trigger detection ----
|
|
|
|
|
|
void loadAreaTriggerDbc();
|
|
|
|
|
|
void checkAreaTriggers();
|
|
|
|
|
|
|
2026-03-09 13:36:23 -07:00
|
|
|
|
// ---- Instance lockout handler ----
|
|
|
|
|
|
void handleRaidInstanceInfo(network::Packet& packet);
|
2026-03-09 14:15:59 -07:00
|
|
|
|
void handleItemTextQueryResponse(network::Packet& packet);
|
2026-03-09 14:14:15 -07:00
|
|
|
|
void handleQuestConfirmAccept(network::Packet& packet);
|
2026-03-09 14:07:50 -07:00
|
|
|
|
void handleSummonRequest(network::Packet& packet);
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
void handleTradeStatus(network::Packet& packet);
|
2026-03-11 00:44:07 -07:00
|
|
|
|
void handleTradeStatusExtended(network::Packet& packet);
|
|
|
|
|
|
void resetTradeState();
|
2026-03-09 13:58:02 -07:00
|
|
|
|
void handleDuelRequested(network::Packet& packet);
|
|
|
|
|
|
void handleDuelComplete(network::Packet& packet);
|
|
|
|
|
|
void handleDuelWinner(network::Packet& packet);
|
2026-03-09 14:01:27 -07:00
|
|
|
|
void handleLootRoll(network::Packet& packet);
|
|
|
|
|
|
void handleLootRollWon(network::Packet& packet);
|
2026-03-09 13:36:23 -07:00
|
|
|
|
|
2026-03-09 13:30:23 -07:00
|
|
|
|
// ---- LFG / Dungeon Finder handlers ----
|
|
|
|
|
|
void handleLfgJoinResult(network::Packet& packet);
|
|
|
|
|
|
void handleLfgQueueStatus(network::Packet& packet);
|
|
|
|
|
|
void handleLfgProposalUpdate(network::Packet& packet);
|
|
|
|
|
|
void handleLfgRoleCheckUpdate(network::Packet& packet);
|
|
|
|
|
|
void handleLfgUpdatePlayer(network::Packet& packet);
|
|
|
|
|
|
void handleLfgPlayerReward(network::Packet& packet);
|
|
|
|
|
|
void handleLfgBootProposalUpdate(network::Packet& packet);
|
|
|
|
|
|
void handleLfgTeleportDenied(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-07 23:47:43 -08:00
|
|
|
|
// ---- Arena / Battleground handlers ----
|
|
|
|
|
|
void handleBattlefieldStatus(network::Packet& packet);
|
2026-02-26 17:56:11 -08:00
|
|
|
|
void handleInstanceDifficulty(network::Packet& packet);
|
2026-02-07 23:47:43 -08:00
|
|
|
|
void handleArenaTeamCommandResult(network::Packet& packet);
|
|
|
|
|
|
void handleArenaTeamQueryResponse(network::Packet& packet);
|
2026-03-12 21:01:51 -07:00
|
|
|
|
void handleArenaTeamRoster(network::Packet& packet);
|
2026-02-07 23:47:43 -08:00
|
|
|
|
void handleArenaTeamInvite(network::Packet& packet);
|
|
|
|
|
|
void handleArenaTeamEvent(network::Packet& packet);
|
2026-03-12 02:35:29 -07:00
|
|
|
|
void handleArenaTeamStats(network::Packet& packet);
|
2026-02-07 23:47:43 -08:00
|
|
|
|
void handleArenaError(network::Packet& packet);
|
2026-03-12 12:02:59 -07:00
|
|
|
|
void handlePvpLogData(network::Packet& packet);
|
2026-02-07 23:47:43 -08:00
|
|
|
|
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
// ---- Bank handlers ----
|
|
|
|
|
|
void handleShowBank(network::Packet& packet);
|
|
|
|
|
|
void handleBuyBankSlotResult(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
// ---- Guild Bank handlers ----
|
|
|
|
|
|
void handleGuildBankList(network::Packet& packet);
|
|
|
|
|
|
|
|
|
|
|
|
// ---- Auction House handlers ----
|
|
|
|
|
|
void handleAuctionHello(network::Packet& packet);
|
|
|
|
|
|
void handleAuctionListResult(network::Packet& packet);
|
|
|
|
|
|
void handleAuctionOwnerListResult(network::Packet& packet);
|
|
|
|
|
|
void handleAuctionBidderListResult(network::Packet& packet);
|
|
|
|
|
|
void handleAuctionCommandResult(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-15 14:00:41 -08:00
|
|
|
|
// ---- Mail handlers ----
|
|
|
|
|
|
void handleShowMailbox(network::Packet& packet);
|
|
|
|
|
|
void handleMailListResult(network::Packet& packet);
|
|
|
|
|
|
void handleSendMailResult(network::Packet& packet);
|
|
|
|
|
|
void handleReceivedMail(network::Packet& packet);
|
2026-02-16 18:46:44 -08:00
|
|
|
|
void handleQueryNextMailTime(network::Packet& packet);
|
2026-02-15 14:00:41 -08:00
|
|
|
|
|
2026-02-07 16:59:20 -08:00
|
|
|
|
// ---- Taxi handlers ----
|
|
|
|
|
|
void handleShowTaxiNodes(network::Packet& packet);
|
|
|
|
|
|
void handleActivateTaxiReply(network::Packet& packet);
|
|
|
|
|
|
void loadTaxiDbc();
|
|
|
|
|
|
|
2026-02-07 12:43:32 -08:00
|
|
|
|
// ---- Server info handlers ----
|
|
|
|
|
|
void handleQueryTimeResponse(network::Packet& packet);
|
|
|
|
|
|
void handlePlayedTime(network::Packet& packet);
|
|
|
|
|
|
void handleWho(network::Packet& packet);
|
|
|
|
|
|
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
// ---- Social handlers ----
|
2026-03-10 01:15:51 -07:00
|
|
|
|
void handleFriendList(network::Packet& packet); // Classic SMSG_FRIEND_LIST
|
|
|
|
|
|
void handleContactList(network::Packet& packet); // WotLK SMSG_CONTACT_LIST (full parse)
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
void handleFriendStatus(network::Packet& packet);
|
|
|
|
|
|
void handleRandomRoll(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-07 12:58:11 -08:00
|
|
|
|
// ---- Logout handlers ----
|
|
|
|
|
|
void handleLogoutResponse(network::Packet& packet);
|
|
|
|
|
|
void handleLogoutComplete(network::Packet& packet);
|
|
|
|
|
|
|
2026-03-13 06:08:21 -07:00
|
|
|
|
void addCombatText(CombatTextEntry::Type type, int32_t amount, uint32_t spellId, bool isPlayerSource, uint8_t powerType = 0);
|
2026-02-05 13:22:15 -08:00
|
|
|
|
void addSystemChatMessage(const std::string& message);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Send CMSG_PING to server (heartbeat)
|
|
|
|
|
|
*/
|
|
|
|
|
|
void sendPing();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Send CMSG_AUTH_SESSION to server
|
|
|
|
|
|
*/
|
|
|
|
|
|
void sendAuthSession();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Generate random client seed
|
|
|
|
|
|
*/
|
|
|
|
|
|
uint32_t generateClientSeed();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Change state with logging
|
|
|
|
|
|
*/
|
|
|
|
|
|
void setState(WorldState newState);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Fail connection with reason
|
|
|
|
|
|
*/
|
|
|
|
|
|
void fail(const std::string& reason);
|
2026-02-12 00:04:53 -08:00
|
|
|
|
void updateAttachedTransportChildren(float deltaTime);
|
|
|
|
|
|
void setTransportAttachment(uint64_t childGuid, ObjectType type, uint64_t transportGuid,
|
|
|
|
|
|
const glm::vec3& localOffset, bool hasLocalOrientation,
|
|
|
|
|
|
float localOrientation);
|
|
|
|
|
|
void clearTransportAttachment(uint64_t childGuid);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
|
// Opcode translation table (expansion-specific wire ↔ logical mapping)
|
|
|
|
|
|
OpcodeTable opcodeTable_;
|
|
|
|
|
|
|
|
|
|
|
|
// Update field table (expansion-specific field index mapping)
|
|
|
|
|
|
UpdateFieldTable updateFieldTable_;
|
|
|
|
|
|
|
|
|
|
|
|
// Packet parsers (expansion-specific binary format handling)
|
|
|
|
|
|
std::unique_ptr<PacketParsers> packetParsers_;
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Network
|
|
|
|
|
|
std::unique_ptr<network::WorldSocket> socket;
|
|
|
|
|
|
|
|
|
|
|
|
// State
|
|
|
|
|
|
WorldState state = WorldState::DISCONNECTED;
|
|
|
|
|
|
|
|
|
|
|
|
// Authentication data
|
|
|
|
|
|
std::vector<uint8_t> sessionKey; // 40-byte session key from auth server
|
|
|
|
|
|
std::string accountName; // Account name
|
|
|
|
|
|
uint32_t build = 12340; // Client build (3.3.5a)
|
2026-02-13 01:51:49 -08:00
|
|
|
|
uint32_t realmId_ = 0; // Realm ID from auth REALM_LIST (used in WotLK AUTH_SESSION)
|
2026-02-02 12:24:50 -08:00
|
|
|
|
uint32_t clientSeed = 0; // Random seed generated by client
|
|
|
|
|
|
uint32_t serverSeed = 0; // Seed from SMSG_AUTH_CHALLENGE
|
|
|
|
|
|
|
|
|
|
|
|
// Characters
|
|
|
|
|
|
std::vector<Character> characters; // Character list from SMSG_CHAR_ENUM
|
|
|
|
|
|
|
|
|
|
|
|
// Movement
|
|
|
|
|
|
MovementInfo movementInfo; // Current player movement state
|
|
|
|
|
|
uint32_t movementTime = 0; // Movement timestamp counter
|
2026-02-20 02:19:17 -08:00
|
|
|
|
std::chrono::steady_clock::time_point movementClockStart_ = std::chrono::steady_clock::now();
|
|
|
|
|
|
uint32_t lastMovementTimestampMs_ = 0;
|
2026-02-18 23:30:38 -08:00
|
|
|
|
bool serverMovementAllowed_ = true;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-03-10 12:36:56 -07:00
|
|
|
|
// Fall/jump tracking for movement packet correctness.
|
|
|
|
|
|
// fallTime must be the elapsed ms since the FALLING flag was set; the server
|
|
|
|
|
|
// uses it for fall-damage calculations and anti-cheat validation.
|
|
|
|
|
|
bool isFalling_ = false;
|
|
|
|
|
|
uint32_t fallStartMs_ = 0; // movementInfo.time value when FALLING started
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Inventory
|
|
|
|
|
|
Inventory inventory;
|
|
|
|
|
|
|
|
|
|
|
|
// Entity tracking
|
|
|
|
|
|
EntityManager entityManager; // Manages all entities in view
|
|
|
|
|
|
|
|
|
|
|
|
// Chat
|
2026-02-04 11:31:08 -08:00
|
|
|
|
std::deque<MessageChatData> chatHistory; // Recent chat messages
|
2026-02-02 12:24:50 -08:00
|
|
|
|
size_t maxChatHistory = 100; // Maximum chat messages to keep
|
2026-02-14 14:30:09 -08:00
|
|
|
|
std::vector<std::string> joinedChannels_; // Active channel memberships
|
|
|
|
|
|
ChatBubbleCallback chatBubbleCallback_;
|
2026-02-14 15:11:43 -08:00
|
|
|
|
EmoteAnimCallback emoteAnimCallback_;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Targeting
|
|
|
|
|
|
uint64_t targetGuid = 0;
|
2026-02-07 13:44:36 -08:00
|
|
|
|
uint64_t focusGuid = 0; // Focus target
|
|
|
|
|
|
uint64_t lastTargetGuid = 0; // Previous target
|
2026-02-02 12:24:50 -08:00
|
|
|
|
std::vector<uint64_t> tabCycleList;
|
|
|
|
|
|
int tabCycleIndex = -1;
|
|
|
|
|
|
bool tabCycleStale = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Heartbeat
|
|
|
|
|
|
uint32_t pingSequence = 0; // Ping sequence number (increments)
|
|
|
|
|
|
float timeSinceLastPing = 0.0f; // Time since last ping sent (seconds)
|
|
|
|
|
|
float pingInterval = 30.0f; // Ping interval (30 seconds)
|
2026-02-11 21:14:35 -08:00
|
|
|
|
float timeSinceLastMoveHeartbeat_ = 0.0f; // Periodic movement heartbeat to keep server position synced
|
|
|
|
|
|
float moveHeartbeatInterval_ = 0.5f;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
uint32_t lastLatency = 0; // Last measured latency (milliseconds)
|
2026-03-11 19:45:03 -07:00
|
|
|
|
std::chrono::steady_clock::time_point pingTimestamp_; // Time CMSG_PING was sent
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-07 16:59:20 -08:00
|
|
|
|
// Player GUID and map
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint64_t playerGuid = 0;
|
2026-02-07 16:59:20 -08:00
|
|
|
|
uint32_t currentMapId_ = 0;
|
2026-02-08 03:39:02 -08:00
|
|
|
|
bool hasHomeBind_ = false;
|
|
|
|
|
|
uint32_t homeBindMapId_ = 0;
|
|
|
|
|
|
glm::vec3 homeBindPos_{0.0f};
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
// ---- Phase 1: Name caches ----
|
|
|
|
|
|
std::unordered_map<uint64_t, std::string> playerNameCache;
|
|
|
|
|
|
std::unordered_set<uint64_t> pendingNameQueries;
|
|
|
|
|
|
std::unordered_map<uint32_t, CreatureQueryResponseData> creatureInfoCache;
|
|
|
|
|
|
std::unordered_set<uint32_t> pendingCreatureQueries;
|
2026-02-08 00:59:40 -08:00
|
|
|
|
std::unordered_map<uint32_t, GameObjectQueryResponseData> gameObjectInfoCache_;
|
|
|
|
|
|
std::unordered_set<uint32_t> pendingGameObjectQueries_;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-03-10 05:46:03 -07:00
|
|
|
|
// ---- Friend/contact list cache ----
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
std::unordered_map<std::string, uint64_t> friendsCache; // name -> guid
|
2026-03-10 01:15:51 -07:00
|
|
|
|
std::unordered_set<uint64_t> friendGuids_; // all known friend GUIDs (for name backfill)
|
2026-02-18 23:30:38 -08:00
|
|
|
|
uint32_t lastContactListMask_ = 0;
|
|
|
|
|
|
uint32_t lastContactListCount_ = 0;
|
2026-03-10 05:46:03 -07:00
|
|
|
|
std::vector<ContactEntry> contacts_; // structured contact list (friends + ignores)
|
2026-02-18 23:30:38 -08:00
|
|
|
|
|
|
|
|
|
|
// ---- World state and faction initialization snapshots ----
|
|
|
|
|
|
uint32_t worldStateMapId_ = 0;
|
|
|
|
|
|
uint32_t worldStateZoneId_ = 0;
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> worldStates_;
|
|
|
|
|
|
std::vector<FactionStandingInit> initialFactions_;
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
|
2026-02-07 12:58:11 -08:00
|
|
|
|
// ---- Ignore list cache ----
|
|
|
|
|
|
std::unordered_map<std::string, uint64_t> ignoreCache; // name -> guid
|
|
|
|
|
|
|
|
|
|
|
|
// ---- Logout state ----
|
|
|
|
|
|
bool loggingOut_ = false;
|
|
|
|
|
|
|
2026-02-07 13:03:21 -08:00
|
|
|
|
// ---- Display state ----
|
|
|
|
|
|
bool helmVisible_ = true;
|
|
|
|
|
|
bool cloakVisible_ = true;
|
Implement SMSG_STANDSTATE_UPDATE and SMSG_ITEM_PUSH_RESULT handlers
SMSG_STANDSTATE_UPDATE:
- Parse uint8 stand state from server confirmation packet
- Store in standState_ member (0=stand, 7=dead, 8=kneel, etc.)
- Expose getStandState(), isSitting(), isDead(), isKneeling() accessors
SMSG_ITEM_PUSH_RESULT:
- Parse full WotLK 3.3.5a payload: guid, received, created, showInChat,
bagSlot, itemSlot, itemId, suffixFactor, randomPropertyId, count, totalCount
- Show "Received: <name> x<count>" chat notification when showInChat=1
- Queue item info lookup via queryItemInfo so name resolves asap
2026-03-09 12:58:52 -07:00
|
|
|
|
uint8_t standState_ = 0; // 0=stand, 1=sit, ..., 7=dead, 8=kneel (server-confirmed)
|
2026-02-07 13:03:21 -08:00
|
|
|
|
|
|
|
|
|
|
// ---- Follow state ----
|
|
|
|
|
|
uint64_t followTargetGuid_ = 0;
|
|
|
|
|
|
|
2026-02-07 13:17:01 -08:00
|
|
|
|
// ---- AFK/DND status ----
|
|
|
|
|
|
bool afkStatus_ = false;
|
|
|
|
|
|
bool dndStatus_ = false;
|
|
|
|
|
|
std::string afkMessage_;
|
|
|
|
|
|
std::string dndMessage_;
|
|
|
|
|
|
std::string lastWhisperSender_;
|
|
|
|
|
|
|
2026-02-06 03:11:43 -08:00
|
|
|
|
// ---- Online item tracking ----
|
|
|
|
|
|
struct OnlineItemInfo {
|
|
|
|
|
|
uint32_t entry = 0;
|
|
|
|
|
|
uint32_t stackCount = 1;
|
2026-03-10 16:21:09 -07:00
|
|
|
|
uint32_t curDurability = 0;
|
|
|
|
|
|
uint32_t maxDurability = 0;
|
2026-02-06 03:11:43 -08:00
|
|
|
|
};
|
|
|
|
|
|
std::unordered_map<uint64_t, OnlineItemInfo> onlineItems_;
|
|
|
|
|
|
std::unordered_map<uint32_t, ItemQueryResponseData> itemInfoCache_;
|
|
|
|
|
|
std::unordered_set<uint32_t> pendingItemQueries_;
|
|
|
|
|
|
std::array<uint64_t, 23> equipSlotGuids_{};
|
|
|
|
|
|
std::array<uint64_t, 16> backpackSlotGuids_{};
|
2026-02-13 22:14:34 -08:00
|
|
|
|
// Container (bag) contents: containerGuid -> array of item GUIDs per slot
|
|
|
|
|
|
struct ContainerInfo {
|
|
|
|
|
|
uint32_t numSlots = 0;
|
|
|
|
|
|
std::array<uint64_t, 36> slotGuids{}; // max 36 slots
|
|
|
|
|
|
};
|
|
|
|
|
|
std::unordered_map<uint64_t, ContainerInfo> containerContents_;
|
2026-02-06 18:34:45 -08:00
|
|
|
|
int invSlotBase_ = -1;
|
|
|
|
|
|
int packSlotBase_ = -1;
|
|
|
|
|
|
std::map<uint16_t, uint32_t> lastPlayerFields_;
|
2026-02-06 03:13:42 -08:00
|
|
|
|
bool onlineEquipDirty_ = false;
|
2026-02-14 16:33:24 -08:00
|
|
|
|
std::array<uint32_t, 19> lastEquipDisplayIds_{};
|
2026-02-06 03:11:43 -08:00
|
|
|
|
|
2026-02-13 20:10:19 -08:00
|
|
|
|
// Visible equipment for other players: detect the update-field layout (base + stride)
|
|
|
|
|
|
// using the local player's own equipped items, then decode other players by index.
|
2026-02-14 15:43:09 -08:00
|
|
|
|
// Default to known WotLK 3.3.5a layout: UNIT_END(148) + 0x0088 = 284, stride 2.
|
|
|
|
|
|
// The heuristic in maybeDetectVisibleItemLayout() can still override if needed.
|
|
|
|
|
|
int visibleItemEntryBase_ = 284;
|
2026-02-13 20:10:19 -08:00
|
|
|
|
int visibleItemStride_ = 2;
|
2026-02-14 15:43:09 -08:00
|
|
|
|
bool visibleItemLayoutVerified_ = false; // true once heuristic confirms/overrides default
|
2026-02-13 20:10:19 -08:00
|
|
|
|
std::unordered_map<uint64_t, std::array<uint32_t, 19>> otherPlayerVisibleItemEntries_;
|
|
|
|
|
|
std::unordered_set<uint64_t> otherPlayerVisibleDirty_;
|
2026-02-13 20:19:33 -08:00
|
|
|
|
std::unordered_map<uint64_t, uint32_t> otherPlayerMoveTimeMs_;
|
2026-02-19 16:45:39 -08:00
|
|
|
|
std::unordered_map<uint64_t, float> otherPlayerSmoothedIntervalMs_; // EMA of packet intervals
|
2026-02-13 20:10:19 -08:00
|
|
|
|
|
2026-02-13 20:26:55 -08:00
|
|
|
|
// Inspect fallback (when visible item fields are missing/unreliable)
|
|
|
|
|
|
std::unordered_map<uint64_t, std::array<uint32_t, 19>> inspectedPlayerItemEntries_;
|
2026-03-12 02:52:40 -07:00
|
|
|
|
InspectResult inspectResult_; // most-recently received inspect response
|
2026-02-13 20:26:55 -08:00
|
|
|
|
std::unordered_set<uint64_t> pendingAutoInspect_;
|
|
|
|
|
|
float inspectRateLimit_ = 0.0f;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 2: Combat ----
|
|
|
|
|
|
bool autoAttacking = false;
|
2026-02-20 03:38:12 -08:00
|
|
|
|
bool autoAttackRequested_ = false; // local intent (CMSG_ATTACKSWING sent)
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint64_t autoAttackTarget = 0;
|
2026-02-06 18:34:45 -08:00
|
|
|
|
bool autoAttackOutOfRange_ = false;
|
2026-02-20 17:53:33 -08:00
|
|
|
|
float autoAttackOutOfRangeTime_ = 0.0f;
|
|
|
|
|
|
float autoAttackRangeWarnCooldown_ = 0.0f;
|
2026-02-17 15:37:02 -08:00
|
|
|
|
float autoAttackResendTimer_ = 0.0f; // Re-send CMSG_ATTACKSWING every ~1s while attacking
|
2026-02-20 03:14:48 -08:00
|
|
|
|
float autoAttackFacingSyncTimer_ = 0.0f; // Periodic facing sync while meleeing
|
2026-02-06 18:34:45 -08:00
|
|
|
|
std::unordered_set<uint64_t> hostileAttackers_;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
std::vector<CombatTextEntry> combatText;
|
feat: add persistent combat log window (/combatlog or /cl)
Stores up to 500 combat events in a rolling deque alongside the existing
floating combat text. Events are populated via the existing addCombatText()
call site, resolving attacker/target names from the entity manager and
player name cache at event time.
- CombatLogEntry struct in spell_defines.hpp (type, amount, spellId,
isPlayerSource, timestamp, sourceName, targetName)
- getCombatLog() / clearCombatLog() accessors on GameHandler
- renderCombatLog() in GameScreen: scrollable two-column table (Time +
Event), color-coded by event category, with Damage/Healing/Misc filter
checkboxes, auto-scroll toggle, and Clear button
- /combatlog (/cl) chat command toggles the window
2026-03-12 11:00:10 -07:00
|
|
|
|
static constexpr size_t MAX_COMBAT_LOG = 500;
|
|
|
|
|
|
std::deque<CombatLogEntry> combatLog_;
|
2026-03-12 11:06:40 -07:00
|
|
|
|
std::deque<std::string> areaTriggerMsgs_;
|
2026-03-12 02:59:09 -07:00
|
|
|
|
// unitGuid → sorted threat list (descending by threat value)
|
|
|
|
|
|
std::unordered_map<uint64_t, std::vector<ThreatEntry>> threatLists_;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
// ---- Phase 3: Spells ----
|
2026-02-05 21:28:21 -08:00
|
|
|
|
WorldEntryCallback worldEntryCallback_;
|
2026-03-10 12:28:11 -07:00
|
|
|
|
KnockBackCallback knockBackCallback_;
|
2026-03-12 19:37:53 -07:00
|
|
|
|
CameraShakeCallback cameraShakeCallback_;
|
2026-02-07 16:59:20 -08:00
|
|
|
|
UnstuckCallback unstuckCallback_;
|
2026-02-08 03:24:12 -08:00
|
|
|
|
UnstuckCallback unstuckGyCallback_;
|
2026-03-07 22:03:28 -08:00
|
|
|
|
UnstuckCallback unstuckHearthCallback_;
|
2026-02-08 03:32:00 -08:00
|
|
|
|
BindPointCallback bindPointCallback_;
|
2026-03-09 21:57:42 -07:00
|
|
|
|
HearthstonePreloadCallback hearthstonePreloadCallback_;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
CreatureSpawnCallback creatureSpawnCallback_;
|
|
|
|
|
|
CreatureDespawnCallback creatureDespawnCallback_;
|
2026-02-13 19:40:54 -08:00
|
|
|
|
PlayerSpawnCallback playerSpawnCallback_;
|
|
|
|
|
|
PlayerDespawnCallback playerDespawnCallback_;
|
2026-02-13 20:10:19 -08:00
|
|
|
|
PlayerEquipmentCallback playerEquipmentCallback_;
|
2026-02-06 13:47:03 -08:00
|
|
|
|
CreatureMoveCallback creatureMoveCallback_;
|
2026-02-08 00:59:40 -08:00
|
|
|
|
TransportMoveCallback transportMoveCallback_;
|
2026-02-11 00:54:38 -08:00
|
|
|
|
TransportSpawnCallback transportSpawnCallback_;
|
2026-02-07 19:44:03 -08:00
|
|
|
|
GameObjectSpawnCallback gameObjectSpawnCallback_;
|
2026-02-12 00:04:53 -08:00
|
|
|
|
GameObjectMoveCallback gameObjectMoveCallback_;
|
2026-02-07 19:44:03 -08:00
|
|
|
|
GameObjectDespawnCallback gameObjectDespawnCallback_;
|
2026-02-23 05:39:02 -08:00
|
|
|
|
GameObjectCustomAnimCallback gameObjectCustomAnimCallback_;
|
2026-02-08 00:59:40 -08:00
|
|
|
|
|
|
|
|
|
|
// Transport tracking
|
2026-02-12 00:04:53 -08:00
|
|
|
|
struct TransportAttachment {
|
|
|
|
|
|
ObjectType type = ObjectType::OBJECT;
|
|
|
|
|
|
uint64_t transportGuid = 0;
|
|
|
|
|
|
glm::vec3 localOffset{0.0f};
|
|
|
|
|
|
float localOrientation = 0.0f;
|
|
|
|
|
|
bool hasLocalOrientation = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
std::unordered_map<uint64_t, TransportAttachment> transportAttachments_;
|
2026-02-08 00:59:40 -08:00
|
|
|
|
std::unordered_set<uint64_t> transportGuids_; // GUIDs of known transport GameObjects
|
2026-02-12 00:04:53 -08:00
|
|
|
|
std::unordered_set<uint64_t> serverUpdatedTransportGuids_;
|
2026-02-08 00:59:40 -08:00
|
|
|
|
uint64_t playerTransportGuid_ = 0; // Transport the player is riding (0 = none)
|
|
|
|
|
|
glm::vec3 playerTransportOffset_ = glm::vec3(0.0f); // Player offset on transport
|
2026-02-12 00:45:24 -08:00
|
|
|
|
uint64_t playerTransportStickyGuid_ = 0; // Last transport player was on (temporary retention)
|
|
|
|
|
|
float playerTransportStickyTimer_ = 0.0f; // Seconds to keep sticky transport alive after transient clears
|
2026-02-10 21:29:10 -08:00
|
|
|
|
std::unique_ptr<TransportManager> transportManager_; // Transport movement manager
|
2026-02-17 15:13:54 -08:00
|
|
|
|
std::unordered_set<uint32_t> knownSpells;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
std::unordered_map<uint32_t, float> spellCooldowns; // spellId -> remaining seconds
|
2026-03-09 20:23:38 -07:00
|
|
|
|
uint32_t weaponProficiency_ = 0; // bitmask from SMSG_SET_PROFICIENCY itemClass=2
|
|
|
|
|
|
uint32_t armorProficiency_ = 0; // bitmask from SMSG_SET_PROFICIENCY itemClass=4
|
2026-03-09 20:36:20 -07:00
|
|
|
|
std::vector<MinimapPing> minimapPings_;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint8_t castCount = 0;
|
|
|
|
|
|
bool casting = false;
|
2026-03-12 00:43:29 -07:00
|
|
|
|
bool castIsChannel = false;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint32_t currentCastSpellId = 0;
|
|
|
|
|
|
float castTimeRemaining = 0.0f;
|
2026-03-09 23:13:30 -07:00
|
|
|
|
// Per-unit cast state (keyed by GUID, populated from SMSG_SPELL_START)
|
|
|
|
|
|
std::unordered_map<uint64_t, UnitCastState> unitCastStates_;
|
2026-02-19 03:31:49 -08:00
|
|
|
|
uint64_t pendingGameObjectInteractGuid_ = 0;
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
|
|
|
|
|
|
// Talents (dual-spec support)
|
|
|
|
|
|
uint8_t activeTalentSpec_ = 0; // Currently active spec (0 or 1)
|
|
|
|
|
|
uint8_t unspentTalentPoints_[2] = {0, 0}; // Unspent points per spec
|
|
|
|
|
|
std::unordered_map<uint32_t, uint8_t> learnedTalents_[2]; // Learned talents per spec
|
2026-03-12 17:39:35 -07:00
|
|
|
|
std::array<std::array<uint16_t, MAX_GLYPH_SLOTS>, 2> learnedGlyphs_{}; // Glyphs per spec
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
std::unordered_map<uint32_t, TalentEntry> talentCache_; // talentId -> entry
|
|
|
|
|
|
std::unordered_map<uint32_t, TalentTabEntry> talentTabCache_; // tabId -> entry
|
|
|
|
|
|
bool talentDbcLoaded_ = false;
|
2026-03-10 07:41:27 -07:00
|
|
|
|
bool talentsInitialized_ = false; // Reset on world entry; guards first-spec selection
|
2026-02-26 17:56:11 -08:00
|
|
|
|
|
|
|
|
|
|
// ---- Area trigger detection ----
|
|
|
|
|
|
struct AreaTriggerEntry {
|
|
|
|
|
|
uint32_t id = 0;
|
|
|
|
|
|
uint32_t mapId = 0;
|
|
|
|
|
|
float x = 0, y = 0, z = 0; // canonical WoW coords (converted from DBC)
|
|
|
|
|
|
float radius = 0;
|
|
|
|
|
|
float boxLength = 0, boxWidth = 0, boxHeight = 0;
|
|
|
|
|
|
float boxYaw = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
bool areaTriggerDbcLoaded_ = false;
|
|
|
|
|
|
std::vector<AreaTriggerEntry> areaTriggers_;
|
|
|
|
|
|
std::unordered_set<uint32_t> activeAreaTriggers_; // triggers player is currently inside
|
|
|
|
|
|
float areaTriggerCheckTimer_ = 0.0f;
|
2026-02-27 04:59:12 -08:00
|
|
|
|
bool areaTriggerSuppressFirst_ = false; // suppress first check after map transfer
|
2026-02-26 17:56:11 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
float castTimeTotal = 0.0f;
|
2026-03-10 06:04:43 -07:00
|
|
|
|
std::array<ActionBarSlot, ACTION_BAR_SLOTS> actionBar{};
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
std::vector<AuraSlot> playerAuras;
|
|
|
|
|
|
std::vector<AuraSlot> targetAuras;
|
2026-03-12 11:44:30 -07:00
|
|
|
|
std::unordered_map<uint64_t, std::vector<AuraSlot>> unitAurasCache_; // per-unit aura cache
|
2026-02-26 10:41:29 -08:00
|
|
|
|
uint64_t petGuid_ = 0;
|
2026-03-09 22:53:09 -07:00
|
|
|
|
uint32_t petActionSlots_[10] = {}; // SMSG_PET_SPELLS action bar (10 slots)
|
|
|
|
|
|
uint8_t petCommand_ = 1; // 0=stay,1=follow,2=attack,3=dismiss
|
|
|
|
|
|
uint8_t petReact_ = 1; // 0=passive,1=defensive,2=aggressive
|
|
|
|
|
|
std::vector<uint32_t> petSpellList_; // known pet spells
|
|
|
|
|
|
std::unordered_set<uint32_t> petAutocastSpells_; // spells with autocast on
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
feat: implement pet stable system (MSG_LIST_STABLED_PETS, CMSG_STABLE_PET, CMSG_UNSTABLE_PET)
- Parse MSG_LIST_STABLED_PETS (SMSG): populate StabledPet list with
petNumber, entry, level, name, displayId, and active status
- Detect stable master via gossip option text/keyword matching and
auto-send MSG_LIST_STABLED_PETS request to open the stable UI
- Refresh list automatically after SMSG_STABLE_RESULT to reflect state
- New packet builders: ListStabledPetsPacket, StablePetPacket, UnstablePetPacket
- New public API: requestStabledPetList(), stablePet(slot), unstablePet(petNumber)
- Stable window UI: shows active/stabled pets with store/retrieve buttons,
slot count, refresh, and close; opens when server sends pet list
- Clear stable state on world logout/disconnect
2026-03-12 19:15:52 -07:00
|
|
|
|
// ---- Pet Stable ----
|
|
|
|
|
|
bool stableWindowOpen_ = false;
|
|
|
|
|
|
uint64_t stableMasterGuid_ = 0;
|
|
|
|
|
|
uint8_t stableNumSlots_ = 0;
|
|
|
|
|
|
std::vector<StabledPet> stabledPets_;
|
|
|
|
|
|
void handleListStabledPets(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-26 17:56:11 -08:00
|
|
|
|
// ---- Battleground queue state ----
|
|
|
|
|
|
std::array<BgQueueSlot, 3> bgQueues_{};
|
|
|
|
|
|
|
2026-03-12 21:54:48 -07:00
|
|
|
|
// ---- Available battleground list (SMSG_BATTLEFIELD_LIST) ----
|
|
|
|
|
|
std::vector<AvailableBgInfo> availableBgs_;
|
|
|
|
|
|
void handleBattlefieldList(network::Packet& packet);
|
|
|
|
|
|
|
2026-02-26 17:56:11 -08:00
|
|
|
|
// Instance difficulty
|
|
|
|
|
|
uint32_t instanceDifficulty_ = 0;
|
|
|
|
|
|
bool instanceIsHeroic_ = false;
|
|
|
|
|
|
|
2026-03-10 06:10:29 -07:00
|
|
|
|
// Raid target markers (icon 0-7 -> guid; 0 = empty slot)
|
|
|
|
|
|
std::array<uint64_t, kRaidMarkCount> raidTargetGuids_ = {};
|
|
|
|
|
|
|
2026-03-09 14:30:48 -07:00
|
|
|
|
// Mirror timers (0=fatigue, 1=breath, 2=feigndeath)
|
|
|
|
|
|
MirrorTimer mirrorTimers_[3];
|
|
|
|
|
|
|
|
|
|
|
|
// Combo points (rogues/druids)
|
|
|
|
|
|
uint8_t comboPoints_ = 0;
|
|
|
|
|
|
uint64_t comboTarget_ = 0;
|
|
|
|
|
|
|
2026-03-09 13:36:23 -07:00
|
|
|
|
// Instance / raid lockouts
|
|
|
|
|
|
std::vector<InstanceLockout> instanceLockouts_;
|
|
|
|
|
|
|
2026-03-12 02:35:29 -07:00
|
|
|
|
// Arena team stats (indexed by team slot, updated by SMSG_ARENA_TEAM_STATS)
|
2026-03-12 21:01:51 -07:00
|
|
|
|
std::vector<ArenaTeamStats> arenaTeamStats_;
|
|
|
|
|
|
// Arena team rosters (updated by SMSG_ARENA_TEAM_ROSTER)
|
|
|
|
|
|
std::vector<ArenaTeamRoster> arenaTeamRosters_;
|
2026-03-12 02:35:29 -07:00
|
|
|
|
|
2026-03-12 12:02:59 -07:00
|
|
|
|
// BG scoreboard (MSG_PVP_LOG_DATA)
|
|
|
|
|
|
BgScoreboardData bgScoreboard_;
|
|
|
|
|
|
|
2026-03-09 19:54:32 -07:00
|
|
|
|
// Instance encounter boss units (slots 0-4 from SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT)
|
|
|
|
|
|
std::array<uint64_t, kMaxEncounterSlots> encounterUnitGuids_ = {}; // 0 = empty slot
|
|
|
|
|
|
|
2026-03-09 13:30:23 -07:00
|
|
|
|
// LFG / Dungeon Finder state
|
|
|
|
|
|
LfgState lfgState_ = LfgState::None;
|
|
|
|
|
|
uint32_t lfgDungeonId_ = 0; // current dungeon entry
|
2026-03-09 13:47:07 -07:00
|
|
|
|
uint32_t lfgProposalId_ = 0; // pending proposal id (0 = none)
|
2026-03-09 13:30:23 -07:00
|
|
|
|
int32_t lfgAvgWaitSec_ = -1; // estimated wait, -1=unknown
|
|
|
|
|
|
uint32_t lfgTimeInQueueMs_= 0; // ms already in queue
|
2026-03-12 02:17:49 -07:00
|
|
|
|
uint32_t lfgBootVotes_ = 0; // current boot-yes votes
|
|
|
|
|
|
uint32_t lfgBootTotal_ = 0; // total votes cast
|
|
|
|
|
|
uint32_t lfgBootTimeLeft_ = 0; // seconds remaining
|
|
|
|
|
|
uint32_t lfgBootNeeded_ = 0; // votes needed to kick
|
2026-03-12 09:09:41 -07:00
|
|
|
|
std::string lfgBootTargetName_; // name of player being voted on
|
|
|
|
|
|
std::string lfgBootReason_; // reason given for kick
|
2026-03-09 13:30:23 -07:00
|
|
|
|
|
2026-03-09 14:48:30 -07:00
|
|
|
|
// Ready check state
|
2026-03-09 20:38:44 -07:00
|
|
|
|
bool pendingReadyCheck_ = false;
|
|
|
|
|
|
uint32_t readyCheckReadyCount_ = 0;
|
|
|
|
|
|
uint32_t readyCheckNotReadyCount_ = 0;
|
2026-03-09 14:48:30 -07:00
|
|
|
|
std::string readyCheckInitiator_;
|
2026-03-12 09:07:37 -07:00
|
|
|
|
std::vector<ReadyCheckResult> readyCheckResults_; // per-player status live during check
|
2026-03-09 14:48:30 -07:00
|
|
|
|
|
|
|
|
|
|
// Faction standings (factionId → absolute standing value)
|
|
|
|
|
|
std::unordered_map<uint32_t, int32_t> factionStandings_;
|
|
|
|
|
|
// Faction name cache (factionId → name), populated lazily from Faction.dbc
|
|
|
|
|
|
std::unordered_map<uint32_t, std::string> factionNameCache_;
|
2026-03-12 23:30:44 -07:00
|
|
|
|
// repListId → factionId mapping (populated with factionNameCache)
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> factionRepListToId_;
|
|
|
|
|
|
// factionId → repListId reverse mapping
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> factionIdToRepList_;
|
2026-03-09 14:48:30 -07:00
|
|
|
|
bool factionNameCacheLoaded_ = false;
|
|
|
|
|
|
void loadFactionNameCache();
|
|
|
|
|
|
std::string getFactionName(uint32_t factionId) const;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 4: Group ----
|
|
|
|
|
|
GroupListData partyData;
|
|
|
|
|
|
bool pendingGroupInvite = false;
|
|
|
|
|
|
std::string pendingInviterName;
|
|
|
|
|
|
|
2026-03-09 14:15:59 -07:00
|
|
|
|
// Item text state
|
|
|
|
|
|
bool itemTextOpen_ = false;
|
|
|
|
|
|
std::string itemText_;
|
|
|
|
|
|
|
2026-03-09 14:14:15 -07:00
|
|
|
|
// Shared quest state
|
|
|
|
|
|
bool pendingSharedQuest_ = false;
|
|
|
|
|
|
uint32_t sharedQuestId_ = 0;
|
|
|
|
|
|
std::string sharedQuestTitle_;
|
|
|
|
|
|
std::string sharedQuestSharerName_;
|
|
|
|
|
|
uint64_t sharedQuestSharerGuid_ = 0;
|
|
|
|
|
|
|
2026-03-09 14:07:50 -07:00
|
|
|
|
// Summon state
|
|
|
|
|
|
bool pendingSummonRequest_ = false;
|
|
|
|
|
|
uint64_t summonerGuid_ = 0;
|
|
|
|
|
|
std::string summonerName_;
|
|
|
|
|
|
float summonTimeoutSec_ = 0.0f;
|
2026-03-12 03:09:52 -07:00
|
|
|
|
uint32_t totalTimePlayed_ = 0;
|
|
|
|
|
|
uint32_t levelTimePlayed_ = 0;
|
2026-03-09 14:07:50 -07:00
|
|
|
|
|
2026-03-12 10:41:18 -07:00
|
|
|
|
// Who results (last SMSG_WHO response)
|
|
|
|
|
|
std::vector<WhoEntry> whoResults_;
|
|
|
|
|
|
uint32_t whoOnlineCount_ = 0;
|
|
|
|
|
|
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
// Trade state
|
|
|
|
|
|
TradeStatus tradeStatus_ = TradeStatus::None;
|
|
|
|
|
|
uint64_t tradePeerGuid_= 0;
|
|
|
|
|
|
std::string tradePeerName_;
|
2026-03-11 00:44:07 -07:00
|
|
|
|
std::array<TradeSlot, TRADE_SLOT_COUNT> myTradeSlots_{};
|
|
|
|
|
|
std::array<TradeSlot, TRADE_SLOT_COUNT> peerTradeSlots_{};
|
|
|
|
|
|
uint64_t myTradeGold_ = 0;
|
|
|
|
|
|
uint64_t peerTradeGold_ = 0;
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
|
2026-03-12 05:16:43 -07:00
|
|
|
|
// Shaman totem state
|
|
|
|
|
|
TotemSlot activeTotemSlots_[NUM_TOTEM_SLOTS];
|
|
|
|
|
|
|
2026-03-09 13:58:02 -07:00
|
|
|
|
// Duel state
|
|
|
|
|
|
bool pendingDuelRequest_ = false;
|
|
|
|
|
|
uint64_t duelChallengerGuid_= 0;
|
|
|
|
|
|
uint64_t duelFlagGuid_ = 0;
|
|
|
|
|
|
std::string duelChallengerName_;
|
2026-03-12 05:06:14 -07:00
|
|
|
|
uint32_t duelCountdownMs_ = 0; // 0 = no active countdown
|
|
|
|
|
|
std::chrono::steady_clock::time_point duelCountdownStartedAt_{};
|
2026-03-09 13:58:02 -07:00
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
// ---- Guild state ----
|
|
|
|
|
|
std::string guildName_;
|
|
|
|
|
|
std::vector<std::string> guildRankNames_;
|
|
|
|
|
|
GuildRosterData guildRoster_;
|
2026-02-25 14:44:44 -08:00
|
|
|
|
GuildInfoData guildInfoData_;
|
|
|
|
|
|
GuildQueryResponseData guildQueryData_;
|
2026-02-13 21:39:48 -08:00
|
|
|
|
bool hasGuildRoster_ = false;
|
|
|
|
|
|
bool pendingGuildInvite_ = false;
|
|
|
|
|
|
std::string pendingGuildInviterName_;
|
|
|
|
|
|
std::string pendingGuildInviteGuildName_;
|
2026-02-25 14:44:44 -08:00
|
|
|
|
bool showPetitionDialog_ = false;
|
|
|
|
|
|
uint32_t petitionCost_ = 0;
|
|
|
|
|
|
uint64_t petitionNpcGuid_ = 0;
|
2026-02-13 21:39:48 -08:00
|
|
|
|
|
2026-02-05 14:35:12 -08:00
|
|
|
|
uint64_t activeCharacterGuid_ = 0;
|
2026-02-08 03:05:38 -08:00
|
|
|
|
Race playerRace_ = Race::HUMAN;
|
2026-02-05 14:35:12 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ---- Phase 5: Loot ----
|
|
|
|
|
|
bool lootWindowOpen = false;
|
2026-02-17 16:31:00 -08:00
|
|
|
|
bool autoLoot_ = false;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
LootResponseData currentLoot;
|
2026-03-12 17:58:24 -07:00
|
|
|
|
std::vector<uint64_t> masterLootCandidates_; // from SMSG_LOOT_MASTER_LIST
|
2026-03-09 14:01:27 -07:00
|
|
|
|
|
|
|
|
|
|
// Group loot roll state
|
|
|
|
|
|
bool pendingLootRollActive_ = false;
|
|
|
|
|
|
LootRollEntry pendingLootRoll_;
|
2026-02-05 14:01:26 -08:00
|
|
|
|
struct LocalLootState {
|
|
|
|
|
|
LootResponseData data;
|
|
|
|
|
|
bool moneyTaken = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
std::unordered_map<uint64_t, LocalLootState> localLootState_;
|
2026-02-18 04:13:26 -08:00
|
|
|
|
struct PendingLootRetry {
|
|
|
|
|
|
uint64_t guid = 0;
|
|
|
|
|
|
float timer = 0.0f;
|
|
|
|
|
|
uint8_t remainingRetries = 0;
|
2026-02-20 19:51:04 -08:00
|
|
|
|
bool sendLoot = false;
|
2026-02-18 04:13:26 -08:00
|
|
|
|
};
|
|
|
|
|
|
std::vector<PendingLootRetry> pendingGameObjectLootRetries_;
|
2026-02-20 19:51:04 -08:00
|
|
|
|
struct PendingLootOpen {
|
|
|
|
|
|
uint64_t guid = 0;
|
|
|
|
|
|
float timer = 0.0f;
|
|
|
|
|
|
};
|
|
|
|
|
|
std::vector<PendingLootOpen> pendingGameObjectLootOpens_;
|
2026-03-13 04:37:36 -07:00
|
|
|
|
// Tracks the last GO we sent CMSG_GAMEOBJ_USE to; used in handleSpellGo
|
|
|
|
|
|
// to send CMSG_LOOT after a gather cast (mining/herbalism) completes.
|
|
|
|
|
|
uint64_t lastInteractedGoGuid_ = 0;
|
2026-02-18 04:11:00 -08:00
|
|
|
|
uint64_t pendingLootMoneyGuid_ = 0;
|
|
|
|
|
|
uint32_t pendingLootMoneyAmount_ = 0;
|
|
|
|
|
|
float pendingLootMoneyNotifyTimer_ = 0.0f;
|
|
|
|
|
|
std::unordered_map<uint64_t, float> recentLootMoneyAnnounceCooldowns_;
|
2026-02-05 14:01:26 -08:00
|
|
|
|
uint64_t playerMoneyCopper_ = 0;
|
2026-02-19 17:45:09 -08:00
|
|
|
|
int32_t playerArmorRating_ = 0;
|
2026-03-12 12:24:15 -07:00
|
|
|
|
int32_t playerResistances_[6] = {}; // [0]=Holy,[1]=Fire,[2]=Nature,[3]=Frost,[4]=Shadow,[5]=Arcane
|
2026-03-10 23:08:15 -07:00
|
|
|
|
// Server-authoritative primary stats: [0]=STR [1]=AGI [2]=STA [3]=INT [4]=SPI; -1 = not received yet
|
|
|
|
|
|
int32_t playerStats_[5] = {-1, -1, -1, -1, -1};
|
2026-02-13 19:47:49 -08:00
|
|
|
|
// Some servers/custom clients shift update field indices. We can auto-detect coinage by correlating
|
|
|
|
|
|
// money-notify deltas with update-field diffs and then overriding UF::PLAYER_FIELD_COINAGE at runtime.
|
|
|
|
|
|
uint32_t pendingMoneyDelta_ = 0;
|
|
|
|
|
|
float pendingMoneyDeltaTimer_ = 0.0f;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
// Gossip
|
|
|
|
|
|
bool gossipWindowOpen = false;
|
|
|
|
|
|
GossipMessageData currentGossip;
|
2026-03-09 14:38:45 -07:00
|
|
|
|
std::vector<GossipPoi> gossipPois_;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-02-19 03:31:49 -08:00
|
|
|
|
void performGameObjectInteractionNow(uint64_t guid);
|
|
|
|
|
|
|
2026-02-06 11:59:51 -08:00
|
|
|
|
// Quest details
|
|
|
|
|
|
bool questDetailsOpen = false;
|
2026-03-11 17:27:23 -07:00
|
|
|
|
std::chrono::steady_clock::time_point questDetailsOpenTime{}; // Delayed opening to allow item data to load
|
2026-02-06 11:59:51 -08:00
|
|
|
|
QuestDetailsData currentQuestDetails;
|
|
|
|
|
|
|
2026-02-06 21:50:15 -08:00
|
|
|
|
// Quest turn-in
|
|
|
|
|
|
bool questRequestItemsOpen_ = false;
|
|
|
|
|
|
QuestRequestItemsData currentQuestRequestItems_;
|
2026-02-19 01:12:14 -08:00
|
|
|
|
uint32_t pendingTurnInQuestId_ = 0;
|
|
|
|
|
|
uint64_t pendingTurnInNpcGuid_ = 0;
|
|
|
|
|
|
bool pendingTurnInRewardRequest_ = false;
|
2026-02-20 17:14:13 -08:00
|
|
|
|
std::unordered_map<uint32_t, float> pendingQuestAcceptTimeouts_;
|
|
|
|
|
|
std::unordered_map<uint32_t, uint64_t> pendingQuestAcceptNpcGuids_;
|
2026-02-06 21:50:15 -08:00
|
|
|
|
bool questOfferRewardOpen_ = false;
|
|
|
|
|
|
QuestOfferRewardData currentQuestOfferReward_;
|
|
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
|
// Quest log
|
|
|
|
|
|
std::vector<QuestLogEntry> questLog_;
|
2026-02-19 00:30:21 -08:00
|
|
|
|
std::unordered_set<uint32_t> pendingQuestQueryIds_;
|
2026-03-10 05:18:45 -07:00
|
|
|
|
std::unordered_set<uint32_t> trackedQuestIds_;
|
2026-02-20 17:14:13 -08:00
|
|
|
|
bool pendingLoginQuestResync_ = false;
|
|
|
|
|
|
float pendingLoginQuestResyncTimeout_ = 0.0f;
|
2026-02-06 13:47:03 -08:00
|
|
|
|
|
2026-02-06 20:10:10 -08:00
|
|
|
|
// Quest giver status per NPC
|
|
|
|
|
|
std::unordered_map<uint64_t, QuestGiverStatus> npcQuestStatus_;
|
|
|
|
|
|
|
2026-02-06 14:24:38 -08:00
|
|
|
|
// Faction hostility lookup (populated from FactionTemplate.dbc)
|
|
|
|
|
|
std::unordered_map<uint32_t, bool> factionHostileMap_;
|
|
|
|
|
|
bool isHostileFaction(uint32_t factionTemplateId) const {
|
|
|
|
|
|
auto it = factionHostileMap_.find(factionTemplateId);
|
|
|
|
|
|
return it != factionHostileMap_.end() ? it->second : true; // default hostile if unknown
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 17:25:00 -07:00
|
|
|
|
// Vehicle (WotLK): non-zero when player is seated in a vehicle
|
|
|
|
|
|
uint32_t vehicleId_ = 0;
|
|
|
|
|
|
|
2026-02-07 16:59:20 -08:00
|
|
|
|
// Taxi / Flight Paths
|
2026-03-09 19:30:18 -07:00
|
|
|
|
std::unordered_map<uint64_t, bool> taxiNpcHasRoutes_; // guid -> has new/available routes
|
2026-02-07 16:59:20 -08:00
|
|
|
|
std::unordered_map<uint32_t, TaxiNode> taxiNodes_;
|
|
|
|
|
|
std::vector<TaxiPathEdge> taxiPathEdges_;
|
2026-02-08 21:32:38 -08:00
|
|
|
|
std::unordered_map<uint32_t, std::vector<TaxiPathNode>> taxiPathNodes_; // pathId -> ordered waypoints
|
2026-02-07 16:59:20 -08:00
|
|
|
|
bool taxiDbcLoaded_ = false;
|
|
|
|
|
|
bool taxiWindowOpen_ = false;
|
|
|
|
|
|
ShowTaxiNodesData currentTaxiData_;
|
|
|
|
|
|
uint64_t taxiNpcGuid_ = 0;
|
|
|
|
|
|
bool onTaxiFlight_ = false;
|
2026-02-08 03:05:38 -08:00
|
|
|
|
bool taxiMountActive_ = false;
|
|
|
|
|
|
uint32_t taxiMountDisplayId_ = 0;
|
|
|
|
|
|
bool taxiActivatePending_ = false;
|
|
|
|
|
|
float taxiActivateTimer_ = 0.0f;
|
|
|
|
|
|
bool taxiClientActive_ = false;
|
2026-02-08 23:15:26 -08:00
|
|
|
|
float taxiLandingCooldown_ = 0.0f; // Prevent re-entering taxi right after landing
|
2026-02-11 21:14:35 -08:00
|
|
|
|
float taxiStartGrace_ = 0.0f; // Ignore transient landing/dismount checks right after takeoff
|
2026-02-08 03:05:38 -08:00
|
|
|
|
size_t taxiClientIndex_ = 0;
|
|
|
|
|
|
std::vector<glm::vec3> taxiClientPath_;
|
2026-02-11 18:25:04 -08:00
|
|
|
|
float taxiClientSpeed_ = 32.0f;
|
2026-02-08 03:05:38 -08:00
|
|
|
|
float taxiClientSegmentProgress_ = 0.0f;
|
|
|
|
|
|
bool taxiRecoverPending_ = false;
|
|
|
|
|
|
uint32_t taxiRecoverMapId_ = 0;
|
|
|
|
|
|
glm::vec3 taxiRecoverPos_{0.0f};
|
2026-02-07 17:59:40 -08:00
|
|
|
|
uint32_t knownTaxiMask_[12] = {}; // Track previously known nodes for discovery alerts
|
|
|
|
|
|
bool taxiMaskInitialized_ = false; // First SMSG_SHOWTAXINODES seeds mask without alerts
|
2026-02-07 19:04:15 -08:00
|
|
|
|
std::unordered_map<uint32_t, uint32_t> taxiCostMap_; // destNodeId -> total cost in copper
|
|
|
|
|
|
void buildTaxiCostMap();
|
2026-02-08 03:05:38 -08:00
|
|
|
|
void applyTaxiMountForCurrentNode();
|
2026-02-20 02:19:17 -08:00
|
|
|
|
uint32_t nextMovementTimestampMs();
|
2026-02-11 22:27:02 -08:00
|
|
|
|
void sanitizeMovementForTaxi();
|
2026-02-08 03:05:38 -08:00
|
|
|
|
void startClientTaxiPath(const std::vector<uint32_t>& pathNodes);
|
|
|
|
|
|
void updateClientTaxi(float deltaTime);
|
2026-02-07 16:59:20 -08:00
|
|
|
|
|
2026-02-15 14:00:41 -08:00
|
|
|
|
// Mail
|
|
|
|
|
|
bool mailboxOpen_ = false;
|
|
|
|
|
|
uint64_t mailboxGuid_ = 0;
|
|
|
|
|
|
std::vector<MailMessage> mailInbox_;
|
|
|
|
|
|
int selectedMailIndex_ = -1;
|
|
|
|
|
|
bool showMailCompose_ = false;
|
2026-02-16 18:46:44 -08:00
|
|
|
|
bool hasNewMail_ = false;
|
2026-02-25 14:11:09 -08:00
|
|
|
|
std::array<MailAttachSlot, MAIL_MAX_ATTACHMENTS> mailAttachments_{};
|
2026-02-15 14:00:41 -08:00
|
|
|
|
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
// Bank
|
|
|
|
|
|
bool bankOpen_ = false;
|
|
|
|
|
|
uint64_t bankerGuid_ = 0;
|
|
|
|
|
|
std::array<uint64_t, 28> bankSlotGuids_{};
|
|
|
|
|
|
std::array<uint64_t, 7> bankBagSlotGuids_{};
|
2026-02-26 11:12:34 -08:00
|
|
|
|
int effectiveBankSlots_ = 28; // 24 for Classic, 28 for TBC/WotLK
|
|
|
|
|
|
int effectiveBankBagSlots_ = 7; // 6 for Classic, 7 for TBC/WotLK
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
|
|
|
|
|
|
// Guild Bank
|
|
|
|
|
|
bool guildBankOpen_ = false;
|
|
|
|
|
|
uint64_t guildBankerGuid_ = 0;
|
|
|
|
|
|
GuildBankData guildBankData_;
|
|
|
|
|
|
uint8_t guildBankActiveTab_ = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Auction House
|
|
|
|
|
|
bool auctionOpen_ = false;
|
|
|
|
|
|
uint64_t auctioneerGuid_ = 0;
|
|
|
|
|
|
uint32_t auctionHouseId_ = 0;
|
|
|
|
|
|
AuctionListResult auctionBrowseResults_;
|
|
|
|
|
|
AuctionListResult auctionOwnerResults_;
|
|
|
|
|
|
AuctionListResult auctionBidderResults_;
|
|
|
|
|
|
int auctionActiveTab_ = 0; // 0=Browse, 1=Bids, 2=Auctions
|
|
|
|
|
|
float auctionSearchDelayTimer_ = 0.0f;
|
2026-02-25 14:44:44 -08:00
|
|
|
|
// Last search params for re-query (pagination, auto-refresh after bid/buyout)
|
|
|
|
|
|
struct AuctionSearchParams {
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
uint8_t levelMin = 0, levelMax = 0;
|
|
|
|
|
|
uint32_t quality = 0xFFFFFFFF;
|
|
|
|
|
|
uint32_t itemClass = 0xFFFFFFFF;
|
|
|
|
|
|
uint32_t itemSubClass = 0xFFFFFFFF;
|
|
|
|
|
|
uint32_t invTypeMask = 0;
|
|
|
|
|
|
uint8_t usableOnly = 0;
|
|
|
|
|
|
uint32_t offset = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
AuctionSearchParams lastAuctionSearch_;
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
// Routing: which result vector to populate from next SMSG_AUCTION_LIST_RESULT
|
|
|
|
|
|
enum class AuctionResultTarget { BROWSE, OWNER, BIDDER };
|
|
|
|
|
|
AuctionResultTarget pendingAuctionTarget_ = AuctionResultTarget::BROWSE;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// Vendor
|
|
|
|
|
|
bool vendorWindowOpen = false;
|
|
|
|
|
|
ListInventoryData currentVendorItems;
|
2026-02-19 05:28:13 -08:00
|
|
|
|
std::deque<BuybackItem> buybackItems_;
|
|
|
|
|
|
std::unordered_map<uint64_t, BuybackItem> pendingSellToBuyback_;
|
|
|
|
|
|
int pendingBuybackSlot_ = -1;
|
2026-02-19 05:48:40 -08:00
|
|
|
|
uint32_t pendingBuybackWireSlot_ = 0;
|
2026-02-19 05:28:13 -08:00
|
|
|
|
uint32_t pendingBuyItemId_ = 0;
|
|
|
|
|
|
uint32_t pendingBuyItemSlot_ = 0;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-02-08 14:33:39 -08:00
|
|
|
|
// Trainer
|
|
|
|
|
|
bool trainerWindowOpen_ = false;
|
|
|
|
|
|
TrainerListData currentTrainerList_;
|
2026-03-12 13:08:41 -07:00
|
|
|
|
struct SpellNameEntry { std::string name; std::string rank; std::string description; uint32_t schoolMask = 0; uint8_t dispelType = 0; };
|
2026-02-08 14:33:39 -08:00
|
|
|
|
std::unordered_map<uint32_t, SpellNameEntry> spellNameCache_;
|
|
|
|
|
|
bool spellNameCacheLoaded_ = false;
|
2026-03-09 19:34:33 -07:00
|
|
|
|
|
2026-03-12 19:05:54 -07:00
|
|
|
|
// Title cache: maps titleBit → title string (lazy-loaded from CharTitles.dbc)
|
|
|
|
|
|
// The strings use "%s" as a player-name placeholder (e.g. "Commander %s", "%s the Explorer").
|
|
|
|
|
|
std::unordered_map<uint32_t, std::string> titleNameCache_;
|
|
|
|
|
|
bool titleNameCacheLoaded_ = false;
|
|
|
|
|
|
void loadTitleNameCache();
|
2026-03-12 20:23:36 -07:00
|
|
|
|
// Set of title bit-indices known to the player (from SMSG_TITLE_EARNED).
|
|
|
|
|
|
std::unordered_set<uint32_t> knownTitleBits_;
|
|
|
|
|
|
// Currently selected title bit, or -1 for no title. Updated from PLAYER_CHOSEN_TITLE.
|
|
|
|
|
|
int32_t chosenTitleBit_ = -1;
|
2026-03-12 19:05:54 -07:00
|
|
|
|
|
2026-03-12 12:49:38 -07:00
|
|
|
|
// Achievement caches (lazy-loaded from Achievement.dbc on first earned event)
|
2026-03-09 19:34:33 -07:00
|
|
|
|
std::unordered_map<uint32_t, std::string> achievementNameCache_;
|
2026-03-12 12:49:38 -07:00
|
|
|
|
std::unordered_map<uint32_t, std::string> achievementDescCache_;
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> achievementPointsCache_;
|
2026-03-09 19:34:33 -07:00
|
|
|
|
bool achievementNameCacheLoaded_ = false;
|
|
|
|
|
|
void loadAchievementNameCache();
|
2026-03-10 20:53:21 -07:00
|
|
|
|
// Set of achievement IDs earned by the player (populated from SMSG_ALL_ACHIEVEMENT_DATA)
|
|
|
|
|
|
std::unordered_set<uint32_t> earnedAchievements_;
|
2026-03-12 07:22:36 -07:00
|
|
|
|
// Earn dates: achievementId → WoW PackedTime (from SMSG_ACHIEVEMENT_EARNED / SMSG_ALL_ACHIEVEMENT_DATA)
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> achievementDates_;
|
2026-03-12 03:03:02 -07:00
|
|
|
|
// Criteria progress: criteriaId → current value (from SMSG_CRITERIA_UPDATE)
|
|
|
|
|
|
std::unordered_map<uint32_t, uint64_t> criteriaProgress_;
|
2026-03-10 20:53:21 -07:00
|
|
|
|
void handleAllAchievementData(network::Packet& packet);
|
2026-03-10 08:06:21 -07:00
|
|
|
|
|
2026-03-12 23:23:02 -07:00
|
|
|
|
// Per-player achievement data from SMSG_RESPOND_INSPECT_ACHIEVEMENTS
|
|
|
|
|
|
// Key: inspected player's GUID; value: set of earned achievement IDs
|
|
|
|
|
|
std::unordered_map<uint64_t, std::unordered_set<uint32_t>> inspectedPlayerAchievements_;
|
|
|
|
|
|
void handleRespondInspectAchievements(network::Packet& packet);
|
|
|
|
|
|
|
2026-03-10 08:06:21 -07:00
|
|
|
|
// Area name cache (lazy-loaded from WorldMapArea.dbc; maps AreaTable ID → display name)
|
|
|
|
|
|
std::unordered_map<uint32_t, std::string> areaNameCache_;
|
|
|
|
|
|
bool areaNameCacheLoaded_ = false;
|
|
|
|
|
|
void loadAreaNameCache();
|
|
|
|
|
|
std::string getAreaName(uint32_t areaId) const;
|
2026-03-13 07:10:10 -07:00
|
|
|
|
|
|
|
|
|
|
// Map name cache (lazy-loaded from Map.dbc; maps mapId → localized display name)
|
|
|
|
|
|
std::unordered_map<uint32_t, std::string> mapNameCache_;
|
|
|
|
|
|
bool mapNameCacheLoaded_ = false;
|
|
|
|
|
|
void loadMapNameCache();
|
|
|
|
|
|
std::string getMapName(uint32_t mapId) const;
|
2026-02-08 14:46:01 -08:00
|
|
|
|
std::vector<TrainerTab> trainerTabs_;
|
2026-02-08 14:33:39 -08:00
|
|
|
|
void handleTrainerList(network::Packet& packet);
|
|
|
|
|
|
void loadSpellNameCache();
|
2026-02-08 14:46:01 -08:00
|
|
|
|
void categorizeTrainerSpells();
|
2026-02-08 14:33:39 -08:00
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Callbacks
|
|
|
|
|
|
WorldConnectSuccessCallback onSuccess;
|
|
|
|
|
|
WorldConnectFailureCallback onFailure;
|
2026-02-05 14:13:48 -08:00
|
|
|
|
CharCreateCallback charCreateCallback_;
|
2026-02-06 03:24:46 -08:00
|
|
|
|
CharDeleteCallback charDeleteCallback_;
|
2026-02-17 13:59:29 -08:00
|
|
|
|
CharLoginFailCallback charLoginFailCallback_;
|
2026-02-06 18:34:45 -08:00
|
|
|
|
uint8_t lastCharDeleteResult_ = 0xFF;
|
2026-02-05 14:18:41 -08:00
|
|
|
|
bool pendingCharCreateResult_ = false;
|
|
|
|
|
|
bool pendingCharCreateSuccess_ = false;
|
|
|
|
|
|
std::string pendingCharCreateMsg_;
|
2026-02-12 02:09:15 -08:00
|
|
|
|
bool requiresWarden_ = false;
|
|
|
|
|
|
bool wardenGateSeen_ = false;
|
|
|
|
|
|
float wardenGateElapsed_ = 0.0f;
|
|
|
|
|
|
float wardenGateNextStatusLog_ = 2.0f;
|
|
|
|
|
|
uint32_t wardenPacketsAfterGate_ = 0;
|
|
|
|
|
|
bool wardenCharEnumBlockedLogged_ = false;
|
|
|
|
|
|
std::unique_ptr<WardenCrypto> wardenCrypto_;
|
2026-02-14 02:00:15 -08:00
|
|
|
|
std::unique_ptr<WardenMemory> wardenMemory_;
|
2026-02-12 02:43:20 -08:00
|
|
|
|
std::unique_ptr<WardenModuleManager> wardenModuleManager_;
|
2026-02-05 12:01:03 -08:00
|
|
|
|
|
2026-02-13 16:53:28 -08:00
|
|
|
|
// Warden module download state
|
|
|
|
|
|
enum class WardenState {
|
|
|
|
|
|
WAIT_MODULE_USE, // Waiting for first SMSG (MODULE_USE)
|
|
|
|
|
|
WAIT_MODULE_CACHE, // Sent MODULE_MISSING, receiving module chunks
|
|
|
|
|
|
WAIT_HASH_REQUEST, // Module received, waiting for HASH_REQUEST
|
|
|
|
|
|
WAIT_CHECKS, // Hash sent, waiting for check requests
|
|
|
|
|
|
};
|
|
|
|
|
|
WardenState wardenState_ = WardenState::WAIT_MODULE_USE;
|
|
|
|
|
|
std::vector<uint8_t> wardenModuleHash_; // 16 bytes MD5
|
|
|
|
|
|
std::vector<uint8_t> wardenModuleKey_; // 16 bytes RC4
|
|
|
|
|
|
uint32_t wardenModuleSize_ = 0;
|
|
|
|
|
|
std::vector<uint8_t> wardenModuleData_; // Downloaded module chunks
|
2026-02-13 18:59:09 -08:00
|
|
|
|
std::vector<uint8_t> wardenLoadedModuleImage_; // Parsed module image for key derivation
|
2026-02-14 19:20:32 -08:00
|
|
|
|
std::shared_ptr<WardenModule> wardenLoadedModule_; // Loaded Warden module
|
2026-02-13 18:59:09 -08:00
|
|
|
|
|
|
|
|
|
|
// Pre-computed challenge/response entries from .cr file
|
|
|
|
|
|
struct WardenCREntry {
|
|
|
|
|
|
uint8_t seed[16];
|
|
|
|
|
|
uint8_t reply[20];
|
|
|
|
|
|
uint8_t clientKey[16]; // Encrypt key (client→server)
|
|
|
|
|
|
uint8_t serverKey[16]; // Decrypt key (server→client)
|
|
|
|
|
|
};
|
|
|
|
|
|
std::vector<WardenCREntry> wardenCREntries_;
|
|
|
|
|
|
// Module-specific check type opcodes [9]: MEM, PAGE_A, PAGE_B, MPQ, LUA, DRIVER, TIMING, PROC, MODULE
|
|
|
|
|
|
uint8_t wardenCheckOpcodes_[9] = {};
|
|
|
|
|
|
bool loadWardenCRFile(const std::string& moduleHashHex);
|
2026-02-13 16:53:28 -08:00
|
|
|
|
|
2026-02-05 12:07:58 -08:00
|
|
|
|
// ---- XP tracking ----
|
|
|
|
|
|
uint32_t playerXp_ = 0;
|
|
|
|
|
|
uint32_t playerNextLevelXp_ = 0;
|
2026-03-10 07:35:30 -07:00
|
|
|
|
uint32_t playerRestedXp_ = 0;
|
|
|
|
|
|
bool isResting_ = false;
|
2026-02-05 12:07:58 -08:00
|
|
|
|
uint32_t serverPlayerLevel_ = 1;
|
|
|
|
|
|
static uint32_t xpForLevel(uint32_t level);
|
|
|
|
|
|
|
2026-02-10 19:30:45 -08:00
|
|
|
|
// ---- Server time tracking (for deterministic celestial/sky systems) ----
|
|
|
|
|
|
float gameTime_ = 0.0f; // Server game time in seconds
|
|
|
|
|
|
float timeSpeed_ = 0.0166f; // Time scale (default: 1 game day = 1 real hour)
|
|
|
|
|
|
void handleLoginSetTimeSpeed(network::Packet& packet);
|
|
|
|
|
|
|
2026-03-12 05:38:13 -07:00
|
|
|
|
// ---- Global Cooldown (GCD) ----
|
|
|
|
|
|
float gcdTotal_ = 0.0f;
|
|
|
|
|
|
std::chrono::steady_clock::time_point gcdStartedAt_{};
|
|
|
|
|
|
|
2026-02-17 17:59:41 -08:00
|
|
|
|
// ---- 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
|
|
|
|
|
|
|
2026-03-09 14:57:46 -07:00
|
|
|
|
// ---- Light override (SMSG_OVERRIDE_LIGHT) ----
|
|
|
|
|
|
uint32_t overrideLightId_ = 0; // 0 = no override
|
|
|
|
|
|
uint32_t overrideLightTransMs_ = 0;
|
|
|
|
|
|
|
2026-02-07 14:21:50 -08:00
|
|
|
|
// ---- Player skills ----
|
|
|
|
|
|
std::map<uint32_t, PlayerSkill> playerSkills_;
|
|
|
|
|
|
std::unordered_map<uint32_t, std::string> skillLineNames_;
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> skillLineCategories_;
|
2026-02-08 14:46:01 -08:00
|
|
|
|
std::unordered_map<uint32_t, uint32_t> spellToSkillLine_; // spellID -> skillLineID
|
2026-02-07 14:21:50 -08:00
|
|
|
|
bool skillLineDbcLoaded_ = false;
|
2026-02-08 14:46:01 -08:00
|
|
|
|
bool skillLineAbilityLoaded_ = false;
|
2026-02-11 18:25:04 -08:00
|
|
|
|
static constexpr size_t PLAYER_EXPLORED_ZONES_COUNT = 128;
|
|
|
|
|
|
std::vector<uint32_t> playerExploredZones_ =
|
|
|
|
|
|
std::vector<uint32_t>(PLAYER_EXPLORED_ZONES_COUNT, 0u);
|
|
|
|
|
|
bool hasPlayerExploredZones_ = false;
|
2026-02-07 14:21:50 -08:00
|
|
|
|
void loadSkillLineDbc();
|
2026-02-08 14:46:01 -08:00
|
|
|
|
void loadSkillLineAbilityDbc();
|
2026-02-07 14:21:50 -08:00
|
|
|
|
void extractSkillFields(const std::map<uint16_t, uint32_t>& fields);
|
2026-02-11 18:25:04 -08:00
|
|
|
|
void extractExploredZoneFields(const std::map<uint16_t, uint32_t>& fields);
|
2026-03-10 23:33:38 -07:00
|
|
|
|
void applyQuestStateFromFields(const std::map<uint16_t, uint32_t>& fields);
|
2026-03-10 23:52:18 -07:00
|
|
|
|
// Apply packed kill counts from player update fields to a quest entry that has
|
|
|
|
|
|
// already had its killObjectives populated from SMSG_QUEST_QUERY_RESPONSE.
|
|
|
|
|
|
void applyPackedKillCountsFromFields(QuestLogEntry& quest);
|
2026-02-07 14:21:50 -08:00
|
|
|
|
|
2026-02-07 00:00:06 -08:00
|
|
|
|
NpcDeathCallback npcDeathCallback_;
|
Add comprehensive NPC voice system with interaction and combat sounds
Implements full NPC voice interaction system supporting 6 different sound categories
for all playable races/genders. System loads ~450+ voice clips from MPQ archives.
Voice Categories:
- Greeting: Play on NPC right-click interaction
- Farewell: Play when closing gossip/dialog windows
- Vendor: Play when opening merchant/vendor windows
- Pissed: Play after clicking NPC 5+ times (spam protection)
- Aggro: Play when NPC enters combat with player
- Flee: Play when NPC is fleeing (ready for low-health triggers)
Features:
- Race/gender detection from NPC display IDs via CreatureDisplayInfoExtra.dbc
- Intelligent click tracking for pissed sounds
- Combat sounds use player character vocal files for humanoid NPCs
- Cooldown system prevents voice spam (2s default, combat sounds bypass)
- Generic fallback voices for unsupported NPC types
- 3D positional audio support
Voice Support:
- All playable races: Human, Dwarf, Gnome, Night Elf, Orc, Tauren, Troll, Undead
- Male and female variants for each race
- StandardNPC sounds for social interactions
- Character vocal sounds for combat
Technical Changes:
- Refactored NpcVoiceManager to support multiple sound categories
- Added callbacks: NpcFarewell, NpcVendor, NpcAggro
- Extended voice loading to parse both StandardNPC and Character vocal paths
- Integrated with GameHandler for gossip, vendor, and combat events
- Added detailed voice detection logging for debugging
Also includes:
- Sound manifest files added to docs/ for reference
- Blacksmith hammer pitch increased to 1.6x (was 1.4x)
- Blacksmith volume reduced 30% to 0.25 (was 0.35)
2026-02-09 16:03:51 -08:00
|
|
|
|
NpcAggroCallback npcAggroCallback_;
|
2026-02-07 00:00:06 -08:00
|
|
|
|
NpcRespawnCallback npcRespawnCallback_;
|
2026-03-10 09:46:46 -07:00
|
|
|
|
StandStateCallback standStateCallback_;
|
2026-03-10 09:57:24 -07:00
|
|
|
|
GhostStateCallback ghostStateCallback_;
|
2026-02-05 14:01:26 -08:00
|
|
|
|
MeleeSwingCallback meleeSwingCallback_;
|
2026-03-12 20:05:36 -07:00
|
|
|
|
uint64_t lastMeleeSwingMs_ = 0; // system_clock ms at last player auto-attack swing
|
2026-03-10 09:42:17 -07:00
|
|
|
|
SpellCastAnimCallback spellCastAnimCallback_;
|
2026-03-10 10:30:50 -07:00
|
|
|
|
UnitAnimHintCallback unitAnimHintCallback_;
|
2026-03-10 10:55:23 -07:00
|
|
|
|
UnitMoveFlagsCallback unitMoveFlagsCallback_;
|
2026-02-07 00:00:06 -08:00
|
|
|
|
NpcSwingCallback npcSwingCallback_;
|
2026-02-09 01:29:44 -08:00
|
|
|
|
NpcGreetingCallback npcGreetingCallback_;
|
Add comprehensive NPC voice system with interaction and combat sounds
Implements full NPC voice interaction system supporting 6 different sound categories
for all playable races/genders. System loads ~450+ voice clips from MPQ archives.
Voice Categories:
- Greeting: Play on NPC right-click interaction
- Farewell: Play when closing gossip/dialog windows
- Vendor: Play when opening merchant/vendor windows
- Pissed: Play after clicking NPC 5+ times (spam protection)
- Aggro: Play when NPC enters combat with player
- Flee: Play when NPC is fleeing (ready for low-health triggers)
Features:
- Race/gender detection from NPC display IDs via CreatureDisplayInfoExtra.dbc
- Intelligent click tracking for pissed sounds
- Combat sounds use player character vocal files for humanoid NPCs
- Cooldown system prevents voice spam (2s default, combat sounds bypass)
- Generic fallback voices for unsupported NPC types
- 3D positional audio support
Voice Support:
- All playable races: Human, Dwarf, Gnome, Night Elf, Orc, Tauren, Troll, Undead
- Male and female variants for each race
- StandardNPC sounds for social interactions
- Character vocal sounds for combat
Technical Changes:
- Refactored NpcVoiceManager to support multiple sound categories
- Added callbacks: NpcFarewell, NpcVendor, NpcAggro
- Extended voice loading to parse both StandardNPC and Character vocal paths
- Integrated with GameHandler for gossip, vendor, and combat events
- Added detailed voice detection logging for debugging
Also includes:
- Sound manifest files added to docs/ for reference
- Blacksmith hammer pitch increased to 1.6x (was 1.4x)
- Blacksmith volume reduced 30% to 0.25 (was 0.35)
2026-02-09 16:03:51 -08:00
|
|
|
|
NpcFarewellCallback npcFarewellCallback_;
|
|
|
|
|
|
NpcVendorCallback npcVendorCallback_;
|
2026-02-19 21:13:13 -08:00
|
|
|
|
ChargeCallback chargeCallback_;
|
2026-02-17 17:23:42 -08:00
|
|
|
|
LevelUpCallback levelUpCallback_;
|
2026-03-12 17:54:49 -07:00
|
|
|
|
LevelUpDeltas lastLevelUpDeltas_;
|
2026-03-12 18:15:51 -07:00
|
|
|
|
std::vector<TempEnchantTimer> tempEnchantTimers_;
|
2026-03-12 18:21:50 -07:00
|
|
|
|
std::vector<BookPage> bookPages_; // pages collected for the current readable item
|
2026-02-19 20:36:25 -08:00
|
|
|
|
OtherPlayerLevelUpCallback otherPlayerLevelUpCallback_;
|
2026-03-09 13:53:42 -07:00
|
|
|
|
AchievementEarnedCallback achievementEarnedCallback_;
|
2026-03-12 15:42:55 -07:00
|
|
|
|
AreaDiscoveryCallback areaDiscoveryCallback_;
|
2026-03-12 15:57:09 -07:00
|
|
|
|
QuestProgressCallback questProgressCallback_;
|
2026-02-07 17:59:40 -08:00
|
|
|
|
MountCallback mountCallback_;
|
2026-02-08 21:32:38 -08:00
|
|
|
|
TaxiPrecacheCallback taxiPrecacheCallback_;
|
2026-02-08 22:00:33 -08:00
|
|
|
|
TaxiOrientationCallback taxiOrientationCallback_;
|
|
|
|
|
|
TaxiFlightStartCallback taxiFlightStartCallback_;
|
2026-03-12 21:24:42 -07:00
|
|
|
|
OpenLfgCallback openLfgCallback_;
|
2026-02-07 17:59:40 -08:00
|
|
|
|
uint32_t currentMountDisplayId_ = 0;
|
2026-02-14 16:42:47 -08:00
|
|
|
|
uint32_t mountAuraSpellId_ = 0; // Spell ID of the aura that caused mounting (for CMSG_CANCEL_AURA fallback)
|
2026-02-07 17:59:40 -08:00
|
|
|
|
float serverRunSpeed_ = 7.0f;
|
Add missing movement ACK responses to avoid server stalls
Implement generic handlers for force speed changes (walk, run back,
swim, swim back, flight, flight back, turn rate, pitch rate),
movement flag toggles (CAN_FLY, HOVER, feather fall, water walk),
and knockback ACKs. Fix SMSG_TIME_SYNC_REQ to respond with
CMSG_TIME_SYNC_RESP instead of silently dropping.
2026-02-26 03:02:51 -08:00
|
|
|
|
float serverWalkSpeed_ = 2.5f;
|
|
|
|
|
|
float serverRunBackSpeed_ = 4.5f;
|
|
|
|
|
|
float serverSwimSpeed_ = 4.722f;
|
|
|
|
|
|
float serverSwimBackSpeed_ = 2.5f;
|
|
|
|
|
|
float serverFlightSpeed_ = 7.0f;
|
|
|
|
|
|
float serverFlightBackSpeed_ = 4.5f;
|
|
|
|
|
|
float serverTurnRate_ = 3.14159f;
|
|
|
|
|
|
float serverPitchRate_ = 3.14159f;
|
2026-02-06 17:27:20 -08:00
|
|
|
|
bool playerDead_ = false;
|
2026-02-07 23:12:24 -08:00
|
|
|
|
bool releasedSpirit_ = false;
|
2026-03-09 22:31:56 -07:00
|
|
|
|
uint32_t corpseMapId_ = 0;
|
|
|
|
|
|
float corpseX_ = 0.0f, corpseY_ = 0.0f, corpseZ_ = 0.0f;
|
2026-03-09 18:28:03 -07:00
|
|
|
|
// Death Knight runes (class 6): slots 0-1=Blood, 2-3=Unholy, 4-5=Frost initially
|
|
|
|
|
|
std::array<RuneSlot, 6> playerRunes_ = [] {
|
|
|
|
|
|
std::array<RuneSlot, 6> r{};
|
|
|
|
|
|
r[0].type = r[1].type = RuneType::Blood;
|
|
|
|
|
|
r[2].type = r[3].type = RuneType::Unholy;
|
|
|
|
|
|
r[4].type = r[5].type = RuneType::Frost;
|
|
|
|
|
|
return r;
|
|
|
|
|
|
}();
|
2026-02-07 21:47:14 -08:00
|
|
|
|
uint64_t pendingSpiritHealerGuid_ = 0;
|
|
|
|
|
|
bool resurrectPending_ = false;
|
2026-02-07 23:12:24 -08:00
|
|
|
|
bool resurrectRequestPending_ = false;
|
2026-03-10 12:53:05 -07:00
|
|
|
|
// ---- Talent wipe confirm dialog ----
|
|
|
|
|
|
bool talentWipePending_ = false;
|
|
|
|
|
|
uint64_t talentWipeNpcGuid_ = 0;
|
|
|
|
|
|
uint32_t talentWipeCost_ = 0;
|
2026-03-09 22:27:24 -07:00
|
|
|
|
bool resurrectIsSpiritHealer_ = false; // true = SMSG_SPIRIT_HEALER_CONFIRM, false = SMSG_RESURRECT_REQUEST
|
2026-02-07 23:12:24 -08:00
|
|
|
|
uint64_t resurrectCasterGuid_ = 0;
|
2026-03-09 22:27:24 -07:00
|
|
|
|
std::string resurrectCasterName_;
|
2026-02-07 21:47:14 -08:00
|
|
|
|
bool repopPending_ = false;
|
|
|
|
|
|
uint64_t lastRepopRequestMs_ = 0;
|
2026-03-09 15:23:02 -07:00
|
|
|
|
|
2026-03-09 15:27:20 -07:00
|
|
|
|
// ---- Completed quest IDs (SMSG_QUERY_QUESTS_COMPLETED_RESPONSE) ----
|
|
|
|
|
|
std::unordered_set<uint32_t> completedQuests_;
|
|
|
|
|
|
|
2026-03-09 15:23:02 -07:00
|
|
|
|
// ---- Equipment sets (SMSG_EQUIPMENT_SET_LIST) ----
|
|
|
|
|
|
struct EquipmentSet {
|
|
|
|
|
|
uint64_t setGuid = 0;
|
|
|
|
|
|
uint32_t setId = 0;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
std::string iconName;
|
|
|
|
|
|
uint32_t ignoreSlotMask = 0;
|
|
|
|
|
|
std::array<uint64_t, 19> itemGuids{};
|
|
|
|
|
|
};
|
|
|
|
|
|
std::vector<EquipmentSet> equipmentSets_;
|
2026-03-12 17:48:08 -07:00
|
|
|
|
std::vector<EquipmentSetInfo> equipmentSetInfo_; // public-facing copy
|
2026-03-09 15:23:02 -07:00
|
|
|
|
|
|
|
|
|
|
// ---- Forced faction reactions (SMSG_SET_FORCED_REACTIONS) ----
|
|
|
|
|
|
std::unordered_map<uint32_t, uint8_t> forcedReactions_; // factionId -> reaction tier
|
2026-03-09 15:46:19 -07:00
|
|
|
|
|
2026-03-09 16:11:19 -07:00
|
|
|
|
// ---- Server-triggered audio ----
|
2026-03-09 15:46:19 -07:00
|
|
|
|
PlayMusicCallback playMusicCallback_;
|
2026-03-09 16:11:19 -07:00
|
|
|
|
PlaySoundCallback playSoundCallback_;
|
2026-03-09 16:16:39 -07:00
|
|
|
|
PlayPositionalSoundCallback playPositionalSoundCallback_;
|
2026-03-12 01:15:11 -07:00
|
|
|
|
|
|
|
|
|
|
// ---- UI error frame callback ----
|
|
|
|
|
|
UIErrorCallback uiErrorCallback_;
|
2026-03-12 01:51:18 -07:00
|
|
|
|
|
|
|
|
|
|
// ---- Reputation change callback ----
|
|
|
|
|
|
RepChangeCallback repChangeCallback_;
|
2026-03-12 05:03:03 -07:00
|
|
|
|
uint32_t watchedFactionId_ = 0; // auto-set to most recently changed faction
|
2026-03-12 04:53:03 -07:00
|
|
|
|
|
2026-03-12 16:19:25 -07:00
|
|
|
|
// ---- PvP honor credit callback ----
|
|
|
|
|
|
PvpHonorCallback pvpHonorCallback_;
|
|
|
|
|
|
|
2026-03-12 16:24:11 -07:00
|
|
|
|
// ---- Item loot callback ----
|
|
|
|
|
|
ItemLootCallback itemLootCallback_;
|
|
|
|
|
|
|
2026-03-12 04:53:03 -07:00
|
|
|
|
// ---- Quest completion callback ----
|
|
|
|
|
|
QuestCompleteCallback questCompleteCallback_;
|
feat: parse SMSG_GMTICKET_GETTICKET/SYSTEMSTATUS and SMSG_SPELLINSTAKILLLOG
Previously SMSG_GMTICKET_GETTICKET and SMSG_GMTICKET_SYSTEMSTATUS were
silently consumed. Now both are fully parsed:
- SMSG_GMTICKET_GETTICKET decodes all four status codes (no ticket,
open ticket, closed, suspended), extracts ticket text, age and
server-estimated wait time, and stores them on GameHandler.
- SMSG_GMTICKET_SYSTEMSTATUS shows a chat message when GM support
goes offline/online.
- Added requestGmTicket() (sends CMSG_GMTICKET_GETTICKET) called
automatically when the GM Ticket UI window is opened, so the player
sees their existing open ticket text and wait time on first open.
- GM Ticket UI window now shows current-ticket status bar, estimated
wait time, and hides the Delete button when no ticket is active.
Also implements SMSG_SPELLINSTAKILLLOG (previously silently consumed):
parses caster/victim/spellId for all expansions and emits combat text
when the local player is involved in an instant-kill spell event (e.g.
Execute, Obliterate).
2026-03-12 22:14:46 -07:00
|
|
|
|
|
|
|
|
|
|
// ---- GM Ticket state (SMSG_GMTICKET_GETTICKET / SMSG_GMTICKET_SYSTEMSTATUS) ----
|
|
|
|
|
|
bool gmTicketActive_ = false; ///< True when an open ticket exists on the server
|
|
|
|
|
|
std::string gmTicketText_; ///< Text of the open ticket (from SMSG_GMTICKET_GETTICKET)
|
|
|
|
|
|
float gmTicketWaitHours_ = 0.0f; ///< Server-estimated wait time in hours
|
|
|
|
|
|
bool gmSupportAvailable_ = true; ///< GM support system online (SMSG_GMTICKET_SYSTEMSTATUS)
|
2026-03-12 22:25:46 -07:00
|
|
|
|
|
|
|
|
|
|
// ---- Battlefield Manager state (WotLK Wintergrasp / outdoor battlefields) ----
|
|
|
|
|
|
bool bfMgrInvitePending_ = false; ///< True when an entry/queue invite is pending acceptance
|
|
|
|
|
|
bool bfMgrActive_ = false; ///< True while the player is inside an outdoor battlefield
|
|
|
|
|
|
uint32_t bfMgrZoneId_ = 0; ///< Zone ID of the pending/active battlefield
|
|
|
|
|
|
|
|
|
|
|
|
// ---- WotLK Calendar: pending invite counter ----
|
|
|
|
|
|
uint32_t calendarPendingInvites_ = 0; ///< Unacknowledged calendar invites (SMSG_CALENDAR_SEND_NUM_PENDING)
|
2026-03-12 23:59:38 -07:00
|
|
|
|
|
|
|
|
|
|
// ---- Spell modifiers (SMSG_SET_FLAT_SPELL_MODIFIER / SMSG_SET_PCT_SPELL_MODIFIER) ----
|
|
|
|
|
|
// Keyed by (SpellModOp, groupIndex); cleared on logout/character change.
|
|
|
|
|
|
std::unordered_map<SpellModKey, int32_t, SpellModKeyHash> spellFlatMods_;
|
|
|
|
|
|
std::unordered_map<SpellModKey, int32_t, SpellModKeyHash> spellPctMods_;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace game
|
|
|
|
|
|
} // namespace wowee
|