Files
benilla-octo/crates/benilla-protocol/tests/reputation.rs
T
SamandClaude Fable 5 55d2c70712 reputation arrives: the wire, the pane and a live watch bar
- the Character window's third tab is in: faction rows under their headers, standing bars, the detail popup and the watch bar, at the reference's own anchors
- all four reputation opcodes parse byte-exact, the flag byte reads the way the client reads it, and a standing gain reveals the faction unless it is hidden
- an end-to-end golden drives live wire bytes through the game data to the exact visible row list
- the reference-code overlap is measured by an instrument instead of asserted — the start of a debt paid in full later in the day

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 19:09:04 +07:00

81 lines
4.1 KiB
Rust

//! The reputation pane's wire: the pane's three client verbs, byte-exact against the vmangos
//! reader side, and the one server push (`SMSG_SET_FACTION_VISIBLE`) that carries no standing.
//!
//! The standing-bearing pair (`SMSG_INITIALIZE_FACTIONS` / `SMSG_SET_FACTION_STANDING`) already
//! has its goldens in `tests/simple_packets.rs`, where it landed with the unit-reaction decode.
mod common;
use benilla_protocol::events::{decode, SessionEvent};
use benilla_protocol::messages::{self, opcode};
use benilla_protocol::ServerPacket;
use common::hx;
/// `CMSG_SET_FACTION_ATWAR` (293): `u32 repListId`, `u8 flag` — the pane's crossed-swords box.
///
/// Byte-exact against vmangos's reader (`WorldPackets::Misc::SetFactionAtWar::ReadFromWorldPacket`,
/// `Server/Packets/Misc.cpp`, over the `uint32 repListId` / `uint8 flag` fields in `Misc.h`), which
/// is the side that has to parse what we send.
#[test]
fn set_faction_at_war_body_is_slot_then_flag() {
assert_eq!(opcode::CMSG_SET_FACTION_ATWAR, 293);
// Slot 21 (Booty Bay is reputationIndex 1; 21 is Darnassus's), war ON.
assert_eq!(messages::set_faction_at_war(21, true), hx("1500000001"));
// …and OFF. The flag is a whole byte, not a bit — `0`, never a cleared bit in a mask.
assert_eq!(messages::set_faction_at_war(21, false), hx("1500000000"));
// Slot 0 is a REAL faction (the Bloodsail Buccaneers hold reputationIndex 0), so a zero slot
// is an ordinary request here — the "nothing" sentinel problem is the watched verb's alone.
assert_eq!(messages::set_faction_at_war(0, true), hx("0000000001"));
}
/// `CMSG_SET_FACTION_INACTIVE` (791): `u32 repListId`, `u8 inactive` — the "move to inactive" box.
///
/// Byte-exact against `WorldPackets::Misc::SetFactionInactive::ReadFromWorldPacket`.
#[test]
fn set_faction_inactive_body_is_slot_then_flag() {
assert_eq!(opcode::CMSG_SET_FACTION_INACTIVE, 791);
assert_eq!(messages::set_faction_inactive(54, true), hx("3600000001"));
assert_eq!(messages::set_faction_inactive(54, false), hx("3600000000"));
}
/// `CMSG_SET_WATCHED_FACTION` (792): one **signed** `i32` slot, `-1` for "watch nothing".
///
/// The signedness is the whole point and is why this has its own assert: vmangos writes the value
/// straight into `PLAYER_FIELD_WATCHED_FACTION_INDEX` with `SetInt32Value`
/// (`HandleSetWatchedFactionOpcode`), and slot `0` is a real faction — so a `0` here would watch the
/// Bloodsail Buccaneers rather than clear the bar. FrameXML's `SetWatchedFactionIndex(0)` means "no
/// display row"; translating that to [`messages::WATCHED_FACTION_NONE`] is the binding's job, above
/// this layer, and this test pins the two values apart so the translation cannot be skipped silently.
#[test]
fn set_watched_faction_body_is_a_signed_slot_and_none_is_minus_one() {
assert_eq!(opcode::CMSG_SET_WATCHED_FACTION, 792);
assert_eq!(messages::WATCHED_FACTION_NONE, -1);
assert_eq!(messages::set_watched_faction(11), hx("0b000000"));
assert_eq!(
messages::set_watched_faction(messages::WATCHED_FACTION_NONE),
hx("ffffffff")
);
// The trap, asserted: watching slot 0 and watching nothing are different bytes.
assert_ne!(
messages::set_watched_faction(0),
messages::set_watched_faction(messages::WATCHED_FACTION_NONE)
);
}
/// `SMSG_SET_FACTION_VISIBLE` (291): one `u32` reputation-list slot, no standing
/// (vmangos `ReputationMgr::SendVisible` / `SetFactionVisible::AppendBodyTo`).
///
/// The server pushes it the first time the player meets a faction. A client that drops it keeps a
/// correct standing for a row the pane will not list — which is exactly the failure this parse arm
/// exists to prevent, so the decode leg is asserted too.
#[test]
fn set_faction_visible_parses_and_decodes() {
assert_eq!(opcode::SMSG_SET_FACTION_VISIBLE, 291);
let p = messages::parse_server(opcode::SMSG_SET_FACTION_VISIBLE, &hx("0d000000")).unwrap();
assert!(matches!(p, ServerPacket::SetFactionVisible { list_id: 13 }));
assert!(matches!(
decode(p)[..],
[SessionEvent::ReputationVisible { list_id: 13 }]
));
}