feat(engine): InfoToastEvent — show locked-mode messages on-screen
Replaces silent info!() log calls with on-screen toasts when the player presses Z/X/T without reaching the required unlock level. Any system can now fire InfoToastEvent(message) to surface a brief text overlay without depending on a specific plugin. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::auto_complete_plugin::AutoCompleteState;
|
|||||||
use crate::card_plugin::CardEntity;
|
use crate::card_plugin::CardEntity;
|
||||||
use crate::challenge_plugin::ChallengeAdvancedEvent;
|
use crate::challenge_plugin::ChallengeAdvancedEvent;
|
||||||
use crate::daily_challenge_plugin::{DailyChallengeCompletedEvent, DailyGoalAnnouncementEvent};
|
use crate::daily_challenge_plugin::{DailyChallengeCompletedEvent, DailyGoalAnnouncementEvent};
|
||||||
use crate::events::NewGameConfirmEvent;
|
use crate::events::{InfoToastEvent, NewGameConfirmEvent};
|
||||||
use crate::events::{AchievementUnlockedEvent, GameWonEvent};
|
use crate::events::{AchievementUnlockedEvent, GameWonEvent};
|
||||||
use crate::game_plugin::GameMutation;
|
use crate::game_plugin::GameMutation;
|
||||||
use crate::layout::LayoutResource;
|
use crate::layout::LayoutResource;
|
||||||
@@ -93,6 +93,7 @@ impl Plugin for AnimationPlugin {
|
|||||||
.add_event::<ChallengeAdvancedEvent>()
|
.add_event::<ChallengeAdvancedEvent>()
|
||||||
.add_event::<SettingsChangedEvent>()
|
.add_event::<SettingsChangedEvent>()
|
||||||
.add_event::<NewGameConfirmEvent>()
|
.add_event::<NewGameConfirmEvent>()
|
||||||
|
.add_event::<InfoToastEvent>()
|
||||||
.init_resource::<EffectiveSlideDuration>()
|
.init_resource::<EffectiveSlideDuration>()
|
||||||
.add_systems(Startup, init_slide_duration)
|
.add_systems(Startup, init_slide_duration)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
@@ -111,6 +112,7 @@ impl Plugin for AnimationPlugin {
|
|||||||
handle_settings_toast,
|
handle_settings_toast,
|
||||||
handle_auto_complete_toast,
|
handle_auto_complete_toast,
|
||||||
handle_new_game_confirm_toast,
|
handle_new_game_confirm_toast,
|
||||||
|
handle_info_toast,
|
||||||
tick_toasts,
|
tick_toasts,
|
||||||
)
|
)
|
||||||
.after(GameMutation),
|
.after(GameMutation),
|
||||||
@@ -322,6 +324,12 @@ fn handle_new_game_confirm_toast(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_info_toast(mut commands: Commands, mut events: EventReader<InfoToastEvent>) {
|
||||||
|
for ev in events.read() {
|
||||||
|
spawn_toast(&mut commands, ev.0.clone(), 3.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn tick_toasts(
|
fn tick_toasts(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use bevy::prelude::*;
|
|||||||
use solitaire_core::game_state::GameMode;
|
use solitaire_core::game_state::GameMode;
|
||||||
use solitaire_data::{challenge_count, challenge_seed_for, save_progress_to};
|
use solitaire_data::{challenge_count, challenge_seed_for, save_progress_to};
|
||||||
|
|
||||||
use crate::events::{GameWonEvent, NewGameRequestEvent};
|
use crate::events::{GameWonEvent, InfoToastEvent, NewGameRequestEvent};
|
||||||
use crate::game_plugin::GameMutation;
|
use crate::game_plugin::GameMutation;
|
||||||
use crate::progress_plugin::{ProgressResource, ProgressStoragePath, ProgressUpdate};
|
use crate::progress_plugin::{ProgressResource, ProgressStoragePath, ProgressUpdate};
|
||||||
use crate::resources::GameStateResource;
|
use crate::resources::GameStateResource;
|
||||||
@@ -31,6 +31,7 @@ impl Plugin for ChallengePlugin {
|
|||||||
app.add_event::<ChallengeAdvancedEvent>()
|
app.add_event::<ChallengeAdvancedEvent>()
|
||||||
.add_event::<GameWonEvent>()
|
.add_event::<GameWonEvent>()
|
||||||
.add_event::<NewGameRequestEvent>()
|
.add_event::<NewGameRequestEvent>()
|
||||||
|
.add_event::<InfoToastEvent>()
|
||||||
// Run after ProgressUpdate so we don't fight ProgressPlugin's add_xp.
|
// Run after ProgressUpdate so we don't fight ProgressPlugin's add_xp.
|
||||||
.add_systems(Update, advance_on_challenge_win.after(ProgressUpdate))
|
.add_systems(Update, advance_on_challenge_win.after(ProgressUpdate))
|
||||||
.add_systems(Update, handle_start_challenge_request.before(GameMutation));
|
.add_systems(Update, handle_start_challenge_request.before(GameMutation));
|
||||||
@@ -66,15 +67,15 @@ fn handle_start_challenge_request(
|
|||||||
keys: Res<ButtonInput<KeyCode>>,
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
progress: Res<ProgressResource>,
|
progress: Res<ProgressResource>,
|
||||||
mut new_game: EventWriter<NewGameRequestEvent>,
|
mut new_game: EventWriter<NewGameRequestEvent>,
|
||||||
|
mut info_toast: EventWriter<InfoToastEvent>,
|
||||||
) {
|
) {
|
||||||
if !keys.just_pressed(KeyCode::KeyX) {
|
if !keys.just_pressed(KeyCode::KeyX) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if progress.0.level < CHALLENGE_UNLOCK_LEVEL {
|
if progress.0.level < CHALLENGE_UNLOCK_LEVEL {
|
||||||
info!(
|
info_toast.send(InfoToastEvent(format!(
|
||||||
"Challenge mode locked — reach level {} (currently {}).",
|
"Challenge mode unlocks at level {CHALLENGE_UNLOCK_LEVEL}"
|
||||||
CHALLENGE_UNLOCK_LEVEL, progress.0.level
|
)));
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(seed) = challenge_seed_for(progress.0.challenge_index) else {
|
let Some(seed) = challenge_seed_for(progress.0.challenge_index) else {
|
||||||
|
|||||||
@@ -75,3 +75,8 @@ pub struct ManualSyncRequestEvent;
|
|||||||
/// confirmation window sends `NewGameRequestEvent`.
|
/// confirmation window sends `NewGameRequestEvent`.
|
||||||
#[derive(Event, Debug, Clone, Copy, Default)]
|
#[derive(Event, Debug, Clone, Copy, Default)]
|
||||||
pub struct NewGameConfirmEvent;
|
pub struct NewGameConfirmEvent;
|
||||||
|
|
||||||
|
/// Generic informational toast message. Any system can fire this to display
|
||||||
|
/// a short string to the player, e.g. "Locked — reach level 5".
|
||||||
|
#[derive(Event, Debug, Clone)]
|
||||||
|
pub struct InfoToastEvent(pub String);
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ use solitaire_core::rules::{can_place_on_foundation, can_place_on_tableau};
|
|||||||
use crate::card_plugin::{CardEntity, TABLEAU_FAN_FRAC};
|
use crate::card_plugin::{CardEntity, TABLEAU_FAN_FRAC};
|
||||||
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
DrawRequestEvent, MoveRejectedEvent, MoveRequestEvent, NewGameConfirmEvent, NewGameRequestEvent,
|
DrawRequestEvent, InfoToastEvent, MoveRejectedEvent, MoveRequestEvent, NewGameConfirmEvent,
|
||||||
StateChangedEvent, UndoRequestEvent,
|
NewGameRequestEvent, StateChangedEvent, UndoRequestEvent,
|
||||||
};
|
};
|
||||||
use crate::game_plugin::GameMutation;
|
use crate::game_plugin::GameMutation;
|
||||||
use crate::progress_plugin::ProgressResource;
|
use crate::progress_plugin::ProgressResource;
|
||||||
@@ -48,6 +48,7 @@ pub struct InputPlugin;
|
|||||||
impl Plugin for InputPlugin {
|
impl Plugin for InputPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_event::<NewGameConfirmEvent>()
|
app.add_event::<NewGameConfirmEvent>()
|
||||||
|
.add_event::<InfoToastEvent>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
@@ -75,6 +76,7 @@ fn handle_keyboard(
|
|||||||
mut undo: EventWriter<UndoRequestEvent>,
|
mut undo: EventWriter<UndoRequestEvent>,
|
||||||
mut new_game: EventWriter<NewGameRequestEvent>,
|
mut new_game: EventWriter<NewGameRequestEvent>,
|
||||||
mut confirm_event: EventWriter<NewGameConfirmEvent>,
|
mut confirm_event: EventWriter<NewGameConfirmEvent>,
|
||||||
|
mut info_toast: EventWriter<InfoToastEvent>,
|
||||||
mut draw: EventWriter<DrawRequestEvent>,
|
mut draw: EventWriter<DrawRequestEvent>,
|
||||||
) {
|
) {
|
||||||
// Tick down any active confirmation window.
|
// Tick down any active confirmation window.
|
||||||
@@ -114,10 +116,9 @@ fn handle_keyboard(
|
|||||||
mode: Some(solitaire_core::game_state::GameMode::Zen),
|
mode: Some(solitaire_core::game_state::GameMode::Zen),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
info!(
|
info_toast.send(InfoToastEvent(format!(
|
||||||
"Zen mode locked — reach level {} (currently {}).",
|
"Zen mode unlocks at level {CHALLENGE_UNLOCK_LEVEL}"
|
||||||
CHALLENGE_UNLOCK_LEVEL, level
|
)));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if keys.just_pressed(KeyCode::KeyD) {
|
if keys.just_pressed(KeyCode::KeyD) {
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ pub use auto_complete_plugin::AutoCompletePlugin;
|
|||||||
pub use audio_plugin::{AudioPlugin, AudioState, SoundLibrary};
|
pub use audio_plugin::{AudioPlugin, AudioState, SoundLibrary};
|
||||||
pub use card_plugin::{CardEntity, CardLabel, CardPlugin};
|
pub use card_plugin::{CardEntity, CardLabel, CardPlugin};
|
||||||
pub use events::{
|
pub use events::{
|
||||||
AchievementUnlockedEvent, CardFlippedEvent, DrawRequestEvent, GameWonEvent, ManualSyncRequestEvent,
|
AchievementUnlockedEvent, CardFlippedEvent, DrawRequestEvent, GameWonEvent, InfoToastEvent,
|
||||||
MoveRejectedEvent, MoveRequestEvent, NewGameConfirmEvent, NewGameRequestEvent, StateChangedEvent,
|
ManualSyncRequestEvent, MoveRejectedEvent, MoveRequestEvent, NewGameConfirmEvent,
|
||||||
UndoRequestEvent,
|
NewGameRequestEvent, StateChangedEvent, UndoRequestEvent,
|
||||||
};
|
};
|
||||||
pub use game_plugin::{GameMutation, GamePlugin, GameStatePath};
|
pub use game_plugin::{GameMutation, GamePlugin, GameStatePath};
|
||||||
pub use help_plugin::{HelpPlugin, HelpScreen};
|
pub use help_plugin::{HelpPlugin, HelpScreen};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use bevy::prelude::*;
|
|||||||
use solitaire_core::game_state::GameMode;
|
use solitaire_core::game_state::GameMode;
|
||||||
|
|
||||||
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
||||||
use crate::events::{GameWonEvent, NewGameRequestEvent};
|
use crate::events::{GameWonEvent, InfoToastEvent, NewGameRequestEvent};
|
||||||
use crate::game_plugin::GameMutation;
|
use crate::game_plugin::GameMutation;
|
||||||
use crate::progress_plugin::ProgressResource;
|
use crate::progress_plugin::ProgressResource;
|
||||||
use crate::resources::GameStateResource;
|
use crate::resources::GameStateResource;
|
||||||
@@ -39,6 +39,7 @@ impl Plugin for TimeAttackPlugin {
|
|||||||
.add_event::<TimeAttackEndedEvent>()
|
.add_event::<TimeAttackEndedEvent>()
|
||||||
.add_event::<GameWonEvent>()
|
.add_event::<GameWonEvent>()
|
||||||
.add_event::<NewGameRequestEvent>()
|
.add_event::<NewGameRequestEvent>()
|
||||||
|
.add_event::<InfoToastEvent>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
handle_start_time_attack_request.before(GameMutation),
|
handle_start_time_attack_request.before(GameMutation),
|
||||||
@@ -53,15 +54,15 @@ fn handle_start_time_attack_request(
|
|||||||
progress: Res<ProgressResource>,
|
progress: Res<ProgressResource>,
|
||||||
mut session: ResMut<TimeAttackResource>,
|
mut session: ResMut<TimeAttackResource>,
|
||||||
mut new_game: EventWriter<NewGameRequestEvent>,
|
mut new_game: EventWriter<NewGameRequestEvent>,
|
||||||
|
mut info_toast: EventWriter<InfoToastEvent>,
|
||||||
) {
|
) {
|
||||||
if !keys.just_pressed(KeyCode::KeyT) {
|
if !keys.just_pressed(KeyCode::KeyT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if progress.0.level < CHALLENGE_UNLOCK_LEVEL {
|
if progress.0.level < CHALLENGE_UNLOCK_LEVEL {
|
||||||
info!(
|
info_toast.send(InfoToastEvent(format!(
|
||||||
"Time Attack locked — reach level {} (currently {}).",
|
"Time Attack unlocks at level {CHALLENGE_UNLOCK_LEVEL}"
|
||||||
CHALLENGE_UNLOCK_LEVEL, progress.0.level
|
)));
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*session = TimeAttackResource {
|
*session = TimeAttackResource {
|
||||||
|
|||||||
Reference in New Issue
Block a user