feat(engine): MoveRejectedEvent + PausePlugin (Esc)

- New MoveRejectedEvent fires from end_drag when the cursor is over
  a real pile but the placement is illegal. AudioPlugin plays
  card_invalid.wav on it.
- New PausePlugin + PausedResource: Esc toggles a full-window
  overlay and the flag. tick_elapsed_time and advance_time_attack
  skip work while paused. Help cheat sheet updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-25 22:56:35 -07:00
parent adacdf533c
commit b720588687
10 changed files with 214 additions and 25 deletions
+15 -8
View File
@@ -26,7 +26,8 @@ use solitaire_core::rules::{can_place_on_foundation, can_place_on_tableau};
use crate::card_plugin::{CardEntity, TABLEAU_FAN_FRAC};
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
use crate::events::{
DrawRequestEvent, MoveRequestEvent, NewGameRequestEvent, StateChangedEvent, UndoRequestEvent,
DrawRequestEvent, MoveRejectedEvent, MoveRequestEvent, NewGameRequestEvent, StateChangedEvent,
UndoRequestEvent,
};
use crate::game_plugin::GameMutation;
use crate::progress_plugin::ProgressResource;
@@ -93,10 +94,7 @@ fn handle_keyboard(
if keys.just_pressed(KeyCode::KeyD) {
draw.send(DrawRequestEvent);
}
if keys.just_pressed(KeyCode::Escape) {
// Pause placeholder — the pause screen hooks this up in a later phase.
info!("pause requested (not yet wired)");
}
// Esc is handled by `PausePlugin` (overlay toggle + paused flag).
}
fn handle_stock_click(
@@ -215,6 +213,7 @@ fn end_drag(
game: Res<GameStateResource>,
mut drag: ResMut<DragState>,
mut moves: EventWriter<MoveRequestEvent>,
mut rejected: EventWriter<MoveRejectedEvent>,
mut changed: EventWriter<StateChangedEvent>,
) {
if !buttons.just_released(MouseButton::Left) || drag.is_idle() {
@@ -234,7 +233,9 @@ fn end_drag(
// Whether we fire a MoveRequestEvent or not, always trigger a resync so
// the dragged cards snap back to their resting positions if the move is
// rejected (or never fired).
// rejected (or never fired). When the cursor was over a real pile but
// the placement is illegal, fire MoveRejectedEvent so AudioPlugin can
// play card_invalid.wav.
let mut fired = false;
if let Some(target) = target {
if target != origin {
@@ -256,11 +257,17 @@ fn end_drag(
};
if ok {
moves.send(MoveRequestEvent {
from: origin,
to: target,
from: origin.clone(),
to: target.clone(),
count,
});
fired = true;
} else {
rejected.send(MoveRejectedEvent {
from: origin.clone(),
to: target.clone(),
count,
});
}
}
}