vmangos citations read in one revision, vmangos core 6b34a36 (#289)

Fixes #32. Every vmangos `File.cpp:N` citation in the crates whose comments have been rewritten now reads in one revision, vmangos core `6b34a36`, the one the comments are written against, a few commits from the server benilla is run against. Some citations had been taken from a newer vmangos checkout, where the lines have moved. #32's own targets came from that newer checkout too, so where this PR and #32 differ, this PR follows `6b34a36`.

- 53 citations in 31 files are re-pointed: most in benilla-protocol, a few in benilla-ui and benilla-app. Each new line was read and is the construct its comment names.
- #32's cases:
  - The root-ack kick is `MovementHandler.cpp:715-722`; three comments cited the falling deferral after it.
  - Movement is discarded before `CMSG_SET_ACTIVE_MOVER` lands at `:291-293`, through `Player::GetConfirmedMover`.
  - The released mover's parting stop is `:955-964`.
  - The mount-special echo is sent with `self` false at `:972`. Whether the sender gets its own echo depends on the server's broadcaster (`Object.cpp:2273-2280`); that is stated once, where the packet is parsed, and the opcode comment points there.
- Ten more citations were a line or two off in every revision. One comment also stated a wrong fact: vmangos's ignoring-you party refusal names the invitee, and the empty name is the raid-convert confirmation's.
- Comment text only. Every file strips to identical code, and `cargo fmt --check` is clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam Sung
2026-09-25 15:55:32 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent d9359a2d16
commit 4ecc33c019
35 changed files with 71 additions and 70 deletions
@@ -225,7 +225,7 @@ fn channel_counts_down_not_up() {
}
/// A channel that runs out closes on the bar's own clock: vmangos defers the stop by 1000 ms on a
/// natural end and sends it at once only on an interrupt (`Spell.cpp:4894-4904`). The stock
/// natural end and sends it at once only on an interrupt (`Spell.cpp:4906-4916`). The stock
/// `OnUpdate` clamps at `endTime` and fades with no green flash, and the late stop lands on a
/// hidden frame, which both stop-arm guards ignore.
#[test]
@@ -675,7 +675,7 @@ fn shipped_panel_slot_pushable_promotes_to_center() {
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// vmangos sends only `SMSG_SHOW_BANK` for the bank option (`Player.cpp:12349-12351`), and the
/// vmangos sends only `SMSG_SHOW_BANK` for the bank option (`Player.cpp:12425-12427`), and the
/// bank (pushable 6) would seat at center, so the app closes gossip itself. Either order ends
/// with the bank at left; opened first, it slides back when gossip hides (`UIParent.lua:772-783`).
#[test]
+1 -1
View File
@@ -34,7 +34,7 @@ impl AreaTriggerRow {
/// distance check, inclusive (`radius² >= Σ(centre − p)²`); a box carries `p` into its frame
/// through gx's matrix ops (`0x7bdc40` load, `0x7bdd60` rotate by `-box_yaw`, `0x7bd700`
/// apply), strict against half of `box_size` on all six faces. The server re-tests with 5 yd
/// of slop (`MiscHandler.cpp:641`).
/// of slop (`MiscHandler.cpp:643`).
pub fn contains(&self, p: [f32; 3]) -> bool {
if self.radius != 0.0 {
let d = [
+1 -1
View File
@@ -18,7 +18,7 @@ pub struct TransportSample {
/// The map of `pos`, which changes as a transport crosses continents.
pub map: u32,
pub pos: [f32; 3],
/// Radians in `[0, 2π)`, `atan2(dir.y, dir.x) + π` (`Transport.cpp:349`).
/// Radians in `[0, 2π)`, `atan2(dir.y, dir.x) + π` (`Transport.cpp:347`).
pub heading: f32,
/// False while parked at a stop or on a keyframe's zero-width instant.
pub moving: bool,
@@ -1,6 +1,6 @@
//! `--worldstate`: `.debug send worldstate <id> <value>` must come back as that exact
//! `SMSG_UPDATE_WORLD_STATE` pair, and a zone change must re-send `SMSG_INIT_WORLD_STATES`
//! (`Player::UpdateZone`, `Player.cpp:6660`), which cannot be requested.
//! (`Player::UpdateZone`, `Player.cpp:6736`), which cannot be requested.
//!
//! The command needs SEC_DEVELOPER, 5 (vmangos `Chat.cpp:323`); below it the server only refuses
//! in chat. The level is read from `realmd.account_access` (a row per `RealmID` and a `-1` row),
@@ -489,7 +489,7 @@ impl World {
} if *guid == self.self_guid => {
let pose = self.self_pose();
// The ack's MovementInfo must carry the mode's own bit: vmangos kicks a player
// whose root ack lacks it (`HandleMoveRootAck`, MovementHandler.cpp:722-729). For
// whose root ack lacks it (`HandleMoveRootAck`, MovementHandler.cpp:715-722). For
// the other modes the word becomes the mover's flags; the probe keeps no others.
let flags = if *apply { mode.flag() } else { 0 };
session.move_mode_ack(*guid, *counter, *mode, *apply, flags, pose)?;
+2 -2
View File
@@ -10,9 +10,9 @@ fn v3(v: Vector3d) -> [f32; 3] {
}
/// The caster of `SMSG_SPELL_START`/`SMSG_SPELL_GO`. vmangos writes the caster slot from
/// `m_casterUnit`, null for a GameObject caster (`Spell.cpp:102`, `Spell.cpp:4479`), so a
/// `m_casterUnit`, null for a GameObject caster (`Spell.cpp:102`, `Spell.cpp:4491`), so a
/// GameObject's spell (Lightwell, GO 181102) arrives as 0; the first slot then holds the caster
/// itself, as it does whenever no item is cast (`Spell.cpp:4475-4477`).
/// itself, as it does whenever no item is cast (`Spell.cpp:4487-4489`).
fn spell_caster(item_or_caster: u64, caster_slot: u64) -> u64 {
if caster_slot == 0 {
item_or_caster
@@ -69,7 +69,7 @@ pub(super) fn read_attacker_state(r: &mut impl Read) -> io::Result<AttackerState
resist += read_u32_le(r)? as i32;
}
let victim_state = read_u32_le(r)?;
// The tail from `victimState` (`Server/Packets/Combat.cpp:81`) lands at `rec+0xa4`..`+0xb0`
// The tail from `TargetState` (`Unit.cpp:4601`) lands at `rec+0xa4`..`+0xb0`
// (`0x625c60`); the reference never reads `attackerState`.
let _attacker_state = read_u32_le(r)?;
let melee_spell_id = read_u32_le(r)?;
+1 -1
View File
@@ -25,7 +25,7 @@ pub fn buy_bank_slot(banker_guid: u64) -> Vec<u8> {
banker_guid.to_le_bytes().to_vec()
}
/// `CMSG_AUTOBANK_ITEM` (`ItemHandler.cpp:874`): deposit only, into the first free bank slot.
/// `CMSG_AUTOBANK_ITEM` (`ItemHandler.cpp:938`): deposit only, into the first free bank slot.
pub fn autobank_item(bag: u8, slot: u8) -> Vec<u8> {
vec![bag, slot]
}
@@ -1,12 +1,12 @@
//! The innkeeper bind: `SMSG_BINDER_CONFIRM` (0x2eb) asks, `CMSG_BINDER_ACTIVATE` (0x1b5)
//! answers, `SMSG_PLAYERBOUND` (0x158) confirms. The gossip line only asks (`Player.cpp:12341`);
//! answers, `SMSG_PLAYERBOUND` (0x158) confirms. The gossip line only asks (`Player.cpp:12417`);
//! the bind happens on the answer, when the innkeeper casts spell 3286 (`NPCHandler.cpp:479`).
use std::io;
use crate::wire::{read_u32_le, read_u64_le};
/// `SMSG_PLAYERBOUND`, sent once the bind has taken (`SpellEffects.cpp:5837`).
/// `SMSG_PLAYERBOUND`, sent once the bind has taken (`SpellEffects.cpp:5806`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlayerBound {
/// The caster of spell 3286, the innkeeper.
@@ -281,7 +281,7 @@ pub fn ping(sequence: u32, last_rtt_ms: u32) -> Vec<u8> {
/// Body of a movement-mode ack: unpacked guid, the echoed counter, our `MovementInfo`, then
/// `u32 apply` for every mode but root (`Server/Packets/Movement.cpp:38-59`). `info.flags` must
/// carry the applied mode bit: vmangos kicks a root ack without it (`MovementHandler.cpp:722-729`)
/// carry the applied mode bit: vmangos kicks a root ack without it (`MovementHandler.cpp:715-722`)
/// and takes the flags as the mover's new ones for the other modes.
pub fn move_flag_ack(guid: u64, counter: u32, info: &MovementInfo, apply: Option<bool>) -> Vec<u8> {
let mut body = Vec::with_capacity(48);
@@ -297,7 +297,7 @@ pub fn move_flag_ack(guid: u64, counter: u32, info: &MovementInfo, apply: Option
/// `CMSG_MOVE_KNOCK_BACK_ACK` body, a movement-mode ack without `apply` (`Movement.cpp:38-68`).
/// `info` must carry `MOVEFLAG_JUMPING` and the launch quad: the server matches the counter and
/// all four floats within 0.01 (`Unit::FindPendingMovementKnockbackChange`), builds observers'
/// `MSG_MOVE_KNOCK_BACK` from this info, and never re-sends a knockback (`Unit.cpp:6887`).
/// `MSG_MOVE_KNOCK_BACK` from this info, and never re-sends a knockback (`Unit.cpp:6912`).
pub fn knock_back_ack(guid: u64, counter: u32, info: &MovementInfo) -> Vec<u8> {
move_flag_ack(guid, counter, info, None)
}
@@ -68,7 +68,7 @@ pub(super) fn read_gameobject_custom_anim(r: &mut &[u8]) -> io::Result<(u64, u32
Ok((guid, anim_id))
}
/// Read `SMSG_GAMEOBJECT_DESPAWN_ANIM`, only the guid (`Objects/Object.cpp:2307`); the 1.12
/// Read `SMSG_GAMEOBJECT_DESPAWN_ANIM`, only the guid (`Objects/Object.cpp:2308`); the 1.12
/// client plays substate 12, AnimationData 157 Despawn.
pub(super) fn read_gameobject_despawn_anim(r: &mut &[u8]) -> io::Result<u64> {
read_u64_le(r)
@@ -119,8 +119,8 @@ pub(super) fn read_group_set_leader(r: &mut impl Read) -> io::Result<String> {
read_cstring(r)
}
/// Read `SMSG_PARTY_COMMAND_RESULT` (`Group.cpp:100-105`): operation, member name, result; the
/// name can be empty, as in the ignoring-you refusal (`GroupHandler.cpp:466`).
/// Read `SMSG_PARTY_COMMAND_RESULT` (`GroupHandler.cpp:47-54`): operation, member name, result;
/// the name can be empty, as in the raid-convert confirmation (`GroupHandler.cpp:466`).
pub(super) fn read_party_command_result(r: &mut impl Read) -> io::Result<(u32, String, u32)> {
Ok((read_u32_le(r)?, read_cstring(r)?, read_u32_le(r)?))
}
@@ -162,7 +162,7 @@ pub struct PartyMemberStatsInfo {
pub max_power: Option<u16>,
pub level: Option<u16>,
pub zone: Option<u16>,
/// Raw WoW x and y, truncated to `i16` by the server (`GroupHandler.cpp:623`).
/// Raw WoW x and y, truncated to `i16` by the server (`GroupHandler.cpp:625`).
pub position: Option<(i16, i16)>,
/// Active buff spell ids in slot order; the wire's `u32` slot mask is not kept.
pub auras: Option<Vec<u16>>,
@@ -247,7 +247,7 @@ impl PartyMemberStatsInfo {
}
}
/// One `u16` spell id per set bit of `mask`, in bit order (`GroupHandler.cpp:624-644`).
/// One `u16` spell id per set bit of `mask`, in bit order (`GroupHandler.cpp:627-648`).
fn read_aura_spells(r: &mut impl Read, mask: u32, bits: u32) -> io::Result<Vec<u16>> {
let mut spells = Vec::new();
for bit in 0..bits {
@@ -258,7 +258,7 @@ fn read_aura_spells(r: &mut impl Read, mask: u32, bits: u32) -> io::Result<Vec<u
Ok(spells)
}
/// Read `SMSG_PARTY_MEMBER_STATS` or `_FULL`; the guid is packed (`GroupHandler.cpp:768`).
/// Read `SMSG_PARTY_MEMBER_STATS` or `_FULL`; the guid is packed (`GroupHandler.cpp:593`).
pub(super) fn read_party_member_stats(
r: &mut impl Read,
) -> io::Result<(u64, PartyMemberStatsInfo)> {
@@ -565,7 +565,7 @@ pub fn set_ammo(entry: u32) -> Vec<u8> {
/// Read `SMSG_INVENTORY_CHANGE_FAILURE` into `(reason, required_level, item_guid, bag_slot)`: a
/// `u8` reason and, unless it is 0, a `u32` level for reason 1 only, two item guids and a bag
/// slot. That slot is the target bag's player-array slot, 255 for the player's own
/// (`Player.cpp:8899`); the reference names that bag in reason 16's message (`0x5ede00`).
/// (`Player.cpp:8975`); the reference names that bag in reason 16's message (`0x5ede00`).
pub(super) fn read_inventory_change_failure(
r: &mut &[u8],
) -> io::Result<(u8, Option<u32>, u64, u8)> {
+7 -7
View File
@@ -45,7 +45,7 @@ pub mod loot_type {
/// `LootError` (vmangos `LootMgr.h:85-100`): the code on the error shape of `SMSG_LOOT_RESPONSE`.
/// `CMSG_LOOT` can draw `DIDNT_KILL`, `PLAYER_NOT_FOUND`, `PLAY_TIME_EXCEEDED`, `NOTSTANDING`,
/// `STUNNED` (`LootHandler.cpp:342-373`) and `TOO_FAR` (`Player.cpp:7938-7947`); the `MASTER_*`
/// `STUNNED` (`LootHandler.cpp:342-373`) and `TOO_FAR` (`Player.cpp:7952-7956`); the `MASTER_*`
/// codes answer a refused [`loot_master_give`], to the master looter only.
pub mod loot_error {
pub const DIDNT_KILL: u8 = 0;
@@ -123,7 +123,7 @@ pub fn loot_master_give(loot_guid: u64, slot: u8, player_guid: u64) -> Vec<u8> {
}
/// `SMSG_LOOT_START_ROLL` (`Loot.cpp:43-51`): a group roll opened on one drop, sent to every
/// eligible roller. Its `randomSuffix` is always 0 (`Group/Group.cpp:764`) and is discarded.
/// eligible roller. Its `randomSuffix` is always 0 (`Group/Group.cpp:765`) and is discarded.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LootStartRoll {
pub looted_target: u64,
@@ -195,7 +195,7 @@ pub(super) fn read_loot_start_roll(r: &mut &[u8]) -> io::Result<LootStartRoll> {
let looted_target = read_u64_le(r)?;
let item_slot = read_u32_le(r)?;
let item_id = read_u32_le(r)?;
let _random_suffix = read_u32_le(r)?; // literal 0 on the wire (Group.cpp:764)
let _random_suffix = read_u32_le(r)?; // literal 0 on the wire (Group.cpp:765)
let random_property_id = read_u32_le(r)?;
let countdown_ms = read_u32_le(r)?;
Ok(LootStartRoll {
@@ -231,7 +231,7 @@ pub(super) fn read_loot_roll_won(r: &mut &[u8]) -> io::Result<LootRollWon> {
let looted_target = read_u64_le(r)?;
let item_slot = read_u32_le(r)?;
let item_id = read_u32_le(r)?;
let _random_suffix = read_u32_le(r)?; // literal 0 on the wire (Group.cpp:822)
let _random_suffix = read_u32_le(r)?; // literal 0 on the wire (Group.cpp:821)
let random_property_id = read_u32_le(r)?;
let winner = read_u64_le(r)?;
let roll_number = read_u8(r)?;
@@ -252,7 +252,7 @@ pub(super) fn read_loot_all_passed(r: &mut &[u8]) -> io::Result<LootAllPassed> {
let item_slot = read_u32_le(r)?;
let item_id = read_u32_le(r)?;
let random_property_id = read_u32_le(r)?;
let _random_suffix = read_u32_le(r)?; // the swapped tail, a literal 0 (Group.cpp:849)
let _random_suffix = read_u32_le(r)?; // the swapped tail, a literal 0 (Group.cpp:850)
Ok(LootAllPassed {
looted_target,
item_slot,
@@ -274,7 +274,7 @@ pub(super) fn read_loot_master_list(r: &mut &[u8]) -> io::Result<Vec<u64>> {
Ok(candidates)
}
/// `SMSG_LOOT_RESPONSE` (vmangos `Player.cpp:8135-8138`): `u64 guid, u8 lootType`, then the
/// `SMSG_LOOT_RESPONSE` (vmangos `Player.cpp:8138-8141`): `u64 guid, u8 lootType`, then the
/// error code when `lootType` is 0, else gold and the [`LootItem`] rows.
pub(super) fn read_loot_response(r: &mut &[u8]) -> io::Result<(u64, LootResponseBody)> {
let guid = read_u64_le(r)?;
@@ -292,7 +292,7 @@ pub(super) fn read_loot_response(r: &mut &[u8]) -> io::Result<(u64, LootResponse
let item_id = read_u32_le(r)?;
let item_count = read_u32_le(r)?;
let display_info_id = read_u32_le(r)?;
let _random_suffix = read_u32_le(r)?; // always a literal 0 on the wire (LootMgr.cpp:841)
let _random_suffix = read_u32_le(r)?; // always a literal 0 on the wire (LootMgr.cpp:842)
let random_property_id = read_u32_le(r)?;
let slot_type = read_u8(r)?;
items.push(LootItem {
+1 -1
View File
@@ -169,7 +169,7 @@ pub fn item_text_query(text_id: u32, mail_id: u32) -> Vec<u8> {
/// `SMSG_MAIL_LIST_RESULT` (vmangos `HandleGetMailList`): `u8 count`, then [`MailListEntry`] rows.
pub(super) fn read_mail_list_result(r: &mut &[u8]) -> io::Result<Vec<MailListEntry>> {
let count = read_u8(r)?;
// vmangos stops the list at 254 (`MailHandler.cpp:761`, `mailsCount >= 254`).
// vmangos stops the list at 254 (`MailHandler.cpp:765`, `mailsCount >= 254`).
let mut mails = Vec::with_capacity(capacity_hint(count, 254));
for _ in 0..count {
let message_id = read_u32_le(r)?;
@@ -13,7 +13,7 @@ pub(super) const MOVEMENT_FLAG_SPLINE_ENABLED: u32 = 0x40_0000;
pub(super) const MOVEMENT_FLAG_SPLINE_ELEVATION: u32 = 0x400_0000;
/// `MSG_MOVE_TIME_SKIPPED` inbound: a packed guid and the ms that mover's client skipped
/// (reference handler `0x603b40`; vmangos `MovementHandler.cpp:1011-1017`). Our own send uses a
/// (reference handler `0x603b40`; vmangos `MovementHandler.cpp:1005-1011`). Our own send uses a
/// plain 8-byte guid ([`super::client::move_time_skipped`]); the asymmetry is the reference's.
pub(super) fn read_move_time_skipped(r: &mut &[u8]) -> io::Result<(u64, u32)> {
let guid = crate::wire::read_packed_guid(r)?;
@@ -191,7 +191,7 @@ pub enum RelayVerb {
Teleport,
/// `MSG_MOVE_ROOT` (`true`) / `MSG_MOVE_UNROOT` (`false`): the opcode, not the flags word,
/// decides. The client then runs `SetRoot` (`0x7c7340`, also wiping motion) or `ClearRoot`
/// (`0x7c7370`) unconditionally; vmangos's flags already agree (`MovementHandler.cpp:1070`).
/// (`0x7c7370`) unconditionally; vmangos's flags already agree (`MovementHandler.cpp:1064`).
Root(bool),
}
@@ -172,7 +172,7 @@ pub const CMSG_MOVE_SPLINE_DONE: u16 = 0x02C9; // 713
/// Our movement clock skipped (a stall, a long frame): the mover and the missing milliseconds.
/// vmangos shifts its clock, and re-creates a just-boarded transport (`MovementHandler.cpp:989`).
pub const CMSG_MOVE_TIME_SKIPPED: u16 = 0x02CE; // 718
/// Another mover's clock skip, relayed: packed guid, `u32` lag (`MovementHandler.cpp:1011-1017`).
/// Another mover's clock skip, relayed: packed guid, `u32` lag (`MovementHandler.cpp:1005-1011`).
/// The reference (`0x603b40`) adds it to that unit's last wire timestamp (`CMovement+0xac`); if
/// dropped, the unit's next packet lands `lag` ms late.
pub const MSG_MOVE_TIME_SKIPPED: u16 = 0x0319; // 793
@@ -191,7 +191,7 @@ pub const SMSG_ATTACKSWING_CANT_ATTACK: u16 = 0x0149; // 329
/// (`0x5e7dd0`) stops our attack and shows nothing.
pub const SMSG_CANCEL_COMBAT: u16 = 0x014E; // 334
/// A Feign Death was resisted: empty body (`Unit.cpp:9445-9451`). The reference (`0x6e9800`) only
/// A Feign Death was resisted: empty body (`Unit.cpp:9465-9471`). The reference (`0x6e9800`) only
/// shows `ERR_FEIGN_DEATH_RESISTED`.
pub const SMSG_FEIGN_DEATH_RESISTED: u16 = 0x02B4; // 692
/// A creature's aggro or alert flare.
@@ -482,8 +482,7 @@ pub const MSG_MOVE_SET_TURN_RATE: u16 = 0x00D8; // 216
pub const SMSG_MOUNTRESULT: u16 = 0x016E; // 366
pub const SMSG_DISMOUNTRESULT: u16 = 0x016F; // 367
// The mounted space-bar flourish: an empty CMSG, echoed as `SMSG_MOUNTSPECIAL_ANIM` (raw u64 guid)
// to all in range, the sender too (`MovementHandler.cpp:967`); receivers play MountSpecial (94).
// The mounted flourish, an empty CMSG; who gets the echo: `ServerPacket::MountSpecialAnim`.
pub const CMSG_MOUNTSPECIAL_ANIM: u16 = 0x0171; // 369
pub const SMSG_MOUNTSPECIAL_ANIM: u16 = 0x0172; // 370
@@ -637,7 +636,7 @@ pub const CMSG_STABLE_SWAP_PET: u16 = 0x0275; // 629
/// Spend a talent point; the rank's `SMSG_LEARNED_SPELL` and `PLAYER_CHARACTER_POINTS1` answer it.
pub const CMSG_LEARN_TALENT: u16 = 0x0251; // 593
/// The respec, both ways: the trainer's gossip line (`Player.cpp:12330`) makes the server ask with
/// The respec, both ways: the trainer's gossip line (`Player.cpp:12406`) makes the server ask with
/// the trainer's guid and the cost; the `CONFIRM_TALENT_WIPE` Accept sends the latched guid back.
pub const MSG_TALENT_WIPE_CONFIRM: u16 = 0x02AA; // 682
/// The instance boot timer: `u32 delayMs`, `u32 reason`; boot START for a positive delay, STOP for
@@ -798,7 +797,7 @@ pub const MSG_MOVE_WATER_WALK: u16 = 0x02B1; // 689
// Knockback. `SMSG_MOVE_KNOCK_BACK` to the mover: packed guid, `u32 counter`, `f32` vcos, vsin,
// speedXY, speedZ, with speedZ down-positive like the jump tail. The mandatory ack is full u64
// guid, counter, `MovementInfo`, whose jump tail must echo the four floats within 0.01 with
// `MOVEFLAG_JUMPING` set; a knockback is never re-sent (`Unit.cpp:6887`). Observers get
// `MOVEFLAG_JUMPING` set; a knockback is never re-sent (`Unit.cpp:6912`). Observers get
// `MSG_MOVE_KNOCK_BACK`: the relay shape plus the four floats, re-launched by `0x6026f0`.
pub const SMSG_MOVE_KNOCK_BACK: u16 = 0x00EF; // 239
pub const CMSG_MOVE_KNOCK_BACK_ACK: u16 = 0x00F0; // 240
@@ -1125,8 +1125,9 @@ pub enum ServerPacket {
mount: bool,
code: u32,
},
/// `SMSG_MOUNTSPECIAL_ANIM`: one raw `u64` guid, always another player's mount, since the
/// sender is left out of the broadcast (`MovementHandler.cpp:969-970`).
/// `SMSG_MOUNTSPECIAL_ANIM`: one raw `u64` guid, sent with `self` false
/// (`MovementHandler.cpp:972`), so the sender's own echo depends on vmangos's broadcaster: off
/// forces `self` true (`Object.cpp:2273-2274`), on keeps it false (`Object.cpp:2276-2280`).
MountSpecialAnim {
guid: u64,
},
+3 -3
View File
@@ -172,7 +172,7 @@ pub(super) fn read_pet_spells(r: &mut &[u8]) -> io::Result<PetSpells> {
}
let spell_count = read_u8(r)?;
// A `u8` count with no tighter server bound (`Player.cpp:17471`).
// A `u8` count with no tighter server bound (`Player.cpp:17544`).
let mut spells = Vec::with_capacity(capacity_hint(spell_count, usize::from(u8::MAX)));
for _ in 0..spell_count {
spells.push(read_u32_le(r)?.into());
@@ -192,7 +192,7 @@ pub(super) fn read_pet_spells(r: &mut &[u8]) -> io::Result<PetSpells> {
/// The trailing cooldown block. The client reads `u8 count` then 12-byte entries
/// `{u16 spell, u16 category, u32, u32}` (`0x4bda58`); vmangos writes `u16 count` then 14-byte
/// entries with a `u32` spell (`Unit.cpp:11225-11258`). Deviation: both are read, told apart
/// entries with a `u32` spell (`Unit.cpp:11245-11278`). Deviation: both are read, told apart
/// exactly by the tail length (`12n` vs `1 + 14n`), since the 1.12 client mis-parses vmangos's.
fn read_cooldown_block(r: &mut &[u8]) -> io::Result<Vec<PetSpellCooldown>> {
let count = usize::from(read_u8(r)?);
@@ -270,7 +270,7 @@ pub fn pet_tame_failure_key(reason: u8) -> &'static str {
/// Sound selector for an accepted order (vmangos `PET_TALK_SPECIAL_SPELL`, `PetHandler.cpp:523`).
pub const PET_TALK_ORDER: u32 = 0;
/// Sound selector for an attack order (vmangos `PET_TALK_ATTACK`, `Unit.cpp:8941`).
/// Sound selector for an attack order (vmangos `PET_TALK_ATTACK`, `Unit.cpp:8961`).
pub const PET_TALK_ATTACK: u32 = 1;
/// Read `SMSG_PET_ACTION_SOUND`: `u64 petGuid, u32 selector` (`0x6040ca`, vmangos
@@ -63,7 +63,7 @@ pub fn learn_talent(talent_id: u32, requested_rank: u32) -> Vec<u8> {
}
/// Inbound `MSG_TALENT_WIPE_CONFIRM`: a class trainer asks whether to unlearn every talent
/// (vmangos `Player.cpp:8338`).
/// (vmangos `Player.cpp:8414`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TalentWipeConfirm {
/// The asking trainer, echoed in the answer and required to be a trainer in range there.
@@ -182,7 +182,7 @@ pub(super) fn read_spell_go(r: &mut impl Read) -> io::Result<SpellGo> {
let spell_id = read_u32_le(r)?;
let cast_flags = read_u16_le(r)?;
// Both counts are `u8`s the server backfills (`Spell.cpp:4607-4609`); no tighter bound.
// Both counts are `u8`s the server backfills (`Spell.cpp:4657-4658`); no tighter bound.
let hit_count = read_u8(r)?;
let mut hits = Vec::with_capacity(capacity_hint(hit_count, usize::from(u8::MAX)));
for _ in 0..hit_count {
@@ -234,7 +234,7 @@ pub(super) fn read_spell_delayed(r: &mut impl Read) -> io::Result<(u64, u32)> {
Ok((caster, delay_ms))
}
/// Read `MSG_CHANNEL_START` (vmangos `Spell.cpp:4951-4954`): `(spell_id, duration_ms)`, sent only
/// Read `MSG_CHANNEL_START` (vmangos `Spell.cpp:4963-4966`): `(spell_id, duration_ms)`, sent only
/// to the caster, so no guid.
pub(super) fn read_channel_start(r: &mut impl Read) -> io::Result<(u32, u32)> {
let spell_id = read_u32_le(r)?;
@@ -242,7 +242,7 @@ pub(super) fn read_channel_start(r: &mut impl Read) -> io::Result<(u32, u32)> {
Ok((spell_id, duration_ms))
}
/// Read `MSG_CHANNEL_UPDATE` (vmangos `Player.cpp:21106-21110`): ms left, caster only; 0 ends the
/// Read `MSG_CHANNEL_UPDATE` (vmangos `Player.cpp:21141-21146`): ms left, caster only; 0 ends the
/// channel, whether it finished or was interrupted.
pub(super) fn read_channel_update(r: &mut impl Read) -> io::Result<u32> {
read_u32_le(r)
@@ -416,7 +416,7 @@ impl ObjectFields {
}
/// The piece in equipment slot `slot` (0..18) as `(ItemDisplayInfo id, InventoryType)`, packed
/// `display | type << 24` (`Player.cpp:4821`); unlike `PLAYER_VISIBLE_ITEM`, not an item entry.
/// `display | type << 24` (`Player.cpp:4822`); unlike `PLAYER_VISIBLE_ITEM`, not an item entry.
pub fn corpse_item(&self, slot: u8) -> Option<(u32, u8)> {
let raw = self.get_u32(13 + u16::from(slot))?;
let display = raw & 0x00ff_ffff;
@@ -474,7 +474,7 @@ impl ObjectFields {
self.corpse_flags() & 0x10 != 0
}
/// `CORPSE_DYNFLAG_LOOTABLE` (`Map.cpp:3660`): the bone pile has insignia; `0x5d6e20` gates the
/// `CORPSE_DYNFLAG_LOOTABLE` (`Map.cpp:3655`): the bone pile has insignia; `0x5d6e20` gates the
/// loot highlight and the `CMSG_LOOT` click on it.
pub fn corpse_lootable(&self) -> bool {
self.get_u32(FIELD_CORPSE_DYNAMIC_FLAGS).unwrap_or(0) & 0x01 != 0
@@ -547,7 +547,7 @@ fn corpse_descriptor_indices_and_packing() {
// `+0x1D = 35` flags · `+0x1E = 36` dynamic flags (vmangos `UpdateFields_1_12_1.h:338-350`).
let corpse = ObjectFields::from_pairs(&[
(12, 49),
// Slot 4 (chest): DisplayInfoID 902 | InventoryType 5 << 24 (`Player.cpp:4821`).
// Slot 4 (chest): DisplayInfoID 902 | InventoryType 5 << 24 (`Player.cpp:4822`).
(13 + 4, 902 | (5 << 24)),
// BYTES_1: (0) | race<<8 | gender<<16 | skin<<24 (`Corpse.cpp:228`).
(32, (6 << 8) | (1 << 16) | (3 << 24)),
@@ -115,7 +115,7 @@ pub(super) fn read_list_inventory(r: &mut &[u8]) -> io::Result<(u64, Vec<VendorI
let _no_inventory = read_u8(r)?;
return Ok((vendor_guid, Vec::new()));
}
// vmangos `MAX_VENDOR_ITEMS` 128 (`Objects/CreatureDefines.h:624`).
// vmangos `MAX_VENDOR_ITEMS` 128 (`Objects/CreatureDefines.h:617`).
let mut items = Vec::with_capacity(capacity_hint(count, 128));
for _ in 0..count {
items.push(VendorItem {
@@ -2,7 +2,7 @@
//! `SMSG_UPDATE_WORLD_STATE` `0x2C3`) that NPC-text `$<n>w`/`$<n>e` tokens and scoreboards read.
//!
//! On `0x2C2` the reference clears the table and takes map and zone as its UI filter (`0x4c5650`)
//! before the pairs run. vmangos counts a trailing `(0, 0)` pair (`Player.cpp:8219-8293`), which
//! before the pairs run. vmangos counts a trailing `(0, 0)` pair (`Player.cpp:8295-8369`), which
//! is read as data. Ids and values stay raw dwords: `$<n>e` reads the table at the negated key, so
//! `$2077e` looks up `0xFFFFF7E3`.
@@ -34,8 +34,9 @@ impl WorldWriter {
}
/// `CMSG_SET_ACTIVE_MOVER`, full guid: the client's claim after `SMSG_CLIENT_CONTROL_UPDATE`.
/// Until it lands, every `MSG_MOVE_*` for that unit is discarded (`MovementHandler.cpp:844`);
/// sent at login for our own body and again on possession.
/// Until it lands, every `MSG_MOVE_*` for that unit is discarded
/// (`MovementHandler.cpp:291-293`, `Player.cpp:20257-20272`); sent at login for our own body
/// and again on possession.
pub fn set_active_mover(&mut self, guid: u64) -> Result<()> {
self.send(opcode::CMSG_SET_ACTIVE_MOVER, &messages::full_guid(guid))
}
@@ -47,7 +48,7 @@ impl WorldWriter {
}
/// `CMSG_MOVE_NOT_ACTIVE_MOVER`: the released full guid, then its parting pose, which vmangos
/// re-broadcasts to observers as a stop (`MovementHandler.cpp:886`).
/// re-broadcasts to observers as a stop (`MovementHandler.cpp:955-964`).
pub fn move_not_active_mover(
&mut self,
guid: u64,
@@ -132,7 +133,7 @@ impl WorldWriter {
/// Ack a granted root, water-walk, feather-fall or hover: full guid, echoed counter and our
/// pose, plus a trailing `u32 apply` for all but root. Unacked, the server never applies it.
/// `flags` must carry the mode's bit (a root apply-ack without it is a kick, vmangos
/// `MovementHandler.cpp:722-729`) and no moving bit beside `MOVEFLAG_ROOT`; turn bits are fine.
/// `MovementHandler.cpp:715-722`) and no moving bit beside `MOVEFLAG_ROOT`; turn bits are fine.
pub fn move_mode_ack(
&mut self,
guid: u64,
+3 -3
View File
@@ -372,8 +372,8 @@ fn group_list_empty_you_left_shape_is_14_bytes() {
}
}
/// `SMSG_PARTY_COMMAND_RESULT` (Group.cpp:100-105): a named refusal, and the ignoring-you refusal,
/// which names no one (`Handlers/GroupHandler.cpp:466`).
/// `SMSG_PARTY_COMMAND_RESULT` (`GroupHandler.cpp:47-54`): a named result, and one with an empty
/// name, as the raid-convert confirmation sends (`GroupHandler.cpp:466`).
#[test]
fn party_command_result_wire() {
let mut body = party_operation::INVITE.to_le_bytes().to_vec();
@@ -546,7 +546,7 @@ fn party_member_stats_full_position_auras_and_pet_block() {
}
}
/// The reply for a member not in our group (`Handlers/GroupHandler.cpp:763-774`):
/// The reply for a member not in our group (`Handlers/GroupHandler.cpp:773-789`):
/// `SMSG_PARTY_MEMBER_STATS_FULL` with only `STATUS`, set to offline (0).
#[test]
fn party_member_stats_offline_miss_is_status_only() {
+1 -1
View File
@@ -667,7 +667,7 @@ fn spline_move_mode_family_parses_golden() {
/// The movement-mode opcodes relayed to observers (root, hover, feather fall, water walk,
/// teleport) use the plain relay shape, no counter. Only root has a separate off opcode: vmangos
/// updates the others' flag before broadcasting (`MovementHandler.cpp:626-638`, `:743-744`), so
/// updates the others' flag before broadcasting (`MovementHandler.cpp:620-631`, `:736-737`), so
/// the flags word carries the state. Only `MSG_MOVE_TELEPORT` is tagged as a teleport.
#[test]
fn observer_move_mode_family_parses_golden() {
+3 -3
View File
@@ -536,7 +536,7 @@ fn spell_visual_wire_golden() {
}
// A GameObject caster (Lightwell 181102, spell 7001): vmangos writes the null `m_casterUnit`
// as a lone zero mask byte (`Spell.cpp:4513`), so the object rides only slot 1.
// as a lone zero mask byte (`Spell.cpp:4525`), so the object rides only slot 1.
const LIGHTWELL: u64 = 0xF110_02C3_6E00_0123; // GO high guid, entry 181102 << 24, counter 0x123
let body = hx(concat!(
"fb23016ec30210f1", // item_or_caster pguid: the GameObject (mask 0xfb: byte 2 is zero)
@@ -633,7 +633,7 @@ fn spell_visual_wire_golden() {
}
// MSG_CHANNEL_START: u32 spellId + u32 duration, no guid since only the caster gets it
// (vmangos `Spell.cpp:4951-4954`). 10797 is Starshards.
// (vmangos `Spell.cpp:4963-4966`). 10797 is Starshards.
let body = hx(concat!("2d2a0000", "70170000"));
let packet = messages::parse_server(messages::opcode::MSG_CHANNEL_START, &body).unwrap();
match &packet {
@@ -651,7 +651,7 @@ fn spell_visual_wire_golden() {
other => panic!("channel start event, got {other:?}"),
}
// MSG_CHANNEL_UPDATE: u32 ms left, 0 = over (vmangos `Player.cpp:21106-21110`).
// MSG_CHANNEL_UPDATE: u32 ms left, 0 = over (vmangos `Player.cpp:21141-21146`).
let body = hx("b80b0000");
let packet = messages::parse_server(messages::opcode::MSG_CHANNEL_UPDATE, &body).unwrap();
match &packet {
+3 -3
View File
@@ -20,7 +20,7 @@ use super::{binding_abi, Model};
/// The `SkillLine.dbc` id behind weapon subclass `0..=20` (vmangos `Item.cpp:700-707`), `None`
/// for the obsolete, exotic and misc rows; with no weapon the skill is [`SKILL_UNARMED`]
/// (`Player.cpp:20144-20155`).
/// (`Player.cpp:20175-20186`).
pub fn weapon_subclass_skill(subclass: u32) -> Option<u32> {
const TABLE: [u32; 21] = [
44, // 0 axe → SKILL_AXES
@@ -48,7 +48,7 @@ pub fn weapon_subclass_skill(subclass: u32) -> Option<u32> {
TABLE.get(subclass as usize).copied().filter(|&s| s != 0)
}
/// The melee skill with no weapon equipped (vmangos `SharedDefines.h:987`, `Player.cpp:20153`).
/// The melee skill with no weapon equipped (vmangos `SharedDefines.h:987`, `Player.cpp:20184`).
pub const SKILL_UNARMED: u32 = 162;
/// The `SkillLine.dbc` Defense row `UnitDefense` reports (vmangos `SharedDefines.h:961`).
@@ -197,7 +197,7 @@ pub struct InvSlotView {
/// The resolved `ITEM_FIELD_CREATOR` name, the tooltip's "<Made by %s>" line.
pub creator: Option<String>,
/// The resolved enchant slots in slot order. An inspected player's record has all 7 too, but
/// vmangos fills only PERM and TEMP (`Player.cpp:10518-10519`).
/// vmangos fills only PERM and TEMP (`Player.cpp:10594-10595`).
pub enchants: Vec<super::EnchantView>,
/// The instance's remaining lifetime in ms; `None` without a timer.
pub duration_ms: Option<u64>,
+1 -1
View File
@@ -131,7 +131,7 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
})?,
)?;
// The server ignores a `rollType` of 3 or more (`GroupHandler.cpp:376`), so those and unknown
// The server ignores a `rollType` of 3 or more (`GroupHandler.cpp:367`), so those and unknown
// ids are dropped. Need or Greed on a bind-on-pickup item sends nothing and fires
// `CONFIRM_LOOT_ROLL` (`0x61bdf0`, `0x61be8b`); the gate is in the C function, so it holds for
// an addon's call too.
+2 -2
View File
@@ -290,7 +290,7 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
// GetInboxText(index): body, stationeryTexture, isTakeable, isInvoice (`MailFrame.lua:292`);
// reading a mail queues its open. isTakeable with `not textCreated` gates the copy button
// (`MailFrame.lua:366`), the pair vmangos requires (`MailHandler.cpp:863`).
// (`MailFrame.lua:366`), the pair vmangos requires (`MailHandler.cpp:867`).
g.set(
"GetInboxText",
lua.create_function(|lua, index: usize| {
@@ -594,7 +594,7 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
})?,
)?;
// GetSendMailPrice(): 30 copper, vmangos's fee (`MailHandler.cpp:243`). The reference's is 30
// GetSendMailPrice(): 30 copper, vmangos's fee (`MailHandler.cpp:247`). The reference's is 30
// (`0x4ae756`) plus an uncarried stationery's price, the package's and the enclosed money
// (`0x4ae7a0`-`0x4ae7d2`); those are not added here.
g.set(
@@ -374,7 +374,7 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
})?,
)?;
// UnitIsPlusMob(unit) (`0x516d40`): `UNIT_FIELD_FLAGS` bit 0x40, never the creature rank
// (`0x605620`). The server sets it for a non-pet above normal rank (`Creature.cpp:634`), so a
// (`0x605620`). The server sets it for a non-pet above normal rank (`Creature.cpp:637`), so a
// rare answers 1, and an uncached creature still answers from its flags.
g.set(
"UnitIsPlusMob",
+1 -1
View File
@@ -861,7 +861,7 @@ fn unit_is_plus_mob_reads_the_flag_bit_and_not_the_rank() {
let mut s = UiScript::new().unwrap();
s.set_unit("player", Some(player()));
// The server sets the bit on `!IsPet() && rank > 0` (`Creature.h:185`), so rare carries it.
// The server sets the bit on `!IsPet() && rank > 0` (`Creature.h:186`), so rare carries it.
for (rank, word) in [
(1u32, "elite"),
(2, "rareelite"),