mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00: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
This commit is contained in:
parent
81b95b4af7
commit
284b98d93a
6 changed files with 285 additions and 0 deletions
|
|
@ -634,6 +634,24 @@ public:
|
|||
void sendPetAction(uint32_t action, uint64_t targetGuid = 0);
|
||||
const std::unordered_set<uint32_t>& getKnownSpells() const { return knownSpells; }
|
||||
|
||||
// ---- 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)
|
||||
|
||||
// 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)
|
||||
|
|
@ -2390,6 +2408,13 @@ private:
|
|||
std::vector<uint32_t> petSpellList_; // known pet spells
|
||||
std::unordered_set<uint32_t> petAutocastSpells_; // spells with autocast on
|
||||
|
||||
// ---- Pet Stable ----
|
||||
bool stableWindowOpen_ = false;
|
||||
uint64_t stableMasterGuid_ = 0;
|
||||
uint8_t stableNumSlots_ = 0;
|
||||
std::vector<StabledPet> stabledPets_;
|
||||
void handleListStabledPets(network::Packet& packet);
|
||||
|
||||
// ---- Battleground queue state ----
|
||||
std::array<BgQueueSlot, 3> bgQueues_{};
|
||||
|
||||
|
|
|
|||
|
|
@ -2699,5 +2699,24 @@ public:
|
|||
static bool parse(network::Packet& packet, AuctionCommandResult& data);
|
||||
};
|
||||
|
||||
/** Pet Stable packet builders */
|
||||
class ListStabledPetsPacket {
|
||||
public:
|
||||
/** MSG_LIST_STABLED_PETS (CMSG): request list from stable master */
|
||||
static network::Packet build(uint64_t stableMasterGuid);
|
||||
};
|
||||
|
||||
class StablePetPacket {
|
||||
public:
|
||||
/** CMSG_STABLE_PET: store active pet in the given stable slot (1-based) */
|
||||
static network::Packet build(uint64_t stableMasterGuid, uint8_t slot);
|
||||
};
|
||||
|
||||
class UnstablePetPacket {
|
||||
public:
|
||||
/** CMSG_UNSTABLE_PET: retrieve a stabled pet by its server-side petNumber */
|
||||
static network::Packet build(uint64_t stableMasterGuid, uint32_t petNumber);
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@ private:
|
|||
void renderQuestOfferRewardWindow(game::GameHandler& gameHandler);
|
||||
void renderVendorWindow(game::GameHandler& gameHandler);
|
||||
void renderTrainerWindow(game::GameHandler& gameHandler);
|
||||
void renderStableWindow(game::GameHandler& gameHandler);
|
||||
void renderTaxiWindow(game::GameHandler& gameHandler);
|
||||
void renderDeathScreen(game::GameHandler& gameHandler);
|
||||
void renderReclaimCorpseButton(game::GameHandler& gameHandler);
|
||||
|
|
|
|||
|
|
@ -2070,6 +2070,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
break;
|
||||
}
|
||||
|
||||
// ---- Pet stable list ----
|
||||
case Opcode::MSG_LIST_STABLED_PETS:
|
||||
if (state == WorldState::IN_WORLD) handleListStabledPets(packet);
|
||||
break;
|
||||
|
||||
// ---- Pet stable result ----
|
||||
case Opcode::SMSG_STABLE_RESULT: {
|
||||
// uint8 result
|
||||
|
|
@ -2086,6 +2091,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
if (msg) addSystemChatMessage(msg);
|
||||
LOG_INFO("SMSG_STABLE_RESULT: result=", static_cast<int>(result));
|
||||
// Refresh the stable list after a result to reflect the new state
|
||||
if (stableWindowOpen_ && stableMasterGuid_ != 0 && socket && result <= 0x08) {
|
||||
auto refreshPkt = ListStabledPetsPacket::build(stableMasterGuid_);
|
||||
socket->send(refreshPkt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -6916,6 +6926,10 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
|
|||
unitAurasCache_.clear();
|
||||
unitCastStates_.clear();
|
||||
petGuid_ = 0;
|
||||
stableWindowOpen_ = false;
|
||||
stableMasterGuid_ = 0;
|
||||
stableNumSlots_ = 0;
|
||||
stabledPets_.clear();
|
||||
playerXp_ = 0;
|
||||
playerNextLevelXp_ = 0;
|
||||
serverPlayerLevel_ = 1;
|
||||
|
|
@ -14622,6 +14636,78 @@ void GameHandler::dismissPet() {
|
|||
socket->send(packet);
|
||||
}
|
||||
|
||||
void GameHandler::requestStabledPetList() {
|
||||
if (state != WorldState::IN_WORLD || !socket || stableMasterGuid_ == 0) return;
|
||||
auto pkt = ListStabledPetsPacket::build(stableMasterGuid_);
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Sent MSG_LIST_STABLED_PETS to npc=0x", std::hex, stableMasterGuid_, std::dec);
|
||||
}
|
||||
|
||||
void GameHandler::stablePet(uint8_t slot) {
|
||||
if (state != WorldState::IN_WORLD || !socket || stableMasterGuid_ == 0) return;
|
||||
if (petGuid_ == 0) {
|
||||
addSystemChatMessage("You do not have an active pet to stable.");
|
||||
return;
|
||||
}
|
||||
auto pkt = StablePetPacket::build(stableMasterGuid_, slot);
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Sent CMSG_STABLE_PET: slot=", static_cast<int>(slot));
|
||||
}
|
||||
|
||||
void GameHandler::unstablePet(uint32_t petNumber) {
|
||||
if (state != WorldState::IN_WORLD || !socket || stableMasterGuid_ == 0 || petNumber == 0) return;
|
||||
auto pkt = UnstablePetPacket::build(stableMasterGuid_, petNumber);
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Sent CMSG_UNSTABLE_PET: petNumber=", petNumber);
|
||||
}
|
||||
|
||||
void GameHandler::handleListStabledPets(network::Packet& packet) {
|
||||
// SMSG MSG_LIST_STABLED_PETS:
|
||||
// uint64 stableMasterGuid
|
||||
// uint8 petCount
|
||||
// uint8 numSlots
|
||||
// per pet:
|
||||
// uint32 petNumber
|
||||
// uint32 entry
|
||||
// uint32 level
|
||||
// string name (null-terminated)
|
||||
// uint32 displayId
|
||||
// uint8 isActive (1 = active/summoned, 0 = stabled)
|
||||
constexpr size_t kMinHeader = 8 + 1 + 1;
|
||||
if (packet.getSize() - packet.getReadPos() < kMinHeader) {
|
||||
LOG_WARNING("MSG_LIST_STABLED_PETS: packet too short (", packet.getSize(), ")");
|
||||
return;
|
||||
}
|
||||
stableMasterGuid_ = packet.readUInt64();
|
||||
uint8_t petCount = packet.readUInt8();
|
||||
stableNumSlots_ = packet.readUInt8();
|
||||
|
||||
stabledPets_.clear();
|
||||
stabledPets_.reserve(petCount);
|
||||
|
||||
for (uint8_t i = 0; i < petCount; ++i) {
|
||||
if (packet.getSize() - packet.getReadPos() < 4 + 4 + 4) break;
|
||||
StabledPet pet;
|
||||
pet.petNumber = packet.readUInt32();
|
||||
pet.entry = packet.readUInt32();
|
||||
pet.level = packet.readUInt32();
|
||||
pet.name = packet.readString();
|
||||
if (packet.getSize() - packet.getReadPos() < 4 + 1) break;
|
||||
pet.displayId = packet.readUInt32();
|
||||
pet.isActive = (packet.readUInt8() != 0);
|
||||
stabledPets_.push_back(std::move(pet));
|
||||
}
|
||||
|
||||
stableWindowOpen_ = true;
|
||||
LOG_INFO("MSG_LIST_STABLED_PETS: stableMasterGuid=0x", std::hex, stableMasterGuid_, std::dec,
|
||||
" petCount=", (int)petCount, " numSlots=", (int)stableNumSlots_);
|
||||
for (const auto& p : stabledPets_) {
|
||||
LOG_DEBUG(" Pet: number=", p.petNumber, " entry=", p.entry,
|
||||
" level=", p.level, " name='", p.name, "' displayId=", p.displayId,
|
||||
" active=", p.isActive);
|
||||
}
|
||||
}
|
||||
|
||||
void GameHandler::setActionBarSlot(int slot, ActionBarSlot::Type type, uint32_t id) {
|
||||
if (slot < 0 || slot >= ACTION_BAR_SLOTS) return;
|
||||
actionBar[slot].type = type;
|
||||
|
|
@ -15958,6 +16044,18 @@ void GameHandler::selectGossipOption(uint32_t optionId) {
|
|||
socket->send(bindPkt);
|
||||
LOG_INFO("Sent CMSG_BINDER_ACTIVATE for npc=0x", std::hex, currentGossip.npcGuid, std::dec);
|
||||
}
|
||||
|
||||
// Stable master detection: GOSSIP_OPTION_STABLE or text keywords
|
||||
if (text == "GOSSIP_OPTION_STABLE" ||
|
||||
textLower.find("stable") != std::string::npos ||
|
||||
textLower.find("my pet") != std::string::npos) {
|
||||
stableMasterGuid_ = currentGossip.npcGuid;
|
||||
stableWindowOpen_ = false; // will open when list arrives
|
||||
auto listPkt = ListStabledPetsPacket::build(currentGossip.npcGuid);
|
||||
socket->send(listPkt);
|
||||
LOG_INFO("Sent MSG_LIST_STABLED_PETS (gossip) to npc=0x",
|
||||
std::hex, currentGossip.npcGuid, std::dec);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5397,5 +5397,29 @@ bool AuctionCommandResultParser::parse(network::Packet& packet, AuctionCommandRe
|
|||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Pet Stable System
|
||||
// ============================================================
|
||||
|
||||
network::Packet ListStabledPetsPacket::build(uint64_t stableMasterGuid) {
|
||||
network::Packet p(wireOpcode(Opcode::MSG_LIST_STABLED_PETS));
|
||||
p.writeUInt64(stableMasterGuid);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet StablePetPacket::build(uint64_t stableMasterGuid, uint8_t slot) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_STABLE_PET));
|
||||
p.writeUInt64(stableMasterGuid);
|
||||
p.writeUInt8(slot);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet UnstablePetPacket::build(uint64_t stableMasterGuid, uint32_t petNumber) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_UNSTABLE_PET));
|
||||
p.writeUInt64(stableMasterGuid);
|
||||
p.writeUInt32(petNumber);
|
||||
return p;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -698,6 +698,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderQuestOfferRewardWindow(gameHandler);
|
||||
renderVendorWindow(gameHandler);
|
||||
renderTrainerWindow(gameHandler);
|
||||
renderStableWindow(gameHandler);
|
||||
renderTaxiWindow(gameHandler);
|
||||
renderMailWindow(gameHandler);
|
||||
renderMailComposeWindow(gameHandler);
|
||||
|
|
@ -13593,6 +13594,123 @@ void GameScreen::renderEscapeMenu() {
|
|||
ImGui::End();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Pet Stable Window
|
||||
// ============================================================
|
||||
|
||||
void GameScreen::renderStableWindow(game::GameHandler& gameHandler) {
|
||||
if (!gameHandler.isStableWindowOpen()) return;
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / 2.0f - 240.0f, screenH / 2.0f - 180.0f),
|
||||
ImGuiCond_Once);
|
||||
ImGui::SetNextWindowSize(ImVec2(480.0f, 360.0f), ImGuiCond_Once);
|
||||
|
||||
bool open = true;
|
||||
if (!ImGui::Begin("Pet Stable", &open,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) {
|
||||
ImGui::End();
|
||||
if (!open) {
|
||||
// User closed the window; clear stable state
|
||||
gameHandler.closeStableWindow();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& pets = gameHandler.getStabledPets();
|
||||
uint8_t numSlots = gameHandler.getStableSlots();
|
||||
|
||||
ImGui::TextDisabled("Stable slots: %u", static_cast<unsigned>(numSlots));
|
||||
ImGui::Separator();
|
||||
|
||||
// Active pets section
|
||||
bool hasActivePets = false;
|
||||
for (const auto& p : pets) {
|
||||
if (p.isActive) { hasActivePets = true; break; }
|
||||
}
|
||||
|
||||
if (hasActivePets) {
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.9f, 0.4f, 1.0f), "Active / Summoned");
|
||||
for (const auto& p : pets) {
|
||||
if (!p.isActive) continue;
|
||||
ImGui::PushID(static_cast<int>(p.petNumber) * -1 - 1);
|
||||
|
||||
const std::string displayName = p.name.empty()
|
||||
? ("Pet #" + std::to_string(p.petNumber))
|
||||
: p.name;
|
||||
ImGui::Text(" %s (Level %u)", displayName.c_str(), p.level);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("[Active]");
|
||||
|
||||
// Offer to stable the active pet if there are free slots
|
||||
uint8_t usedSlots = 0;
|
||||
for (const auto& sp : pets) { if (!sp.isActive) ++usedSlots; }
|
||||
if (usedSlots < numSlots) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Store in stable")) {
|
||||
// Slot 1 is first stable slot; server handles free slot assignment.
|
||||
gameHandler.stablePet(1);
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
// Stabled pets section
|
||||
ImGui::TextColored(ImVec4(0.9f, 0.8f, 0.4f, 1.0f), "Stabled Pets");
|
||||
|
||||
bool hasStabledPets = false;
|
||||
for (const auto& p : pets) {
|
||||
if (!p.isActive) { hasStabledPets = true; break; }
|
||||
}
|
||||
|
||||
if (!hasStabledPets) {
|
||||
ImGui::TextDisabled(" (No pets in stable)");
|
||||
} else {
|
||||
for (const auto& p : pets) {
|
||||
if (p.isActive) continue;
|
||||
ImGui::PushID(static_cast<int>(p.petNumber));
|
||||
|
||||
const std::string displayName = p.name.empty()
|
||||
? ("Pet #" + std::to_string(p.petNumber))
|
||||
: p.name;
|
||||
ImGui::Text(" %s (Level %u, Entry %u)",
|
||||
displayName.c_str(), p.level, p.entry);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Retrieve")) {
|
||||
gameHandler.unstablePet(p.petNumber);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
// Empty slots
|
||||
uint8_t usedStableSlots = 0;
|
||||
for (const auto& p : pets) { if (!p.isActive) ++usedStableSlots; }
|
||||
if (usedStableSlots < numSlots) {
|
||||
ImGui::TextDisabled(" %u empty slot(s) available",
|
||||
static_cast<unsigned>(numSlots - usedStableSlots));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Refresh")) {
|
||||
gameHandler.requestStabledPetList();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Close")) {
|
||||
gameHandler.closeStableWindow();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
if (!open) {
|
||||
gameHandler.closeStableWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Taxi Window
|
||||
// ============================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue