diff --git a/crates/benilla-app/src/ui_battlefield.rs b/crates/benilla-app/src/ui_battlefield.rs index e20f0406..98df9e61 100644 --- a/crates/benilla-app/src/ui_battlefield.rs +++ b/crates/benilla-app/src/ui_battlefield.rs @@ -211,9 +211,8 @@ fn feed_battlefield( let slots = queue .slots() - .iter() - .map(|s| slot_view(s.as_ref(), catalog, now)) - .collect(); + .each_ref() + .map(|s| slot_view(s.as_ref(), catalog, now)); script.set_battlefield_queue(slots, queue.instance_expiration_ms(now)); if std::mem::take(&mut state.show) { diff --git a/crates/benilla-app/src/ui_script/battlefield_tests.rs b/crates/benilla-app/src/ui_script/battlefield_tests.rs index 7dfebde0..6b97d0f1 100644 --- a/crates/benilla-app/src/ui_script/battlefield_tests.rs +++ b/crates/benilla-app/src/ui_script/battlefield_tests.rs @@ -77,7 +77,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() { let mut s = session(); assert!(!visible(&s, "MiniMapBattlefieldFrame"), "no queue at load"); - s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 1, 0), empty(), empty()], 0); + s.set_battlefield_queue([slot(529, "Arathi Basin", 1, 0), empty(), empty()], 0); s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]); assert!( visible(&s, "MiniMapBattlefieldFrame"), @@ -103,7 +103,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() { assert!(!visible(&s, "StaticPopup1"), "queued is not yet a question"); // Status 2, ready to enter: the confirm dialog and the port countdown. - s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 2, 3), empty(), empty()], 0); + s.set_battlefield_queue([slot(529, "Arathi Basin", 2, 3), empty(), empty()], 0); s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]); assert!( visible(&s, "StaticPopup1"), @@ -127,7 +127,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() { s.run("StaticPopup1Button1:Click()").unwrap(); assert_eq!(s.take_battlefield_port_requests(), vec![(1, true)]); - s.set_battlefield_queue(vec![empty(), empty(), empty()], 0); + s.set_battlefield_queue([empty(), empty(), empty()], 0); s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]); assert!( !visible(&s, "MiniMapBattlefieldFrame"), @@ -154,7 +154,7 @@ fn the_list_window_opens_on_battlefields_show_and_joins_the_selection() { }), group_queue: true, }); - s.set_battlefield_queue(vec![empty(), empty(), empty()], 0); + s.set_battlefield_queue([empty(), empty(), empty()], 0); assert!(!visible(&s, "BattlefieldFrame")); s.fire_event("BATTLEFIELDS_SHOW", vec![]); assert!(visible(&s, "BattlefieldFrame"), "the event shows the panel"); @@ -228,7 +228,7 @@ fn the_list_rows_carry_the_queue_status() { }), ..Default::default() }); - s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 1, 4), empty(), empty()], 0); + s.set_battlefield_queue([slot(529, "Arathi Basin", 1, 4), empty(), empty()], 0); s.fire_event("BATTLEFIELDS_SHOW", vec![]); assert_eq!( s.eval::("return BattlefieldZone2Status:GetText()") @@ -243,3 +243,14 @@ fn the_list_rows_carry_the_queue_status() { (`FontString:GetText 0x79d690` substitutes nil)" ); } + +#[test] +fn a_party_leader_change_before_any_queue_push_updates_the_list_cleanly() { + let _data = benilla_formats::wow_data_or_skip!(); + let mut s = session(); + s.take_errors(); + // A party login fires PARTY_LEADER_CHANGED, which runs `BattlefieldFrame_Update`; it reads + // `GetBattlefieldStatus(1..3)` and concatenates the name of any status but "none" (line 242). + s.fire_event("PARTY_LEADER_CHANGED", vec![]); + assert_eq!(s.take_errors(), Vec::::new()); +} diff --git a/crates/benilla-ui/src/script/battlefield_queue.rs b/crates/benilla-ui/src/script/battlefield_queue.rs index 2fbf1fac..f4ce7364 100644 --- a/crates/benilla-ui/src/script/battlefield_queue.rs +++ b/crates/benilla-ui/src/script/battlefield_queue.rs @@ -13,7 +13,8 @@ use super::Model; pub struct BattlefieldQueueSlot { /// `+0x00`, the Map.dbc row id; 0 is a cleared slot, its name looked up like any other. pub map_id: u32, - /// The localized map name, or `None` when the id has no row (a real `nil`). + /// The localized map name, or `None` when the id has no row (a real `nil`). The VM holds no + /// Map.dbc, so a slot not yet pushed answers `nil` where the reference names row 0. pub map_name: Option, /// `+0x04`: `0` none, `1` queued, `2` confirm, `3` active; anything else answers `"error"`. pub status: u32, @@ -102,7 +103,7 @@ impl super::UiScript { /// clock getters move. pub fn set_battlefield_queue( &mut self, - slots: Vec, + slots: [BattlefieldQueueSlot; 3], instance_expiration_ms: u32, ) { let mut model = self.model_mut(); @@ -498,7 +499,7 @@ mod tests { #[test] fn get_battlefield_status_answers_five_values_on_every_leg() { let mut s = UiScript::new().unwrap(); - s.set_battlefield_queue(vec![slot(489, 1, 5), slot(0, 0, 0), slot(529, 9, 2)], 0); + s.set_battlefield_queue([slot(489, 1, 5), slot(0, 0, 0), slot(529, 9, 2)], 0); let n = |s: &mut UiScript, i: &str| s.arity(&format!("GetBattlefieldStatus({i})")).unwrap(); assert_eq!(n(&mut s, "1"), 5); assert_eq!(n(&mut s, "0"), 5); @@ -532,7 +533,7 @@ mod tests { assert!(err.contains("Usage: GetBattlefieldStatus(index)"), "{err}"); let mut empty = slot(0, 0, 0); empty.map_name = None; - s.set_battlefield_queue(vec![empty], 0); + s.set_battlefield_queue([empty, Default::default(), Default::default()], 0); assert!( s.eval::("local _, name = GetBattlefieldStatus(1) return name == nil") .unwrap(), @@ -540,10 +541,26 @@ mod tests { ); } + #[test] + fn every_slot_answers_none_before_the_first_push() { + let s = UiScript::new().unwrap(); + for i in 1..=3 { + assert_eq!( + s.eval::(&format!("return (GetBattlefieldStatus({i}))")) + .unwrap(), + "none", + "slot {i}" + ); + } + } + #[test] fn the_time_getters_read_the_pushed_milliseconds() { let mut s = UiScript::new().unwrap(); - s.set_battlefield_queue(vec![slot(489, 1, 5)], 120_000); + s.set_battlefield_queue( + [slot(489, 1, 5), Default::default(), Default::default()], + 120_000, + ); assert_eq!( s.eval::("return GetBattlefieldPortExpiration(1)") .unwrap(), @@ -594,7 +611,7 @@ mod tests { #[test] fn show_battlefield_list_gates_on_a_queued_slot() { let mut s = UiScript::new().unwrap(); - s.set_battlefield_queue(vec![slot(489, 1, 5), slot(529, 2, 1), slot(0, 1, 0)], 0); + s.set_battlefield_queue([slot(489, 1, 5), slot(529, 2, 1), slot(0, 1, 0)], 0); s.run("ShowBattlefieldList(1) ShowBattlefieldList(2) ShowBattlefieldList(3) ShowBattlefieldList(4)") .unwrap(); assert_eq!(s.take_battlefield_list_requests(), vec![489]); diff --git a/crates/benilla-ui/src/script/model.rs b/crates/benilla-ui/src/script/model.rs index d52534ce..db879f0d 100644 --- a/crates/benilla-ui/src/script/model.rs +++ b/crates/benilla-ui/src/script/model.rs @@ -490,8 +490,10 @@ pub(crate) struct Model { pub(crate) battlefield_list: super::battlefield_queue::BattlefieldListView, /// The selected instance id (`[0xb6eba0]`), not an index; written by `SetSelectedBattlefield`. pub(crate) battlefield_selected: u32, - /// The three queue slots, pushed each frame with their clocks reduced to values. - pub(crate) battlefield_slots: Vec, + /// The three queue slots, pushed each frame with their clocks reduced to values. Zeroed + /// until the first push, as the reference's static slots (`0xb6e9d0`) are, so each answers + /// "none" from the first frame. + pub(crate) battlefield_slots: [super::battlefield_queue::BattlefieldQueueSlot; 3], /// `GetBattlefieldInstanceExpiration()`, pushed each frame. pub(crate) battlefield_instance_expiration_ms: u32, /// `JoinBattlefield` calls, `(instance id, as group)`. @@ -1191,7 +1193,7 @@ impl Model { battlefield_leave_requests: 0, battlefield_list: Default::default(), battlefield_selected: 0, - battlefield_slots: Vec::new(), + battlefield_slots: Default::default(), battlefield_instance_expiration_ms: 0, battlefield_join_requests: Vec::new(), battlefield_list_requests: Vec::new(),