ea1014285d
Test / test (pull_request) Successful in 15m21s
New SolutionPlaybackPlugin: the pause modal's 'Show solution' button resumes the game and requests a solve of the live position via GameState::winning_line on AsyncComputeTaskPool (never blocks the main thread; stale results discarded via a move_count snapshot). The line then plays back one instruction per 0.45 s through the normal MoveRequestEvent / DrawRequestEvent pipeline — animations, scoring, undo history and win detection behave as if the player made the moves. Tableau run counts are decoded against the live state at step time. Playback cancels on Esc, pause, undo / new-game requests, and any rejected move (the signature of the player diverging the board). Toasts cover search start, line found, unwinnable, and budget- exhausted outcomes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
137 lines
5.6 KiB
Rust
137 lines
5.6 KiB
Rust
//! Central plugin that groups all gameplay plugins.
|
|
//!
|
|
//! Register [`CoreGamePlugin`] once in the app instead of the individual
|
|
//! plugins. Plugin registration lives here rather than directly in the app
|
|
//! entry point.
|
|
|
|
use std::sync::Mutex;
|
|
|
|
use bevy::prelude::*;
|
|
|
|
use crate::platform::{
|
|
ClipboardBackendResource, StorageBackendResource, default_clipboard_backend,
|
|
default_storage_backend,
|
|
};
|
|
use crate::{
|
|
AchievementPlugin, AnimationPlugin, AssetSourcesPlugin, AutoCompletePlugin,
|
|
CardAnimationPlugin, CardPlugin, ChallengePlugin, CursorPlugin, DailyChallengePlugin,
|
|
DiagnosticsHudPlugin, DifficultyPlugin, FeedbackAnimPlugin, FontPlugin, GamePlugin, HelpPlugin,
|
|
HomePlugin, HudPlugin, InputPlugin, OnboardingPlugin, PausePlugin, PlayBySeedPlugin,
|
|
ProfilePlugin, ProgressPlugin, RadialMenuPlugin, ReplayOverlayPlugin, ReplayPlaybackPlugin,
|
|
SafeAreaInsetsPlugin, SelectionPlugin, SettingsPlugin, SolutionPlaybackPlugin, SplashPlugin,
|
|
StatsPlugin, SyncProvider, TablePlugin, ThemePlugin, ThemeRegistryPlugin, TimeAttackPlugin,
|
|
TouchSelectionPlugin, UiFocusPlugin, UiModalPlugin, UiTooltipPlugin, WeeklyGoalsPlugin,
|
|
WinSummaryPlugin,
|
|
};
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use crate::{
|
|
AnalyticsPlugin, AudioPlugin, AvatarPlugin, LeaderboardPlugin, SyncPlugin, SyncSetupPlugin,
|
|
ThemeStorePlugin,
|
|
};
|
|
|
|
/// Groups all Ferrous Solitaire gameplay plugins.
|
|
pub struct CoreGamePlugin {
|
|
sync_provider: Mutex<Option<Box<dyn SyncProvider + Send + Sync>>>,
|
|
}
|
|
|
|
impl CoreGamePlugin {
|
|
/// Create a new [`CoreGamePlugin`] with the sync provider used by [`SyncPlugin`].
|
|
pub fn new(sync_provider: Box<dyn SyncProvider + Send + Sync>) -> Self {
|
|
Self {
|
|
sync_provider: Mutex::new(Some(sync_provider)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Plugin for CoreGamePlugin {
|
|
fn build(&self, app: &mut App) {
|
|
let mut sync_provider = match self.sync_provider.lock() {
|
|
Ok(guard) => guard,
|
|
Err(poisoned) => poisoned.into_inner(),
|
|
};
|
|
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
|
|
let sync_provider = sync_provider
|
|
.take()
|
|
.expect("CoreGamePlugin::build called twice");
|
|
|
|
match default_storage_backend() {
|
|
Ok(storage) => {
|
|
app.insert_resource(StorageBackendResource(storage));
|
|
}
|
|
Err(err) => {
|
|
warn!("storage: failed to initialize platform backend: {err}");
|
|
}
|
|
}
|
|
match default_clipboard_backend() {
|
|
Ok(clipboard) => {
|
|
app.insert_resource(ClipboardBackendResource(clipboard));
|
|
}
|
|
Err(err) => {
|
|
warn!("clipboard: failed to initialize platform backend: {err}");
|
|
}
|
|
}
|
|
|
|
app.add_plugins(AssetSourcesPlugin)
|
|
.add_plugins(ThemePlugin)
|
|
.add_plugins(ThemeRegistryPlugin)
|
|
.add_plugins(FontPlugin)
|
|
.add_plugins(GamePlugin)
|
|
.add_plugins(TablePlugin)
|
|
.add_plugins(CardPlugin)
|
|
// Cursor-icon feedback is desktop-only; Android has no pointer cursor.
|
|
// The drop-target highlight systems (update_drop_highlights,
|
|
// update_drop_target_overlays) live in CursorPlugin but ARE useful
|
|
// on Android — they've been left running because their Bevy system
|
|
// params compile and function on Android; only the CursorIcon insert
|
|
// is inert. Gate the whole plugin if the cursor APIs ever cause
|
|
// Android linker issues; for now it's harmless to leave it registered.
|
|
.add_plugins(CursorPlugin)
|
|
.add_plugins(InputPlugin)
|
|
.add_plugins(RadialMenuPlugin)
|
|
.add_plugins(SelectionPlugin)
|
|
.add_plugins(TouchSelectionPlugin)
|
|
.add_plugins(AnimationPlugin)
|
|
.add_plugins(FeedbackAnimPlugin)
|
|
.add_plugins(CardAnimationPlugin)
|
|
.add_plugins(AutoCompletePlugin)
|
|
.add_plugins(SolutionPlaybackPlugin)
|
|
.add_plugins(ReplayPlaybackPlugin)
|
|
.add_plugins(ReplayOverlayPlugin)
|
|
.add_plugins(StatsPlugin::default())
|
|
.add_plugins(ProgressPlugin::default())
|
|
.add_plugins(AchievementPlugin::default())
|
|
.add_plugins(DailyChallengePlugin)
|
|
.add_plugins(WeeklyGoalsPlugin)
|
|
.add_plugins(ChallengePlugin)
|
|
.add_plugins(PlayBySeedPlugin)
|
|
.add_plugins(DifficultyPlugin)
|
|
.add_plugins(TimeAttackPlugin)
|
|
.add_plugins(SafeAreaInsetsPlugin)
|
|
.add_plugins(HudPlugin)
|
|
.add_plugins(HelpPlugin)
|
|
.add_plugins(HomePlugin::default())
|
|
.add_plugins(ProfilePlugin)
|
|
.add_plugins(PausePlugin)
|
|
.add_plugins(SettingsPlugin::default())
|
|
.add_plugins(OnboardingPlugin)
|
|
.add_plugins(WinSummaryPlugin)
|
|
.add_plugins(UiModalPlugin)
|
|
.add_plugins(UiFocusPlugin)
|
|
.add_plugins(UiTooltipPlugin)
|
|
.add_plugins(SplashPlugin)
|
|
.add_plugins(DiagnosticsHudPlugin);
|
|
|
|
// Plugins that use kira/cpal audio or multi-threaded Tokio are not
|
|
// compatible with the single-threaded wasm32 runtime. Gate them out
|
|
// so the browser build boots silently and without a sync backend.
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
app.add_plugins(AvatarPlugin)
|
|
.add_plugins(AudioPlugin)
|
|
.add_plugins(SyncPlugin::new(sync_provider))
|
|
.add_plugins(SyncSetupPlugin)
|
|
.add_plugins(ThemeStorePlugin)
|
|
.add_plugins(AnalyticsPlugin)
|
|
.add_plugins(LeaderboardPlugin);
|
|
}
|
|
}
|