diff --git a/crates/benilla-app/src/entities/attach/mod.rs b/crates/benilla-app/src/entities/attach/mod.rs index b6c4d7e0..a4a04b53 100644 --- a/crates/benilla-app/src/entities/attach/mod.rs +++ b/crates/benilla-app/src/entities/attach/mod.rs @@ -280,10 +280,12 @@ pub(super) fn attach_entity_visuals( ) = skin_build; let now = time.elapsed_secs(); for (entity, net, equipment, reattached, mount_child, mount_body, anchored) in &pending { - // A player attaches once its equipment settles, so it never flashes naked. - if net.kind == EntityKind::Player && !equipment.is_some_and(|e| e.settled) { - continue; - } + // A player attaches as soon as its display model is ready. 1.12.1 withholds a unit only + // until its first composite (`0x477860`) is done, never until every `PLAYER_VISIBLE_ITEM` + // template is answered: the compositor paints the armour it knows and re-dresses when a + // template lands (`redress_player_looks`). Gating on `Equipment::settled` here withheld a + // body for good on a chain that never answers one visible-item query — in a battleground + // crowd that is every other player. // A corpse waits for `resolve_corpse_equipment`: it is never re-dressed, so a body // composited early would stay naked. if net.kind == EntityKind::Corpse && equipment.is_none() { diff --git a/crates/benilla-app/src/ui_unit.rs b/crates/benilla-app/src/ui_unit.rs index 6bbc4100..a4e2ae89 100644 --- a/crates/benilla-app/src/ui_unit.rs +++ b/crates/benilla-app/src/ui_unit.rs @@ -518,15 +518,24 @@ pub(crate) fn class_names(class: u8) -> Option<(&'static str, &'static str)> { /// A playable race's fixed side, for where no faction template is at hand, as at world entry, /// where addons concatenate `UnitFactionGroup("player")` at file scope; [`faction_group`] reads /// the live template. +/// +/// Deviation: races 9 (Goblin) and 10 (High Elf) are TurtleWoW's additions, absent from stock +/// 1.12's eight; their sides come from TurtleWoW's `FactionTemplate.dbc` group masks (Goblin's +/// template 1609 is mask 5, Horde; High Elf's 1610 is mask 3, Alliance). Without a side here the +/// roster seat's `UnitFactionGroup("player")` is nil, and a 2.0-era addon that concatenates it at +/// load (aux-addon's `history.lua` `format('%s|%s', realm, UnitFactionGroup'player')`) raises and +/// loses its whole load phase. pub(crate) fn race_faction_group(race: u8) -> Option<&'static str> { - if !(1..=8).contains(&race) { + if !(1..=10).contains(&race) { return None; } - Some(if crate::char_create::ALLIANCE.contains(&race) { - "Alliance" - } else { - "Horde" - }) + Some( + if crate::char_create::ALLIANCE.contains(&race) || race == 10 { + "Alliance" + } else { + "Horde" + }, + ) } /// A unit's PvP team digit, `0x5efe00`'s `0` Horde, `1` Alliance, `-1` no side, from the race and @@ -1640,6 +1649,16 @@ mod tests { ); assert_eq!(race_names(0), None, "race 0 has no ChrRaces row"); assert_eq!(race_names(11), None, "race 11 has no ChrRaces row"); + // Sides for the roster seat's `UnitFactionGroup("player")`, per TurtleWoW's + // `FactionTemplate` group masks (Goblin 5, High Elf 3). + assert_eq!(race_faction_group(9), Some("Horde"), "race 9 is Goblin"); + assert_eq!( + race_faction_group(10), + Some("Alliance"), + "race 10 is High Elf" + ); + assert_eq!(race_faction_group(0), None, "race 0 has no side"); + assert_eq!(race_faction_group(11), None, "race 11 has no side"); } /// The team digit comes off the race byte, whatever template sits beside it (a GM's 35). diff --git a/crates/benilla-ui/src/script/stdlib.rs b/crates/benilla-ui/src/script/stdlib.rs index dd24e42e..8fd77600 100644 --- a/crates/benilla-ui/src/script/stdlib.rs +++ b/crates/benilla-ui/src/script/stdlib.rs @@ -303,16 +303,20 @@ function GetGameTime() return __benilla_game_hour, __benilla_game_minute end -- -- This is a deviation from the reference, kept because 1.12's own UI cannot load without it. The -- shapes are 2.0's, not 1.12's, and each is scoped to what the reference-corpus callers use: --- `wipe` is single-argument (no corpus caller passes a key) and no-ops on nil — TurtleWoW's --- `Blizzard_RaidUI.lua:77` wipes `RAID_SUBGROUP_LISTS` entries that may never be populated, and --- `strsplit` splits on PLAIN text. +-- `wipe` is single-argument (no corpus caller passes a key) and hands back a table for nil — +-- TurtleWoW's `Blizzard_RaidUI.lua:77` clears `RAID_SUBGROUP_LISTS` entries with `= wipe(t[i])` +-- and then `tinsert`s into them, and `strsplit` splits on PLAIN text. do local _find, _sub, _insert, _concat, _len = string.find, string.sub, table.insert, table.concat, string.len local _gsub = string.gsub function wipe(t) - if t == nil then return end + -- `t = t or {}` and a returned table: TurtleWoW's `Blizzard_RaidUI.lua:77,116` clears its + -- `RAID_SUBGROUP_LISTS` with `t[i] = wipe(t[i])` and then `tinsert`s into it, so `wipe` of + -- a never-populated entry must hand back a table to insert into. + t = t or {} for k in pairs(t) do t[k] = nil end + return t end -- `explode(subject, sep [, out])`: 2.0-era split into a TABLE, unlike `strsplit`'s vararg @@ -393,6 +397,14 @@ end -- define the name themselves under their own namespace. An addon reaching for one of these is -- reaching for a later client's API, and it should find what it would find there: nothing. +-- ── TurtleWoW's arena-score request ──────────────────────────────────────────────────────────── +-- TurtleWoW's `WorldStateFrame.lua:217` calls `RequestArenaScoreInfo()` on the battleground score +-- events. The function is native to its client — the reply fills `this.arenaData`, which its Lua +-- only reads — and benilla has no arena scoreboard (1.12 has no arenas, and the reply is parsed +-- by TurtleWoW's native code, never this Lua). So the request is a no-op that keeps the frame +-- from raising; `RequestCustomScoreInfo`, the battleground twin, is defined by the file itself. +RequestArenaScoreInfo = function() end + -- ── Blizzard's positional string.format (%N$) ────────────────────────────────────────────────── do -- `_find`/`_sub` are captured for the same reason `_format` is: this wrapper runs on every diff --git a/crates/benilla-ui/src/script/tests/reference_surface.rs b/crates/benilla-ui/src/script/tests/reference_surface.rs index 9248c97d..f8d10286 100644 --- a/crates/benilla-ui/src/script/tests/reference_surface.rs +++ b/crates/benilla-ui/src/script/tests/reference_surface.rs @@ -49,6 +49,9 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> { "ADDON_MSG_FIELD_DELIMITER", "ADDON_MSG_ARRAY_DELIMITER", "ADDON_MSG_SUBFIELD_DELIMITER", + // TurtleWoW's native arena-score request: a no-op here (its reply is parsed by TurtleWoW's + // client, and benilla has no arena scoreboard), so its WorldStateFrame.lua:217 cannot raise. + "RequestArenaScoreInfo", // ── WoW API past 1.12 ── // Only `SubmitChatInput` is called from our UI files (`ScriptLogFrame.xml`'s slash // commands); each of the rest awaits its 1.12 equivalent, removal or a reason.