271 lines
8.6 KiB
Rust
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 } = ¤t_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,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|