Tie opcode handlers into movement, quest log, and world state caches

This commit is contained in:
Kelsi 2026-02-18 23:30:38 -08:00
parent a1c16762af
commit 494a8b5acc
2 changed files with 101 additions and 12 deletions

View file

@ -707,6 +707,23 @@ public:
}; };
const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; } const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; }
void abandonQuest(uint32_t questId); void abandonQuest(uint32_t questId);
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_; }
struct FactionStandingInit {
uint8_t flags = 0;
int32_t standing = 0;
};
const std::vector<FactionStandingInit>& getInitialFactions() const { return initialFactions_; }
uint32_t getLastContactListMask() const { return lastContactListMask_; }
uint32_t getLastContactListCount() const { return lastContactListCount_; }
bool isServerMovementAllowed() const { return serverMovementAllowed_; }
// Quest giver status (! and ? markers) // Quest giver status (! and ? markers)
QuestGiverStatus getQuestGiverStatus(uint64_t guid) const { QuestGiverStatus getQuestGiverStatus(uint64_t guid) const {
@ -1185,6 +1202,7 @@ private:
// Movement // Movement
MovementInfo movementInfo; // Current player movement state MovementInfo movementInfo; // Current player movement state
uint32_t movementTime = 0; // Movement timestamp counter uint32_t movementTime = 0; // Movement timestamp counter
bool serverMovementAllowed_ = true;
// Inventory // Inventory
Inventory inventory; Inventory inventory;
@ -1232,6 +1250,14 @@ private:
// ---- Friend list cache ---- // ---- Friend list cache ----
std::unordered_map<std::string, uint64_t> friendsCache; // name -> guid std::unordered_map<std::string, uint64_t> friendsCache; // name -> guid
uint32_t lastContactListMask_ = 0;
uint32_t lastContactListCount_ = 0;
// ---- 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_;
// ---- Ignore list cache ---- // ---- Ignore list cache ----
std::unordered_map<std::string, uint64_t> ignoreCache; // name -> guid std::unordered_map<std::string, uint64_t> ignoreCache; // name -> guid

View file

@ -971,10 +971,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
// - Minimal/legacy keepalive-ish form observed on some servers: 1 byte. // - Minimal/legacy keepalive-ish form observed on some servers: 1 byte.
size_t remaining = packet.getSize() - packet.getReadPos(); size_t remaining = packet.getSize() - packet.getReadPos();
if (remaining >= 8) { if (remaining >= 8) {
/*uint32_t listMask =*/ packet.readUInt32(); lastContactListMask_ = packet.readUInt32();
/*uint32_t count =*/ packet.readUInt32(); lastContactListCount_ = packet.readUInt32();
} else if (remaining == 1) { } else if (remaining == 1) {
/*uint8_t marker =*/ packet.readUInt8(); /*uint8_t marker =*/ packet.readUInt8();
lastContactListMask_ = 0;
lastContactListCount_ = 0;
} else if (remaining > 0) { } else if (remaining > 0) {
// Unknown short variant: consume to keep stream aligned, no warning spam. // Unknown short variant: consume to keep stream aligned, no warning spam.
packet.setReadPos(packet.getSize()); packet.setReadPos(packet.getSize());
@ -1043,6 +1045,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
} }
uint8_t guidMask = packet.readUInt8(); uint8_t guidMask = packet.readUInt8();
size_t guidBytes = 0; size_t guidBytes = 0;
uint64_t controlGuid = 0;
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
if (guidMask & (1u << i)) ++guidBytes; if (guidMask & (1u << i)) ++guidBytes;
} }
@ -1051,10 +1054,33 @@ void GameHandler::handlePacket(network::Packet& packet) {
packet.setReadPos(packet.getSize()); packet.setReadPos(packet.getSize());
break; break;
} }
for (size_t i = 0; i < guidBytes; ++i) { for (int i = 0; i < 8; ++i) {
packet.readUInt8(); if (guidMask & (1u << i)) {
uint8_t b = packet.readUInt8();
controlGuid |= (static_cast<uint64_t>(b) << (i * 8));
}
}
bool allowMovement = (packet.readUInt8() != 0);
if (controlGuid == 0 || controlGuid == playerGuid) {
bool changed = (serverMovementAllowed_ != allowMovement);
serverMovementAllowed_ = allowMovement;
if (changed && !allowMovement) {
// Force-stop local movement immediately when server revokes control.
movementInfo.flags &= ~(static_cast<uint32_t>(MovementFlags::FORWARD) |
static_cast<uint32_t>(MovementFlags::BACKWARD) |
static_cast<uint32_t>(MovementFlags::STRAFE_LEFT) |
static_cast<uint32_t>(MovementFlags::STRAFE_RIGHT) |
static_cast<uint32_t>(MovementFlags::TURN_LEFT) |
static_cast<uint32_t>(MovementFlags::TURN_RIGHT));
sendMovement(Opcode::CMSG_MOVE_STOP);
sendMovement(Opcode::CMSG_MOVE_STOP_STRAFE);
sendMovement(Opcode::CMSG_MOVE_STOP_TURN);
sendMovement(Opcode::CMSG_MOVE_STOP_SWIM);
addSystemChatMessage("Movement disabled by server.");
} else if (changed && allowMovement) {
addSystemChatMessage("Movement re-enabled.");
}
} }
/*uint8_t allowMovement =*/ packet.readUInt8();
break; break;
} }
@ -1297,8 +1323,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
LOG_WARNING("SMSG_INIT_WORLD_STATES too short: ", packet.getSize(), " bytes"); LOG_WARNING("SMSG_INIT_WORLD_STATES too short: ", packet.getSize(), " bytes");
break; break;
} }
/*uint32_t mapId =*/ packet.readUInt32(); worldStateMapId_ = packet.readUInt32();
/*uint32_t zoneId =*/ packet.readUInt32(); worldStateZoneId_ = packet.readUInt32();
uint16_t count = packet.readUInt16(); uint16_t count = packet.readUInt16();
size_t needed = static_cast<size_t>(count) * 8; size_t needed = static_cast<size_t>(count) * 8;
if (packet.getSize() - packet.getReadPos() < needed) { if (packet.getSize() - packet.getReadPos() < needed) {
@ -1307,9 +1333,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
packet.setReadPos(packet.getSize()); packet.setReadPos(packet.getSize());
break; break;
} }
worldStates_.clear();
worldStates_.reserve(count);
for (uint16_t i = 0; i < count; ++i) { for (uint16_t i = 0; i < count; ++i) {
packet.readUInt32(); uint32_t key = packet.readUInt32();
packet.readUInt32(); uint32_t val = packet.readUInt32();
worldStates_[key] = val;
} }
break; break;
} }
@ -1327,9 +1356,13 @@ void GameHandler::handlePacket(network::Packet& packet) {
packet.setReadPos(packet.getSize()); packet.setReadPos(packet.getSize());
break; break;
} }
initialFactions_.clear();
initialFactions_.reserve(count);
for (uint32_t i = 0; i < count; ++i) { for (uint32_t i = 0; i < count; ++i) {
packet.readUInt8(); FactionStandingInit fs{};
packet.readUInt32(); fs.flags = packet.readUInt8();
fs.standing = static_cast<int32_t>(packet.readUInt32());
initialFactions_.push_back(fs);
} }
break; break;
} }
@ -1667,7 +1700,32 @@ void GameHandler::handlePacket(network::Packet& packet) {
LOG_WARNING("SMSG_QUEST_FORCE_REMOVE too short"); LOG_WARNING("SMSG_QUEST_FORCE_REMOVE too short");
break; break;
} }
/*uint32_t questId =*/ packet.readUInt32(); uint32_t questId = packet.readUInt32();
std::string removedTitle;
for (auto it = questLog_.begin(); it != questLog_.end(); ++it) {
if (it->questId == questId) {
removedTitle = it->title;
questLog_.erase(it);
break;
}
}
if (currentQuestDetails.questId == questId) {
questDetailsOpen = false;
currentQuestDetails = QuestDetailsData{};
}
if (currentQuestRequestItems_.questId == questId) {
questRequestItemsOpen_ = false;
currentQuestRequestItems_ = QuestRequestItemsData{};
}
if (currentQuestOfferReward_.questId == questId) {
questOfferRewardOpen_ = false;
currentQuestOfferReward_ = QuestOfferRewardData{};
}
if (!removedTitle.empty()) {
addSystemChatMessage("Quest removed: " + removedTitle);
} else {
addSystemChatMessage("Quest removed (ID " + std::to_string(questId) + ").");
}
break; break;
} }
case Opcode::SMSG_QUEST_QUERY_RESPONSE: { case Opcode::SMSG_QUEST_QUERY_RESPONSE: {
@ -3200,6 +3258,7 @@ void GameHandler::sendMovement(Opcode opcode) {
(opcode == Opcode::CMSG_MOVE_STOP_STRAFE) || (opcode == Opcode::CMSG_MOVE_STOP_STRAFE) ||
(opcode == Opcode::CMSG_MOVE_STOP_TURN) || (opcode == Opcode::CMSG_MOVE_STOP_TURN) ||
(opcode == Opcode::CMSG_MOVE_STOP_SWIM); (opcode == Opcode::CMSG_MOVE_STOP_SWIM);
if (!serverMovementAllowed_ && !taxiAllowed) return;
if ((onTaxiFlight_ || taxiMountActive_) && !taxiAllowed) return; if ((onTaxiFlight_ || taxiMountActive_) && !taxiAllowed) return;
if (resurrectPending_ && !taxiAllowed) return; if (resurrectPending_ && !taxiAllowed) return;
@ -9536,6 +9595,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
movementInfo.orientation = core::coords::serverToCanonicalYaw(orientation); movementInfo.orientation = core::coords::serverToCanonicalYaw(orientation);
movementInfo.flags = 0; movementInfo.flags = 0;
movementInfo.flags2 = 0; movementInfo.flags2 = 0;
serverMovementAllowed_ = true;
resurrectPending_ = false; resurrectPending_ = false;
resurrectRequestPending_ = false; resurrectRequestPending_ = false;
onTaxiFlight_ = false; onTaxiFlight_ = false;
@ -9554,6 +9614,9 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
// Clear world state for the new map // Clear world state for the new map
entityManager.clear(); entityManager.clear();
hostileAttackers_.clear(); hostileAttackers_.clear();
worldStates_.clear();
worldStateMapId_ = mapId;
worldStateZoneId_ = 0;
stopAutoAttack(); stopAutoAttack();
casting = false; casting = false;
currentCastSpellId = 0; currentCastSpellId = 0;