From 0e39273f13c07859578417df37db70ce55ab5b03 Mon Sep 17 00:00:00 2001 From: Sam Sung <51686767+samwhosung@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:28:30 +0700 Subject: [PATCH] "mouseover" names nobody once no unit wins the hover: empty ground, a corpse or a nearer GameObject clear it, and the fading tooltip's health bar follows the unit's guid, not the token (#518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `"mouseover"` now names nobody once the mouse leaves a unit, as in 1.12.1, where the publisher `0x492890` zeroes the mouseover guid pair on teardown (`0x4928e8`, `0x4928f2`) and a corpse or GameObject hover leaves it naming no unit. Before, `UnitExists("mouseover")`, `UnitName("mouseover")` and `UnitIsUnit("mouseover", …)` kept answering for the last unit hovered. - The world tooltip's health bar follows its subject's guid, as the builder `0x529fe0` registers its health watcher on the guid (`0x52a0a8`), so clearing the token leaves the fading plate and its bar as they were; the clear fires no `UPDATE_MOUSEOVER_UNIT`, whose one fire site is the unit arm (`0x4929b3`). - One definition of the hovered unit (none when a nearer GameObject wins the pick) serves the tooltip, the unit tokens and the aura feed, so they clear together. Fixes #500 Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/benilla-app/src/target/mod.rs | 10 ++ crates/benilla-app/src/ui_aura.rs | 7 +- crates/benilla-app/src/ui_aura/units.rs | 20 +++ crates/benilla-app/src/ui_tooltip/mod.rs | 18 ++- crates/benilla-app/src/ui_tooltip/tests.rs | 146 +++++++++++++----- crates/benilla-app/src/ui_unit.rs | 46 +++++- .../src/script/tests/tooltip_unit.rs | 23 ++- crates/benilla-ui/src/script/tooltip/mod.rs | 3 +- crates/benilla-ui/src/script/tooltip_unit.rs | 38 +++-- crates/benilla-ui/src/script/unit/mod.rs | 2 +- crates/benilla-ui/src/widget/kinds/mod.rs | 6 +- 11 files changed, 252 insertions(+), 67 deletions(-) diff --git a/crates/benilla-app/src/target/mod.rs b/crates/benilla-app/src/target/mod.rs index ae43540f..e04e190a 100644 --- a/crates/benilla-app/src/target/mod.rs +++ b/crates/benilla-app/src/target/mod.rs @@ -113,6 +113,16 @@ impl Hovered { pub(crate) fn any(&self) -> Option { self.target.or(self.corpse) } + + /// The unit `"mouseover"` names: the hovered unit, unless a nearer GameObject won the pick. + /// The publisher `0x492890` writes whatever won to `0xb4e2c8`/`0xb4e2cc` (`0x492927`, + /// `0x492938`), and the token resolver re-resolves it as a unit (`0x515bca mov ecx,8`), so a + /// GameObject, a corpse or nothing names nobody (`0x515bd9 je`). + pub(crate) fn mouseover(&self, go: &HoveredObject) -> Option<(Entity, u64)> { + self.target + .zip(self.guid) + .filter(|_| !go_is_nearest(self, go)) + } } /// The GameObject under the cursor this frame, by [`hover::update_hovered_object`]: usable, never diff --git a/crates/benilla-app/src/ui_aura.rs b/crates/benilla-app/src/ui_aura.rs index 575fc7a4..6e16327e 100644 --- a/crates/benilla-app/src/ui_aura.rs +++ b/crates/benilla-app/src/ui_aura.rs @@ -393,6 +393,7 @@ struct AuraUnits<'w> { // anyone. group: Option>, hovered: Option>, + hovered_go: Option>, interact: Option>, } @@ -548,8 +549,10 @@ fn feed_auras( mouseover: units .hovered .as_deref() - .and_then(|h| h.target.and(h.guid)) - .unwrap_or(0), + .and_then(|h| { + h.mouseover(&units.hovered_go.as_deref().copied().unwrap_or_default()) + }) + .map_or(0, |(_, guid)| guid), npc: units.interact.as_deref().and_then(|i| i.1).unwrap_or(0), held, buffs_visible, diff --git a/crates/benilla-app/src/ui_aura/units.rs b/crates/benilla-app/src/ui_aura/units.rs index afb3cf90..40eeb002 100644 --- a/crates/benilla-app/src/ui_aura/units.rs +++ b/crates/benilla-app/src/ui_aura/units.rs @@ -963,6 +963,26 @@ mod tests { assert_eq!(listed(&mut app, "party1target", true), [770, 589]); } + /// A nearer GameObject wins the pick, and the publisher writes its guid, which the resolver + /// rejects as a unit (`0x515bd9 je`): the unit behind it is no longer the mouseover. + #[test] + fn a_nearer_gameobject_takes_the_mouseover_from_the_unit_behind_it() { + let mut app = chain_app(); + assert_eq!(listed(&mut app, "mouseover", true), [770]); + app.world_mut() + .resource_mut::() + .distance = 10.0; + let chest = app.world_mut().spawn_empty().id(); + app.insert_resource(crate::target::HoveredObject { + target: Some(chest), + guid: Some(0xF110_0000_0000_0004), + distance: 5.0, + }); + app.update(); + assert!(listed(&mut app, "mouseover", true).is_empty()); + assert!(listed(&mut app, "mouseovertarget", true).is_empty()); + } + /// A raid member's target that is out of view but on the roster reads its record, as /// `UnitDebuff`'s roster walk finds it (`0x519b25`); a hop off a unit not held is nobody. #[test] diff --git a/crates/benilla-app/src/ui_tooltip/mod.rs b/crates/benilla-app/src/ui_tooltip/mod.rs index 6008a93e..a49ec218 100644 --- a/crates/benilla-app/src/ui_tooltip/mod.rs +++ b/crates/benilla-app/src/ui_tooltip/mod.rs @@ -565,6 +565,8 @@ pub(crate) struct HoverMemo<'s> { last_lines: Local<'s, crate::ui_script::VmMemo>>, /// The hover probe's last trace line, so a stationary probe logs it once. trace: Local<'s, String>, + /// Whether the VM holds a `"mouseover"` unit, so the clear is pushed once. + published: Local<'s, crate::ui_script::VmMemo>, } fn lines_view(s: &UnitState) -> UnitState { @@ -619,10 +621,11 @@ fn drive_mouseover_tooltip( let (last, last_lines, trace) = (&mut memo.last, &mut memo.last_lines, &mut *memo.trace); let last = last.get(&script); let last_lines = last_lines.get(&script); + let published = memo.published.get(&script); let self_store = self_q.iter().next(); let chr = classes.as_deref().map(|t| &t.0); - let unit = hovered.target.zip(hovered.guid).and_then(|(entity, guid)| { + let unit = hovered.mouseover(&hovered_go).and_then(|(entity, guid)| { let store = stores.get(entity).ok()?; let name = names .resolve_unit(guid, Some(store), &commands) @@ -648,11 +651,12 @@ fn drive_mouseover_tooltip( (unit.is_none() && hovered.corpse.is_none()) || go_is_nearest(&hovered, &hovered_go) }); - if let Some((guid, state)) = unit.filter(|_| go.is_none()) { + if let Some((guid, state)) = unit { // Push first: the builder reads the token. Rebuild on a new target or a late name or // creature answer; health and power stay out of the key, since the watcher drives the bar. let key = lines_view(&state); script.set_unit("mouseover", Some(state)); + *published = true; if *last != LastHover::Unit(guid) || last_lines.as_ref() != Some(&key) { script.world_tooltip_unit("mouseover"); *last = LastHover::Unit(guid); @@ -660,6 +664,14 @@ fn drive_mouseover_tooltip( } return; } + // No unit won the pick: the publisher's teardown zeroes the mouseover pair (`0x4928e8`, + // `0x4928f2`) and writes a null, corpse or GameObject guid, none of which resolves as a unit, + // so `"mouseover"` names nobody from this frame. A fading plate keeps its lines and its bar, + // which follows the unit's guid, not the token; the null publish fires no event (the sole + // `UPDATE_MOUSEOVER_UNIT` is the unit arm's, `0x4929b3`). + if std::mem::take(published) { + script.set_unit("mouseover", None); + } // The corpse plate, "Corpse of " (`0x52aef0`): a name and nothing else, corner-seated. // The publisher fires no event for a corpse, so `UnitName("mouseover")` on one stays nil. if let Some((entity, guid)) = hovered @@ -830,7 +842,7 @@ fn drive_mouseover_tooltip( return; } if !matches!(*last, LastHover::None) { - // Hover lost: arm the fade. The `mouseover` state stays, so the fading plate keeps it. + // Hover lost: arm the fade (`0x492909` → `0x530ae0`). script.world_tooltip_fade(); *last = LastHover::None; } diff --git a/crates/benilla-app/src/ui_tooltip/tests.rs b/crates/benilla-app/src/ui_tooltip/tests.rs index 588538fa..b82b59b8 100644 --- a/crates/benilla-app/src/ui_tooltip/tests.rs +++ b/crates/benilla-app/src/ui_tooltip/tests.rs @@ -518,17 +518,14 @@ fn the_spell_feed_runs_after_the_trainer_feed() { )); } -/// The hover pushes `"mouseover"` with the hovered guid, the pair `0x492890` writes to -/// `0xb4e2c8`/`0xb4e2cc`: `UnitIsUnit` (`0x516070`) resolves both tokens through `0x515970` and -/// compares guids, so hovering the target answers 1 and hovering anyone else nil. -#[test] -fn the_mouseover_token_carries_the_hovered_guid() { - use benilla_protocol::ObjectFields; - use bevy::ecs::system::RunSystemOnce; +const ME: u64 = 0x77; +const WOLF: u64 = 0xF130_0000_4500_0001; +const BOAR: u64 = 0xF130_0000_4600_0002; - const ME: u64 = 0x77; - const WOLF: u64 = 0xF130_0000_4500_0001; - const BOAR: u64 = 0xF130_0000_4600_0002; +/// The world-hover driver over a bare VM holding `"player"` (us) and `"target"` (the wolf), each +/// with its guid, as the unit feed pushes them; returns the app, the wolf and the boar. +fn mouseover_app() -> (App, Entity, Entity) { + use benilla_protocol::ObjectFields; let mut app = App::new(); let (tx, _rx) = crossbeam_channel::unbounded(); @@ -541,19 +538,17 @@ fn the_mouseover_token_carries_the_hovered_guid() { .init_resource::() .init_resource::() .init_resource::() - .init_resource::(); + .init_resource::() + .add_systems(Update, drive_mouseover_tooltip); app.world_mut() .spawn((SelfPlayer, ObjectStore(ObjectFields::default()))); - let wolf = app - .world_mut() - .spawn(ObjectStore(ObjectFields::default())) - .id(); - let boar = app - .world_mut() - .spawn(ObjectStore(ObjectFields::default())) - .id(); + let mut unit = || { + app.world_mut() + .spawn(ObjectStore(ObjectFields::default())) + .id() + }; + let (wolf, boar) = (unit(), unit()); - // `"player"` and `"target"` as the unit feed pushes them, each with its guid. let mut script = UiScript::new().unwrap(); for (token, guid) in [("player", ME), ("target", WOLF)] { script.set_unit( @@ -567,29 +562,51 @@ fn the_mouseover_token_carries_the_hovered_guid() { ); } app.insert_non_send_resource(script); + (app, wolf, boar) +} - let hover = |app: &mut App, entity: Entity, guid: u64| { - *app.world_mut().resource_mut::() = Hovered { - target: Some(entity), - guid: Some(guid), - ..Default::default() - }; - app.world_mut() - .run_system_once(drive_mouseover_tooltip) - .unwrap(); +/// Set this frame's pick and run the driver. +fn hover(app: &mut App, pick: Hovered, object: HoveredObject) { + *app.world_mut().resource_mut::() = pick; + *app.world_mut().resource_mut::() = object; + app.update(); +} + +fn hover_unit(app: &mut App, entity: Entity, guid: u64) { + let pick = Hovered { + target: Some(entity), + guid: Some(guid), + distance: 10.0, + ..Default::default() }; - let is_unit = |app: &mut App, other: &str| -> Option { - app.world_mut() - .non_send_resource_mut::() - .eval::>(&format!(r#"return UnitIsUnit("mouseover", "{other}")"#)) - .unwrap() + hover(app, pick, HoveredObject::default()); +} + +fn eval(app: &mut App, lua: &str) -> Option { + app.world_mut() + .non_send_resource_mut::() + .eval::>(lua) + .unwrap() +} + +/// The hover pushes `"mouseover"` with the hovered guid, the pair `0x492890` writes to +/// `0xb4e2c8`/`0xb4e2cc`: `UnitIsUnit` (`0x516070`) resolves both tokens through `0x515970` and +/// compares guids, so hovering the target answers 1 and hovering anyone else nil. +#[test] +fn the_mouseover_token_carries_the_hovered_guid() { + let (mut app, wolf, boar) = mouseover_app(); + let is_unit = |app: &mut App, other: &str| { + eval( + app, + &format!(r#"return UnitIsUnit("mouseover", "{other}")"#), + ) }; - hover(&mut app, wolf, WOLF); + hover_unit(&mut app, wolf, WOLF); assert_eq!(is_unit(&mut app, "target"), Some(1), "hovering the target"); assert_eq!(is_unit(&mut app, "player"), None, "the target is not us"); - hover(&mut app, boar, BOAR); + hover_unit(&mut app, boar, BOAR); assert_eq!( is_unit(&mut app, "target"), None, @@ -601,3 +618,60 @@ fn the_mouseover_token_carries_the_hovered_guid() { "the token is itself" ); } + +/// Once no unit wins the pick, `"mouseover"` names nobody: the publisher `0x492890` zeroes the pair +/// (`0x4928e8`, `0x4928f2`) and writes a null, corpse or GameObject guid, which the resolver +/// rejects as a unit (`0x515bca mov ecx,8`, `0x515bd9 je`). Empty ground, a corpse and a nearer +/// GameObject each clear it, in the frame the hover leaves the unit. +#[test] +fn the_mouseover_token_clears_when_no_unit_is_hovered() { + use benilla_protocol::ObjectFields; + + let (mut app, wolf, _) = mouseover_app(); + let corpse = app + .world_mut() + .spawn(ObjectStore(ObjectFields::default())) + .id(); + let chest = app + .world_mut() + .spawn(ObjectStore(ObjectFields::default())) + .id(); + let named = |app: &mut App| { + ( + eval(app, r#"return UnitExists("mouseover")"#), + eval(app, r#"return UnitIsUnit("mouseover", "target")"#), + ) + }; + let empty = Hovered::default(); + let on_corpse = Hovered { + corpse: Some(corpse), + corpse_guid: Some(0xF100_0000_0000_0003), + distance: 10.0, + ..Default::default() + }; + let under_chest = Hovered { + target: Some(wolf), + guid: Some(WOLF), + distance: 10.0, + ..Default::default() + }; + let chest_nearer = HoveredObject { + target: Some(chest), + guid: Some(0xF110_0000_0000_0004), + distance: 5.0, + }; + for (leave, object, what) in [ + (empty, HoveredObject::default(), "empty ground"), + (on_corpse, HoveredObject::default(), "a corpse"), + (under_chest, chest_nearer, "a nearer GameObject"), + ] { + hover_unit(&mut app, wolf, WOLF); + assert_eq!( + named(&mut app), + (Some(1), Some(1)), + "over the wolf, before {what}" + ); + hover(&mut app, leave, object); + assert_eq!(named(&mut app), (None, None), "after {what}"); + } +} diff --git a/crates/benilla-app/src/ui_unit.rs b/crates/benilla-app/src/ui_unit.rs index 273448a8..8cf66f4e 100644 --- a/crates/benilla-app/src/ui_unit.rs +++ b/crates/benilla-app/src/ui_unit.rs @@ -582,6 +582,7 @@ pub(crate) struct UnitTokens<'w, 's> { index: Option>, pet: Option>, hovered: Option>, + hovered_go: Option>, group: Res<'w, crate::ui_party::GroupState>, pub(crate) stores: Query<'w, 's, &'static ObjectStore>, me: Query<'w, 's, (Entity, &'static Guid), With>, @@ -610,11 +611,11 @@ impl UnitTokens<'_, '_> { .and_then(|s| s.0.unit_target()) .filter(|g| *g != 0) .and_then(|g| self.held(g)), - // The same `Hovered` pick `ui_tooltip` pushes `"mouseover"` from. - "mouseover" => { - let h = self.hovered.as_ref()?; - h.target.zip(h.guid) - } + // The same pick `ui_tooltip` pushes `"mouseover"` from. + "mouseover" => self + .hovered + .as_ref()? + .mouseover(&self.hovered_go.as_deref().copied().unwrap_or_default()), // Off the pet bar's cached guid, as the `"pet"` snapshot reads it. "pet" => { let guid = self.pet.as_ref()?.spells.pet_guid; @@ -1629,6 +1630,41 @@ fn combo_edge(last: Option<(u8, u64)>, now: (u8, u64)) -> Option { mod tests { use super::*; + /// `"mouseover"` resolves through the pick the tooltip publishes: the hovered unit, until a + /// nearer GameObject wins it and the resolver rejects the GameObject's guid (`0x515bd9 je`). + #[test] + fn the_mouseover_token_resolves_nobody_behind_a_nearer_gameobject() { + use crate::target::{Hovered, HoveredObject}; + use bevy::ecs::system::RunSystemOnce; + const WOLF: u64 = 0xF130_0000_4500_0001; + + let mut app = App::new(); + app.init_resource::() + .init_resource::(); + let wolf = app.world_mut().spawn_empty().id(); + app.insert_resource(Hovered { + target: Some(wolf), + guid: Some(WOLF), + distance: 10.0, + ..Default::default() + }); + let resolve = |app: &mut App| { + app.world_mut() + .run_system_once(|tokens: UnitTokens| { + tokens.resolve("mouseover", &Selection::default()) + }) + .unwrap() + }; + assert_eq!(resolve(&mut app), Some((wolf, WOLF)), "the hovered unit"); + let chest = app.world_mut().spawn_empty().id(); + app.insert_resource(HoveredObject { + target: Some(chest), + guid: Some(0xF110_0000_0000_0004), + distance: 5.0, + }); + assert_eq!(resolve(&mut app), None, "a nearer GameObject names nobody"); + } + /// The team digit comes off the race byte, whatever template sits beside it (a GM's 35). #[test] fn the_team_digit_comes_off_the_race_byte_not_the_faction_template() { diff --git a/crates/benilla-ui/src/script/tests/tooltip_unit.rs b/crates/benilla-ui/src/script/tests/tooltip_unit.rs index 195ec01e..d4c48414 100644 --- a/crates/benilla-ui/src/script/tests/tooltip_unit.rs +++ b/crates/benilla-ui/src/script/tests/tooltip_unit.rs @@ -25,6 +25,7 @@ fn seed_level_strings(s: &mut UiScript) { fn wolf() -> UnitState { UnitState { exists: true, + guid: 0xF130_0000_4500_0001, name: Some("Timber Wolf".into()), health: 30, max_health: 50, @@ -454,8 +455,28 @@ fn world_hover_drive_and_health_watcher() { r#"assert(GameTooltipStatusBar:GetValue() == 12, "the health watcher tracked the push")"#, ) .unwrap(); + // Hover lost: the fade arms and `"mouseover"` names nobody (`0x492890` zeroes the pair), yet + // the fading plate keeps its bar, whose watcher follows the unit's guid, not the token. s.world_tooltip_fade(); - s.tick(0.6); + s.set_unit("mouseover", None); + s.tick(0.1); + s.run( + r#" + assert(UnitExists("mouseover") == nil, "the token is cleared") + assert(GameTooltip:IsShown(), "still fading") + assert(GameTooltipStatusBar:IsShown(), "the fading plate keeps its bar") + assert(GameTooltipStatusBar:GetValue() == 12, "and its value") + assert(recolored == 1, "the clear fires no UPDATE_MOUSEOVER_UNIT") + "#, + ) + .unwrap(); + // The same unit pushed under another token still drives the bar. + let mut as_target = wolf(); + as_target.health = 8; + s.set_unit("target", Some(as_target)); + s.run(r#"assert(GameTooltipStatusBar:GetValue() == 8, "the watcher follows the guid")"#) + .unwrap(); + s.tick(0.5); s.run(r#"assert(not GameTooltip:IsShown(), "faded out after the ramp")"#) .unwrap(); assert!(s.take_errors().is_empty()); diff --git a/crates/benilla-ui/src/script/tooltip/mod.rs b/crates/benilla-ui/src/script/tooltip/mod.rs index 56f421d0..da15e8d5 100644 --- a/crates/benilla-ui/src/script/tooltip/mod.rs +++ b/crates/benilla-ui/src/script/tooltip/mod.rs @@ -271,7 +271,8 @@ pub(super) fn clear_content(model: &mut Model, h: FrameHandle) { if let Ok(t) = tip_mut(model, h) { t.num_lines = 0; t.min_width = 0.0; - t.unit_token = None; + // Clear drops the subject and unregisters its health watcher (`0x5300ad`, `0x53007c`). + t.unit_guid = None; t.world_owned = false; } // The `StatusBar` health bar is unit content: it hides with the lines, and the next diff --git a/crates/benilla-ui/src/script/tooltip_unit.rs b/crates/benilla-ui/src/script/tooltip_unit.rs index d22b8a78..3c2c5b8b 100644 --- a/crates/benilla-ui/src/script/tooltip_unit.rs +++ b/crates/benilla-ui/src/script/tooltip_unit.rs @@ -113,7 +113,7 @@ fn render_unit(lua: &Lua, this: &Table, token: &str) -> mlua::Result { } fire_cleared(lua, h); let Some(u) = unit else { - set_live_token(lua, h, None); + set_subject(lua, h, None); show_or_hide_empty(lua, h); return Ok(false); }; @@ -149,7 +149,8 @@ fn render_unit(lua: &Lua, this: &Table, token: &str) -> mlua::Result { if u.racial_leader && u.pvp { append_line(lua, this, ("Leader".into(), WHITE), None, false)?; } - set_live_token(lua, h, Some(token.to_string())); + // An unresolved guid (0) is no subject: nothing can push for it. + set_subject(lua, h, Some(u.guid).filter(|&g| g != 0)); update_bar(lua, h, Some(&u)); show_or_hide_empty(lua, h); Ok(true) @@ -165,12 +166,12 @@ fn set_world_owned(lua: &Lua, h: FrameHandle) { } } -/// Remember (or drop) the tooltip's live unit token, the health watcher's key. -fn set_live_token(lua: &Lua, h: FrameHandle, token: Option) { +/// Remember (or drop) the guid of the unit the tooltip shows, the health watcher's key. +fn set_subject(lua: &Lua, h: FrameHandle, guid: Option) { let mut model = lua.app_data_mut::().expect("model app_data"); if let Some(f) = model.arena.frame_mut(h) { if let KindState::Tooltip(t) = &mut f.kind_state { - t.unit_token = token; + t.unit_guid = guid; } } } @@ -227,13 +228,18 @@ fn update_bar(lua: &Lua, h: FrameHandle, unit: Option<&UnitState>) { } } -/// A `set_unit` push for a tooltip's live token updates its health bar without rebuilding the -/// lines, as the reference's HEALTH field watcher does. +/// A `set_unit` push of the unit a tooltip shows updates its health bar without rebuilding the +/// lines, as the reference's HEALTH field watcher does: it is keyed on the unit's guid (`0x52a0a8` +/// registers `0x529560` on `+0x368`/`+0x36c`), so a token that stops naming the unit, as +/// `"mouseover"` does when the hover ends, leaves a fading plate's bar standing. pub(super) fn on_unit_push(lua: &Lua, token: &str) { - let hits: Vec = { + let (unit, hits): (UnitState, Vec) = { let model = lua.app_data_mut::().expect("model app_data"); + let Some(unit) = model.unit(token).filter(|u| u.exists && u.guid != 0) else { + return; + }; // Registered frames only, as `update_bar` writes layout. - model + let hits: Vec = model .arena .tooltip_kinds() .iter() @@ -242,17 +248,17 @@ pub(super) fn on_unit_push(lua: &Lua, token: &str) { model.frame_to_id.contains_key(&h) && matches!( model.arena.frame(h).map(|f| &f.kind_state), - Some(KindState::Tooltip(t)) if t.unit_token.as_deref() == Some(token) + Some(KindState::Tooltip(t)) if t.unit_guid == Some(unit.guid) ) }) - .collect() + .collect(); + if hits.is_empty() { + return; + } + (unit.clone(), hits) }; for h in hits { - let unit = { - let model = lua.app_data_mut::().expect("model app_data"); - model.unit(token).cloned().filter(|u| u.exists) - }; - update_bar(lua, h, unit.as_ref()); + update_bar(lua, h, Some(&unit)); } } diff --git a/crates/benilla-ui/src/script/unit/mod.rs b/crates/benilla-ui/src/script/unit/mod.rs index 6d314662..aca069ba 100644 --- a/crates/benilla-ui/src/script/unit/mod.rs +++ b/crates/benilla-ui/src/script/unit/mod.rs @@ -308,7 +308,7 @@ impl super::UiScript { } } } - // A push for a tooltip's live unit token re-drives its health bar, without a line rebuild. + // A push of the unit a tooltip shows re-drives its health bar, without a line rebuild. super::tooltip_unit::on_unit_push(&self.lua, token); } diff --git a/crates/benilla-ui/src/widget/kinds/mod.rs b/crates/benilla-ui/src/widget/kinds/mod.rs index f94deefd..b30fab67 100644 --- a/crates/benilla-ui/src/widget/kinds/mod.rs +++ b/crates/benilla-ui/src/widget/kinds/mod.rs @@ -189,8 +189,10 @@ pub struct TooltipState { pub min_width: f32, /// `FadeOut()`'s start on the `GetTime` clock; any fresh content or `Show` cancels the fade. pub fade_start: Option, - /// The unit this tooltip shows; a `set_unit` push for it re-drives the health bar. - pub unit_token: Option, + /// The guid of the unit this tooltip shows (`+0x368`/`+0x36c`, stored at `0x52a070`), whose + /// health watcher (`0x467e70`, registered at `0x52a0a8`) drives the bar; a `set_unit` push of + /// that unit, under any token, re-drives it. + pub unit_guid: Option, /// Shows world-hover content, which fades when the hover is lost; a window hover never fades. pub world_owned: bool, /// Armed for a compare render: the next `SetInventoryItem` prepends "Currently Equipped"