From ff49eb5b8fdd5d71565e7e87a1edae0fa798e189 Mon Sep 17 00:00:00 2001 From: Sam Sung <51686767+samwhosung@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:32:43 +0700 Subject: [PATCH] UNIT_FACTION fires on the faction template's own edge (#858) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A target whose faction template the server swaps live (35 to 14, neither naming a faction group) kept its friendly plate colour: `UNIT_FACTION` fired only when the pvp, FFA, faction-group or tapped state moved. The reference's per-field watch fires `UNIT_FACTION` on any change of `UNIT_FIELD_FACTIONTEMPLATE`, as it fires `UNIT_FLAGS` for that field. `fire_transitions` now also fires it on the faction template's edge. Test: `a_live_faction_template_change_recolours_the_target_plate`. From #855 by @kalistoking, landed here because the fork refuses maintainer pushes. Co-authored-by: kalistoking <148718830+kalistoking@users.noreply.github.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 --- .../src/ui_script/unit_frame_tests.rs | 51 +++++++++++++++++++ crates/benilla-app/src/ui_unit.rs | 18 +++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/crates/benilla-app/src/ui_script/unit_frame_tests.rs b/crates/benilla-app/src/ui_script/unit_frame_tests.rs index 9f759fef..843dacae 100644 --- a/crates/benilla-app/src/ui_script/unit_frame_tests.rs +++ b/crates/benilla-app/src/ui_script/unit_frame_tests.rs @@ -958,6 +958,57 @@ fn an_exalted_npc_target_reads_revered_and_its_plate_is_green() { } } +/// A target whose faction template the server changes live recolours its plate: template 35 +/// (friendly) to 14 (hostile) names no side either way, so only the template's own edge fires +/// `UNIT_FACTION`. No edge, or another unit's, leaves the plate as it was. +#[test] +fn a_live_faction_template_change_recolours_the_target_plate() { + benilla_formats::wow_data_or_skip!(); + use crate::net::FieldEdges; + use benilla_protocol::field::FIELD_UNIT_FACTIONTEMPLATE; + + const MOB: u64 = 0xF130_0000_3796_0001; + let mut s = UiScript::new().unwrap(); + s.set_screen_size(1024.0, 768.0); + load_unit_frames(&s); + let mob = |reaction: u8| UnitState { + exists: true, + has_object: true, + guid: MOB, + name: Some("Pusillin".into()), + health: 100, + max_health: 100, + level: 58, + reaction, + can_attack: reaction < 4, + ..UnitState::default() + }; + let is_green = |c: [f32; 4]| c[0].abs() < 1e-6 && (c[1] - 1.0).abs() < 1e-6; + let is_red = |c: [f32; 4]| (c[0] - 1.0).abs() < 1e-6 && c[1].abs() < 1e-6; + + let friendly = mob(5); + s.set_unit("target", Some(friendly.clone())); + s.fire_event("PLAYER_TARGET_CHANGED", vec![]); + let before = plate_color(&mut s); + assert!(is_green(before), "template 35 reads green, got {before:?}"); + + let hostile = mob(2); + s.set_unit("target", Some(hostile.clone())); + let fire = |s: &mut UiScript, prev: &UnitState, edges: &[(u64, u16)]| { + crate::ui_unit::fire_transitions(s, "target", Some(prev), &hostile, &FieldEdges::of(edges)); + }; + fire(&mut s, &friendly, &[]); + let stale = plate_color(&mut s); + assert!(is_green(stale), "no edge, no repaint, got {stale:?}"); + fire(&mut s, &hostile, &[(MOB + 1, FIELD_UNIT_FACTIONTEMPLATE)]); + let other = plate_color(&mut s); + assert!(is_green(other), "another unit's edge, got {other:?}"); + fire(&mut s, &hostile, &[(MOB, FIELD_UNIT_FACTIONTEMPLATE)]); + let after = plate_color(&mut s); + assert!(is_red(after), "template 14 reads red, got {after:?}"); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); +} + /// Stock `TargetFrame_CheckClassification` (`TargetFrame.lua:205-218`), asserted on the drawn /// quads: elite, rare-elite and world boss share the Elite art (1.12 ships no rare-elite border), /// and `UNIT_CLASSIFICATION_CHANGED` alone repaints it. diff --git a/crates/benilla-app/src/ui_unit.rs b/crates/benilla-app/src/ui_unit.rs index 1f363c8f..a89fed08 100644 --- a/crates/benilla-app/src/ui_unit.rs +++ b/crates/benilla-app/src/ui_unit.rs @@ -1185,11 +1185,19 @@ pub(crate) fn fire_transitions( } // `UNIT_FACTION`, the PvP-icon repaint, on the fields the icon reads and on the tapped bit: the // reference's dynamic-flags watcher (`0x600440`) fires event 29 on bit `0x4` (`0x6005a1` → - // `0x6005b0`) and has no arm for `0x8`. - if prev.is_none_or(|p| { - (p.pvp, p.is_pvp_ffa, &p.faction_group, p.tapped) - != (cur.pvp, cur.is_pvp_ffa, &cur.faction_group, cur.tapped) - }) { + // `0x6005b0`) and has no arm for `0x8`. Also on any change of `UNIT_FIELD_FACTIONTEMPLATE`, the + // watch bridge's id 29 (`0x51bbb0`, as for `UNIT_FLAGS` above): a template swap between two + // that name no faction group moves nothing in the tuple, yet changes the reaction. + let template_moved = edges.moved( + cur.guid, + benilla_protocol::field::FIELD_UNIT_FACTIONTEMPLATE, + ); + if template_moved + || prev.is_none_or(|p| { + (p.pvp, p.is_pvp_ffa, &p.faction_group, p.tapped) + != (cur.pvp, cur.is_pvp_ffa, &cur.faction_group, cur.tapped) + }) + { script.fire_event("UNIT_FACTION", vec![tok()]); } }