Unit tooltips title the unit with its PvP name, as 1.12.1 does (#802)
A unit tooltip's title read the bare name where 1.12.1 decorates it. The tooltip calls the name builder `0x609370` itself (`0x52a1ab`, or `0x52a1db` when there is no second part) with the decoration flag a literal `1`, the same call `UnitPVPName` makes, so no CVar gates it. With the flag on, a ranked player reads `UNIT_PVP_NAME` of the gendered `PVP_RANK_<rank>_<team>` and the name, a city protector's `PVP_MEDAL<n>` follows on a second line (`0x6093ef`), and a civilian kill (`0x612550`) reads the `PVP_RANK_CIVILIAN` prefix (`0x609461`). The title now runs the builder `UnitPVPName` already uses. The test covers rank, gender, medal, civilian, an unranked player and a creature; an on-demand `ui-tooltip-rank` capture shows a ranked player's tooltip. Fixes #794 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Sam <samuv.eth@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Sam
parent
ab909df6d9
commit
bb84f1f1fb
@@ -783,6 +783,32 @@ pub(super) fn seed_ui_fixture(
|
||||
warn!("capture: ui-tooltip-world seed failed to open the tooltip");
|
||||
}
|
||||
}
|
||||
UiFixture::TooltipRank => {
|
||||
let Some(mut script) = script else {
|
||||
return;
|
||||
};
|
||||
// A ranked player under the cursor, pushed as the mouseover feed does, then the call
|
||||
// `drive_mouseover_tooltip` makes: the title is `0x609370`'s rank leg over the name,
|
||||
// "Sergeant Bob".
|
||||
script.set_unit(
|
||||
"mouseover",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
is_player: true,
|
||||
name: Some("Bob".into()),
|
||||
level: 60,
|
||||
reaction: 5,
|
||||
pvp: true,
|
||||
pvp_rank: 7,
|
||||
pvp_team: 1,
|
||||
sex: 2,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
if !script.world_tooltip_unit("mouseover") {
|
||||
warn!("capture: ui-tooltip-rank seed failed to open the tooltip");
|
||||
}
|
||||
}
|
||||
UiFixture::Character => {
|
||||
// A synthetic self player carrying the full stat block, which the `ui_char` feed turns
|
||||
// into snapshots and events as live. A level-12 warrior; positive (stamina, fire) and
|
||||
|
||||
@@ -53,6 +53,9 @@ pub(super) enum UiFixture {
|
||||
/// The world-mouseover tooltip over a seeded unit: the default anchor puts it at the screen's
|
||||
/// bottom-right (`GameTooltip.lua:73-77`), never on the hovered model.
|
||||
TooltipWorld,
|
||||
/// The world-mouseover tooltip over a seeded ranked player: the same anchor, with the title's
|
||||
/// PvP rank leg ("Sergeant Bob").
|
||||
TooltipRank,
|
||||
/// The character window fed through a synthetic self player's stat block and equipped item
|
||||
/// guids, with the items in [`crate::items::Items`].
|
||||
Character,
|
||||
@@ -715,6 +718,15 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
minute: 720,
|
||||
ui: Some(UiFixture::TooltipWorld),
|
||||
},
|
||||
// The same anchor over a ranked player: the tooltip title carries the rank, "Sergeant Bob".
|
||||
Scenario {
|
||||
name: "ui-tooltip-rank",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
ui: Some(UiFixture::TooltipRank),
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-char",
|
||||
map: Some(MAP_AZEROTH),
|
||||
|
||||
@@ -317,9 +317,9 @@ pub fn decorated_name(
|
||||
Some(format_two_strings(&template, &title, name))
|
||||
}
|
||||
|
||||
/// `0x609370`, the name builder behind `UnitPVPName` (its legs are at the binding); `name` is the
|
||||
/// plain `UnitName`.
|
||||
fn pvp_name(lua: &Lua, u: &super::UnitState, name: &str, player_level: u32) -> String {
|
||||
/// `0x609370`, the name builder behind `UnitPVPName` (its legs are at the binding) and the unit
|
||||
/// tooltip's title (`0x52a1ab`), both with the decoration flag `1`; `name` is the plain `UnitName`.
|
||||
pub(super) fn pvp_name(lua: &Lua, u: &super::UnitState, name: &str, player_level: u32) -> String {
|
||||
// Leg A: a ranked player; the title is gendered by this unit and range-unchecked.
|
||||
if u.is_player && u.pvp_rank != 0 {
|
||||
let decorated = decorated_name(
|
||||
|
||||
@@ -182,6 +182,105 @@ fn a_pending_name_titles_unknownobject_and_the_answer_replaces_it() {
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
/// The title is decorated by `0x609370` with the flag on (`0x52a1ab`), the builder `UnitPVPName`
|
||||
/// runs: a ranked player's rank first through `UNIT_PVP_NAME`, gendered by the unit's sex, and a
|
||||
/// city protector's medal on a second line; a civilian kill's `PVP_RANK_CIVILIAN` prefix; anyone
|
||||
/// else the plain name.
|
||||
#[test]
|
||||
fn the_tooltip_title_is_the_pvp_name() {
|
||||
let mut s = script();
|
||||
seed_level_strings(&mut s);
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.set_player_req_state(PlayerReqState {
|
||||
level: 30,
|
||||
..Default::default()
|
||||
});
|
||||
let player = |sex: u8, pvp_rank: u8| UnitState {
|
||||
exists: true,
|
||||
is_player: true,
|
||||
guid: 0x0000_0000_0000_002A,
|
||||
name: Some("Pfedh".into()),
|
||||
level: 1,
|
||||
race: Some("Human".into()),
|
||||
class: Some("Warrior".into()),
|
||||
sex,
|
||||
pvp_rank,
|
||||
pvp_team: 1,
|
||||
..Default::default()
|
||||
};
|
||||
s.run(
|
||||
r#"
|
||||
UNIT_PVP_NAME = "%s %s"
|
||||
PVP_RANK_7_1 = "Sergeant"
|
||||
PVP_RANK_7_1_FEMALE = "Sergeant (f)"
|
||||
PVP_MEDAL1 = "Guardian of Stormwind"
|
||||
PVP_RANK_CIVILIAN = "Civilian"
|
||||
local a = CreateFrame("Button", "UF1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let title = |s: &UiScript| -> String { s.eval("return TTTextLeft1:GetText()").unwrap() };
|
||||
|
||||
s.set_unit("target", Some(player(2, 7)));
|
||||
s.run(r#"assert(TT:SetUnit("target") == 1)"#).unwrap();
|
||||
assert_eq!(title(&s), "Sergeant Pfedh", "the rank rides first");
|
||||
|
||||
s.set_unit("target", Some(player(3, 7)));
|
||||
s.run(r#"assert(TT:SetUnit("target") == 1)"#).unwrap();
|
||||
assert_eq!(
|
||||
title(&s),
|
||||
"Sergeant (f) Pfedh",
|
||||
"the unit's sex picks the twin"
|
||||
);
|
||||
|
||||
s.set_unit("target", Some(player(2, 0)));
|
||||
s.run(r#"assert(TT:SetUnit("target") == 1)"#).unwrap();
|
||||
assert_eq!(
|
||||
title(&s),
|
||||
"Pfedh",
|
||||
"an unranked player keeps the plain name"
|
||||
);
|
||||
|
||||
let mut protector = player(2, 7);
|
||||
protector.pvp_medal = 1;
|
||||
s.set_unit("target", Some(protector));
|
||||
s.run(r#"assert(TT:SetUnit("target") == 1)"#).unwrap();
|
||||
assert_eq!(
|
||||
title(&s),
|
||||
"Sergeant Pfedh\nGuardian of Stormwind",
|
||||
"the medal rides a second line"
|
||||
);
|
||||
|
||||
let mut beast = wolf();
|
||||
beast.pvp_rank = 7; // a creature carries no rank byte, and must not read one anyway
|
||||
s.set_unit("target", Some(beast));
|
||||
s.run(r#"assert(TT:SetUnit("target") == 1)"#).unwrap();
|
||||
assert_eq!(title(&s), "Timber Wolf", "a creature takes no rank");
|
||||
|
||||
// Hostile, PvP-flagged, a civilian and grey (`0x612550`): the dishonorable-kill prefix.
|
||||
s.set_unit(
|
||||
"target",
|
||||
Some(UnitState {
|
||||
exists: true,
|
||||
name: Some("Defias Civilian".into()),
|
||||
level: 20,
|
||||
reaction: 2,
|
||||
pvp: true,
|
||||
civilian: true,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
s.run(r#"assert(TT:SetUnit("target") == 1)"#).unwrap();
|
||||
assert_eq!(
|
||||
title(&s),
|
||||
"Civilian Defias Civilian",
|
||||
"a civilian kill warns"
|
||||
);
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
/// The faction line sits between the level line and "PvP" (`0x529fe0`). Civilian needs the PvP
|
||||
/// bit, the flag, hostility and a grey level (`0x612550`); Leader only the PvP bit and the flag
|
||||
/// (`0x6125c0`).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! The engine unit tooltip, in `0x529fe0`'s line order: the name (gold; FrameXML recolours it by
|
||||
//! reaction), the creature subtitle, the level line, the faction name, then "PvP", "Skinnable",
|
||||
//! "Civilian" and "Leader", with health on the `<name>StatusBar` child. The world mouseover drives
|
||||
//! it through [`super::UiScript::world_tooltip_unit`]; unit-frame hovers call `SetUnit` from Lua.
|
||||
//! The engine unit tooltip, in `0x529fe0`'s line order: the name (gold, decorated by `0x609370`
|
||||
//! as `UnitPVPName`'s is; FrameXML recolours it by reaction), the creature subtitle, the level
|
||||
//! line, the faction name, then "PvP", "Skinnable", "Civilian" and "Leader", with health on the
|
||||
//! `<name>StatusBar` child. The world mouseover drives it through
|
||||
//! [`super::UiScript::world_tooltip_unit`]; unit-frame hovers call `SetUnit` from Lua.
|
||||
|
||||
use mlua::{Lua, Table};
|
||||
|
||||
@@ -118,10 +119,12 @@ fn render_unit(lua: &Lua, this: &Table, token: &str) -> mlua::Result<bool> {
|
||||
return Ok(false);
|
||||
};
|
||||
// The name: `CGUnit_C::GetUnitName` `0x609210` (`0x52a187`), whose every miss falls to
|
||||
// `UNKNOWNOBJECT`, as `UnitName`'s does. A token with no object never reaches the builder
|
||||
// `UNKNOWNOBJECT`, as `UnitName`'s does, then decorated by `0x609370` with the flag a literal
|
||||
// `1` (`0x52a19b`), as `UnitPVPName` does, so no CVar gates it: a ranked player's rank and
|
||||
// medal line, a civilian kill's prefix. A token with no object never reaches the builder
|
||||
// (`0x468460`): no plate, and `SetUnit` answers nil.
|
||||
let title = match &u.name {
|
||||
Some(n) => n.clone(),
|
||||
Some(name) => super::pvp::pvp_name(lua, &u, name, player_level),
|
||||
None => unknownobject(lua)?.to_str()?.to_string(),
|
||||
};
|
||||
append_line(lua, this, (title, GOLD), None, false)?;
|
||||
|
||||
Reference in New Issue
Block a user