Files
pomomon-garden/src/features/phase/mod.rs

271 lines
8.6 KiB
Rust

use crate::features::savegame::messages::SavegameDumpMessage;
use crate::prelude::*;
use components::{SessionTracker, TimerSettings};
use messages::*;
pub mod components;
pub mod messages;
pub mod utils;
/// Plugin managing the Pomodoro phase timer and state.
pub struct PhasePlugin;
impl Plugin for PhasePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<TimerSettings>();
app.init_resource::<SessionTracker>();
app.insert_resource(CurrentPhase(Phase::Focus {
duration: 25.0 * 60.0,
}));
app.add_message::<PhaseTimerFinishedMessage>();
app.add_systems(OnEnter(AppState::GameScreen), load_rules);
app.add_systems(
Update,
(
tick_timer,
handle_pause,
handle_continue,
grant_focus_rewards,
)
.run_if(in_state(AppState::GameScreen)),
);
#[cfg(debug_assertions)]
app.add_systems(Update, debug_short_phase_duration);
}
}
/// Debug system to shorten phase duration for testing.
#[cfg(debug_assertions)]
fn debug_short_phase_duration(
mut phase_res: ResMut<CurrentPhase>,
keys: Res<ButtonInput<KeyCode>>,
) {
if keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight])
&& keys.just_pressed(KeyCode::Enter)
{
let phase = &mut phase_res.0;
match phase {
Phase::Focus { duration } | Phase::Break { duration } => {
*duration = 3.0;
println!("Debug: Phase duration set to 3 seconds!");
}
_ => {}
}
}
}
/// Updates the current phase duration from settings.
fn load_rules(mut phase_res: ResMut<CurrentPhase>, settings: Res<TimerSettings>) {
let phase = &mut phase_res.0;
let new_phase = match phase {
Phase::Focus { .. } => Some(Phase::Focus {
duration: settings.focus_duration as f32,
}),
Phase::Break { .. } => Some(Phase::Break { duration: 0.0 }),
_ => None,
};
if let Some(p) = new_phase {
*phase = p;
}
}
/// Ticks the phase timer and handles completion.
fn tick_timer(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut phase_res: ResMut<CurrentPhase>,
mut finish_writer: MessageWriter<PhaseTimerFinishedMessage>,
mut savegame_messages: MessageWriter<SavegameDumpMessage>,
) {
let delta = time.delta_secs();
let phase = &mut phase_res.0;
match phase {
Phase::Focus { duration } | Phase::Break { duration } => {
*duration -= delta;
if *duration <= 0.0 {
finish_writer.write(PhaseTimerFinishedMessage {
phase: phase.clone(),
});
let completed = phase.clone();
*phase = Phase::Finished {
completed_phase: Box::new(completed),
};
println!("Phase ended");
commands.spawn((
AudioPlayer::new(asset_server.load("sounds/beep.mp3")),
PlaybackSettings::DESPAWN,
));
savegame_messages.write(SavegameDumpMessage);
}
}
_ => {}
}
}
/// Rewards the player at the end of a focus phase with `berries_per_focus_minute` * `focus_duration`.
fn grant_focus_rewards(
mut messages: MessageReader<PhaseTimerFinishedMessage>,
config: Res<GameConfig>,
mut inventory: ResMut<Inventory>,
mut commands: Commands,
mut items_query: Query<&mut ItemStack>,
mut session_tracker: ResMut<SessionTracker>,
game_config: Res<GameConfig>,
mut notifications: ResMut<Notifications>,
timer_settings: Res<TimerSettings>,
) {
for message in messages.read() {
if matches!(message.phase, Phase::Focus { .. }) {
let berries = config.berries_per_focus_minute
* (timer_settings.focus_duration as f32 / 60.0).floor() as u32;
inventory.update_item_stack(
&mut commands,
&mut items_query,
ItemType::Berry,
berries as i32,
);
session_tracker.total_berries_earned += berries;
session_tracker.completed_focus_phases += 1;
let berries_name = match berries {
1 => ItemType::Berry.singular(&game_config),
_ => ItemType::Berry.plural(&game_config),
};
notifications.info(
Some("Fokus Belohnung"),
format!(
"Du hast {} {} als Belohnung für das Abschließen einer Fokus-Phase erhalten!",
berries, berries_name
),
);
}
}
}
/// Toggles pause state of the timer.
fn handle_pause(
mut messages: MessageReader<PhaseTimerPauseMessage>,
mut phase_res: ResMut<CurrentPhase>,
) {
for _ in messages.read() {
let phase = &mut phase_res.0;
match phase {
Phase::Focus { .. } | Phase::Break { .. } => {
let current_state = phase.clone();
*phase = Phase::Paused {
previous_phase: Box::new(current_state),
};
println!("Phase paused");
}
Phase::Paused { previous_phase } => {
*phase = *previous_phase.clone();
println!("Phase resumed");
}
_ => {}
}
}
}
/// Transitions to the next phase based on current state.
pub fn next_phase(
current_phase: &mut CurrentPhase,
session_tracker: &SessionTracker,
settings: &TimerSettings,
) {
if let Phase::Finished { completed_phase } = &current_phase.0 {
match **completed_phase {
Phase::Focus { .. } => {
let is_long_break = session_tracker.completed_focus_phases > 0
&& session_tracker.completed_focus_phases % settings.long_break_interval == 0;
if is_long_break {
current_phase.0 = Phase::Break {
duration: settings.long_break_duration as f32,
};
} else {
current_phase.0 = Phase::Break {
duration: settings.short_break_duration as f32,
};
}
}
Phase::Break { .. } => {
current_phase.0 = Phase::Focus {
duration: settings.focus_duration as f32,
};
}
_ => {}
}
}
}
/// Handles transition to the next phase after user confirmation.
pub fn handle_continue(
mut messages: MessageReader<NextPhaseMessage>,
mut phase_res: ResMut<CurrentPhase>,
session_tracker: Res<SessionTracker>,
settings: Res<TimerSettings>,
mut tile_query: Query<&mut TileState>,
game_config: Res<GameConfig>,
) {
for _ in messages.read() {
let entering_break = if let Phase::Finished { completed_phase } = &phase_res.0 {
matches!(**completed_phase, Phase::Focus { .. })
} else {
false
};
next_phase(&mut phase_res, &session_tracker, &settings);
if entering_break {
println!("Growing crops and resetting watered state.");
for mut state in tile_query.iter_mut() {
if let TileState::Occupied {
seed,
watered,
growth_stage,
withered,
dry_counter,
} = &*state
{
let mut new_stage = *growth_stage;
let mut new_withered = *withered;
let mut new_dry_counter = *dry_counter;
if *watered {
new_dry_counter = 0;
if let Some(config) = seed.get_seed_config(&game_config) {
if new_stage < config.growth_stages && !new_withered {
new_stage += 1;
}
}
} else {
new_dry_counter += 1;
if new_dry_counter >= 2 {
new_withered = true;
}
}
*state = TileState::Occupied {
seed: seed.clone(),
watered: false,
growth_stage: new_stage,
withered: new_withered,
dry_counter: new_dry_counter,
};
}
}
}
}
}