- when GenAI response invalid for starting a bot conversation it should fall back to the static sytem. That was not the case since there was a hardcoded simple fallback. Removed
Signed-off-by: luis <[email protected]>
Completes GrantRenownReward: TransmogID resolves an ItemModifiedAppearance and adds it via CollectionMgr::AddItemAppearance(itemId, appearanceModId); TransmogSetID via AddTransmogSet; GarrFollowerID via Garrison::AddFollower (guarded on the player having a garrison). Only TransmogIllusionID (no add-illusion API) and QuestID (reward-quest semantics) remain documented follow-ups.
The covenant renown LEVEL is already a renown-reputation handled by TC's
ReputationMgr and synced to the client by the standard reputation packets,
so the only covenant-specific gap was granting the per-level rewards.
- Add the RenownRewards.db2 store (Meta 0x30AFBCF8, ParentIndex=CovenantID)
and a DB2Manager (covenantId, level) -> rewards map (GetRenownRewards).
- Track the highest renown level whose rewards were granted, per covenant
(Player::m_renownRewardsGranted), persisted to a new character_covenant_renown
table and loaded at login, so each level's rewards are granted exactly once.
- Player::ReputationChanged now grants rewards for every renown level crossed
when a covenant renown reputation increases; a login catch-up
(UpdateAllRenownRewards, run in-world after SendInitialPacketsAfterAddToMap)
grants anything earned before this feature existed. UpdateRenownRewards is
guarded on IsInWorld() so nothing is granted mid-load.
- GrantRenownReward grants item / spell / title / mount via the existing
Player + CollectionMgr APIs. The cosmetic-collection fields RenownRewards
also carries (TransmogID/TransmogSetID/TransmogIllusionID, GarrFollowerID,
QuestID) are documented as follow-ups rather than half-implemented.
TC already has the full pet-specialization machinery — Pet::SetSpecialization
validates the ChrSpecialization, swaps the spec spells, rebuilds the action
bar and echoes SMSG_SET_PET_SPECIALIZATION — but the client request that lets
a player pick the spec was left as Handle_NULL. Wire the handler in:
- New CMSG packet SetPetSpecializationRequest { uint32 SpecID; ObjectGuid
PetGUID } (68275 wire).
- HandleSetPetSpecialization validates the pet is the caller's and that SpecID
is a real pet specialization (ChrSpecialization.ClassID == 0) before applying.
- Opcode registered STATUS_LOGGEDIN / PROCESS_THREADUNSAFE.
The client's SetPetSpecialization binding drives this, so it is a live,
user-triggerable path, not a vestigial opcode.
Emit customer-provided reagents (and a customerPlayer sub-struct) in the crafting-order
list wire, closing the one part of the flagship that the prior pass parked as
"needs full ItemInstance serialization / server data-model gap".
Both of those blockers were a misattribution. Re-decompiling the reader tree shows the
order's reagents (JamCraftingOrder.reagents) are read by sub_7FF72915FF20 as FLAT
JamCraftingOrderItem records (orderItemID, type, itemGUID, ownerGUID, quantity,
qualityID, flags, reagentBase{itemID,currencyID}, optional slot) - NOT ItemInstances.
The ItemInstance readers (sub_7FF7291CBD40) belong to the JamCliCraftingOrder *recraft*
fields, a different sub-struct. The server's existing OrderReagent {ItemID, CurrencyID,
Quantity, Slot} already holds everything a posting carries; ownerGUID = the customer and
flags = 1 are derived at emit time.
Validated byte-exact against the live 12.0.7 sniff
(C:\sniff\ingame-shop_ordersCrafting_professions.pkt): every order in the 2054-byte
(12 orders) and 1875-byte (11 orders) responses re-encodes to the byte, 0 leftover,
including the reagent blocks and the presence-gated customerNpc sub-struct.
- CraftingOrderPackets.h/.cpp: CraftingOrderReagentData + reagent loop in operator<<
(mirror of sub_7FF72915FF20), presence-byte bit assignment (bit5 customerPlayer,
bit4 customerNpc, bit3 outputOrderItem, bit2 outputItem), customerPlayer emission.
- CraftingOrderHandler.cpp: BuildCraftingOrderData fills reagents from Order::Reagents
(ownerGUID = CustomerGUID) and sets customerPlayer for player orders.
The fulfil wire turned out to be recoverable, not reflection-walled: the client's FULFILL send serializer
(sub_7FF729155000) is BYTE-IDENTICAL to the already-decoded REJECT serializer (sub_7FF7291552B0), so
CraftingOrderFulfill reads the same { u64 OrderID; u8; string note; bit hasContext; [ClientContext] }. No crafted
item rides the wire - the server derives the output from the order's recipe, matching the client's craft-then-fulfil.
Fulfil flow (HandleCraftingOrderFulfill):
- validates the order is Claimed by the sender;
- resolves the recipe output (SkillLineAbility -> spell -> SPELL_EFFECT_CREATE_ITEM) and mails the crafted item
to the customer;
- transitions Claimed -> Fulfilled and releases the escrowed tip to the crafter;
- replies with SMSG_CRAFTING_ORDER_FULFILL_RESULT and broadcasts UPDATE_STATE.
SMSG_CRAFTING_ORDER_FULFILL_RESULT: recovered byte-exact from the client reader sub_7FF7290B9950 (all plain reads -
the "CompressedUInt32" reader 0x7FF72BE6C410 is ReadUInt32): { u8 Result; u64 OrderID; u64; u8; PackedGUID; u32;
u32; u32 }. Result+OrderID are populated; the trailing fields have no offline-resolvable meaning and are sent
0/empty (structurally exact, honest - fulfilment is also signalled via UPDATE_STATE).
Tip escrow (Blizzlike, mirrors the auction deposit; no gold is created or lost):
- CREATE now requires + deducts the tip up front (escrow), failing with MissingCurrency if the customer is short;
- FULFILL pays the escrowed tip to the crafter;
- CANCEL / REJECT / posting-expiry refund the escrowed tip to the customer.
Each order escrows once at create and releases exactly once at its single terminal transition.
Adds the order-state-change notification the client uses to refresh open browse windows in real time.
- CraftingOrderUpdateState packet (SMSG_CRAFTING_ORDER_UPDATE_STATE 0x42033D). Wire is sniff-resolved from
C:\sniff\ingame-shop_ordersCrafting_professions.pkt and cross-checked against the SAME order in the
LIST_ORDERS_RESPONSE capture - OrderID / OrderState / CrafterGUID / SkillLineAbilityID / OrderType all match
byte-exact. Layout: { u64 OrderID; u8(0); u8 OrderState; u16(0); PackedGUID CrafterGUID; u32 SkillLineAbilityID;
u32(0); u8 OrderType; u32 Field30; u32 Field34 }. Field30/Field34 hold small per-order values (~96/~38) that map
to none of the order's known fields; their semantics are not offline-resolvable, so they are sent 0 (honest).
- BroadcastCraftingOrderState(): pushes the update to the online customer and (once assigned) crafter.
- Wired into the claim / release / reject handlers after a successful state change; the freshly-updated order is
re-fetched from the mgr and broadcast. Cancel removes an unclaimed order whose only interested party (the customer)
already receives the CancelResult, so it is intentionally not broadcast.
Turns CMSG_CONTRIBUTION_CONTRIBUTE (was Handle_NULL) into the working war-effort turn-in,
closing the loop over the P1a DB2 stores and the P1 ManagedWorldStateMgr.
ContributionMgr:
- Load(): builds the collector-creature -> contribution index from CreatureXContribution.db2.
- Contribute(player, collectorGuid, contributionId):
1. Validates the collector is an NPC the player can currently interact with
(GetNPCIfCanInteractWith, no flag requirement) and that its creature entry actually
offers this contribution (CreatureXContribution authorization).
2. Resolves Contribution -> ManagedWorldStateInput, gating on the input's
ValidInputConditionID (PlayerCondition).
3. Takes the turn-in cost = the input QuestID's item/currency objectives: a first pass
verifies the player can pay the full cost (nothing is consumed unless everything is
affordable), a second pass consumes items (DestroyItemCount) and currency
(RemoveCurrency, QuestTurnin reason).
4. Feeds the total contributed amount into ManagedWorldStateMgr::AddProgress, which
advances the realm-wide progress world state (and completes an occurrence/stage at the
target). A contribution with no consumable objectives counts as one unit.
Wired: HandleContributionContribute routes the opcode (STATUS_LOGGEDIN, THREADUNSAFE);
sContributionMgr->Load() at startup after the managed world states. Builds clean (game.dll).
Progress increment = total quantity turned in (progress bar == resources contributed);
documented as the modelling choice where a state counts resources rather than turn-ins.
The war-effort Contribution system is now end-to-end: DB2 model -> collector turn-in ->
managed world-state progress. SMSG_CONTRIBUTION_LAST_UPDATE_RESPONSE remains deferred
(factory/thunk reader; no guessed wire).
Adds the realm-wide progress engine TC lacked. For each ManagedWorldState.db2 row the manager
holds a runtime state (progress, stage, occurrences, up/down phase, timers) and:
- Cycles the up/down phase (UpTimeSecs/DownTimeSecs windows); a state with zero windows is
purely contribution-driven and stays in its Up phase.
- Once per elapsed minute, accumulates AccumulationAmountPerMinute toward
AccumulationStateTargetValue during the Up phase, or depletes DepletionAmountPerMinute toward
DepletionStateTargetValue during the Down phase.
- Pushes Progress / CurrentStage / Occurrences through their world states via
WorldStateMgr::SetValueAndSaveInDb (realm-global, persisted), so the client draws the
war-effort/building progress bar and it survives restarts (restored on Load via GetValue).
- Reaching the target completes one occurrence and advances the stage.
- AddProgress(managedWorldStateId, amount) is the seam the Contribute path (P2) feeds into,
clamped to the depletion/target bounds.
Wired into World: Load() after world-state templates load (so persisted progress restores),
Update(diff) in the world tick next to the outdoor-PvP update. Builds clean (game.dll).
Note: values are pushed as realm-global world states, which covers realm-wide managed states;
zone-scoped states would additionally need per-map propagation (documented, future refinement).
Adds the four DB2 stores the Contribution system is built on (TC had the *Meta hotfix
schemas but never loaded these tables): Contribution, CreatureXContribution,
ManagedWorldState, ManagedWorldStateInput.
Field layouts derived from the authoritative TC *Meta structs (DB2Metadata.h) cross-checked
against the 68275 WoWDBDefs layouts (Contribution 167173C3, ManagedWorldState A4E9EA9F,
ManagedWorldStateInput 16683306, CreatureXContribution 29D9C37F). All four have IndexField=-1
(ID first). ManagedWorldState carries the accumulate/deplete progress driver bound to
Current/Progress/Occurrences WorldState IDs; ManagedWorldStateInput carries the QuestID that
is the contribution turn-in cost.
- DB2Structure.h: 4 entry structs.
- DB2LoadInfo.h: 4 load infos (arrays expanded per element, signedness per Meta).
- DB2Stores.h/.cpp: storage globals + LOAD_DB2 registration.
- HotfixDatabase.h/.cpp: hotfix SELECT statements (+ locale for Contribution's two
localized strings) and enum entries.
- sql/updates/hotfixes/master/2026_07_07_00_hotfixes.sql: the five hotfix tables.
Builds clean (game.dll). Next: the ManagedWorldStateMgr runtime (P1) that ticks these
states, then Contribute (P2).
to the client DB2 model (Contribution -> ManagedWorldStateInput -> ManagedWorldState), per
the architecture decision to keep it decoupled from the major-factions branch.
- doc/contribution/CONTRIBUTION_IMPLEMENTATION_PLAN_68275.md: full RE + phased plan.
Wire (CMSG both dirs RE'd byte-exact from client serializers); DB2 model incl. the key
finding that Contribution.db2 has NO FactionID (it feeds a ManagedWorldStateInput, not a
faction) and the cost = the input's QuestID turn-in (no separate cost column).
- ContributionPackets.h/.cpp: readers for CMSG_CONTRIBUTION_CONTRIBUTE
({ObjectGuid CollectorGUID, uint32 ContributionID}) and CMSG_CONTRIBUTION_LAST_UPDATE_REQUEST
({uint32 ContributionID, uint32 Field1}). Included in AllPackets.h.
Opcodes intentionally remain Handle_NULL: routing lands with the real ContributionMgr +
ManagedWorldState runtime (P1/P2) so no stub handler is introduced. SMSG_CONTRIBUTION_LAST_
UPDATE_RESPONSE deferred with a documented reason (reader is a factory/thunk maze; needs a
dispatch decompile or live sniff, no guessed wire).
Implements the full client<->server surface for "Recent Allies" (the players you
recently grouped with, so you can note who they were), turning three Handle_NULL
opcodes into a working, persistent system.
Wire recovered byte-exact by decompiling the client readers/serializers:
- CMSG_SET_ALLOW_RECENT_ALLIES_SEE_LOCATION (sub_7FF72914CCD0) = 1 packed bit.
- CMSG_RECENT_ALLY_REQUEST_DATA (sub_7FF7290805B0) = empty.
- CMSG_RECENT_ALLY_SET_NOTE (sub_7FF7290806C0) = { PackedGuid, bit-length note, bit }.
- SMSG_RECENT_ALLY_DATA_RESPONSE (reader sub_7FF7290BBE20 -> entry sub_7FF729162B00)
= { u32 count, entry[]{ PackedGuid, u64 wowAccount, u32, activity-vector{u8,u64,u32,u32},
11-bit note + 3 flag bits, note bytes } }.
- SMSG_RECENT_ALLY_NOTE_UPDATED (reader sub_7FF7290BBF10) = { u32, PackedGuid, u64, note }.
The note length is an 11-bit bit-length field (2048 = 1<<11 sentinel in the readers);
the 3 entry flag bits and the 24-byte "shared activity" sub-records have no
offline-resolvable semantics, so they are sent 0/empty (structure honest, values zeroed).
System:
- RecentAlliesMgr: query-on-demand, DB-backed (no session cache) - RecordGrouping (mutual,
both directions), GetAllies (most-recent-first, capped 100), SetNote, and the persisted
Set/GetAllowSeeLocation toggle.
- Tracking hook in Group::AddMember: on joining a (non-BG) party/raid, records the new
member and every other online member as mutual recent allies.
- Two tables (character_recent_allies + character_recent_ally_settings) with base schema +
update SQL, and five CharacterDatabase prepared statements.
- RecentAllyHandler: the three CMSG handlers; SET_NOTE echoes SMSG_RECENT_ALLY_NOTE_UPDATED.
On CMSG_ACCEPT_WARGAME_INVITE acceptance, actually start the war game instead of
just echoing the response: create the chosen battleground/arena, queue both premade
groups onto forced opposing teams (challenger ALLIANCE, opponent HORDE), and invite
them in. Players then port via the existing NeedConfirmation -> CMSG_BATTLEFIELD_PORT
-> SendToBattleground handshake, exactly like a normal queue pop.
Built against the rated-arena start path (BattlegroundQueue::Update: CreateNewBattleground
-> AddGroup -> InviteGroupToBG -> StartBattleground), but with no matchmaking since both
groups are known up front. Validates the recorded challenge's BattlemasterListID against
sBattlemasterListStore, resolves the level bracket from the challenger, and derives the
arena team size from the challenging group (0 for battlegrounds).
- BattlegroundQueue: new public AddWargameSide(leader, group, bg, bracketEntry, team) that
queues a whole premade side onto a forced team, registers each member's queue slot, and
fires the enter-confirmation (wraps the private InviteGroupToBG; keeps its visibility).
- BattleGroundHandler: HandleAcceptWargameInvite builds the instance and queues both sides.
War Games (arranged PvP practice matches between two premade groups) were entirely unhandled: all four
CMSG were Handle_NULL and TC never constructed the SMSG, despite the queue infrastructure
(BattlegroundQueueIdType::Wargame, Battleground::Wargame type, ERR_WARGAME_* results) already existing.
This builds the full challenge handshake:
- CMSG_START_WAR_GAME: a party leader challenges another group. Validates leadership, resolves the opposing
group + its leader, records the pending request, and sends SMSG_CHECK_WARGAME_ENTRY to the opposing leader
plus SMSG_WARGAME_REQUEST_SUCCESSFULLY_SENT_TO_OPPONENT back to the challenger.
- CMSG_ACCEPT_WARGAME_INVITE: the opposing leader answers. Correlates against the recorded challenge and
replies to the challenger with SMSG_WARGAME_REQUEST_OPPONENT_RESPONSE (accepted/declined).
Wire recovered byte-exact from the 12.0.7 client:
- CMSG_START_WAR_GAME (send serializer sub_7FF72906F9F0): PackedGUID OpposingPartyMember, uint32, uint16,
uint64 QueueID, trailing bit TournamentRules. (The flat opcode-layout tool under-reported this as guid+u32+bit;
decompiling the serializer recovered the additional uint16 + uint64.)
- CMSG_ACCEPT_WARGAME_INVITE (sub_7FF72906FC80): PackedGUID OpposingPartyMember, uint64 QueueID, trailing bit.
- SMSG_CHECK_WARGAME_ENTRY (client deserializer sub_7FF729086A80): PackedGUID, uint64 QueueID, uint64 Time,
bit7-of-a-byte TournamentRules (written as an explicit MSB-first byte, matching the client's >>7 read).
Field48/52 in the challenge (chosen battleground selection) are named by inference from wire position and are
read to consume the packet; only OpposingPartyMember/QueueID/TournamentRules drive P0 logic. The challenge
registry is in-memory and ephemeral (no persistence), keyed by the opposing leader; handlers run on the world
thread so no locking is needed. P1 (queue both groups into a wargame instance on acceptance) is left documented.