78,000 words of grounded, citation-backed research across 13 major AC
subsystems, produced by 13 parallel Opus-4.7 high-effort agents. Plus
compact C# port scaffolds for the top-5 systems and a phase-E-through-H
roadmap update sequencing the work.
Research (docs/research/deepdives/):
- 00-master-synthesis.md (navigation hub + dependency graph)
- r01-spell-system.md 5.4K words (fizzle sigmoid, 8 tabs, 0x004A wire)
- r02-combat-system.md 5.9K words (damage formula, crit, body table)
- r03-motion-animation.md 8.2K words (450+ commands, 27 hook types)
- r04-vfx-particles.md 5.8K words (13 ParticleType, PhysicsScript)
- r05-audio-sound.md 5.6K words (DirectSound 8, CPU falloff)
- r06-items-inventory.md 7.4K words (ItemType flags, EquipMask 31 slots)
- r07-character-creation.md 6.3K words (CharGen dat, 13 heritages)
- r08-network-protocol-atlas 9.7K words (63+149+94 opcodes mapped)
- r09-dungeon-portal-space.md 6.3K words (EnvCell, PlayerTeleport flow)
- r10-quest-dialogs.md 7.1K words (emote-script VM, 122 actions)
- r11-allegiance.md 5.4K words (tree + XP passup + 5 channels)
- r12-weather-daynight.md 4.5K words (deterministic client-side)
- r13-dynamic-lighting.md 4.9K words (8-light cap, hard Range cutoff)
Every claim cites a FUN_ address, ACE file path, DatReaderWriter type,
or holtburger/ACViewer reference. The master synthesis ties them into a
dependency graph and phase sequence.
Key architectural finding: of 94 GameEvents in the 0xF7B0 envelope,
ZERO are handled today — that's the largest network-protocol gap and
blocks F.2 (items) + F.5 (panels) + H.1 (chat).
C# scaffolds (src/AcDream.Core/):
- Items/ItemInstance.cs — ItemType/EquipMask enums, ItemInstance,
Container, PropertyBundle, BurdenMath
- Spells/SpellModel.cs — SpellDatEntry, SpellComponentEntry,
SpellCastStateMachine, ActiveBuff,
SpellMath (fizzle sigmoid + mana cost)
- Combat/CombatModel.cs — CombatMode/AttackType/DamageType/BodyPart,
DamageEvent record, CombatMath (hit-chance
sigmoids, power/accuracy mods, damage formula),
ArmorBuild
- Audio/AudioModel.cs — SoundId enum, SoundEntry, WaveData,
IAudioEngine / ISoundCache contracts,
AudioFalloff (inverse-square)
- Vfx/VfxModel.cs — 13 ParticleType integrators, EmitterDesc,
PhysicsScript + hooks, Particle struct,
ParticleEmitter, IParticleSystem contract
All Core-layer data models; platform-backed engines live in AcDream.App.
Compiles clean; 470 tests still pass.
Roadmap (docs/plans/2026-04-11-roadmap.md):
- Phase E — "Feel alive": motion-hooks + audio + VFX
- Phase F — Fight + cast + gear: GameEvent dispatch, inventory,
combat, spell, core panels
- Phase G — World systems: sky/weather, dynamic lighting, dungeons
- Phase H — Social + progression: chat, allegiance, quests, char creation
- Phase J — Long-tail (renumbered from old Phase E)
Quick-lookup table updated with 10+ new rows mapping observations to
new phase letters.
57 KiB
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 forTell,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):
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?"
NumTimesCompletedis 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 viaSetQuestBitsOn/SetQuestBitsOff/InqQuestBitsOn/InqQuestBitsOff(see section 3). That's the "BitField (bool quest flags)" notion — a singleNumTimesCompletedrow can store up to 32 boolean sub-flags.LastTimeCompleteddrives the "quest stamp" — timestamp + cooldown. Combined with the world-quest-tableMinDelta, 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):
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/%tqtetc. - Directly, via the Contract tracker messages
(
SendClientContractTrackerTable0x0314 andSendClientContractTracker0x0315). 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 case0x43e). 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 <Tell:IIDString:...>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 +DeleteContractbool +SetAsDisplayContractbool.
Structure written by server (ContractTracker.Write at
references/ACE/Source/ACE.Server/Network/Structure/ContractTracker.cs:137):
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:
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:
- A contract tracker refresh (if the changed quest flag is referenced by a Contract in the dat).
- 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, reliableLocalBroadcast (17)— broadcast in areaWorldBroadcast (16)— broadcast to all players on serverFellowBroadcast (65)— broadcast to player's fellowshipTellFellow (64)— Tell delivered to all fellowship membersAdminSpam (26)— log-channel broadcastBLog (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 NPCForceMotion (52)— play animation on the player targetMove (6)— walk to home-relative offsetMoveHome (4)— walk to home positionMoveToPos (87)— walk to absolute positionTurn (11)— turn to headingTurnToTarget (12)— face the playerResetHomePosition (57)— snap home to currentSetSanctuaryPosition (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 successInqQuest (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 += amountDecrementQuest (32)/DecrementMyQuest (84)— NumTimesCompleted -= amountEraseQuest (31)/EraseMyQuest (83)— delete the quest row entirelySetQuestCompletions (70)/SetMyQuestCompletions (86)— overwrite NumTimesCompletedInqQuestSolves (30)/InqMyQuestSolves (82)— branch on N in [min, max]InqQuestBitsOn (102)/InqQuestBitsOff (103)/InqMyQuestBitsOn (104)/InqMyQuestBitsOff (105)— branch on bitmaskSetQuestBitsOn (106)/SetQuestBitsOff (107)/SetMyQuestBitsOn (108)/SetMyQuestBitsOff (109)— OR / AND-NOT bitsInqFellowNum (59)— branch on fellowship sizeUpdateFellowQuest (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 slotsInqOwnsItems (76)— branch on "player has WCID × N"InqContractsFull (121)InqEvent (51)— branch on world event startedInqYesNo (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 spellbookAddContract (119)/RemoveContract (120)— add/remove a quest-tracker entry
Actions on the weenie itself:
Generate (72)— trigger a generator to spawnCreateTreasure (56)— spawn loot into player's packGive (3)— give a specific WCID item to the playerTakeItems (74)— consume items from player's packDeleteSelf (77)— remove the weenieKillSelf (78)— smite the weenieOpenMe (116)/CloseMe (117)— container/doorActivate (15)— chain-activate linked objectCastSpell (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 controlLocalSignal (88)— broadcast signal that other weenies'ReceiveLocalSignalemotes can listen forTeleportSelf (100)— NPC teleports (unused in retail)TeleportTarget (99)— player teleportsInflictVitaePenalty (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 UISetAltRacialSkills (111)— for race-change flows
Misc:
LockFellow (66)— lock fellowship rosterUntrainSkill (110)Invalid (0)— no-opEnlightenment (9001)— ACE-custom (not retail)
3.3 PropertiesEmote / PropertiesEmoteAction shape
Source:
references/ACE/Source/ACE.Entity/Models/PropertiesEmote.cs and
PropertiesEmoteAction.cs. Schema:
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> 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 <Tell:IIDString:ID:name>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):
if (sender_guid < 0x50000001 || sender_guid > 0x6FFFFFFF) {
FUN_00402710(&out, "%s tells you, \"%s\"\n", name, message);
} else {
FUN_00402710(&out, "<Tell:IIDString:%d:%s>%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 onOnUse. 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 aHearChatemote with the NPC's own name — not reliable — or to cheat and trigger from anInqYesNo. Retail quests do not depend on appraisal. - Activation (
EmoteCategory.Activation) — fires when a switch is flipped, chained viaOnActivate. 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 onQuestfield. This is how "say the magic word to the door" puzzles work. - ReceiveTalkDirect (
EmoteCategory.ReceiveTalkDirect) — fires when a player sends a/tellto this NPC containing a keyword. This is the "text-input quest" pattern.
6. Quest turn-in: dragging an item onto an NPC
Wire path:
-
Client sends
GameActionGiveObjectRequest(GameActionType.GiveObjectRequest≈ 0x00EA). Payload:uint32 targetGuid, uint32 objectGuid, int32 amount. Source:references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionGiveObjectRequest.cs. -
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 viaCreateMoveToChain. -
On arrival, calls
GiveObjectToNPC(target, item, ...)(Player_Inventory.cs:3380). -
Target looks for a matching emote set:
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 theAiAcceptEverythingbool property on the weenie.) -
If Give emote matches: remove the item from the player's inventory, send chat:
"You give <NPC> <item>.", broadcast aReceiveItemsound 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. -
If Refuse emote matches: NPC "examines" the item (it is returned), chat:
"You allow <NPC> to examine your <item>.", and the Refuse emote runs — typically just aTellexplaining why it's not accepted. This is how NPCs react to items that are "close but not quite" quest items. -
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=<ring>, Prob=1.0} // turn-in
StampQuest "arwicMayorQuestDone"
Tell "Excellent! Here is your reward."
AwardXP 50000
Give <reward WCID>
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
ContractNameas the list item title. - Renders
Description(orDescriptionProgressifStage == ProgressCounter + N) as the body. - Shows a timer if
TimeWhenDone > 0orTimeWhenRepeats > 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
// 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<ContractEntry>
{
event Action<ContractEntry> Added;
event Action<ContractEntry> Updated;
event Action<uint> 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.
// 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<EmoteAction> 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<ChatLine> ChatLines { get; } // every rendered line
IObservable<PopupRequest> 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
// AcDream.App/UI/Panels/ChatPanel.cs
// - Appends ChatLine records with colour by ChatMessageType
// - Renders <Tell:IIDString:ID:name>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
// 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:
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<ChatLine> ChatStream { get; }
IObservable<PopupRequest> 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)
AcDream.Core/Emotes/EmoteCategory.cs+EmoteType.csas typed enums.AcDream.Core/Quests/ContractEntry.cs+ContractTracker.cswith events.AcDream.Core.Net/Messages/*decoders for opcodes listed in section 10.1, matched to holtburger test fixtures.AcDream.App/UI/Panels/ChatPanel.csrenders all of: NPC Tell / player Tell / HearSpeech / HearRangedSpeech / EmoteText / SoulEmote / SystemChat / channel broadcasts, with the<Tell:IIDString:...>markup parsed into clickable spans.AcDream.App/UI/Panels/PopupDialogPanel.csfor PopupString (simple ack dialog).AcDream.App/UI/Panels/YesNoDialogPanel.csfor ConfirmationRequest (context round-tripping).AcDream.App/UI/Panels/ContractTrackerPanel.csrenders ContractTracker + dat-lookup of Contract records.- 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:
%tqtnot rendering — if the server's string-substitution path doesn't get a live QuestManager handle,%tqtstays 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.- ConfirmationRequest context round-trip — if the client's
ConfirmationResponse drops the server-chosen
contextuint32, the server will reject it silently and the emote chain will stall. Always echo exactly what the server sent. - Contract stage =
ProgressCounter + N— note this is ADD, not replace.Stagearrives as (4 + progress). Panel decoder must splitstage % 4 == 0 && stage >= 4as progress vs 1/2/3 stages. - Tell markup parsing — the string
<Tell:IIDString:0x%08x:name>can appear inside the Say/Tellmessagefield, 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. - Sender GUID range
0x50000001 – 0x6FFFFFFF— this is the retail player-GUID range. Only wrap speech in<Tell>markup when the sender is in this range. NPCs use other GUID ranges (typically landblock-local).chunk_00570000.c:1189-1195is the reference. - Emote category filtering by
Quest— some NPCs have fifty Use emote sets, each with a differentQuestvalue. 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. - 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). - HearChat / ReceiveTalkDirect keywords — the emote set's
Questfield 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<Tell:IIDString>markup and the player-GUID range test0x50000001 – 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@emotecommand.docs/research/decompiled/chunk_00580000.c:2095-2170— chat command registration foremote/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.csand.../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—HandleActionGiveObjectRequestandGiveObjectToNPCflow.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.