feat: Add shop logic and popup UI (#34). Update config tests and

defaults
This commit is contained in:
demenik
2025-12-01 13:42:19 +01:00
parent 91300e3f4d
commit 4b28f80bcb
10 changed files with 140 additions and 2 deletions

View File

@@ -2,6 +2,8 @@
"grid_width": 12, "grid_width": 12,
"grid_height": 4, "grid_height": 4,
"pom_speed": 1.5, "pom_speed": 1.5,
"shovel_base_price": 10,
"shovel_rate": 0.5,
"berry_seeds": [ "berry_seeds": [
{ {
"name": "Normale Samen", "name": "Normale Samen",

View File

@@ -7,6 +7,8 @@ pub struct GameConfig {
pub grid_width: u32, pub grid_width: u32,
pub grid_height: u32, pub grid_height: u32,
pub pom_speed: f32, pub pom_speed: f32,
pub shovel_base_price: u32,
pub shovel_rate: f32,
pub berry_seeds: Vec<BerrySeedConfig>, pub berry_seeds: Vec<BerrySeedConfig>,
} }
@@ -25,6 +27,8 @@ impl Default for GameConfig {
grid_width: 12, grid_width: 12,
grid_height: 4, grid_height: 4,
pom_speed: 1.5, pom_speed: 1.5,
shovel_base_price: 10,
shovel_rate: 0.2,
berry_seeds: vec![ berry_seeds: vec![
BerrySeedConfig { BerrySeedConfig {
name: "Normale Samen".to_string(), name: "Normale Samen".to_string(),

View File

@@ -4,12 +4,14 @@ use crate::{features::config::components::BerrySeedConfig, prelude::*};
pub enum ItemType { pub enum ItemType {
Berry, Berry,
BerrySeed { name: String }, BerrySeed { name: String },
Shovel,
} }
impl ItemType { impl ItemType {
pub fn singular(&self, game_config: &GameConfig) -> String { pub fn singular(&self, game_config: &GameConfig) -> String {
match self { match self {
ItemType::Berry => "Beere".into(), ItemType::Berry => "Beere".into(),
ItemType::Shovel => "Schaufel".into(),
ItemType::BerrySeed { name } => { ItemType::BerrySeed { name } => {
let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name); let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name);
seed_config seed_config
@@ -22,6 +24,7 @@ impl ItemType {
pub fn plural(&self, game_config: &GameConfig) -> String { pub fn plural(&self, game_config: &GameConfig) -> String {
match self { match self {
ItemType::Berry => "Beeren".into(), ItemType::Berry => "Beeren".into(),
ItemType::Shovel => "Schaufeln".into(),
ItemType::BerrySeed { name } => { ItemType::BerrySeed { name } => {
let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name); let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name);
seed_config seed_config
@@ -36,6 +39,7 @@ impl ItemType {
ItemType::Berry => { ItemType::Berry => {
"Von Pflanzen erntbar. Kann im Shop zum Einkaufen benutzt werden.".into() "Von Pflanzen erntbar. Kann im Shop zum Einkaufen benutzt werden.".into()
} }
ItemType::Shovel => "Schaltet ein neues Feld im Garten frei.".into(),
ItemType::BerrySeed { name } => { ItemType::BerrySeed { name } => {
let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name); let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name);
if let Some(s) = seed_config { if let Some(s) = seed_config {
@@ -57,7 +61,7 @@ impl ItemType {
pub fn get_seed_config<'a>(&self, game_config: &'a GameConfig) -> Option<&'a BerrySeedConfig> { pub fn get_seed_config<'a>(&self, game_config: &'a GameConfig) -> Option<&'a BerrySeedConfig> {
match self { match self {
ItemType::Berry => None, ItemType::Berry | ItemType::Shovel => None,
ItemType::BerrySeed { name } => { ItemType::BerrySeed { name } => {
game_config.berry_seeds.iter().find(|s| s.name == *name) game_config.berry_seeds.iter().find(|s| s.name == *name)
} }
@@ -70,6 +74,10 @@ impl ItemType {
name: "Berry".into(), name: "Berry".into(),
aseprite: asset_server.load("berry.aseprite"), aseprite: asset_server.load("berry.aseprite"),
}, },
ItemType::Shovel => AseSlice {
name: "Berry".into(),
aseprite: asset_server.load("berry.aseprite"),
},
ItemType::BerrySeed { name } => { ItemType::BerrySeed { name } => {
let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name); let seed_config = game_config.berry_seeds.iter().find(|s| s.name == *name);
if let Some(s) = seed_config { if let Some(s) = seed_config {

View File

@@ -8,6 +8,7 @@ pub mod inventory;
pub mod phase; pub mod phase;
pub mod pom; pub mod pom;
pub mod savegame; pub mod savegame;
pub mod shop;
pub mod start_screen; pub mod start_screen;
pub mod ui; pub mod ui;
@@ -20,5 +21,6 @@ pub use inventory::InventoryPlugin;
pub use phase::PhasePlugin; pub use phase::PhasePlugin;
pub use pom::PomPlugin; pub use pom::PomPlugin;
pub use savegame::SavegamePlugin; pub use savegame::SavegamePlugin;
pub use shop::ShopPlugin;
pub use start_screen::StartScreenPlugin; pub use start_screen::StartScreenPlugin;
pub use ui::UiPlugin; pub use ui::UiPlugin;

View File

@@ -0,0 +1,51 @@
use crate::prelude::*;
#[derive(Component)]
pub enum RootMarker {
Shop,
}
#[derive(Component)]
pub enum ButtonType {
ShopClose,
}
#[derive(Clone)]
pub struct ShopOffer {
pub item: ItemStack,
pub cost: u32,
}
impl ShopOffer {
pub fn list_all(game_config: &GameConfig, tile_count: u32) -> Vec<ShopOffer> {
let mut offers = Vec::new();
for seed in &game_config.berry_seeds {
offers.push(ShopOffer {
item: ItemStack {
item_type: ItemType::BerrySeed {
name: seed.name.clone(),
},
amount: 1,
},
cost: seed.cost,
});
}
let mut shovel_cost = game_config.shovel_base_price as f32;
for _ in 0..=tile_count {
shovel_cost = shovel_cost + (game_config.shovel_rate * shovel_cost);
shovel_cost = shovel_cost.ceil();
}
offers.push(ShopOffer {
item: ItemStack {
item_type: ItemType::Shovel,
amount: 1,
},
cost: shovel_cost as u32,
});
offers
}
}

10
src/features/shop/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
use crate::prelude::*;
pub mod components;
pub mod ui;
pub struct ShopPlugin;
impl Plugin for ShopPlugin {
fn build(&self, app: &mut App) {}
}

View File

@@ -0,0 +1,3 @@
pub mod shop;
pub use shop::open_shop;

View File

@@ -0,0 +1,54 @@
use super::super::components::*;
use crate::prelude::*;
pub fn open_shop(commands: &mut Commands) {
commands
.spawn((
RootMarker::Shop,
Node {
position_type: PositionType::Absolute,
width: percent(100),
height: percent(100),
..Node::center()
},
ZIndex(1),
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)),
GlobalTransform::default(),
))
.with_children(|parent| {
parent
.spawn((
Node {
width: px(700),
padding: UiRect::all(px(20.0)),
..Node::vstack(px(20))
},
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
BorderRadius::all(px(10.0)),
))
.with_children(|parent| {
parent.spawn((
Node {
justify_content: JustifyContent::SpaceBetween,
..Node::hstack(px(20))
},
children![
text("Shop", 40.0, Color::WHITE),
pill_button(
ButtonType::ShopClose,
ButtonVariant::Destructive,
Node {
width: px(40),
height: px(40),
..default()
},
"X",
24.0
),
],
));
parent.spawn(Node::vstack(px(10))).with_children(|_| {});
});
});
}

View File

@@ -33,6 +33,7 @@ fn main() {
features::SavegamePlugin, features::SavegamePlugin,
features::UiPlugin, features::UiPlugin,
features::InventoryPlugin, features::InventoryPlugin,
features::ShopPlugin,
)) ))
.insert_resource(config) .insert_resource(config)
.run(); .run();

View File

@@ -22,7 +22,10 @@ fn test_load_valid_config() {
r#"{ r#"{
"grid_width": 10, "grid_width": 10,
"grid_height": 5, "grid_height": 5,
"pom_speed": 2.0 "pom_speed": 2.0,
"shovel_base_price": 10,
"shovel_rate": 0.2,
"berry_seeds": []
}"#, }"#,
); );