From 5fa31a073bcb5e61cf442a74f4f1f8fb197a577b Mon Sep 17 00:00:00 2001 From: andi79h-claude Date: Sun, 18 Oct 2026 09:54:36 +0200 Subject: [PATCH] IsRaidOfficer answers 1 for an assistant or the leader, as 1.12.1 does (#1894) It was a nil stub, so an assistant got no raid target icons in the unit popup (UnitPopup.lua offers RAID_TARGET_ICON to the leader or to IsRaidOfficer) and the raid frame's add-member button stayed off for them. 0x4bb910 walks the raid roster (0xb712a8, count [0xb713e0]) for the row whose guid is the local player's and pushes 1 when that row's rank ([row+0xc], GetRaidRosterInfo's return 2) is above 0, so for an assistant and for the leader; outside a raid, without a player or without our row it answers nil. Verified: cargo test -p benilla-ui --lib (is_raid_officer_reads_our_own_roster_rank), clippy -D warnings. Built on macOS only. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit ed630ffb784a4038a0aee343fb909d35147aa388) # Conflicts: # crates/benilla-ui/src/script/party.rs Absorbed into the octo fork. Adjacency only: our SuperWoW SetRaidTarget flag test sits immediately before the test upstream retitled, so both are kept. The binding change applies unchanged; party:: tests green (34). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn --- crates/benilla-ui/src/script/party.rs | 47 ++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/crates/benilla-ui/src/script/party.rs b/crates/benilla-ui/src/script/party.rs index 2880dbab..4cadd54a 100644 --- a/crates/benilla-ui/src/script/party.rs +++ b/crates/benilla-ui/src/script/party.rs @@ -530,12 +530,23 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> { })?, )?; - // IsRaidOfficer() → always nil: its reference body (`0x4bb910`) is untraced. So the RaidFrame - // enables its add-member button for the leader only, where the reference also enables it for - // an assistant. + // IsRaidOfficer() → 1 or nil (`0x4bb910`): it walks the raid roster (`0xb712a8`, count + // `[0xb713e0]`) for the row whose guid is the local player's (`0x468550`, `edx:eax`) and pushes + // 1 when that row's rank (`[row+0xc]`, `GetRaidRosterInfo`'s return 2) is above 0, so for an + // assistant and for the leader. Outside a raid, without a player or without our row: nil. This + // is what lets assistants set raid target icons (`UnitPopup.lua`) and invite to the raid. g.set( "IsRaidOfficer", - lua.create_function(|_, ()| Ok(Value::Nil))?, + lua.create_function(|lua, ()| { + let model = lua.app_data_ref::().expect("model app_data"); + let me = model.unit("player").map_or(0, |u| u.guid); + let officer = me != 0 && model.party.raid.iter().any(|m| m.guid == me && m.rank > 0); + Ok(if officer { + Value::Integer(1) + } else { + Value::Nil + }) + })?, )?; // ── The raid-management verbs ──────────────────────────────────────────── @@ -1191,11 +1202,31 @@ mod tests { assert_eq!(flags, [true, true, false, false]); } - /// Nil until `0x4bb910`'s body is traced; "rank >= 1" would be a guess. + /// `0x4bb910`: our own roster row, rank above 0 (assistant or leader); nil otherwise. #[test] - fn is_raid_officer_is_still_nil_and_that_is_a_missing_carve_not_a_missing_feature() { - let s = UiScript::new().unwrap(); - assert!(s.eval::("return IsRaidOfficer() == nil").unwrap()); + fn is_raid_officer_reads_our_own_roster_rank() { + let officer = |s: &UiScript| s.eval::>("return IsRaidOfficer()").unwrap(); + let mut s = UiScript::new().unwrap(); + assert_eq!(officer(&s), None, "ungrouped"); + let mut raid = ten_player_raid(); + raid.raid[0].rank = 0; // we are a plain member + raid.raid[1].rank = 1; // someone else assists + s.set_party(raid.clone()); + assert_eq!(officer(&s), None, "no player unit yet"); + s.set_unit("player", Some(unit(true, 0x100))); + assert_eq!( + officer(&s), + None, + "a member, another row's rank does not count" + ); + raid.raid[0].rank = 1; + s.set_party(raid.clone()); + assert_eq!(officer(&s), Some(1), "assistant"); + raid.raid[0].rank = 2; + s.set_party(raid); + assert_eq!(officer(&s), Some(1), "leader"); + s.set_unit("player", Some(unit(true, 0x999))); + assert_eq!(officer(&s), None, "no row of ours"); } // ── The identity predicates ─────────────────────────────────────────────