Merge branch '36-savegame-dumping' into 'dev'
Add savegame dumping See merge request softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik!10
This commit is contained in:
66
Cargo.lock
generated
66
Cargo.lock
generated
@@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
@@ -1092,7 +1092,7 @@ dependencies = [
|
||||
"critical-section",
|
||||
"foldhash 0.2.0",
|
||||
"futures-channel",
|
||||
"getrandom",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.0",
|
||||
"js-sys",
|
||||
"portable-atomic",
|
||||
@@ -2079,6 +2079,27 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@@ -2380,6 +2401,17 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
@@ -2783,7 +2815,7 @@ version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -3464,6 +3496,12 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "orbclient"
|
||||
version = "0.3.48"
|
||||
@@ -3622,6 +3660,7 @@ dependencies = [
|
||||
"bevy",
|
||||
"bevy_aseprite_ultra",
|
||||
"bevy_dev_tools",
|
||||
"directories",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -3754,7 +3793,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3819,6 +3858,17 @@ dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
@@ -4575,7 +4625,7 @@ version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
@@ -4620,6 +4670,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
|
||||
@@ -19,3 +19,4 @@ bevy_aseprite_ultra = "0.7.0"
|
||||
bevy_dev_tools = "0.17.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
directories = "6.0"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod phase;
|
||||
pub mod pom;
|
||||
pub mod savegame;
|
||||
pub mod tile;
|
||||
pub mod ui;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Phase {
|
||||
Break { duration: f32 },
|
||||
Focus { duration: f32 },
|
||||
@@ -51,10 +52,10 @@ impl Phase {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug)]
|
||||
#[derive(Resource, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CurrentPhase(pub Phase);
|
||||
|
||||
#[derive(Resource, Debug)]
|
||||
#[derive(Resource, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct TimerSettings {
|
||||
pub focus_duration: f32,
|
||||
pub short_break_duration: f32,
|
||||
@@ -73,7 +74,7 @@ impl Default for TimerSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Default)]
|
||||
#[derive(Resource, Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct SessionTracker {
|
||||
pub completed_focus_phases: u32,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Pom;
|
||||
|
||||
#[derive(Component)]
|
||||
#[derive(Component, Serialize, Deserialize, Clone, Copy)]
|
||||
pub struct GridPosition {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
|
||||
33
src/components/savegame.rs
Normal file
33
src/components/savegame.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use bevy::prelude::*;
|
||||
use directories::ProjectDirs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct SavegamePath(pub PathBuf);
|
||||
|
||||
impl SavegamePath {
|
||||
fn get_project_path() -> Option<PathBuf> {
|
||||
let project_dirs = ProjectDirs::from("de", "demenik", "pomomon-garden");
|
||||
|
||||
if let Some(dirs) = project_dirs {
|
||||
Some(dirs.data_local_dir().to_path_buf())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(name: &str) -> Self {
|
||||
let base_path = Self::get_project_path().unwrap_or_else(|| {
|
||||
println!(
|
||||
"Could not determine platform-specific save directory. Falling back to `./saves/"
|
||||
);
|
||||
PathBuf::from("./saves/")
|
||||
});
|
||||
|
||||
if let Err(e) = std::fs::create_dir_all(&base_path) {
|
||||
panic!("Failed to create save directory at {:?}: {}", base_path, e);
|
||||
}
|
||||
|
||||
Self(base_path.join(name))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::GridError;
|
||||
|
||||
@@ -8,7 +9,7 @@ pub struct Tile {
|
||||
pub y: u32,
|
||||
}
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[derive(Component, Default, Serialize, Deserialize, Clone, Copy, Debug)]
|
||||
pub enum TileState {
|
||||
#[default]
|
||||
Unclaimed,
|
||||
|
||||
@@ -8,3 +8,8 @@ pub struct UiPhaseText;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct UiTimerText;
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum UiStatusButton {
|
||||
SavegameDump,
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ fn main() {
|
||||
plugins::InputPlugin,
|
||||
plugins::PhasePlugin,
|
||||
plugins::StatusPlugin,
|
||||
plugins::SavegamePlugin,
|
||||
))
|
||||
.insert_resource(config)
|
||||
.run();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod interact;
|
||||
pub mod r#move;
|
||||
pub mod phase;
|
||||
pub mod savegame;
|
||||
|
||||
4
src/messages/savegame.rs
Normal file
4
src/messages/savegame.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Message)]
|
||||
pub struct SavegameDumpMessage;
|
||||
@@ -4,6 +4,7 @@ pub mod grid;
|
||||
pub mod input;
|
||||
pub mod phase;
|
||||
pub mod pom;
|
||||
pub mod savegame;
|
||||
pub mod start_screen;
|
||||
pub mod status;
|
||||
|
||||
@@ -13,5 +14,6 @@ pub use grid::GridPlugin;
|
||||
pub use input::InputPlugin;
|
||||
pub use phase::PhasePlugin;
|
||||
pub use pom::PomPlugin;
|
||||
pub use savegame::SavegamePlugin;
|
||||
pub use start_screen::StartScreenPlugin;
|
||||
pub use status::StatusPlugin;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{components::phase::*, messages::phase::*, states::AppState};
|
||||
use crate::{
|
||||
components::phase::*,
|
||||
messages::{phase::*, savegame::SavegameDumpMessage},
|
||||
states::AppState,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct PhasePlugin;
|
||||
@@ -63,6 +67,7 @@ fn tick_timer(
|
||||
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;
|
||||
@@ -81,6 +86,7 @@ fn tick_timer(
|
||||
};
|
||||
|
||||
println!("phase ended");
|
||||
savegame_messages.write(SavegameDumpMessage);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
95
src/plugins/savegame.rs
Normal file
95
src/plugins/savegame.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use crate::{
|
||||
components::{
|
||||
phase::{CurrentPhase, SessionTracker, TimerSettings},
|
||||
pom::{GridPosition, Pom},
|
||||
savegame::SavegamePath,
|
||||
tile::{Grid, TileState},
|
||||
},
|
||||
messages::savegame::*,
|
||||
states::AppState,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
pub struct SavegamePlugin;
|
||||
|
||||
impl Plugin for SavegamePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_message::<SavegameDumpMessage>();
|
||||
|
||||
app.add_systems(Update, dump_savegame.run_if(in_state(AppState::GameScreen)));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SaveData {
|
||||
grid_width: u32,
|
||||
grid_height: u32,
|
||||
tiles: Vec<Vec<TileState>>,
|
||||
current_phase: CurrentPhase,
|
||||
session_tracker: SessionTracker,
|
||||
timer_settings: TimerSettings,
|
||||
pom_position: GridPosition,
|
||||
}
|
||||
|
||||
fn dump_savegame(
|
||||
mut messages: MessageReader<SavegameDumpMessage>,
|
||||
grid: Res<Grid>,
|
||||
tile_query: Query<&TileState>,
|
||||
phase: Res<CurrentPhase>,
|
||||
tracker: Res<SessionTracker>,
|
||||
settings: Res<TimerSettings>,
|
||||
pom_query: Query<&GridPosition, With<Pom>>,
|
||||
save_path: Res<SavegamePath>,
|
||||
) {
|
||||
for _ in messages.read() {
|
||||
let mut tile_states = Vec::new();
|
||||
|
||||
for x in 0..grid.width {
|
||||
let mut col = Vec::new();
|
||||
for y in 0..grid.height {
|
||||
if let Ok(entity) = grid.get_tile((x, y)) {
|
||||
if let Ok(state) = tile_query.get(entity) {
|
||||
col.push(*state);
|
||||
} else {
|
||||
col.push(TileState::Unclaimed);
|
||||
}
|
||||
} else {
|
||||
col.push(TileState::Unclaimed);
|
||||
}
|
||||
}
|
||||
tile_states.push(col);
|
||||
}
|
||||
|
||||
let pom_pos = pom_query.single().unwrap();
|
||||
|
||||
let save_data = SaveData {
|
||||
grid_width: grid.width,
|
||||
grid_height: grid.height,
|
||||
tiles: tile_states,
|
||||
current_phase: phase.clone(),
|
||||
session_tracker: tracker.clone(),
|
||||
timer_settings: settings.clone(),
|
||||
pom_position: *pom_pos,
|
||||
};
|
||||
|
||||
match serde_json::to_string_pretty(&save_data) {
|
||||
Ok(serialized) => {
|
||||
if let Ok(mut file) = File::create(&save_path.0) {
|
||||
if let Err(e) = file.write_all(serialized.as_bytes()) {
|
||||
panic!("Failed to write save file: {}", e);
|
||||
} else {
|
||||
println!("Game saved to {}", save_path.0.display());
|
||||
}
|
||||
} else {
|
||||
panic!("Failed to create save file at {}", save_path.0.display());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Failed to serialize save data: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::states::*;
|
||||
use crate::{components::savegame::SavegamePath, states::*};
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct StartScreenPlugin;
|
||||
@@ -115,6 +115,7 @@ fn setup(mut commands: Commands) {
|
||||
}
|
||||
|
||||
fn menu(
|
||||
mut commands: Commands,
|
||||
mut next_state: ResMut<NextState<AppState>>,
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &ButtonType, &mut BackgroundColor),
|
||||
@@ -127,7 +128,10 @@ fn menu(
|
||||
*color = PRESSED_BUTTON.into();
|
||||
|
||||
match button_type {
|
||||
ButtonType::NewGame => next_state.set(AppState::GameScreen),
|
||||
ButtonType::NewGame => {
|
||||
commands.insert_resource(SavegamePath::new("savegame.json"));
|
||||
next_state.set(AppState::GameScreen);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
components::{phase::CurrentPhase, ui::*},
|
||||
messages::savegame::SavegameDumpMessage,
|
||||
states::AppState,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
@@ -11,6 +12,10 @@ impl Plugin for StatusPlugin {
|
||||
app.add_systems(OnEnter(AppState::GameScreen), setup);
|
||||
app.add_systems(OnExit(AppState::GameScreen), cleanup);
|
||||
app.add_systems(Update, update_status.run_if(in_state(AppState::GameScreen)));
|
||||
app.add_systems(
|
||||
Update,
|
||||
status_buttons.run_if(in_state(AppState::GameScreen)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +48,16 @@ fn setup(mut commands: Commands) {
|
||||
Text::new("--:--"),
|
||||
TextFont::from_font_size(16.0),
|
||||
TextColor(Color::WHITE)
|
||||
),
|
||||
(
|
||||
Button,
|
||||
UiStatusButton::SavegameDump,
|
||||
Node::default(),
|
||||
children![
|
||||
Text::new("Save"),
|
||||
TextFont::from_font_size(16.0),
|
||||
TextColor(Color::WHITE)
|
||||
]
|
||||
)
|
||||
],
|
||||
));
|
||||
@@ -67,6 +82,25 @@ fn update_status(
|
||||
}
|
||||
}
|
||||
|
||||
fn status_buttons(
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &UiStatusButton),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
mut savegamedump_messages: MessageWriter<SavegameDumpMessage>,
|
||||
) {
|
||||
for (interaction, button_type) in &mut interaction_query {
|
||||
match *interaction {
|
||||
Interaction::Pressed => match button_type {
|
||||
UiStatusButton::SavegameDump => {
|
||||
savegamedump_messages.write(SavegameDumpMessage);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup(mut commands: Commands, query: Query<Entity, With<UiStatusRootContainer>>) {
|
||||
for entity in query.iter() {
|
||||
commands.entity(entity).despawn();
|
||||
|
||||
Reference in New Issue
Block a user