Missing followers
This commit is contained in:
@@ -105,13 +105,6 @@ namespace WorldPackets::Garrison
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sniff-verified for 12.0.1.66102 ? observed in the 8316-byte SMSG_GET_GARRISON_INFO_RESULT
|
|
||||||
// body (SNIFF_AUDIT_12.0.1.66102.md -5.3). int32 ItemFileDataID is always present
|
|
||||||
// (sometimes zero, sometimes a real DBD ID like 1599042); the trailing OptionalInit bit
|
|
||||||
// gates an optional ItemInstance blob.
|
|
||||||
// The brief's Deserialize_JamGarrisonMissionReward @ 0x7FF75C1754C0 (Byte-gated VarUInt32)
|
|
||||||
// decodes the JAM-mirror code path, NOT this dedicated SMSG. Both formats coexist for
|
|
||||||
// different sub-collection sync paths.
|
|
||||||
ByteBuffer& operator<<(ByteBuffer& data, GarrisonMissionReward const& missionRewardItem)
|
ByteBuffer& operator<<(ByteBuffer& data, GarrisonMissionReward const& missionRewardItem)
|
||||||
{
|
{
|
||||||
data << int32(missionRewardItem.ItemID);
|
data << int32(missionRewardItem.ItemID);
|
||||||
@@ -121,9 +114,7 @@ namespace WorldPackets::Garrison
|
|||||||
data << uint32(missionRewardItem.FollowerXP);
|
data << uint32(missionRewardItem.FollowerXP);
|
||||||
data << uint32(missionRewardItem.GarrMssnBonusAbilityID);
|
data << uint32(missionRewardItem.GarrMssnBonusAbilityID);
|
||||||
data << int32(missionRewardItem.ItemFileDataID);
|
data << int32(missionRewardItem.ItemFileDataID);
|
||||||
// itemInstance = OptionalInit bit + FlushBits. This is the SNIFF-CONFIRMED encoding (ADD_MISSION_RESULT
|
|
||||||
// decoded byte-exact / 0 trailing bytes). The talent socketData is the SAME reflection kind and must match
|
|
||||||
// this (bit), NOT a uint32 count - see operator<<(GarrisonTalent).
|
|
||||||
data << OptionalInit(missionRewardItem.ItemInstance);
|
data << OptionalInit(missionRewardItem.ItemInstance);
|
||||||
data.FlushBits();
|
data.FlushBits();
|
||||||
|
|
||||||
@@ -133,13 +124,6 @@ namespace WorldPackets::Garrison
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sniff-verified for 12.0.1.66102 (SNIFF_AUDIT_12.0.1.66102.md -3.15, embedded in
|
|
||||||
// the 188-byte SMSG_GET_GARRISON_INFO_RESULT). MissionScalar(float) sits between
|
|
||||||
// Flags and ContentTuningID followed by 3 size fields. Note: SMSG_GARRISON_START_MISSION_RESULT
|
|
||||||
// embeds a Mission whose exact byte layout has a residual ~2-byte misalignment vs this
|
|
||||||
// writer (audit -4.1) ? needs IDA pseudocode of Build_GarrisonStartMissionResult to fully
|
|
||||||
// resolve. The brief's Deserialize_JamGarrisonMission @ 0x7FF75C1755E0 with Currency/
|
|
||||||
// BonusActions/AutoMissionData tail is the JAM-mirror path, NOT this dedicated SMSG.
|
|
||||||
ByteBuffer& operator<<(ByteBuffer& data, GarrisonMission const& mission)
|
ByteBuffer& operator<<(ByteBuffer& data, GarrisonMission const& mission)
|
||||||
{
|
{
|
||||||
data << uint64(mission.DbID);
|
data << uint64(mission.DbID);
|
||||||
@@ -188,43 +172,16 @@ namespace WorldPackets::Garrison
|
|||||||
|
|
||||||
ByteBuffer& operator<<(ByteBuffer& data, GarrisonTalent const& talent)
|
ByteBuffer& operator<<(ByteBuffer& data, GarrisonTalent const& talent)
|
||||||
{
|
{
|
||||||
// Wire order (client JamGarrisonTalent, Ghidra-confirmed via ctor @client 0x729093890): garrTalentID(i32),
|
|
||||||
// garrTalentRank(i32), researchStartTime(i64), flags(i32), socketData(OptionalInit bit + FlushBits).
|
|
||||||
//
|
|
||||||
// The client reads the wire `flags` field with its OWN semantics, which differ from TC's internal
|
|
||||||
// Garrison::Talent::Flags. Bits were pinned by live client testing (the builder that computes these is
|
|
||||||
// control-flow-flattened, so it isn't offline-decompilable):
|
|
||||||
// client bit 0x4 = "this talent is being researched" ? gates whether the Order Advancement UI reads
|
|
||||||
// researchStartTime and renders the research timer. Verified live: flags=0 kept the client's startTime in
|
|
||||||
// memory (proven by a server-side diag log + a sentinel injection) but drew the talent idle; setting 0x4
|
|
||||||
// makes C_Garrison.GetTalentInfo(id).isBeingResearched=true with the real DB2 researchDuration.
|
|
||||||
// client bit 0x2 = a duration override that forces researchDuration to a fixed 24h; MUST NEVER be set.
|
|
||||||
// client bit 0x1 = "researched / talent taken". Recovered by static RE of the 12.0.7 client, which also
|
|
||||||
// proves it is PURELY server-supplied: the complete set of writers to the client-side talent record's
|
|
||||||
// flags word (+0x40) is three sites - RVA 0x22992A5 and 0x2299395, both verbatim copies of the wire
|
|
||||||
// struct inside Garrison::SetTalent (RVA 0x2298FF0), and RVA 0x2299448 in Garrison::RemoveTalent,
|
|
||||||
// which only zeroes it. There is no or/bts against that field anywhere in the image and no
|
|
||||||
// research-timer expiry that recomputes it, so the client can never derive "researched" on its own.
|
|
||||||
// Readers gated on the bit: ModifierTree 201/202/317 (evaluator RVA 0x20664B0 - the covenant/taxi
|
|
||||||
// PlayerCondition path), C_Garrison.GetTalentInfo's `researched` (builder RVA 0x22B95F0 via
|
|
||||||
// 0x228E2F0), GetTalentAvailability (RVA 0x2171390, bit set => availability 2) and
|
|
||||||
// CanResearchAtThisTier (RVA 0x2298DF0, which early-outs "already taken" only when the bit is set).
|
|
||||||
// Leaving it clear is why a fully researched sanctum/order-hall talent rendered as un-researched and
|
|
||||||
// why talent-gated unlocks (e.g. the covenant transport-network taxi nodes) never opened.
|
|
||||||
// TC's internal flags (only GARRISON_TALENT_FLAG_TEMPORARY=0x1 today, an unrelated meaning) are not
|
|
||||||
// wire-compatible, so derive the wire value purely from research state.
|
|
||||||
int32 wireFlags = 0;
|
int32 wireFlags = 0;
|
||||||
if (talent.Rank >= 1)
|
if (talent.Rank >= 1)
|
||||||
wireFlags |= 0x1; // client "researched / taken" bit; every client test pairs it with a rank check
|
wireFlags |= 0x1;
|
||||||
if (talent.ResearchStartTime != 0)
|
if (talent.ResearchStartTime != 0)
|
||||||
wireFlags |= 0x4; // client "being researched" bit (never 0x2 = duration override)
|
wireFlags |= 0x4;
|
||||||
data << int32(talent.GarrTalentID);
|
data << int32(talent.GarrTalentID);
|
||||||
data << int32(talent.Rank);
|
data << int32(talent.Rank);
|
||||||
data << talent.ResearchStartTime;
|
data << talent.ResearchStartTime;
|
||||||
data << int32(wireFlags);
|
data << int32(wireFlags);
|
||||||
// socketData is the SAME reflection kind (0x80003) as the reward itemInstance, which is sniff-confirmed as
|
|
||||||
// an OptionalInit bit (NOT a uint32 count). The prior uint32 "fix" made each talent 3 bytes too long,
|
|
||||||
// shifting the mission-reward data written right after the Talents block (rewards showed value x 2^16).
|
|
||||||
data << OptionalInit(talent.Socket);
|
data << OptionalInit(talent.Socket);
|
||||||
data.FlushBits();
|
data.FlushBits();
|
||||||
if (talent.Socket)
|
if (talent.Socket)
|
||||||
@@ -420,10 +377,7 @@ namespace WorldPackets::Garrison
|
|||||||
{
|
{
|
||||||
_worldPacket >> GarrTalentID;
|
_worldPacket >> GarrTalentID;
|
||||||
uint32 count = _worldPacket.read<uint32>();
|
uint32 count = _worldPacket.read<uint32>();
|
||||||
// Sanity cap the client-supplied socket count before resize(): a soulbind/conduit tree has at most a few dozen
|
|
||||||
// sockets, so this is well above any legitimate value while stopping a crafted count (e.g. 0xFFFFFFFF) from
|
|
||||||
// requesting a multi-gigabyte allocation. resize() throws std::bad_alloc, which the opcode dispatcher does not
|
|
||||||
// catch (only ByteBufferException), so an unbounded count would crash the world thread instead of disconnecting.
|
|
||||||
count = std::min<uint32>(count, 64);
|
count = std::min<uint32>(count, 64);
|
||||||
Sockets.resize(count);
|
Sockets.resize(count);
|
||||||
for (GarrisonTalentSocketData& socket : Sockets)
|
for (GarrisonTalentSocketData& socket : Sockets)
|
||||||
@@ -559,7 +513,6 @@ namespace WorldPackets::Garrison
|
|||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conservative shape: u32 Result, u32 GarrSpecID, u32 GarrPlotInstanceID.
|
|
||||||
WorldPacket const* GarrisonLearnSpecializationResult::Write()
|
WorldPacket const* GarrisonLearnSpecializationResult::Write()
|
||||||
{
|
{
|
||||||
_worldPacket << uint32(Result);
|
_worldPacket << uint32(Result);
|
||||||
@@ -568,7 +521,6 @@ namespace WorldPackets::Garrison
|
|||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conservative shape: u32 Result, u32 GarrPlotInstanceID, u32 GarrSpecID.
|
|
||||||
WorldPacket const* GarrisonBuildingSetActiveSpecializationResult::Write()
|
WorldPacket const* GarrisonBuildingSetActiveSpecializationResult::Write()
|
||||||
{
|
{
|
||||||
_worldPacket << uint32(Result);
|
_worldPacket << uint32(Result);
|
||||||
@@ -579,8 +531,6 @@ namespace WorldPackets::Garrison
|
|||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// u32 GarrPlotInstanceID, u64 TimeBuilt, u32 Result - see the header for the disassembly that inverts the
|
|
||||||
// order the old comment claimed. Result LAST is the field the client's `cmp [rcx+0x30], 0` gate reads.
|
|
||||||
WorldPacket const* GarrisonCompleteBuildingConstructionResult::Write()
|
WorldPacket const* GarrisonCompleteBuildingConstructionResult::Write()
|
||||||
{
|
{
|
||||||
_worldPacket << uint32(GarrPlotInstanceID);
|
_worldPacket << uint32(GarrPlotInstanceID);
|
||||||
@@ -590,7 +540,6 @@ namespace WorldPackets::Garrison
|
|||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDA case 4980791 (-8.45): PackedGuid + sub-call + varU32 size + varU32[size].
|
|
||||||
WorldPacket const* GarrisonOpenCrafter::Write()
|
WorldPacket const* GarrisonOpenCrafter::Write()
|
||||||
{
|
{
|
||||||
_worldPacket << NpcGUID;
|
_worldPacket << NpcGUID;
|
||||||
@@ -602,8 +551,6 @@ namespace WorldPackets::Garrison
|
|||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDA case 4980817 (-8.51): generic byte-block helper. Conservative: u32 NewMinLevel.
|
|
||||||
// 8 bytes, not 4 - the client's handler (0x22A0BA0) reads two u32s out of the opaque tail. See the header.
|
|
||||||
WorldPacket const* GarrisonAutoTroopMinLevelUpdateResult::Write()
|
WorldPacket const* GarrisonAutoTroopMinLevelUpdateResult::Write()
|
||||||
{
|
{
|
||||||
_worldPacket << uint32(UnkLookupKey);
|
_worldPacket << uint32(UnkLookupKey);
|
||||||
@@ -612,8 +559,6 @@ namespace WorldPackets::Garrison
|
|||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDA case 4980772 (-8.30): u8 GarrTypeID + GarrisonSmallStruct.
|
|
||||||
// Conservative inner shape: {u32 MissionRecID, u32 BonusAbilityID}.
|
|
||||||
WorldPacket const* GarrisonActivateMissionBonusAbility::Write()
|
WorldPacket const* GarrisonActivateMissionBonusAbility::Write()
|
||||||
{
|
{
|
||||||
_worldPacket << uint8(GarrTypeID);
|
_worldPacket << uint8(GarrTypeID);
|
||||||
@@ -667,7 +612,7 @@ namespace WorldPackets::Garrison
|
|||||||
|
|
||||||
void GarrisonGetMissionReward::Read()
|
void GarrisonGetMissionReward::Read()
|
||||||
{
|
{
|
||||||
_worldPacket >> DbID; // RAW uint64, not a PackedGuid ? see GarrisonPackets.h
|
_worldPacket >> DbID;
|
||||||
_worldPacket >> MissionRecID;
|
_worldPacket >> MissionRecID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,8 +692,6 @@ namespace WorldPackets::Garrison
|
|||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT -10.2. 3 CONFIRMED + 2 HIGH
|
|
||||||
// fields. FollowerInfo struct shape matches COMPLETE_MISSION_RESULT (-10.1.3).
|
|
||||||
WorldPacket const* GarrisonMissionBonusRollResult::Write()
|
WorldPacket const* GarrisonMissionBonusRollResult::Write()
|
||||||
{
|
{
|
||||||
_worldPacket << Mission;
|
_worldPacket << Mission;
|
||||||
|
|||||||
@@ -52,12 +52,15 @@ public:
|
|||||||
if (!player->GetGroup() || !player->GetGroup()->GetLeaderGUID())
|
if (!player->GetGroup() || !player->GetGroup()->GetLeaderGUID())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
player->GetGroup()->AddFollowerModeBots(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayerExit(Player* player)
|
void OnPlayerExit(Player* player)
|
||||||
{
|
{
|
||||||
if (!player->GetGroup())
|
if (!player->GetGroup())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
player->GetGroup()->DespawnFollowerModeBots(player);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user