channel lines get their warm color, and a /wave gets its sentence
- a channel line renders in the channel family's color, not the grey notice row the client consults and throws away - a text emote composes its sentence — gender and target picked from the game's own tables — so a /wave is a line in chat, not just an animation - a FontString's `font=` resolves a font object by name and gates the whole block, `<Gradient>` tints the art it sits on, and buttons' state fonts read `font=` too — Bagnon's white boxes and bare stack counts are fixed - the money frame sizes its coins from the digits themselves, so Bagnon's gold stops opening cramped Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -437,8 +437,13 @@ pub enum SessionEvent {
|
||||
instant: bool,
|
||||
},
|
||||
/// A nearby unit's chat emote (`SMSG_TEXT_EMOTE`): the `EmotesText.dbc` id; the guid names
|
||||
/// the performer (voice race/sex resolve from its descriptor).
|
||||
TextEmote { guid: u64, text_emote: u32 },
|
||||
/// the performer (voice race/sex resolve from its descriptor). `target_name` is the wire's
|
||||
/// target display name, empty when untargeted — the sentence-form selector, decision 1274.
|
||||
TextEmote {
|
||||
guid: u64,
|
||||
text_emote: u32,
|
||||
target_name: String,
|
||||
},
|
||||
/// A unit's anim emote (`SMSG_EMOTE`): the `Emotes.dbc` id (drives the anim + its
|
||||
/// `EventSoundID`).
|
||||
Emote { guid: u64, emote_id: u32 },
|
||||
|
||||
@@ -45,8 +45,16 @@ pub fn decode(packet: ServerPacket) -> Vec<SessionEvent> {
|
||||
sound_id,
|
||||
instant,
|
||||
}],
|
||||
ServerPacket::TextEmote { guid, text_emote } => {
|
||||
vec![SessionEvent::TextEmote { guid, text_emote }]
|
||||
ServerPacket::TextEmote {
|
||||
guid,
|
||||
text_emote,
|
||||
target_name,
|
||||
} => {
|
||||
vec![SessionEvent::TextEmote {
|
||||
guid,
|
||||
text_emote,
|
||||
target_name,
|
||||
}]
|
||||
}
|
||||
ServerPacket::Emote { guid, emote_id } => vec![SessionEvent::Emote { guid, emote_id }],
|
||||
ServerPacket::InitialSpells {
|
||||
|
||||
@@ -330,11 +330,19 @@ pub enum ServerPacket {
|
||||
},
|
||||
/// `SMSG_TEXT_EMOTE` — a nearby unit performed a chat emote (`/wave`). Payload VERIFIED
|
||||
/// vmangos `EmoteChatBuilder`: `u64 guid, u32 textEmote (EmotesText.dbc), u32 emoteNum,
|
||||
/// u32 namelen, char name[namelen]` (the target's name; namelen ≥ 1). The name is parsed
|
||||
/// past and dropped (the chat line renders from it later; audio keys on ids).
|
||||
/// u32 namelen, char name[namelen]` (the target's name; namelen ≥ 1, a lone NUL when there was
|
||||
/// no target).
|
||||
///
|
||||
/// **`target_name` is load-bearing and used to be dropped here** (decision 1274 / B156): it is
|
||||
/// what selects the emote's sentence FORM — untargeted vs targeted vs "…at you" — in
|
||||
/// `benilla_formats::EmoteTextCatalog`, and it is the raw wire string, never round-tripped
|
||||
/// through a name cache, exactly as the reference passes it. `emoteNum` really is
|
||||
/// dropped: the receive side reads it off the wire and never consumes it (wow-re
|
||||
/// `ui/scratch/text-emote-composition.md` §3).
|
||||
TextEmote {
|
||||
guid: u64,
|
||||
text_emote: u32,
|
||||
target_name: String,
|
||||
},
|
||||
/// `SMSG_EMOTE` — a unit plays an anim emote. Payload VERIFIED vmangos
|
||||
/// `Unit::HandleEmote`: `u32 emoteId (Emotes.dbc), u64 guid`.
|
||||
|
||||
@@ -314,12 +314,25 @@ pub fn parse_server(opcode: u16, body: &[u8]) -> io::Result<ServerPacket> {
|
||||
opcode::SMSG_TEXT_EMOTE => {
|
||||
let guid = read_u64_le(&mut r)?;
|
||||
let text_emote = read_u32_le(&mut r)?;
|
||||
// Read and dropped on purpose — `emoteNum` selects neither the sentence nor the voice
|
||||
// kit on the receive side (wow-re `ui/scratch/text-emote-composition.md` §3).
|
||||
let _emote_num = read_u32_le(&mut r)?;
|
||||
let namelen = read_u32_le(&mut r)?;
|
||||
// The target's name, length-prefixed **including its NUL** (vmangos writes a lone
|
||||
// `0x00` and `namelen == 1` when there was no target). Trimmed at the first NUL, so an
|
||||
// untargeted emote arrives as the empty string — which is precisely the "untargeted"
|
||||
// bit of the sentence-form selector, not a missing value.
|
||||
let namelen = read_u32_le(&mut r)? as usize;
|
||||
let mut name = Vec::with_capacity(namelen.min(64));
|
||||
for _ in 0..namelen {
|
||||
read_u8(&mut r)?; // the target's name — chat consumes it later; audio keys on ids
|
||||
name.push(read_u8(&mut r)?);
|
||||
}
|
||||
let end = name.iter().position(|&b| b == 0).unwrap_or(name.len());
|
||||
let target_name = String::from_utf8_lossy(&name[..end]).into_owned();
|
||||
ServerPacket::TextEmote {
|
||||
guid,
|
||||
text_emote,
|
||||
target_name,
|
||||
}
|
||||
ServerPacket::TextEmote { guid, text_emote }
|
||||
}
|
||||
opcode::SMSG_EMOTE => {
|
||||
let emote_id = read_u32_le(&mut r)?;
|
||||
|
||||
Reference in New Issue
Block a user