superwow: TrackUnit/UntrackUnit, and the four chat-bubble CVars
TrackUnit is the one with a live consumer and a real feature behind it: 1.12 draws party members on the minimap and raid members NOWHERE, and pfUI puts the whole raid on it with these two — for every party and raid token on PARTY_MEMBERS_CHANGED/RAID_ROSTER_UPDATE/PLAYER_ENTERING_WORLD, each inside a pcall, so on a client without the function the feature is simply absent, which is what it has been here. The set is guids, not tokens, so a tracked raid member keeps its dot while the roster shuffles its token out from under it. The dot itself is drawn by a fourth always-show clause beside the reference's own three (`shows_tracking_dot`) — `tracked_creature` stays exactly `0x5ed210`, so the predicate that file documents is still the client's, and a tracked unit gets the ordinary cell, hover name and distance cull with nothing new drawn. The set is read off the VM handle the blip system already holds rather than behind a resource of its own: `BlipInputs` is at the 16-param ceiling and its last element is already a nested pair for that very reason. The bubble four come straight off the DLL's string pool. Three are nampower's `NP_ChatBubbles*` under SuperWoW's unprefixed names, writing the same fields — two mods, one knob, last writer wins, as the nameplate reach already is — each keeping 0 because the reference bubbles none of those kinds. `ChatBubblesCreatures` is SuperWoW's alone and the only behaviour of the four: it NARROWS `ChatBubbles` rather than standing beside it, because the mod hooks the reference's own gate and a switch added at that site can only subtract; the other reading would bubble creatures with the master switch off. It ships 1, so an untouched client bubbles monster say and yell exactly as 1.12 does. All four are welded to their struct literals in the defaults-mirror test. CREATE_CHATBUBBLE is deliberately NOT fired, and the row says why: its payload can only be the bubble, and this client's bubbles are not Lua frames — the same wall `C_ChatBubbles.GetAllChatBubbles` already answers an empty list for. Argless would be worse than silence, since an addon written against upstream reaches for arg1's regions and raises once per bubble. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -218,6 +218,45 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
// is dropped by the same rule every routed line follows, which is upstream's behaviour too —
|
||||
// the function appends to an open file.
|
||||
//
|
||||
// TrackUnit("unit") / UntrackUnit("unit"): put a minimap tracking dot on a named unit, and
|
||||
// take it off. The set is by **guid**, not by token, so a tracked raid member keeps its dot
|
||||
// while the roster shuffles its token out from under it.
|
||||
//
|
||||
// Why this is a real feature rather than a cosmetic one: 1.12 draws party members on the
|
||||
// minimap and **raid members nowhere** (`minimap::blips::emit_party_dots` walks
|
||||
// `party_slots()`, which is what the reference's own placement does). pfUI calls `TrackUnit`
|
||||
// for all four party and all forty raid tokens on every `PARTY_MEMBERS_CHANGED`,
|
||||
// `RAID_ROSTER_UPDATE` and `PLAYER_ENTERING_WORLD` under `C.unitframes.track_group`
|
||||
// (`modules/superwow.lua:96-127`), each inside a `pcall` — so on a client without the
|
||||
// function the whole feature is simply absent, which is what it has been here.
|
||||
//
|
||||
// The dot is drawn by the same always-show clause the reference's own creature tracking uses
|
||||
// (`UNIT_DYNFLAG_TRACK_UNIT`, `0x5ed210`), so a tracked unit gets the ordinary tracked-unit
|
||||
// cell, hover name and distance cull, and nothing new had to be drawn. A token naming
|
||||
// nothing live is ignored rather than raising: pfUI calls these on units that may have just
|
||||
// left the group, which is exactly the case its `pcall` is defending against.
|
||||
for (name, track) in [("TrackUnit", true), ("UntrackUnit", false)] {
|
||||
g.set(
|
||||
name,
|
||||
lua.create_function(move |lua, token: Option<String>| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
let guid = token
|
||||
.as_deref()
|
||||
.and_then(|t| model.unit(t))
|
||||
.map(|u| u.guid)
|
||||
.filter(|g| *g != 0);
|
||||
if let Some(guid) = guid {
|
||||
if track {
|
||||
model.tracked_units.insert(guid);
|
||||
} else {
|
||||
model.tracked_units.remove(&guid);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
}
|
||||
|
||||
// CanLootUnit("unit"): whether that unit's corpse holds loot for us. Needs no new feed —
|
||||
// [`UnitState`] already carries `dynamic_flags` raw and `dead`, so this is the same two-part
|
||||
// test the loot sparkle makes on the engine side (`creature_anim/spell_visual.rs`'s
|
||||
@@ -516,6 +555,16 @@ impl crate::script::UiScript {
|
||||
pub fn take_combat_log_adds(&mut self) -> Vec<String> {
|
||||
std::mem::take(&mut self.model_mut().combat_log_adds)
|
||||
}
|
||||
|
||||
/// The `TrackUnit` set as a sorted slice of guids, for the minimap's blip pass; empty when
|
||||
/// nothing is tracked, which is the usual case and allocates nothing.
|
||||
///
|
||||
/// A copy rather than a borrow because the caller holds the VM through a `NonSendMut` it then
|
||||
/// spends elsewhere, and because the set is at most the player's own raid — forty-odd `u64`s.
|
||||
/// Sorted, so the blip pass binary-searches it instead of keeping an index of its own.
|
||||
pub fn tracked_units(&self) -> Vec<u64> {
|
||||
self.model_mut().tracked_units.iter().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -764,6 +813,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// `TrackUnit`/`UntrackUnit` keep a set of **guids**, so a tracked raid member keeps its dot
|
||||
/// while the roster shuffles its token; and a token naming nothing is ignored, not raised,
|
||||
/// because pfUI calls these on units that may have just left the group.
|
||||
#[test]
|
||||
fn track_unit_keeps_a_sorted_guid_set_and_untrack_removes_from_it() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
let unit = |guid: u64| {
|
||||
Some(UnitState {
|
||||
exists: true,
|
||||
has_object: true,
|
||||
guid,
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
s.set_unit("raid1", unit(0x30));
|
||||
s.set_unit("raid2", unit(0x10));
|
||||
s.set_unit("raid3", unit(0x20));
|
||||
assert!(s.tracked_units().is_empty());
|
||||
|
||||
s.run(
|
||||
r#"TrackUnit("raid1") TrackUnit("raid2") TrackUnit("raid3")
|
||||
TrackUnit("raid2")
|
||||
TrackUnit("raid9") TrackUnit()"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.tracked_units(),
|
||||
vec![0x10, 0x20, 0x30],
|
||||
"sorted, deduplicated, and a token naming nothing added nothing"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "handler errors: {:?}", s.errors());
|
||||
|
||||
s.run(r#"UntrackUnit("raid3") UntrackUnit("raid9") UntrackUnit()"#)
|
||||
.unwrap();
|
||||
assert_eq!(s.tracked_units(), vec![0x10, 0x30]);
|
||||
assert!(
|
||||
s.tracked_units().windows(2).all(|w| w[0] < w[1]),
|
||||
"the blip pass binary-searches this, so the order is load-bearing"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "handler errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// `CanLootUnit("unit")` is the loot sparkle's own two-part test read through the token map:
|
||||
/// dead, and `UNIT_DYNAMIC_FLAGS` bit `0x1`. A unit with no descriptor answers nil, not 0.
|
||||
#[test]
|
||||
|
||||
@@ -796,6 +796,11 @@ pub(crate) struct Model {
|
||||
pub(crate) uninvite_requests: Vec<String>,
|
||||
/// `ConsoleExec` lines that were not CVar writes.
|
||||
pub(crate) console_lines: Vec<String>,
|
||||
/// SuperWoW's `TrackUnit`/`UntrackUnit` set, by guid: the units the minimap draws a tracking
|
||||
/// dot for on top of whatever the player's own tracking spell finds
|
||||
/// (`benilla-app`'s `minimap::blips`). Sorted, so the app can binary-search it per candidate
|
||||
/// without building an index of its own.
|
||||
pub(crate) tracked_units: std::collections::BTreeSet<u64>,
|
||||
/// SuperWoW's `CombatLogAdd` lines, queued for the app to append to `WoWCombatLog.txt`
|
||||
/// (`compat/superwow.rs`). Not chat: nothing is printed to a window.
|
||||
pub(crate) combat_log_adds: Vec<String>,
|
||||
@@ -1950,6 +1955,7 @@ impl Model {
|
||||
roll_requests: Vec::new(),
|
||||
uninvite_requests: Vec::new(),
|
||||
console_lines: Vec::new(),
|
||||
tracked_units: std::collections::BTreeSet::new(),
|
||||
combat_log_adds: Vec::new(),
|
||||
logging_chat: false,
|
||||
logging_combat: false,
|
||||
|
||||
@@ -284,6 +284,12 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> {
|
||||
"CanLootUnit",
|
||||
"GetSpeed",
|
||||
"UnitNameplate",
|
||||
// SuperWoW's `TrackUnit`/`UntrackUnit` (script/compat/superwow.rs): a minimap tracking dot
|
||||
// on a named unit. 1.12 draws party members and raid members nowhere, and has no verb for
|
||||
// tracking anything by hand — the dots it draws come from a tracking spell alone. pfUI
|
||||
// puts the whole raid on the minimap with these.
|
||||
"TrackUnit",
|
||||
"UntrackUnit",
|
||||
// SuperWoW's `CombatLogAdd(text)` (script/compat/superwow.rs): one line appended to
|
||||
// `WoWCombatLog.txt`, which 1.12 writes C-side off its own chat router and gives Lua no
|
||||
// way into. It is also a detection name in its own right — BigWigs' Spell Requests gates
|
||||
|
||||
Reference in New Issue
Block a user