ui: a nameplate now carries the unit token its own event names
`plate.namePlateUnitToken` was set on nothing. It is how every later-era plate addon gets from a frame back to its unit — `plate.namePlateUnitToken or (plate.UnitFrame and plate.UnitFrame.unit)`, six sites in `Cinematic/Effects.lua` and `PlateParts.lua` alone — so an addon walking `C_NamePlate.GetNamePlates()` had a frame's rect and nothing about its unit, and could not tell a friendly plate from a hostile one. The measured symptom was grom's friendly nameplates switching themselves on the moment CineMode started, on a profile whose every friendly-plate box is unticked. That CVar write is the addon's and it is deliberate: outside instances Cinematic switches every plate kind on so visibility is decided by *fading* instead, because fades can change in a fight and plate settings cannot. Having switched them all on, it then faded none of them, because it could not identify one. The token already existed — `script/nameplate.rs` has had the slot-bound `nameplate<N>` tokens and the three plate events since the pool was built. All that was missing was publishing the slot's token on the frame: set before `NAME_PLATE_UNIT_ADDED` and cleared after `NAME_PLATE_UNIT_REMOVED`, so a handler for either event reads the field that event is about, and a pooled frame reused for the next unit cannot answer the departed one's token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -382,14 +382,62 @@ impl UiScript {
|
||||
super::event::fire_global(lua, "NAME_PLATE_CREATED", &[ScriptValue::Frame(id)]);
|
||||
}
|
||||
for slot in effects.added {
|
||||
self.set_plate_unit_token(slot, true);
|
||||
super::event::fire_global(
|
||||
lua,
|
||||
self.lua(),
|
||||
"NAME_PLATE_UNIT_ADDED",
|
||||
&[ScriptValue::Str(nameplate_token(slot))],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Put `namePlateUnitToken` on (or take it off) the plate frame holding `slot`'s GUID.
|
||||
///
|
||||
/// Retail's nameplates are FrameXML's, and the field is how **every** later-era plate addon
|
||||
/// gets from a frame back to its unit: `plate.namePlateUnitToken or plate.UnitFrame.unit` is
|
||||
/// the idiom, six times in `Cinematic/Effects.lua` and `PlateParts.lua` alone. Without it an
|
||||
/// addon that walks `GetNamePlates()` can read a plate's rect and nothing about its unit, so
|
||||
/// it cannot tell a friendly plate from a hostile one — and Cinematic, which switches every
|
||||
/// plate kind ON in CineMode precisely so it can decide visibility by *fading* instead, then
|
||||
/// faded none of them. The measured symptom was friendly nameplates appearing the moment
|
||||
/// CineMode started, on a profile whose every friendly-plate box is unticked.
|
||||
///
|
||||
/// Set before `NAME_PLATE_UNIT_ADDED` and cleared after `NAME_PLATE_UNIT_REMOVED`, so a
|
||||
/// handler for either event reads the field the event is about. The token string is the same
|
||||
/// one the event carries, so `UnitIsFriend(plate.namePlateUnitToken, "player")` resolves
|
||||
/// through the ordinary unit path.
|
||||
fn set_plate_unit_token(&mut self, slot: usize, present: bool) {
|
||||
let Some(frame) = ({
|
||||
let model = self.model_mut();
|
||||
model
|
||||
.nameplates
|
||||
.token_slots
|
||||
.get(slot)
|
||||
.copied()
|
||||
.flatten()
|
||||
.and_then(|guid| model.nameplates.frame_for_guid(guid))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
let lua = self.lua();
|
||||
let id = lua
|
||||
.app_data_mut::<Model>()
|
||||
.expect("model app_data")
|
||||
.frame_id(frame);
|
||||
let Ok(table) = crate::script::object::frame_wrapper(lua, id) else {
|
||||
return;
|
||||
};
|
||||
let value = if present {
|
||||
mlua::Value::String(match lua.create_string(nameplate_token(slot)) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return,
|
||||
})
|
||||
} else {
|
||||
mlua::Value::Nil
|
||||
};
|
||||
let _ = table.raw_set("namePlateUnitToken", value);
|
||||
}
|
||||
|
||||
/// Fire `NAME_PLATE_UNIT_REMOVED` for each slot, then clear its token pair and free the slot.
|
||||
/// The firing comes first on purpose: see [`UiScript::sync_nameplates`] step 1.
|
||||
fn fire_plate_removals(&mut self, slots: &[usize]) {
|
||||
@@ -399,6 +447,7 @@ impl UiScript {
|
||||
"NAME_PLATE_UNIT_REMOVED",
|
||||
&[ScriptValue::Str(nameplate_token(slot))],
|
||||
);
|
||||
self.set_plate_unit_token(slot, false);
|
||||
self.set_unit(&nameplate_token(slot), None);
|
||||
self.set_unit(&nameplate_target_token(slot), None);
|
||||
// Freed only now, so the slot is reusable from the next claim on — and never inside
|
||||
|
||||
@@ -650,3 +650,57 @@ fn pfuis_own_oncreate_body_runs_on_the_frame_the_created_event_hands_it() {
|
||||
with a nil plate there, that cache would leak for the session"
|
||||
);
|
||||
}
|
||||
|
||||
/// `plate.namePlateUnitToken`: the field every later-era plate addon reads to get from a frame
|
||||
/// back to its unit (`plate.namePlateUnitToken or plate.UnitFrame.unit`, six sites in Cinematic
|
||||
/// alone). Live by the time `NAME_PLATE_UNIT_ADDED` runs and gone once `_REMOVED` has, so a
|
||||
/// handler for either reads the field the event is about; and a plate walked out of
|
||||
/// `GetNamePlates()` carries it too, which is the call an addon that fades plates actually makes.
|
||||
#[test]
|
||||
fn a_plate_carries_the_unit_token_its_event_names() {
|
||||
let mut s = vm();
|
||||
drive(&mut s, &[feed(WOLF, "Wolf")]);
|
||||
assert_eq!(
|
||||
s.eval::<String>(
|
||||
r#"local p = C_NamePlate.GetNamePlates()[1]
|
||||
return tostring(p.namePlateUnitToken) .. "/" .. tostring(UnitName(p.namePlateUnitToken))"#
|
||||
)
|
||||
.unwrap(),
|
||||
"nameplate1/Wolf",
|
||||
"the walked plate names its own unit"
|
||||
);
|
||||
|
||||
// The field is the event's own token while the added handler runs: recorded there, not after.
|
||||
s.run(
|
||||
r#"seen = nil
|
||||
watch = CreateFrame("Frame")
|
||||
watch:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
||||
watch:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
||||
watch:SetScript("OnEvent", function()
|
||||
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
|
||||
seen = event .. ":" .. tostring(plate and plate.namePlateUnitToken)
|
||||
end)"#,
|
||||
)
|
||||
.unwrap();
|
||||
drive(&mut s, &[feed(WOLF, "Wolf"), feed(BEAR, "Bear")]);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return tostring(seen)").unwrap(),
|
||||
"NAME_PLATE_UNIT_ADDED:nameplate2",
|
||||
"set before the added event, not after it"
|
||||
);
|
||||
|
||||
// Everything leaves: the field goes with the unit, so a pooled frame reused for the next unit
|
||||
// cannot answer the departed one's token.
|
||||
let frames = s.eval::<i64>("return table.getn(createdFrames)").unwrap();
|
||||
drive(&mut s, &[]);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return tostring(createdFrames[1].namePlateUnitToken)")
|
||||
.unwrap(),
|
||||
"nil",
|
||||
"cleared once the plate's unit is gone"
|
||||
);
|
||||
assert_eq!(
|
||||
frames, 2,
|
||||
"two pooled frames, both of which outlive their units"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user