From 0fde3c281f6e832f40efefee7dd07c5ad51294a9 Mon Sep 17 00:00:00 2001 From: Sam Sung <51686767+samwhosung@users.noreply.github.com> Date: Sat, 17 Oct 2026 23:34:54 +0700 Subject: [PATCH] Login notices show after the loading screen, as in 1.12.1 (#1809) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The red "GM mode is ON" line did not show on a session's first login, only on a relog. In 1.12.1 the interface and the world load before `CMSG_PLAYER_LOGIN` goes out (`0x46c272`), and the loading screen drops when the player is created (`0x5debbd`). vmangos sends that create ahead of the login notices (`Handlers/CharacterHandler.cpp:675`), so the line is added after the reveal and gets its full time on screen. benilla's cover stays up through its own warm-up, about 6 s on a cold login, and the line faded underneath it. A relog is warm, so the line survived. Server notice lines (`SMSG_NOTIFICATION`, area-trigger messages) now wait while the cover is up and fire on the frame it drops. Test: a notice under the cover fires once, at the reveal. A live cold login shows the line the frame after the cover clears. Fixes #1252 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 86c71919284d51473602a5ee668b5f818d71d980) --- crates/benilla-app/src/ui_action/feed.rs | 31 ++++++++---- .../benilla-app/src/ui_action/feed_tests.rs | 47 +++++++++++++++++++ crates/benilla-app/src/ui_action/mod.rs | 10 ++++ 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/crates/benilla-app/src/ui_action/feed.rs b/crates/benilla-app/src/ui_action/feed.rs index 830e98c2..468e969a 100644 --- a/crates/benilla-app/src/ui_action/feed.rs +++ b/crates/benilla-app/src/ui_action/feed.rs @@ -71,8 +71,13 @@ pub(super) fn feed_actions( mut cast_errors: ResMut, mut mount_errors: ResMut, mut pet_tame_failures: ResMut, - // The by-key and resolved-text queues as one parameter, at Bevy's 16-parameter limit. - ui_errors: (ResMut, ResMut), + // The by-key and resolved-text queues and the cover that holds the latter, as one parameter, + // at Bevy's 16-parameter limit. + ui_errors: ( + ResMut, + ResMut, + Option>, + ), spells: Option>, self_q: Query<&ObjectStore, With>, objects: crate::net::Objects, @@ -87,7 +92,7 @@ pub(super) fn feed_actions( let Some(mut script) = script else { return; }; - let (mut ui_error_keys, mut ui_error_texts) = ui_errors; + let (mut ui_error_keys, mut ui_error_texts, cover) = ui_errors; let memory = memory.get(&script); // Cast failures become error lines through [`cast_fail`], all resolved before any event @@ -296,13 +301,19 @@ pub(super) fn feed_actions( .collect(); show_messages(&mut script, &mut sink, "ui_action", key_lines); - // Already-resolved lines ([`UiErrorTexts`]); the queued kind is the `0x4945b0` flag. - let resolved: Vec = ui_error_texts - .0 - .drain(..) - .map(|(text, kind)| Shown::unkeyed(kind, text)) - .collect(); - show_messages(&mut script, &mut sink, "ui_action", resolved); + // Already-resolved lines ([`UiErrorTexts`]); the queued kind is the `0x4945b0` flag. Held + // while the loading cover is up, so a login notice's hold starts at the reveal: the reference + // sends `CMSG_PLAYER_LOGIN` only after its world load (`0x46c272`), and its cover drops at the + // player's create (`0x5debbd`), which vmangos sends ahead of the notices (`Maps/Map.cpp:432`, + // `Handlers/CharacterHandler.cpp:675`). + if !cover.is_some_and(|c| c.covering()) { + let resolved: Vec = ui_error_texts + .0 + .drain(..) + .map(|(text, kind)| Shown::unkeyed(kind, text)) + .collect(); + show_messages(&mut script, &mut sink, "ui_action", resolved); + } // The engine's own by-key refusals (`ERR_PASSIVE_ABILITY`): `benilla_ui` cannot reach // [`UiErrorKeys`], so it queues keys that show here, one frame late. diff --git a/crates/benilla-app/src/ui_action/feed_tests.rs b/crates/benilla-app/src/ui_action/feed_tests.rs index d317ee35..ab4818b4 100644 --- a/crates/benilla-app/src/ui_action/feed_tests.rs +++ b/crates/benilla-app/src/ui_action/feed_tests.rs @@ -434,6 +434,53 @@ fn pre_resolved_lines_land_on_the_errors_frame_in_the_arms_colour() { ); } +/// A login notice queued while the loading cover is up waits for the reveal: the reference's +/// cover drops at the player's create (`0x5debbd`), ahead of the notice, so its `UIErrorsFrame` +/// hold is never spent under the cover. +#[test] +fn a_notice_under_the_loading_cover_fires_at_the_reveal() { + let (mut app, _rx) = app_with_food_on_the_bar(); + app.insert_resource(crate::loading_screen::LoadingScreen::test_covering()); + app.world_mut() + .non_send_resource_mut::() + .run( + r#" + BENILLA_TEST_ERRORS = {} + local f = CreateFrame("Frame") + f:RegisterEvent("UI_ERROR_MESSAGE") + f:SetScript("OnEvent", function() table.insert(BENILLA_TEST_ERRORS, arg1) end) + "#, + ) + .unwrap(); + let fired = |app: &mut App| { + app.world_mut() + .non_send_resource::() + .eval::>("return BENILLA_TEST_ERRORS") + .unwrap() + }; + + app.world_mut() + .resource_mut::() + .error("GM mode is ON".to_string()); + app.update(); + app.update(); + assert!(fired(&mut app).is_empty(), "nothing fires under the cover"); + assert_eq!( + app.world().resource::().0.len(), + 1, + "the notice stays queued" + ); + + app.insert_resource(crate::loading_screen::LoadingScreen::default()); + app.update(); + assert_eq!( + fired(&mut app), + ["GM mode is ON"], + "the reveal fires it once" + ); + assert!(app.world().resource::().0.is_empty()); +} + /// `HandleCastFailed 0x6e1a00` logs through `0x62c360`; `HandlePetCastFailed 0x6e8eb0` calls only /// the packet readers and `0x496720`, with no log line and no error sound. Both entries name the /// same spell, so only the caster differs. diff --git a/crates/benilla-app/src/ui_action/mod.rs b/crates/benilla-app/src/ui_action/mod.rs index 5efe070d..43b680fa 100644 --- a/crates/benilla-app/src/ui_action/mod.rs +++ b/crates/benilla-app/src/ui_action/mod.rs @@ -250,10 +250,20 @@ impl Plugin for UiActionPlugin { .in_set(UnitFeed) .after(feed::feed_actions), ), + ) + .add_systems( + OnExit(crate::client_state::ClientState::InWorld), + drop_held_lines, ); } } +/// `OnExit(InWorld)`: a line still held under the cover dies with the world's interface, as the +/// reference's, drawn at once by `0x4945b0`, dies in the UI teardown (`0x490bd0`). +fn drop_held_lines(mut texts: ResMut) { + texts.0.clear(); +} + fn load_spells(mut commands: Commands, assets: Option>) { let Some(assets) = assets else { return }; let loaded = {