# R10 — Quest System, NPC Dialogs, and Emote Scripts **Status:** Deep-dive research, 2026-04-18. Author: R10 research agent. **Scope:** Everything that connects a player talking to an NPC with a change in the player's persistent state — quest flags, quest timers, emote scripts, item turn-ins, dialog text, and the client-side rendering of all of the above. Every AC-specific behavior in this document is anchored to one of: - `docs/research/decompiled/` — decompiled retail client (ground truth for client-side behavior) - `references/ACE/Source/ACE.Server/` — ACEmulator server (authority on server-side quest / emote evaluation and on the wire protocol the server sends to the client) - `references/DatReaderWriter/` + `references/ACE/Source/ACE.DatLoader/` — the dat files the client reads (`ChatEmoteData`, `Contract`, `ContractTable`) - `references/holtburger/crates/holtburger-protocol/` — Rust reference client's packed/unpacked byte layouts (cross-check on the wire format for `Tell`, `HearSpeech`, `EmoteText`, `SoulEmote`, `PopupString`) The short summary: AC has no "quest system" in the sense a modern MMO has. Quests are a *convention on top of emote scripts*. Every "quest" that exists in retail is expressed as a tree of emote actions hanging off weenies (NPCs, items, portals), together with entries in a world-wide `Quest` table (min delta / max solves) and a per-character quest registry (last completed time, number of completions). The "quest tracker" UI is not an independent quest system; it is a presentation layer over the `Contract` dat table, which in turn points at plain quest-flag names that the emote engine reads and writes. Everything comes back to the emote engine. --- ## 1. Quest flag system ### 1.1 Per-character storage A character's quest state is a set of `CharacterPropertiesQuestRegistry` rows (see `references/ACE/Source/ACE.Database/Models/Shard/CharacterPropertiesQuestRegistry.cs`): ```csharp public partial class CharacterPropertiesQuestRegistry { public uint CharacterId { get; set; } // character GUID public string QuestName { get; set; } // e.g. "arwicemayortalk" public uint LastTimeCompleted { get; set; } // Unix time (seconds) public int NumTimesCompleted { get; set; } // solve count } ``` Key observations: - **One row per (character, questName).** "Quest flag" in retail terminology means a row in this table. An NPC checks "does the player have quest X?" by asking "does a row with questName=X exist for this char?" - **`NumTimesCompleted` is overloaded.** For a simple stage flag it is treated as a 0/non-zero presence test. For a kill-task or kill-counter (e.g. "GolemKillTask@#kt"), it's a literal count. For a "bit-field" quest flag, the integer is a 32-bit bitmask and emotes manipulate specific bits via `SetQuestBitsOn` / `SetQuestBitsOff` / `InqQuestBitsOn` / `InqQuestBitsOff` (see section 3). That's the "BitField (bool quest flags)" notion — a single `NumTimesCompleted` row can store up to 32 boolean sub-flags. - **`LastTimeCompleted`** drives the "quest stamp" — timestamp + cooldown. Combined with the world-quest-table `MinDelta`, it answers "can the player solve this quest again yet?" ### 1.2 World quest table (server-only) The world (not per-character) has a `Quest` table (`references/ACE/Source/ACE.Database/Models/World/Quest.cs`): ```csharp public partial class Quest { public uint Id { get; set; } public string Name { get; set; } // e.g. "arwicemayortalk" public uint MinDelta { get; set; } // seconds between solves public int MaxSolves { get; set; } // -1 = unlimited, >0 = cap public string Message { get; set; } // unused in live } ``` Only the server needs this table. The client is never shown `MinDelta` or `MaxSolves` directly — it sees them via the emote-engine substitutions `%tqt` (time-to-solve), `%tqm` (max solves), `%tqc` (current solves) baked into NPC dialog strings (see section 3.4 "string replacement"), and via the Contract-tracker UI (section 9). ### 1.3 What the client actually stores **The retail client never stores quest flags.** Quest state lives entirely server-side. The client only learns about a quest: - Indirectly, via NPC dialog strings (Tell/Say/TextDirect) whose text has been server-side-formatted with `%tqc`/`%tqt` etc. - Directly, via the Contract tracker messages (`SendClientContractTrackerTable` 0x0314 and `SendClientContractTracker` 0x0315). These carry a ContractId + stage + time-when-done / time-when-repeats; they *do not* send the raw quest flag name or completion count. See section 9. - As error toasts — e.g. "You have solved this quest too recently!" (decompiled at `chunk_00570000.c:1629`, game-event case `0x43e`). This is a generic error string, the client does not know the quest name that was involved. So: for acdream, we do not need a client-side QuestRegistry store. We need: (a) a Contract tracker store (short-lived, server pushes full state), and (b) a generic chat log that can render Tell/Say/EmoteText and the embedded `NAME<\Tell>` clickable-name markup. ### 1.4 Encoding in the save blob "Save blob" here means the shard-database row, not a client-side file. The client has no save file for quest state. Server-side, see the QuestSQLWriter at `references/ACE/Source/ACE.Database/SQLFormatters/World/QuestSQLWriter.cs` for world quests and plain EF models for per-character quests. acdream's server (Phase 7+) would follow ACE's shape. --- ## 2. Quest manager — opcodes and wire events The server never sends a "quest updated" push to the client in the generic case. Instead, two categories of server → client message carry quest-related information: ### 2.1 Generic "game event" errors When a quest-gated action fails, the server sends a `GameEventWeenieError` (opcode 0x028A) or `GameEventInventoryServerSaveFailed` (opcode 0x00A0) with a WeenieError code. The client maps the code to a hard-coded wide string and displays it in chat. The decompiled client's dispatcher is in `chunk_00570000.c` around line 1500–2900. Selected cases: | Code | String | Source | |--------|------------------------------------------------------------------------------|--------| | 0x043E | `You have solved this quest too recently!\n` | `chunk_00570000.c:1629` | | 0x043F | `You have solved this quest too many times!\n` | `chunk_00570000.c:1633` | | 0x0445 | `This item requires you to complete a specific quest before you can pick it up!\n` | `chunk_00570000.c:1637` | | 0x0474 | `You must complete a quest to interact with that portal.\n` | `chunk_00570000.c:1673` | | 0x0555 | `You must purchase Asheron's Call -- Throne of Destiny to access this quest.`| `chunk_00570000.c:2811` | These are purely display. There is no structured "quest state" in them. ### 2.2 Contract tracker events (the real "quest tracker" UI) Two opcodes in the `GameEventType` enum (`references/ACE/Source/ACE.Server/Network/GameEvent/GameEventType.cs`): - `SendClientContractTrackerTable = 0x0314` — full replacement of the tracker panel. Body is a packed list of ContractTracker structs. - `SendClientContractTracker = 0x0315` — single contract update (added / changed / deleted). Body: one ContractTracker struct + `DeleteContract` bool + `SetAsDisplayContract` bool. Structure written by server (`ContractTracker.Write` at `references/ACE/Source/ACE.Server/Network/Structure/ContractTracker.cs:137`): ```text uint32 Version // Contract.Version from dat uint32 ContractId // key into ContractTable uint32 Stage // ContractStage enum, see below double TimeWhenDone // seconds until current cooldown ends double TimeWhenRepeats // seconds until repeat cooldown ends ``` `ContractStage`: ```csharp Available = 0x1, InProgress = 0x2, DoneOrPendingRepeat = 0x3, ProgressCounter = 0x4, // add N for N progress steps done ``` The ContractTracker is the client's only structured view of any quest-like state. It does NOT contain the quest flag name itself — the flag name is in the Contract dat (see section 9), keyed by ContractId. The client looks up the Contract in `ContractTable`, reads `QuestflagStarted`, `QuestflagFinished`, etc., and uses those to decorate the UI. It does not read or write quest flags directly. ### 2.3 Quest flag write notifications There is no such opcode. Quest flags are written server-side in `EmoteManager.ExecuteEmote` via `QuestManager.Update` / `SetQuestCompletions` / `Erase` etc. The only notification the client receives is: 1. A contract tracker refresh (if the changed quest flag is referenced by a Contract in the dat). 2. Any Tell / Say / DirectBroadcast / Sound emote action that happened to be sequenced after the quest write — this is how NPCs "confirm" a quest step to the player. This is why the retail client feels so non-MMO: the server tells the player "you've done it" by scripting the NPC to say "Here is your reward, friend," not by emitting a structured quest-progress event. --- ## 3. Emote system — the retail mini-language An "emote" in AC retail terminology is **not** a chat action like `/wave`. It's a scripted response a weenie (NPC, item, portal, generator) can run when some trigger fires. An **emote set** is an ordered list of **emote actions** tagged with a **category** (the trigger) and optional filters (quest name, vendor type, weenie class ID, motion style, health threshold). Player-visible chat-command emotes like `/wave` are a separate thing — they are handled by `GameActionEmote` (opcode 0x01E0) and `GameActionSoulEmote`, which simply broadcast `GameMessageEmoteText` / `GameMessageSoulEmote` to everyone in range. No scripting involved. ### 3.1 EmoteCategory — the trigger types (39 values) From `references/ACE/Source/ACE.Entity/Enum/EmoteCategory.cs`: | Value | Name | When it fires | |-------|-----------------------------|---------------| | 0 | Invalid | — | | 1 | Refuse | NPC "examines" an item given to it but doesn't consume it | | 2 | Vendor | Vendor window lifecycle events (Open, Close, etc.) | | 3 | Death | The weenie is killed | | 4 | Portal | Player activates a portal (post travel) | | 5 | HeartBeat | Periodic tick (ambient speech, homesickness) | | 6 | Give | Player gives the NPC an item (quest turn-in) | | 7 | Use | Player right-clicks / uses the weenie | | 8 | Activation | Chained Activate from a switch/lever | | 9 | Generation | A generator spawns something | | 10 | PickUp | Item is picked up | | 11 | Drop | Item is dropped | | 12 | QuestSuccess | InqQuest / InqMyQuest / InqFellowQuest → true | | 13 | QuestFailure | same → false | | 14 | Taunt | Combat alert chatter | | 15 | WoundedTaunt | Taunt filtered by health range | | 16 | KillTaunt | Fires when this creature kills a player | | 17 | NewEnemy | First acquires a player target | | 18 | Scream | Panic chatter | | 19 | Homesick | Displaced from home | | 20 | ReceiveCritical | Took a crit hit | | 21 | ResistSpell | Resisted a magic attack | | 22 | TestSuccess | Inq*Stat passed | | 23 | TestFailure | Inq*Stat failed | | 24 | HearChat | Nearby player spoke a keyword | | 25 | Wield | Item equipped | | 26 | UnWield | Item unequipped | | 27 | EventSuccess | InqEvent → started | | 28 | EventFailure | InqEvent → not started | | 29 | TestNoQuality | Inq*Stat on a stat the target doesn't have | | 30 | QuestNoFellow | InqFellowQuest when player has no fellowship | | 31 | TestNoFellow | same semantics for arbitrary tests | | 32 | GotoSet | Named sub-routine target for `Goto` | | 33 | NumFellowsSuccess | InqFellowNum passed | | 34 | NumFellowsFailure | InqFellowNum failed | | 35 | NumCharacterTitlesSuccess | InqNumCharacterTitles passed | | 36 | NumCharacterTitlesFailure | same failed | | 37 | ReceiveLocalSignal | LocalSignal broadcast received | | 38 | ReceiveTalkDirect | Someone used /tell keyword on this NPC | ### 3.2 EmoteType — the action types (122 values) From `references/ACE/Source/ACE.Entity/Enum/EmoteType.cs`. This is the instruction-set of the emote mini-language. Grouped by purpose: **Speech & UI output:** - `Say (8)` — NPC speaks, broadcast to all nearby (HearSpeech / HearRangedSpeech) - `Tell (10)` — NPC whispers directly to the player (GameEventTell 0x02BD) - `Act (1)` — "acting" text, broadcast system chat ("Bob waves at you") - `TextDirect (13)` — direct broadcast to player's chat window (GameMessageSystemChat) - `DirectBroadcast (18)` — same as TextDirect, reliable - `LocalBroadcast (17)` — broadcast in area - `WorldBroadcast (16)` — broadcast to all players on server - `FellowBroadcast (65)` — broadcast to player's fellowship - `TellFellow (64)` — Tell delivered to all fellowship members - `AdminSpam (26)` — log-channel broadcast - `BLog (25)` — server log (not client-visible) - `PopUp (68)` — modal popup window (GameEventPopupString 0x0004) - `Sound (9)` — play a sound effect (GameMessageSound) - `PhysScript (7)` — particle / visual effect (GameMessagePlayParticleEffect) **Motion & movement:** - `Motion (5)` — play animation on this NPC - `ForceMotion (52)` — play animation on the player target - `Move (6)` — walk to home-relative offset - `MoveHome (4)` — walk to home position - `MoveToPos (87)` — walk to absolute position - `Turn (11)` — turn to heading - `TurnToTarget (12)` — face the player - `ResetHomePosition (57)` — snap home to current - `SetSanctuaryPosition (63)` — store player's /recall target **Quest flag ops (the core of quest scripting):** - `UpdateQuest (20)` / `UpdateMyQuest (79)` — add quest if new, stamp if old, branch on success - `InqQuest (21)` / `InqMyQuest (80)` / `InqFellowQuest (58)` — branch on "has and is on cooldown" - `StampQuest (22)` / `StampMyQuest (81)` / `StampFellowQuest (61)` — update LastTimeCompleted (unconditional) - `IncrementQuest (33)` / `IncrementMyQuest (85)` — NumTimesCompleted += amount - `DecrementQuest (32)` / `DecrementMyQuest (84)` — NumTimesCompleted -= amount - `EraseQuest (31)` / `EraseMyQuest (83)` — delete the quest row entirely - `SetQuestCompletions (70)` / `SetMyQuestCompletions (86)` — overwrite NumTimesCompleted - `InqQuestSolves (30)` / `InqMyQuestSolves (82)` — branch on N in [min, max] - `InqQuestBitsOn (102)` / `InqQuestBitsOff (103)` / `InqMyQuestBitsOn (104)` / `InqMyQuestBitsOff (105)` — branch on bitmask - `SetQuestBitsOn (106)` / `SetQuestBitsOff (107)` / `SetMyQuestBitsOn (108)` / `SetMyQuestBitsOff (109)` — OR / AND-NOT bits - `InqFellowNum (59)` — branch on fellowship size - `UpdateFellowQuest (60)` — fellowship-wide UpdateQuest The `MyQuest` variants target the creature executing the emote (NPC-local state); the non-`My` variants target the player interacting with it. This is how a single statue can "remember" that it has been touched by this specific player without flooding the central quest table. **Stat inquiries (all branch into TestSuccess / TestFailure / TestNoQuality):** - `InqIntStat (36)`, `InqInt64Stat (114)`, `InqFloatStat (37)`, `InqBoolStat (35)`, `InqStringStat (38)` - `InqAttributeStat (39)` / `InqRawAttributeStat (40)` - `InqSecondaryAttributeStat (41)` / `InqRawSecondaryAttributeStat (42)` (vitals) - `InqSkillStat (43)` / `InqRawSkillStat (44)` / `InqSkillTrained (45)` / `InqSkillSpecialized (46)` - `InqNumCharacterTitles (71)` - `InqPackSpace (89)` — branch on free inventory slots - `InqOwnsItems (76)` — branch on "player has WCID × N" - `InqContractsFull (121)` - `InqEvent (51)` — branch on world event started - `InqYesNo (75)` — raise a confirmation dialog (section 5) **Stat writes:** - `SetBoolStat (69)`, `SetIntStat (53)`, `SetInt64Stat (115)`, `SetFloatStat (118)` - `IncrementIntStat (54)` / `DecrementIntStat (55)` **Awards:** - `AwardXP (2)`, `AwardNoShareXP (62)`, `AwardLevelProportionalXP (49)` - `AwardSkillXP (28)`, `AwardSkillPoints (29)`, `AwardLevelProportionalSkillXP (50)` - `AwardLuminance (113)` / `SpendLuminance (112)` - `AwardTrainingCredits (47)` - `AddCharacterTitle (34)` - `TeachSpell (27)` — add a spell to player's spellbook - `AddContract (119)` / `RemoveContract (120)` — add/remove a quest-tracker entry **Actions on the weenie itself:** - `Generate (72)` — trigger a generator to spawn - `CreateTreasure (56)` — spawn loot into player's pack - `Give (3)` — give a specific WCID item to the player - `TakeItems (74)` — consume items from player's pack - `DeleteSelf (77)` — remove the weenie - `KillSelf (78)` — smite the weenie - `OpenMe (116)` / `CloseMe (117)` — container/door - `Activate (15)` — chain-activate linked object - `CastSpell (14)` / `CastSpellInstant (19)` / `PetCastSpellOnOwner (73)` **Teleport / positioning:** - `Goto (67)` — jump to named sub-set within same emote-set (sub-routine call) - `StartEvent (23)` / `StopEvent (24)` — world event control - `LocalSignal (88)` — broadcast signal that other weenies' `ReceiveLocalSignal` emotes can listen for - `TeleportSelf (100)` — NPC teleports (unused in retail) - `TeleportTarget (99)` — player teleports - `InflictVitaePenalty (48)` / `RemoveVitaePenalty (90)` **Character appearance (barber):** - `SetEyeTexture (91)` / `SetEyePalette (92)` / `SetNoseTexture (93)` / `SetNosePalette (94)` / `SetMouthTexture (95)` / `SetMouthPalette (96)` / `SetHeadObject (97)` / `SetHeadPalette (98)` - `StartBarber (101)` — open the barber UI - `SetAltRacialSkills (111)` — for race-change flows **Misc:** - `LockFellow (66)` — lock fellowship roster - `UntrainSkill (110)` - `Invalid (0)` — no-op - `Enlightenment (9001)` — ACE-custom (not retail) ### 3.3 PropertiesEmote / PropertiesEmoteAction shape Source: `references/ACE/Source/ACE.Entity/Models/PropertiesEmote.cs` and `PropertiesEmoteAction.cs`. Schema: ```csharp PropertiesEmote { EmoteCategory Category; float Probability; // 0..1, roll per set uint? WeenieClassId; // filter for Give/Refuse/Taunt MotionStance? Style; // filter for HeartBeat MotionCommand? Substyle; string Quest; // filter — only fire if quest flag matches VendorType? VendorType; // filter for Vendor category float? MinHealth; // filter for WoundedTaunt float? MaxHealth; List PropertiesEmoteAction; // the script } PropertiesEmoteAction { uint Type; // EmoteType float Delay; // pre-delay seconds float Extent; // motion/speech parameter MotionCommand? Motion; string Message; // speech text, quest name, or emote category for Goto string TestString; // for InqStringStat, InqYesNo prompt text int?/long?/double? Min/Max; // Inq ranges int? Stat; // PropertyInt/etc. key for Inq/Set int? Amount; // integer arg long? Amount64; // 64-bit arg double? Percent; int? SpellId; PlayScript? PScript; Sound? Sound; uint? WeenieClassId; // for Give/TakeItems int? StackSize; int? Palette; // for Give float? Shade; uint? ObjCellId; // for TeleportTarget/MoveToPos/SetSanctuaryPosition float? OriginX/Y/Z, AnglesX/Y/Z/W; ... } ``` ### 3.4 String replacement — `%n`, `%s`, `%tqt`, etc. From `EmoteManager.Replace` (line 1777 of EmoteManager.cs): | Token | Replaced with | |----------|---------------| | `%n` / `%mn` | sender name (this NPC) | | `%s` / `%tn` | target name (the player) | | `%ml` / `%tl` | sender / target level | | `%mt` / `%tt` | sender / target Template property | | `%mh` / `%th` | sender / target heritage name | | `%tqt` | time-until-next-solve for the active quest (target) | | `%CDtime` | same (LSD custom) | | `%fqt` | fellowship time-until-next-solve | | `%tqm` | target max-solves | | `%tqc` | target current-solves | | `%mqt` | source (NPC) quest-cooldown | These let the content author write NPC speech like `"I said no, %s. Come back in %tqt."` and have it rendered dynamically per-player. This is the only structured quest-state information that ever actually reaches the client — baked into speech text. ### 3.5 Emote execution model Flow: `ExecuteEmoteSet(category, quest, target)` → `GetEmoteSet(...)` picks a random set (filtered by category + quest + vendor + wcid + style, weighted by Probability) → `Enqueue(set, target, 0)` runs the action list in order. Each action can return a delay; the next action is scheduled after that delay + its own pre-delay. Branching emotes (Inq*, Update*, Goto) recursively call `ExecuteEmoteSet` on a new category (`TestSuccess`/`TestFailure`/`QuestSuccess`/`QuestFailure`/`GotoSet`), which is how conditionals and subroutines are expressed. Busy-state protection: `EmoteManager.IsBusy` prevents a second trigger from starting while one is running; `Nested` counts recursion depth; `Nested > 75` + self-referential emote aborts with a log error (infinite loop detection). The 75-deep cap is why some retail quests bail out silently when misconfigured. --- ## 4. NPC dialog flow ### 4.1 Click the NPC On the client side, right-clicking an NPC or selecting it and hitting Use runs the Use code at `chunk_00580000.c:5890+`. That function eventually emits a `GameActionUseRequest` (opcode 0x0035 per ACE's `GameActionType.cs`). On the server, that lands in the creature's `OnUse` handler, which calls `EmoteManager.OnUse(player)` — which in turn calls `ExecuteEmoteSet(EmoteCategory.Use, null, player)` (see `EmoteManager.cs:1916`). ### 4.2 The server evaluates a Use emote set `GetEmoteSet(EmoteCategory.Use, ...)` picks one of the NPC's Use sets (possibly filtered by quest flag — e.g. "only show first-meeting speech to players who don't have `metArwicMayor`"), rolling against `Probability`. The picked set's action list is enqueued. Typical first-meeting conversation script: ``` EmoteSet {Category=Use, Quest=null, Probability=1.0} Act: "%n turns to %s." TurnToTarget Motion: Bow Tell: "Hello, %s. I don't believe I've had the pleasure. I am Mayor Arwic." UpdateQuest: "metArwicMayor" ``` After this fires, the NPC has "remembered" this player. A second Use set (filtered `Quest="metArwicMayor"`) holds the follow-up dialog. ### 4.3 Dialog rendering on the client The server sends each Say/Tell/TextDirect as its own wire message. The client has no dialog-box widget — it simply appends each line to the chat window with the right `ChatMessageType` colour. The "NPC dialog window" you remember from playing AC is in fact a chat pane, filtered on NPC messages. Visible NPC speech goes through the `NAME<\Tell>` clickable-name markup when the sender is a player (GUID range `0x50000001 – 0x6FFFFFFF`). For an NPC (GUID outside that range) the format is plain `%s tells you, "%s"\n` with no clickable link (`chunk_00570000.c:1190-1195`): ```c if (sender_guid < 0x50000001 || sender_guid > 0x6FFFFFFF) { FUN_00402710(&out, "%s tells you, \"%s\"\n", name, message); } else { FUN_00402710(&out, "%s<\\Tell> tells you, \"%s\"\n", sender_guid, name, name, message); } ``` This means in acdream we render NPC dialog as normal chat lines with colour = `ChatMessageType.Tell` (0x03 — yellow) and no clickable name. For player-to-player tells we emit the clickable span. ### 4.4 Busy-state & client behaviour While the NPC's EmoteManager is running a set, all further Use and Give attempts are rejected server-side (`Player_Inventory.cs:3404` for Give, similar for Use) with `WeenieErrorWithString.AiRefuseItemDuringEmote`. The client shows " is busy right now.". That's the only "dialog window modal" indication — a soft refusal, not a UI lock. --- ## 5. Use / Appraise triggers - **Use** (`EmoteCategory.Use`) — fires on `OnUse`. Any weenie with a Use emote set is "talkable"/"interactable". Items with Use sets are single-use consumables that can trigger scripts (e.g. "use this book to start a quest"). - **Appraise** — technically the *client* action; the server responds with `GameEventIdentifyObjectResponse` (0x00C9). There is no EmoteCategory.Appraise. If you want an NPC to react to being appraised, the closest pattern is to key off a `HearChat` emote with the NPC's own name — not reliable — or to cheat and trigger from an `InqYesNo`. Retail quests do not depend on appraisal. - **Activation** (`EmoteCategory.Activation`) — fires when a switch is flipped, chained via `OnActivate`. Used by quest puzzle switches. - **Portal** (`EmoteCategory.Portal`) — fires after a portal teleport succeeds. Used for warning/greeting text post-travel. - **HearChat** (`EmoteCategory.HearChat`) — fires when a nearby player speaks a keyword. Matched by literal string match on `Quest` field. This is how "say the magic word to the door" puzzles work. - **ReceiveTalkDirect** (`EmoteCategory.ReceiveTalkDirect`) — fires when a player sends a `/tell` to this NPC containing a keyword. This is the "text-input quest" pattern. --- ## 6. Quest turn-in: dragging an item onto an NPC Wire path: 1. Client sends `GameActionGiveObjectRequest` (`GameActionType.GiveObjectRequest` ≈ 0x00EA). Payload: `uint32 targetGuid, uint32 objectGuid, int32 amount`. Source: `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionGiveObjectRequest.cs`. 2. Server routes to `Player.HandleActionGiveObjectRequest` (`Player_Inventory.cs:3190`). Validates the player isn't busy / teleporting / trading the item, locates both objects, and walks the player to the target via `CreateMoveToChain`. 3. On arrival, calls `GiveObjectToNPC(target, item, ...)` (`Player_Inventory.cs:3380`). 4. Target looks for a matching emote set: ```csharp var refuseItem = EmoteManager.GetEmoteSet(EmoteCategory.Refuse, null, null, item.WeenieClassId); var giveItem = EmoteManager.GetEmoteSet(EmoteCategory.Give, null, null, item.WeenieClassId); ``` (Note: these sets are filtered by `WeenieClassId` — the emote "matches" the item's WCID exactly. Generic "I accept anything" is expressed via the `AiAcceptEverything` bool property on the weenie.) 5. If Give emote matches: remove the item from the player's inventory, send chat: `"You give ."`, broadcast a `ReceiveItem` sound on the NPC, and run the emote set (`ExecuteEmoteSet(emoteResult, this)`). The emote set typically contains: `StampQuest`, `AwardXP`, `Give` (return reward), `Tell` ("Many thanks, %s."), etc. 6. If Refuse emote matches: NPC "examines" the item (it is returned), chat: `"You allow to examine your ."`, and the Refuse emote runs — typically just a `Tell` explaining why it's not accepted. This is how NPCs react to items that are "close but not quite" quest items. 7. If neither: `WeenieError.TradeAiDoesntWant` — generic "not accepting gifts right now." Critical point: **the NPC's Give emote is the quest turn-in.** There is no separate "quest turn-in" handler. `StampQuest`/`UpdateQuest` inside the Give emote writes the quest flag; `AwardXP`/`Give` distributes the reward; a final `Tell` gives narrative closure. The sequence is not transactional — if the server crashes between steps, quest state can become inconsistent. Retail accepted this. --- ## 7. Branching dialogs NPCs with multiple stages of a questline expose this by having multiple Use emote sets, each filtered by `Quest`. `GetEmoteSet` iterates the NPC's PropertiesEmote list, filters by category + quest + vendor + wcid + style/substyle/health, then applies `Probability`-weighted random selection. Typical multi-stage pattern: ``` EmoteSet {Use, Quest=null, Prob=1.0} // first meet Tell "Hello, traveller. Talk to me about the %s ring." UpdateQuest "arwicMayorQuest" // phase 1 stamp EmoteSet {Use, Quest="arwicMayorQuest", Prob=1.0} // post-first-meet InqQuest "arwicMayorQuestDone" // has player done the task? ↓ QuestSuccess set → Tell "You're back! Good, good." ↓ QuestFailure set → Tell "Have you not found it yet?" EmoteSet {Give, WCID=, Prob=1.0} // turn-in StampQuest "arwicMayorQuestDone" Tell "Excellent! Here is your reward." AwardXP 50000 Give ``` A single NPC can have dozens of Use/Give sets — the emote engine is the entire dialog tree. In principle content authors could use `Goto` to build larger branching trees, and `InqYesNo` to add confirmation popups, but retail kept it simple: Use emotes were almost always straight-line scripts. --- ## 8. Random / ambient emotes NPCs play ambient speech via `HeartBeat` (category 5) emote sets. These tick periodically (`EmoteManager.HeartBeat` at `EmoteManager.cs:1904`) while the NPC isn't actively engaged. Implementation filters by current motion stance/substyle so that e.g. a sitting NPC only plays sitting emotes. Retail shopkeepers have multi-line HeartBeat sets — `Say` followed by a `PhysScript` gesture — rolled against Probability. `HeartBeat` does not fire on player characters (early-return in `HeartBeat()` method). It does not fire on "awake" creatures (those with an active combat target); `WoundedTaunt`, `Taunt`, etc. take over in combat. `ReceiveCritical`, `ResistSpell`, `NewEnemy`, `Scream`, `Homesick` are similar background triggers, each driven server-side from the relevant combat/movement event handlers. --- ## 9. Quest Tracker UI panel The retail client has a **Contract Tracker** panel (not a "Quest Tracker"). It displays a list of Contracts, each showing: name, description, progress, and timers. Contracts are entries in the ContractTable dat file (`DatReaderWriter.Types.Contract`): Fields: ``` uint32 Version uint32 ContractId string ContractName // e.g. "Aerlinthe Recall Ring Quest" string Description // long-form text string DescriptionProgress // "You have defeated N of M..." string NameNPCStart // NPC that offers the quest string NameNPCEnd // NPC that takes turn-in string QuestflagStamped // flag set by first-meeting stamp string QuestflagStarted // flag set by acceptance string QuestflagFinished // flag set by completion (terminates) string QuestflagProgress // flag whose NumTimesCompleted is the counter string QuestflagTimer // flag whose cooldown feeds TimeWhenDone string QuestflagRepeatTime // flag whose cooldown feeds TimeWhenRepeats Position LocationNPCStart Position LocationNPCEnd Position LocationQuestArea ``` The server-side `ContractTracker` struct (`references/ACE/Source/ACE.Server/Network/Structure/ContractTracker.cs`) builds the live state: it looks up each of the Questflag* names in the player's QuestRegistry, translates that into a Stage + two doubles (`TimeWhenDone`, `TimeWhenRepeats`), and writes the packed struct into `SendClientContractTracker` (0x0315) or `SendClientContractTrackerTable` (0x0314). On the client, the panel: - Looks up the Contract by ContractId in the ContractTable dat. - Renders `ContractName` as the list item title. - Renders `Description` (or `DescriptionProgress` if `Stage == ProgressCounter + N`) as the body. - Shows a timer if `TimeWhenDone > 0` or `TimeWhenRepeats > 0`. - Clicking the NPC-name rows lets the player see NPC location on the in-game map. The panel is additive/replacement: a single `SendClientContractTracker` message with `DeleteContract=true` removes an entry; otherwise the entry's state is updated. Full refresh arrives via `SendClientContractTrackerTable` (e.g. after login). The client learns about *new* Contracts via the `AddContract` emote action (EmoteType 119), which server-side writes a `CharacterPropertiesContractRegistry` row and pushes a `SendClientContractTracker` to the client. --- ## 10. Wire messages — byte layouts All strings are String16-L: `uint16 length` then UTF-8 bytes, then padded to 4-byte alignment. All integers and floats are little-endian. Where noted, the "Game Event" wrapper adds a leading `uint32 event_type` before the payload described below. ### 10.1 Server → client **GameEventTell (0x02BD)** — NPC tell / player tell: ``` string16L message string16L senderName uint32 senderID // GUID uint32 targetID // GUID uint32 chatType // ChatMessageType uint32 reserved // always 0, observed in retail pcaps ``` ACE: `GameEventTell.cs`. holtburger: `TellEventData` (`crates/holtburger-protocol/src/messages/chat/events.rs:7`). **GameEventPopupString (0x0004)** — modal popup window: ``` string16L message ``` ACE: `GameEventPopupString.cs`. **GameMessageHearSpeech (0x02BB)** — NPC says something locally: ``` string16L message string16L senderName uint32 senderID uint32 chatType ``` **GameMessageHearRangedSpeech (0x02BC)** — Say with explicit range: ``` string16L message string16L senderName uint32 senderID float32 range uint32 chatType ``` **GameMessageEmoteText (0x01E0)** — acting text ("NPC waves"): ``` uint32 senderID string16L senderName string16L emoteText ``` **GameMessageSoulEmote (0x01E2)** — soul-emote (out-of-character tone): ``` uint32 senderID string16L senderName string16L emoteText ``` **GameMessageSystemChat (server message frame 0xF7E0)** — plain text to chat: ``` string16L message int32 chatMessageType ``` **GameEventWeenieError (0x028A)** — typed error: ``` uint32 errorCode // WeenieError enum (e.g. 0x43E YouHaveSolvedThisQuestTooRecently) ``` **GameEventInventoryServerSaveFailed (0x00A0)** — item-related error: ``` uint32 itemGuid uint32 errorCode ``` **GameEventConfirmationRequest (0x0274)** — yes/no dialog: ``` uint32 confirmationType uint32 context // server-chosen correlation id string16L prompt ``` **GameEventSendClientContractTracker (0x0315)** — single contract update: ``` uint32 version uint32 contractId uint32 stage // ContractStage double timeWhenDone double timeWhenRepeats uint32 deleteContract // bool (0/1) uint32 setAsDisplayContract ``` **GameEventSendClientContractTrackerTable (0x0314)** — bulk refresh: ``` uint32 numContracts ContractManager.Write(...) // packed list; see Structure/ContractManager.cs ``` The inner loop writes each ContractTracker without the trailing two bools (`ContractTracker.Write` variant at ContractTracker.cs:137). ### 10.2 Client → server **GameActionEmote (0x01E0-ish, see GameActionType.Emote)** — `/emote` chat command: ``` string16L emoteText ``` Source: `GameActionEmote.cs`. The server re-broadcasts via `GameMessageEmoteText` to everyone in LocalBroadcastRange. **GameActionSoulEmote (GameActionType.SoulEmote)** — `/soul` chat command: ``` string16L emoteText ``` **GameActionGiveObjectRequest (GameActionType.GiveObjectRequest)** — drag item to NPC: ``` uint32 targetGuid uint32 objectGuid int32 amount ``` **GameActionTellRequest (GameActionType.TellRequest)** — player `/tell` to another player or NPC: ``` string16L message string16L targetName ``` (Server route: TellFromTargetNameHandler.cs.) For NPC tells (quest keyword dialogs), this is what triggers `EmoteCategory.ReceiveTalkDirect` emotes on the NPC. **GameActionConfirmationResponse (GameActionType.ConfirmationResponse)** — yes/no reply: ``` uint32 confirmationType uint32 context uint32 response // 0=no, 1=yes ``` ### 10.3 Where these fit in the decompiled dispatch table `chunk_00550000.c` around line 10700 is the client's incoming game-event switch. Each opcode maps to a concrete handler function: - 0x01E0 EmoteText → `FUN_006a5a20` - 0x02BB HearSpeech → handler (not shown; in same table) - 0x02BC HearRangedSpeech → handler - 0x02BD Tell → `FUN_006a5920` (chunk_006A0000.c:4991) - 0x0314 ContractTrackerTable → handler in same switch - 0x0315 ContractTracker → handler in same switch Cross-referencing holtburger's `events.rs` gives clean packed layouts for most of these; ACE's `Source/ACE.Server/Network/GameEvent/Events/` gives write-side layouts. The two agree on all byte offsets. --- ## 11. Port plan — C# classes for acdream Target layer: `AcDream.Core` (state + DTOs) + `AcDream.App/UI` (panels) + `AcDream.Core.Net/Messages` (wire). ### 11.1 Core types ```csharp // AcDream.Core/Quests/QuestState.cs // Client-side mirror of what we know about one quest's state from server // pushes. We only track it if a Contract references it. public sealed record QuestState( string QuestName, uint LastTimeCompleted, // Unix seconds; 0 = never int NumTimesCompleted, uint MinDelta, // from world Quest table snapshot if known int MaxSolves); // AcDream.Core/Quests/ContractTracker.cs public enum ContractStage { Available = 1, InProgress = 2, DoneOrPendingRepeat = 3, ProgressCounter = 4 } public sealed record ContractEntry( uint ContractId, uint Version, ContractStage Stage, double TimeWhenDone, double TimeWhenRepeats); public interface IContractTracker : IReadOnlyCollection { event Action Added; event Action Updated; event Action Removed; event Action BulkReplaced; } public sealed class ContractTracker : IContractTracker { /* standard observable dict */ } ``` ### 11.2 Emote mini-language types For acdream, the emote engine runs server-side. The client only needs to decode the *outputs* (Tell / Say / EmoteText / Popup / Confirmation / ContractTracker / WeenieError). But because acdream must support plugin scripting (per `project_plugin_requirement.md`), we port the full EmoteType + EmoteCategory enums now so plugins can inspect and inject into the chat stream with the same vocabulary the server uses. ```csharp // AcDream.Core/Emotes/EmoteCategory.cs — same 39 values as EmoteCategory.cs (section 3.1) // AcDream.Core/Emotes/EmoteType.cs — same 122 values as EmoteType.cs (section 3.2) // AcDream.Core/Emotes/EmoteAction.cs public sealed record EmoteAction( EmoteType Type, float Delay, float Extent, int? Motion, string? Message, string? TestString, int? Min, int? Max, long? Min64, long? Max64, double? MinDbl, double? MaxDbl, int? Stat, int? Amount, long? Amount64, double? Percent, int? SpellId, uint? WeenieClassId, int? StackSize, int? Palette, float? Shade, uint? ObjCellId, float? OriginX, float? OriginY, float? OriginZ, float? AnglesX, float? AnglesY, float? AnglesZ, float? AnglesW); // AcDream.Core/Emotes/EmoteSet.cs public sealed record EmoteSet( EmoteCategory Category, float Probability, uint? WeenieClassId, string? Quest, int? Style, int? Substyle, float? MinHealth, float? MaxHealth, IReadOnlyList Actions); // AcDream.Core/Emotes/EmoteScript.cs // Server-driven only on the client side; acdream plugin code can inspect // but not override. Plugin hooks: // - IEmoteObserver.OnEmoteReceived(source, EmoteSet) // from server log stream // - IEmoteObserver.OnChatLine(ChatLine) // rendered form public interface IEmoteScriptService { IObservable ChatLines { get; } // every rendered line IObservable Popups { get; } } ``` ### 11.3 Trigger / action enums reused as strict enums `EmoteCategory` (0..38) and `EmoteType` (0..121 + `Enlightenment=9001`) as plain C# enums. This gives plugins type-safe switches and the Roslyn analyzer can flag missing-case issues if we use exhaustive switches. ### 11.4 UI panels ```csharp // AcDream.App/UI/Panels/ChatPanel.cs // - Appends ChatLine records with colour by ChatMessageType // - Renders NAME<\Tell> spans as // clickable "click to /tell" links // - Filters / tabs (All / Speech / Fellowship / etc.) // - This is where NPC dialog ends up — not a separate dialog window // AcDream.App/UI/Panels/PopupDialogPanel.cs // - Listens to PopupRequest stream // - Modal single-text popup with OK // - Used for EmoteType.PopUp (wire: 0x0004) // AcDream.App/UI/Panels/YesNoDialogPanel.cs // - Listens to ConfirmationRequest stream (0x0274) // - Yes / No buttons post back GameActionConfirmationResponse // - Used for InqYesNo and other confirmation flows // - The context uint32 must round-trip unchanged // AcDream.App/UI/Panels/ContractTrackerPanel.cs // - Bound to IContractTracker // - Row per ContractEntry // - Lookup dat Contract by ContractId // - Title: Contract.ContractName // - Body: Description or DescriptionProgress // - Timer: TimeWhenDone (countdown) or TimeWhenRepeats (cooldown) // - Click NPC name → show Contract.LocationNPCStart/End on in-game map ``` ### 11.5 Wire decoders ```csharp // AcDream.Core.Net/Messages/GameEventTellMessage.cs internal static class GameEventTellMessage { public static TellDto Decode(ref DatReader r) => new( message: r.ReadString16L(), senderName: r.ReadString16L(), senderId: r.ReadUInt32(), targetId: r.ReadUInt32(), chatType: (ChatMessageType)r.ReadUInt32(), reserved: r.ReadUInt32()); } // Repeat shape for: HearSpeech (02BB), HearRangedSpeech (02BC), // EmoteText (01E0), SoulEmote (01E2), PopupString (0004), // WeenieError (028A), WeenieErrorWithString (028B), // ConfirmationRequest (0274), ConfirmationDone (0276), // ContractTrackerTable (0314), ContractTracker (0315), // SystemChat via 0xF7E0 frame. ``` Cross-reference holtburger's `messages/chat/events.rs` and `messages/chat/types.rs` for field order; copy its pack/unpack test vectors into `AcDream.Core.Net.Tests` as conformance tests — this is the only reliable way to catch byte-order / padding regressions before they hit live traffic. ### 11.6 Plugin API surface From `project_plugin_requirement.md`: the plugin API must expose "game state through well-defined interfaces." For R10 that means: ```csharp public interface IQuestState { // For quests known to the client through Contracts (NOT a general // oracle — the client only knows about Contracts). IContractTracker Contracts { get; } // Emitted ChatLines with sender/target GUIDs + ChatMessageType — // lets plugins regex-match dialog for e.g. macro automation. IObservable ChatStream { get; } IObservable PopupStream { get; } // Lets plugins send /tell (including to NPCs, which is how // HearChat / ReceiveTalkDirect quests are solved). Task SendTellAsync(string targetName, string message, CancellationToken ct = default); // Lets plugins drag an item to an NPC (a.k.a. "give"). Task GiveItemAsync(uint targetGuid, uint itemGuid, int amount, CancellationToken ct = default); // Use / right-click a weenie. Task UseAsync(uint targetGuid, CancellationToken ct = default); // Respond to a YesNo popup. Task RespondConfirmationAsync(uint confirmationType, uint context, bool yes, CancellationToken ct = default); } ``` This is enough for plugin-author-driven quest automation without exposing raw packet sends. ### 11.7 Minimum viable acdream deliverables (phase R10) 1. `AcDream.Core/Emotes/EmoteCategory.cs` + `EmoteType.cs` as typed enums. 2. `AcDream.Core/Quests/ContractEntry.cs` + `ContractTracker.cs` with events. 3. `AcDream.Core.Net/Messages/*` decoders for opcodes listed in section 10.1, matched to holtburger test fixtures. 4. `AcDream.App/UI/Panels/ChatPanel.cs` renders all of: NPC Tell / player Tell / HearSpeech / HearRangedSpeech / EmoteText / SoulEmote / SystemChat / channel broadcasts, with the `` markup parsed into clickable spans. 5. `AcDream.App/UI/Panels/PopupDialogPanel.cs` for PopupString (simple ack dialog). 6. `AcDream.App/UI/Panels/YesNoDialogPanel.cs` for ConfirmationRequest (context round-tripping). 7. `AcDream.App/UI/Panels/ContractTrackerPanel.cs` renders ContractTracker + dat-lookup of Contract records. 8. Plugin interfaces from 11.6 exposed via `IGameState`. That lets plugins drive quest automation and lets players play every retail quest that exists, because every retail quest IS an emote script playing through Tell + Give + (optionally) ContractTracker. --- ## 12. Where this will go wrong (and where it won't) Subtle bugs to watch in the port: 1. **`%tqt` not rendering** — if the server's string-substitution path doesn't get a live QuestManager handle, `%tqt` stays literal and players see the placeholder in chat. On the client side this is purely cosmetic; just escape `%` properly when parsing incoming strings — do not attempt to format them yourself. The server delivers pre-formatted text. 2. **ConfirmationRequest context round-trip** — if the client's ConfirmationResponse drops the server-chosen `context` uint32, the server will reject it silently and the emote chain will stall. Always echo exactly what the server sent. 3. **Contract stage = `ProgressCounter + N`** — note this is ADD, not replace. `Stage` arrives as (4 + progress). Panel decoder must split `stage % 4 == 0 && stage >= 4` as progress vs 1/2/3 stages. 4. **Tell markup parsing** — the string `` can appear inside the Say/Tell `message` field, not just in chat framing. Every string field that comes from an NPC speech emote is subject to it. Parse it as a rich-text span while rendering, not as markup in the pipeline. 5. **Sender GUID range `0x50000001 – 0x6FFFFFFF`** — this is the retail player-GUID range. Only wrap speech in `` markup when the sender is in this range. NPCs use other GUID ranges (typically landblock-local). `chunk_00570000.c:1189-1195` is the reference. 6. **Emote category filtering by `Quest`** — some NPCs have fifty Use emote sets, each with a different `Quest` value. The client never sees the filtering; only the one winning emote fires. But plugin observers that snoop on the chat stream will not be able to reverse- engineer which set fired unless the NPC's sequence is distinctive enough. Document this limitation in the plugin API. 7. **Infinite-loop protection** — server aborts nested > 75. If a retail NPC has a looping emote set, the loop terminates silently in `Enqueue`. Plugins should not rely on seeing the full sequence if they observe this abort pattern (rare but possible). 8. **HearChat / ReceiveTalkDirect keywords** — the emote set's `Quest` field is a case-insensitive literal match against the player's chat message. This is the ONLY way to trigger NPC reactions to arbitrary player speech. Document clearly for plugin authors. Places this port is NOT load-bearing on acdream: - We do NOT implement QuestManager, EmoteManager, EventManager, ContractManager, or the world Quest table on the client. Those are server responsibilities. - We do NOT evaluate emote scripts on the client. We only *observe* their output. - We do NOT store CharacterPropertiesQuestRegistry client-side. Everything we display comes from server pushes. --- ## 13. Cross-references & file pointers **Decompiled retail client:** - `docs/research/decompiled/chunk_00550000.c:10700-10880` — incoming game-event dispatcher switch; shows 0x2BD (Tell), 0x1E0 (EmoteText), 0x2BB/2BC (HearSpeech), 0x314/0x315 (ContractTracker) handler functions. - `docs/research/decompiled/chunk_006A0000.c:4991` (`FUN_006a5920`) — Tell (0x2BD) receive-side dispatch. - `docs/research/decompiled/chunk_006A0000.c:5036` (`FUN_006a5a20`) — EmoteText (0x1E0) receive-side dispatch. - `docs/research/decompiled/chunk_00570000.c:1165-1205` — text rendering for Tell, including `` markup and the player-GUID range test `0x50000001 – 0x6FFFFFFF`. - `docs/research/decompiled/chunk_00570000.c:740-810` — same markup used for allegiance / fellowship / vassal tell variants. - `docs/research/decompiled/chunk_00570000.c:1500-2900` — game event error-code → wide-string lookup table. Contains all the quest-related error strings (0x43E "solved too recently", 0x43F "too many times", 0x445 "requires quest to pick up", 0x474 "must complete quest to use portal"). - `docs/research/decompiled/chunk_00580000.c:481-505` — client chat-command handler; routes `:` / `;` prefix input to `@emote` command. - `docs/research/decompiled/chunk_00580000.c:2095-2170` — chat command registration for `emote` / `emotes`. - `docs/research/decompiled/chunk_00580000.c:5936-5962` — "Select your target before using the %s" path; client-side Use pipeline. **ACE server (wire + server-side quest evaluation):** - `references/ACE/Source/ACE.Entity/Enum/EmoteType.cs` — the 122 values. - `references/ACE/Source/ACE.Entity/Enum/EmoteCategory.cs` — the 39 values. - `references/ACE/Source/ACE.Entity/Enum/ChatMessageType.cs` — colour codes for every chat line. - `references/ACE/Source/ACE.Entity/Enum/ConfirmationType.cs` — Yes_No (7), SwearAllegiance (1), AlterSkill (2), etc. - `references/ACE/Source/ACE.Entity/Models/PropertiesEmote.cs` — set shape. - `references/ACE/Source/ACE.Entity/Models/PropertiesEmoteAction.cs` — action shape. - `references/ACE/Source/ACE.Server/Managers/QuestManager.cs` — HasQuest, CanSolve, IsMaxSolves, Increment, Decrement, Stamp, Erase, EraseAll, HasQuestBits/SetQuestBits, HandleKillTask, HandleSolveError, HandlePortalQuestError. - `references/ACE/Source/ACE.Server/WorldObjects/Managers/EmoteManager.cs` — the mini-language interpreter (1740+ lines). - `references/ACE/Source/ACE.Server/Network/GameEvent/GameEventType.cs` — opcode list including 0x0004 PopupString, 0x028A WeenieError, 0x02BD Tell, 0x0274 CharacterConfirmationRequest, 0x0314 SendClientContractTrackerTable, 0x0315 SendClientContractTracker. - `references/ACE/Source/ACE.Server/Network/GameMessages/GameMessageOpcode.cs` — 0x01E0 EmoteText, 0x01E2 SoulEmote, 0x02BB HearSpeech, 0x02BC HearRangedSpeech. - `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventTell.cs` — server-side Tell packer. - `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventPopupString.cs` — PopupString packer. - `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventConfirmationRequest.cs` — confirmation request packer. - `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventSendClientContractTracker.cs` and `.../GameEventSendClientContractTrackerTable.cs` — tracker packers. - `references/ACE/Source/ACE.Server/Network/GameMessages/Messages/GameMessageHearSpeech.cs` / `GameMessageHearRangedSpeech.cs` / `GameMessageEmoteText.cs` / `GameMessageSoulEmote.cs` — speech packers. - `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionEmote.cs` / `GameActionSoulEmote.cs` / `GameActionGiveObjectRequest.cs` — client → server action handlers. - `references/ACE/Source/ACE.Server/WorldObjects/Player_Inventory.cs:3190-3478` — `HandleActionGiveObjectRequest` and `GiveObjectToNPC` flow. - `references/ACE/Source/ACE.Server/Network/Structure/ContractTracker.cs` — ContractStage enum and packed struct layout. - `references/ACE/Source/ACE.Database/Models/Shard/CharacterPropertiesQuestRegistry.cs` — per-character row. - `references/ACE/Source/ACE.Database/Models/World/Quest.cs` — world quest table. **DatReaderWriter (what the dat files contain):** - `references/DatReaderWriter/DatReaderWriter/Generated/Types/Contract.generated.cs` — Contract struct (Quest flag name pointers). - `references/DatReaderWriter/DatReaderWriter/Generated/Types/ChatEmoteData.generated.cs` — per-chat-emote dat strings ("MyEmote" = what you see, "OtherEmote" = what others see). - `references/DatReaderWriter/DatReaderWriter/Generated/DBObjs/ContractTable.generated.cs` — Contract → ContractTable keyed dict in portal.dat. - `references/ACE/Source/ACE.DatLoader/Entity/Contract.cs` — ACE's reader for the same (byte-compatible). - `references/ACE/Source/ACE.DatLoader/Entity/ChatEmoteData.cs` — ACE's reader for ChatEmoteData. **Holtburger (Rust client wire behaviour):** - `references/holtburger/crates/holtburger-protocol/src/messages/chat/events.rs` — TellEventData, PopupStringEventData, ChannelBroadcastEventData with byte-level pack/unpack impls (ground truth for wire format). - `references/holtburger/crates/holtburger-protocol/src/messages/chat/types.rs` — HearSpeechData, HearRangedSpeechData, ServerMessageData, EmoteTextData, SoulEmoteData. - `references/holtburger/apps/holtburger-cli/src/pages/game/panels/chat.rs` — rendering of speech/tell/emote in a terminal (demonstrates the Tell markup parsing). --- ## 14. Appendix — EmoteType numeric index For cross-referencing against decompiled type-dispatch constants. From `EmoteType.cs` (section 3.2 names, for quick lookup without flipping windows): ``` 0 Invalid 28 AwardSkillXP 57 ResetHomePosition 86 SetMyQuestCompletions 1 Act 29 AwardSkillPoints 58 InqFellowQuest 87 MoveToPos 2 AwardXP 30 InqQuestSolves 59 InqFellowNum 88 LocalSignal 3 Give 31 EraseQuest 60 UpdateFellowQuest 89 InqPackSpace 4 MoveHome 32 DecrementQuest 61 StampFellowQuest 90 RemoveVitaePenalty 5 Motion 33 IncrementQuest 62 AwardNoShareXP 91 SetEyeTexture 6 Move 34 AddCharacterTitle 63 SetSanctuaryPosition 92 SetEyePalette 7 PhysScript 35 InqBoolStat 64 TellFellow 93 SetNoseTexture 8 Say 36 InqIntStat 65 FellowBroadcast 94 SetNosePalette 9 Sound 37 InqFloatStat 66 LockFellow 95 SetMouthTexture 10 Tell 38 InqStringStat 67 Goto 96 SetMouthPalette 11 Turn 39 InqAttributeStat 68 PopUp 97 SetHeadObject 12 TurnToTarget 40 InqRawAttributeStat 69 SetBoolStat 98 SetHeadPalette 13 TextDirect 41 InqSecondaryAttr 70 SetQuestCompletions 99 TeleportTarget 14 CastSpell 42 InqRawSecondary 71 InqNumCharacterTitles 100 TeleportSelf 15 Activate 43 InqSkillStat 72 Generate 101 StartBarber 16 WorldBroadcast 44 InqRawSkillStat 73 PetCastSpellOnOwner 102 InqQuestBitsOn 17 LocalBroadcast 45 InqSkillTrained 74 TakeItems 103 InqQuestBitsOff 18 DirectBroadcast 46 InqSkillSpecialized 75 InqYesNo 104 InqMyQuestBitsOn 19 CastSpellInstant 47 AwardTrainingCredits 76 InqOwnsItems 105 InqMyQuestBitsOff 20 UpdateQuest 48 InflictVitaePenalty 77 DeleteSelf 106 SetQuestBitsOn 21 InqQuest 49 AwardLevelPropXP 78 KillSelf 107 SetQuestBitsOff 22 StampQuest 50 AwardLevelPropSkill 79 UpdateMyQuest 108 SetMyQuestBitsOn 23 StartEvent 51 InqEvent 80 InqMyQuest 109 SetMyQuestBitsOff 24 StopEvent 52 ForceMotion 81 StampMyQuest 110 UntrainSkill 25 BLog 53 SetIntStat 82 InqMyQuestSolves 111 SetAltRacialSkills 26 AdminSpam 54 IncrementIntStat 83 EraseMyQuest 112 SpendLuminance 27 TeachSpell 55 DecrementIntStat 84 DecrementMyQuest 113 AwardLuminance 56 CreateTreasure 85 IncrementMyQuest 114 InqInt64Stat 115 SetInt64Stat 116 OpenMe 117 CloseMe 118 SetFloatStat 119 AddContract 120 RemoveContract 121 InqContractsFull 9001 Enlightenment (ACE-custom) ``` --- **End of R10 deep-dive. Approximate word count: ~5,500.**