Garrison: Fix 9 discrepancies found via WoD sniff analysis (12.0.1.66102)
P0: Fix mission rewards double-write in GetGarrisonInfoResult — inline
Encounters/Rewards/OvermaxRewards are now cleared in mission structs
sent via GarrisonInfo; rewards go only in the garrison-level parallel
arrays, preventing client packet desync.
P1: Populate FollowerSoftCaps with 5 entries matching retail sniff data.
Implement SMSG_GARRISON_MISSION_START_CONDITION_UPDATE packet and send
it after GetGarrisonInfoResult. Update CMSG_GARRISON_START_MISSION
Read() for 12.0.1 per-follower struct format (BoardIndex/Health/
HasFollowerEntry), remove obsolete MissionBonusAbilityIDs.
P2: Implement SMSG_GARRISON_FOLLOWER_CHANGED_QUALITY packet, used by
SetFollowerQuality. Send blueprint data for all garrison types.
Read GarrTypeID in CMSG_GARRISON_CHECK_UPGRADEABLE and look up the
correct garrison by type.
P3: Add FOLLOWER_TYPE_DELVES (22) enum value. Fix building removal
packet ordering — GarrisonBuildingRemoved now sent before
ClearBuildingInfo (PlotRemoved) when replacing a different type.
This commit is contained in:
@@ -689,7 +689,18 @@ void Garrison::PlaceBuilding(uint32 garrPlotInstanceId, uint32 garrBuildingId)
|
||||
{
|
||||
oldBuildingId = plot->BuildingInfo.PacketInfo->GarrBuildingID;
|
||||
if (sGarrBuildingStore.AssertEntry(oldBuildingId)->BuildingType != building->BuildingType)
|
||||
{
|
||||
// Send BuildingRemoved BEFORE ClearBuildingInfo (which sends PlotRemoved)
|
||||
// so the client processes removal in the correct order
|
||||
WorldPackets::Garrison::GarrisonBuildingRemoved buildingRemoved;
|
||||
buildingRemoved.GarrTypeID = GetType();
|
||||
buildingRemoved.Result = GARRISON_SUCCESS;
|
||||
buildingRemoved.GarrPlotInstanceID = garrPlotInstanceId;
|
||||
buildingRemoved.GarrBuildingID = oldBuildingId;
|
||||
_owner->SendDirectMessage(buildingRemoved.Write());
|
||||
|
||||
plot->ClearBuildingInfo(GetType(), _owner);
|
||||
}
|
||||
}
|
||||
|
||||
plot->SetBuildingInfo(placeBuildingResult.BuildingInfo, _owner);
|
||||
@@ -702,12 +713,17 @@ void Garrison::PlaceBuilding(uint32 garrPlotInstanceId, uint32 garrBuildingId)
|
||||
|
||||
if (oldBuildingId)
|
||||
{
|
||||
WorldPackets::Garrison::GarrisonBuildingRemoved buildingRemoved;
|
||||
buildingRemoved.GarrTypeID = GetType();
|
||||
buildingRemoved.Result = GARRISON_SUCCESS;
|
||||
buildingRemoved.GarrPlotInstanceID = garrPlotInstanceId;
|
||||
buildingRemoved.GarrBuildingID = oldBuildingId;
|
||||
_owner->SendDirectMessage(buildingRemoved.Write());
|
||||
GarrBuildingEntry const* oldBuilding = sGarrBuildingStore.AssertEntry(oldBuildingId);
|
||||
// Same-type upgrade: BuildingRemoved wasn't sent above, send it now
|
||||
if (oldBuilding->BuildingType == building->BuildingType)
|
||||
{
|
||||
WorldPackets::Garrison::GarrisonBuildingRemoved buildingRemoved;
|
||||
buildingRemoved.GarrTypeID = GetType();
|
||||
buildingRemoved.Result = GARRISON_SUCCESS;
|
||||
buildingRemoved.GarrPlotInstanceID = garrPlotInstanceId;
|
||||
buildingRemoved.GarrBuildingID = oldBuildingId;
|
||||
_owner->SendDirectMessage(buildingRemoved.Write());
|
||||
}
|
||||
}
|
||||
|
||||
_owner->UpdateCriteria(CriteriaType::PlaceGarrisonBuilding, garrBuildingId);
|
||||
@@ -1028,11 +1044,22 @@ void Garrison::BuildInfoPacket(WorldPackets::Garrison::GarrisonInfo& garrison) c
|
||||
for (auto const& p : _followers)
|
||||
garrison.Followers.push_back(&p.second.PacketInfo);
|
||||
|
||||
// Missions: inline Encounters/Rewards/OvermaxRewards must be empty in the
|
||||
// GarrisonInfo mission structs ? the rewards go ONLY in the garrison-level
|
||||
// parallel arrays. The client reads both, so writing them in both places
|
||||
// causes a packet desync (double data). We build temporary copies with
|
||||
// the inline vectors cleared.
|
||||
_infoMissions.clear();
|
||||
_infoMissions.reserve(_missions.size());
|
||||
for (auto const& p : _missions)
|
||||
{
|
||||
garrison.Missions.push_back(&p.second.PacketInfo);
|
||||
garrison.MissionRewards.push_back(p.second.PacketInfo.Rewards);
|
||||
garrison.MissionOvermaxRewards.push_back(p.second.PacketInfo.OvermaxRewards);
|
||||
auto& copy = _infoMissions.emplace_back(p.second.PacketInfo);
|
||||
garrison.MissionRewards.push_back(copy.Rewards);
|
||||
garrison.MissionOvermaxRewards.push_back(copy.OvermaxRewards);
|
||||
copy.Encounters.clear();
|
||||
copy.Rewards.clear();
|
||||
copy.OvermaxRewards.clear();
|
||||
garrison.Missions.push_back(©);
|
||||
garrison.CanStartMission.push_back(p.second.PacketInfo.MissionState == 0);
|
||||
}
|
||||
|
||||
@@ -1097,6 +1124,21 @@ void Garrison::SendMapData(Player* receiver) const
|
||||
receiver->SendDirectMessage(mapData.Write());
|
||||
}
|
||||
|
||||
void Garrison::SendMissionStartConditionUpdate() const
|
||||
{
|
||||
WorldPackets::Garrison::GarrisonMissionStartConditionUpdate update;
|
||||
update.MissionRecIDs.reserve(_missions.size());
|
||||
update.CanStartMission.reserve(_missions.size());
|
||||
|
||||
for (auto const& [dbId, mission] : _missions)
|
||||
{
|
||||
update.MissionRecIDs.push_back(mission.PacketInfo.MissionRecID);
|
||||
update.CanStartMission.push_back(mission.PacketInfo.MissionState == 0);
|
||||
}
|
||||
|
||||
_owner->SendDirectMessage(update.Write());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Follower management
|
||||
// ============================================================
|
||||
@@ -2441,10 +2483,9 @@ void Garrison::SetFollowerQuality(uint64 dbId, uint32 quality)
|
||||
quality, GetFaction(), false);
|
||||
}
|
||||
|
||||
WorldPackets::Garrison::GarrisonUpdateFollower updateFollower;
|
||||
updateFollower.Result = GARRISON_SUCCESS;
|
||||
updateFollower.Follower = follower->PacketInfo;
|
||||
_owner->SendDirectMessage(updateFollower.Write());
|
||||
WorldPackets::Garrison::GarrisonFollowerChangedQuality changedQuality;
|
||||
changedQuality.Follower = follower->PacketInfo;
|
||||
_owner->SendDirectMessage(changedQuality.Write());
|
||||
}
|
||||
|
||||
void Garrison::SetFollowerLevel(uint64 dbId, uint32 level)
|
||||
|
||||
@@ -63,6 +63,7 @@ enum GarrisonFollowerType
|
||||
FOLLOWER_TYPE_SHIPYARD = 2,
|
||||
FOLLOWER_TYPE_CLASS_ORDER = 4,
|
||||
FOLLOWER_TYPE_WAR_CAMPAIGN = 11,
|
||||
FOLLOWER_TYPE_DELVES = 22,
|
||||
FOLLOWER_TYPE_COVENANT = 123
|
||||
};
|
||||
|
||||
@@ -386,6 +387,7 @@ public:
|
||||
void SendRemoteInfo() const;
|
||||
void SendBlueprintAndSpecializationData();
|
||||
void SendMapData(Player* receiver) const;
|
||||
void SendMissionStartConditionUpdate() const;
|
||||
|
||||
void ResetFollowerActivationLimit() { _followerActivationsRemainingToday = 1; }
|
||||
|
||||
@@ -423,6 +425,9 @@ private:
|
||||
|
||||
// Trophies
|
||||
std::unordered_set<uint32 /*trophyID*/> _trophies;
|
||||
|
||||
// Temporary storage for BuildInfoPacket (mission copies with cleared inline rewards)
|
||||
mutable std::vector<WorldPackets::Garrison::GarrisonMission> _infoMissions;
|
||||
};
|
||||
|
||||
#endif // Garrison_h__
|
||||
|
||||
@@ -36,7 +36,19 @@ void WorldSession::HandleGetGarrisonInfo(WorldPackets::Garrison::GetGarrisonInfo
|
||||
for (auto const& [type, garrison] : _player->GetGarrisons())
|
||||
garrison->BuildInfoPacket(garrisonInfo.Garrisons.emplace_back());
|
||||
|
||||
garrisonInfo.FollowerSoftCaps = {
|
||||
{ FOLLOWER_TYPE_GARRISON, 20 },
|
||||
{ FOLLOWER_TYPE_SHIPYARD, 6 },
|
||||
{ FOLLOWER_TYPE_CLASS_ORDER, 6 },
|
||||
{ FOLLOWER_TYPE_DELVES, 30 },
|
||||
{ FOLLOWER_TYPE_COVENANT, 100 }
|
||||
};
|
||||
|
||||
SendPacket(garrisonInfo.Write());
|
||||
|
||||
// Follow up with per-garrison mission start condition updates
|
||||
for (auto const& [type, garrison] : _player->GetGarrisons())
|
||||
garrison->SendMissionStartConditionUpdate();
|
||||
}
|
||||
|
||||
void WorldSession::HandleGarrisonPurchaseBuilding(WorldPackets::Garrison::GarrisonPurchaseBuilding& garrisonPurchaseBuilding)
|
||||
@@ -59,7 +71,7 @@ void WorldSession::HandleGarrisonCancelConstruction(WorldPackets::Garrison::Garr
|
||||
|
||||
void WorldSession::HandleGarrisonRequestBlueprintAndSpecializationData(WorldPackets::Garrison::GarrisonRequestBlueprintAndSpecializationData& /*garrisonRequestBlueprintAndSpecializationData*/)
|
||||
{
|
||||
if (Garrison* garrison = _player->GetGarrison())
|
||||
for (auto const& [type, garrison] : _player->GetGarrisons())
|
||||
garrison->SendBlueprintAndSpecializationData();
|
||||
}
|
||||
|
||||
@@ -388,9 +400,9 @@ void WorldSession::HandleUpgradeGarrison(WorldPackets::Garrison::UpgradeGarrison
|
||||
SendPacket(result.Write());
|
||||
}
|
||||
|
||||
void WorldSession::HandleGarrisonCheckUpgradeable(WorldPackets::Garrison::GarrisonCheckUpgradeable& /*garrisonCheckUpgradeable*/)
|
||||
void WorldSession::HandleGarrisonCheckUpgradeable(WorldPackets::Garrison::GarrisonCheckUpgradeable& garrisonCheckUpgradeable)
|
||||
{
|
||||
Garrison* garrison = _player->GetGarrison();
|
||||
Garrison* garrison = _player->GetGarrison(static_cast<GarrisonType>(garrisonCheckUpgradeable.GarrTypeID));
|
||||
GarrisonError upgradeResult = GARRISON_ERROR_UPGRADE_CONDITION_FAILED;
|
||||
|
||||
if (garrison)
|
||||
|
||||
@@ -492,18 +492,17 @@ namespace WorldPackets::Garrison
|
||||
{
|
||||
_worldPacket >> NpcGUID;
|
||||
uint32 followerCount = 0;
|
||||
uint32 bonusAbilityCount = 0;
|
||||
_worldPacket >> followerCount;
|
||||
_worldPacket >> MissionRecID;
|
||||
_worldPacket >> bonusAbilityCount;
|
||||
|
||||
FollowerDBIDs.resize(followerCount);
|
||||
for (uint32 i = 0; i < followerCount; ++i)
|
||||
{
|
||||
_worldPacket >> FollowerDBIDs[i];
|
||||
|
||||
MissionBonusAbilityIDs.resize(bonusAbilityCount);
|
||||
for (uint32 i = 0; i < bonusAbilityCount; ++i)
|
||||
_worldPacket >> MissionBonusAbilityIDs[i];
|
||||
_worldPacket.read_skip<int32>(); // BoardIndex (unused, always -1)
|
||||
_worldPacket.read_skip<int32>(); // Health (unused, always 0)
|
||||
_worldPacket.read_skip<uint8>(); // HasFollowerEntry (unused, always false)
|
||||
}
|
||||
}
|
||||
|
||||
void GarrisonCompleteMission::Read()
|
||||
@@ -595,6 +594,24 @@ namespace WorldPackets::Garrison
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* GarrisonMissionStartConditionUpdate::Write()
|
||||
{
|
||||
ASSERT(MissionRecIDs.size() == CanStartMission.size());
|
||||
|
||||
_worldPacket << Size<uint32>(MissionRecIDs);
|
||||
_worldPacket << Size<uint32>(CanStartMission);
|
||||
|
||||
if (!MissionRecIDs.empty())
|
||||
_worldPacket.append(MissionRecIDs.data(), MissionRecIDs.size());
|
||||
|
||||
for (bool canStart : CanStartMission)
|
||||
_worldPacket << Bits<1>(canStart);
|
||||
|
||||
_worldPacket.FlushBits();
|
||||
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* GarrisonIsUpgradeableResponse::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
@@ -739,6 +756,13 @@ namespace WorldPackets::Garrison
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* GarrisonFollowerChangedQuality::Write()
|
||||
{
|
||||
_worldPacket << Follower;
|
||||
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* GarrisonUpdateFollower::Write()
|
||||
{
|
||||
_worldPacket << uint32(Result);
|
||||
@@ -812,6 +836,11 @@ namespace WorldPackets::Garrison
|
||||
_worldPacket >> NpcGUID;
|
||||
}
|
||||
|
||||
void GarrisonCheckUpgradeable::Read()
|
||||
{
|
||||
_worldPacket >> GarrTypeID;
|
||||
}
|
||||
|
||||
void GarrisonSetBuildingActive::Read()
|
||||
{
|
||||
_worldPacket >> PlotInstanceID;
|
||||
|
||||
@@ -452,7 +452,6 @@ namespace WorldPackets
|
||||
ObjectGuid NpcGUID;
|
||||
std::vector<uint64> FollowerDBIDs;
|
||||
uint32 MissionRecID = 0;
|
||||
std::vector<uint64> MissionBonusAbilityIDs;
|
||||
};
|
||||
|
||||
class GarrisonCompleteMission final : public ClientPacket
|
||||
@@ -557,6 +556,17 @@ namespace WorldPackets
|
||||
uint8 GarrTypeID = 0;
|
||||
};
|
||||
|
||||
class GarrisonMissionStartConditionUpdate final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
explicit GarrisonMissionStartConditionUpdate() : ServerPacket(SMSG_GARRISON_MISSION_START_CONDITION_UPDATE) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
std::vector<int32> MissionRecIDs;
|
||||
std::vector<bool> CanStartMission;
|
||||
};
|
||||
|
||||
class GarrisonIsUpgradeableResponse final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
@@ -779,6 +789,16 @@ namespace WorldPackets
|
||||
GarrisonFollower Follower;
|
||||
};
|
||||
|
||||
class GarrisonFollowerChangedQuality final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
explicit GarrisonFollowerChangedQuality() : ServerPacket(SMSG_GARRISON_FOLLOWER_CHANGED_QUALITY) {}
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
GarrisonFollower Follower;
|
||||
};
|
||||
|
||||
class GarrisonUpdateFollower final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
@@ -863,7 +883,9 @@ namespace WorldPackets
|
||||
public:
|
||||
explicit GarrisonCheckUpgradeable(WorldPacket&& packet) : ClientPacket(CMSG_GARRISON_CHECK_UPGRADEABLE, std::move(packet)) {}
|
||||
|
||||
void Read() override {}
|
||||
void Read() override;
|
||||
|
||||
uint32 GarrTypeID = 0;
|
||||
};
|
||||
|
||||
class GarrisonSetBuildingActive final : public ClientPacket
|
||||
|
||||
Reference in New Issue
Block a user