Merge branch '48-inventory' into 'dev'
Implement Inventory See merge request softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik!14
This commit is contained in:
@@ -14,7 +14,6 @@ pub enum TextType {
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum ButtonType {
|
||||
SavegameDump,
|
||||
SettingsOpen,
|
||||
SettingsClose,
|
||||
SettingsExit,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::features::inventory;
|
||||
use crate::features::phase::components::TimerSettings;
|
||||
use crate::features::savegame::messages::SavegameDumpMessage;
|
||||
use crate::prelude::*;
|
||||
@@ -50,15 +51,25 @@ fn setup(mut commands: Commands) {
|
||||
TextFont::from_font_size(16.0),
|
||||
TextColor(Color::WHITE)
|
||||
),
|
||||
(
|
||||
Button,
|
||||
inventory::components::ButtonType::InventoryOpen,
|
||||
Node::default(),
|
||||
children![(
|
||||
Text::new("Inventar"),
|
||||
TextFont::from_font_size(16.0),
|
||||
TextColor(Color::WHITE)
|
||||
)]
|
||||
),
|
||||
(
|
||||
Button,
|
||||
ButtonType::SettingsOpen,
|
||||
Node::default(),
|
||||
children![
|
||||
children![(
|
||||
Text::new("Einstellungen"),
|
||||
TextFont::from_font_size(16.0),
|
||||
TextColor(Color::WHITE)
|
||||
]
|
||||
)]
|
||||
)
|
||||
],
|
||||
));
|
||||
@@ -115,7 +126,6 @@ fn buttons(
|
||||
timer_settings.change(timer_type, *amount)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
51
src/features/inventory/components.rs
Normal file
51
src/features/inventory/components.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub enum ItemType {
|
||||
Berry,
|
||||
}
|
||||
|
||||
impl ItemType {
|
||||
pub fn singular(&self) -> String {
|
||||
match self {
|
||||
ItemType::Berry => "Beere",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn plural(&self) -> String {
|
||||
match self {
|
||||
ItemType::Berry => "Beeren",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn description(&self) -> String {
|
||||
match self {
|
||||
ItemType::Berry => "Von Pflanzen erntbar. Kann im Shop zum Einkaufen benutzt werden.",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||
pub struct ItemStack {
|
||||
pub item_type: ItemType,
|
||||
pub amount: u32,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Serialize, Deserialize)]
|
||||
pub struct Inventory {
|
||||
pub items: Vec<Entity>,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum RootMarker {
|
||||
Inventory,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum ButtonType {
|
||||
InventoryOpen,
|
||||
InventoryClose,
|
||||
}
|
||||
40
src/features/inventory/mod.rs
Normal file
40
src/features/inventory/mod.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::{features::inventory::ui::open_inventory, prelude::*};
|
||||
use components::*;
|
||||
|
||||
pub mod components;
|
||||
pub mod ui;
|
||||
|
||||
pub struct InventoryPlugin;
|
||||
|
||||
impl Plugin for InventoryPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<Inventory>();
|
||||
|
||||
app.add_systems(Update, buttons.run_if(in_state(AppState::GameScreen)));
|
||||
}
|
||||
}
|
||||
|
||||
fn buttons(
|
||||
mut commands: Commands,
|
||||
mut interaction_query: Query<(&Interaction, &ButtonType), (Changed<Interaction>, With<Button>)>,
|
||||
itemstack_query: Query<&ItemStack>,
|
||||
root_query: Query<(Entity, &RootMarker)>,
|
||||
) {
|
||||
for (interaction, button_type) in &mut interaction_query {
|
||||
match *interaction {
|
||||
Interaction::Pressed => match button_type {
|
||||
ButtonType::InventoryOpen => {
|
||||
open_inventory(&mut commands, itemstack_query);
|
||||
}
|
||||
ButtonType::InventoryClose => {
|
||||
for (entity, root) in root_query.iter() {
|
||||
match *root {
|
||||
RootMarker::Inventory => commands.entity(entity).despawn(),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/features/inventory/ui/inventory.rs
Normal file
83
src/features/inventory/ui/inventory.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use super::super::components::{ButtonType, RootMarker};
|
||||
use crate::{features::inventory::ui::list_itemstack, prelude::*};
|
||||
|
||||
pub fn open_inventory(commands: &mut Commands, items: Query<&ItemStack>) {
|
||||
commands
|
||||
.spawn((
|
||||
RootMarker::Inventory,
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
width: percent(100),
|
||||
height: percent(100),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
ZIndex(1),
|
||||
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent
|
||||
.spawn((
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::all(px(20.0)),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
|
||||
BorderRadius::all(px(10.0)),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
Node {
|
||||
width: percent(100.0),
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
align_items: AlignItems::Center,
|
||||
margin: UiRect::bottom(px(20.0)),
|
||||
column_gap: px(20.0),
|
||||
..default()
|
||||
},
|
||||
children![
|
||||
(
|
||||
Text::new("Inventar"),
|
||||
TextFont::from_font_size(40.0),
|
||||
TextColor(Color::WHITE),
|
||||
),
|
||||
(
|
||||
Button,
|
||||
ButtonType::InventoryClose,
|
||||
Node {
|
||||
width: px(40.0),
|
||||
height: px(40.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(Color::srgb(0.8, 0.2, 0.2)),
|
||||
BorderRadius::MAX,
|
||||
children![(
|
||||
Text::new("X"),
|
||||
TextFont::from_font_size(24.0),
|
||||
TextColor(Color::WHITE),
|
||||
)]
|
||||
)
|
||||
],
|
||||
));
|
||||
|
||||
parent
|
||||
.spawn(Node {
|
||||
width: percent(100),
|
||||
flex_direction: FlexDirection::Column,
|
||||
margin: UiRect::top(px(10.0)),
|
||||
row_gap: px(10.0),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
for itemstack in items.iter() {
|
||||
parent.spawn(list_itemstack(itemstack));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
53
src/features/inventory/ui/item.rs
Normal file
53
src/features/inventory/ui/item.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn list_itemstack(itemstack: &ItemStack) -> impl Bundle {
|
||||
let name = match itemstack.amount {
|
||||
1 => itemstack.item_type.singular(),
|
||||
_ => itemstack.item_type.plural(),
|
||||
};
|
||||
|
||||
(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Row,
|
||||
column_gap: px(8),
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::all(px(4)),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(NORMAL_BUTTON),
|
||||
BorderRadius::all(px(10)),
|
||||
children![
|
||||
(
|
||||
// Placeholder for icon
|
||||
Node {
|
||||
height: percent(100),
|
||||
aspect_ratio: Some(1.0),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(HOVERED_BUTTON),
|
||||
BorderRadius::all(px(10))
|
||||
),
|
||||
(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::Center,
|
||||
row_gap: px(4),
|
||||
padding: UiRect::vertical(px(4)),
|
||||
..default()
|
||||
},
|
||||
children![
|
||||
(
|
||||
Text::new(format!("{} ({})", name, itemstack.amount)),
|
||||
TextFont::from_font_size(14.0),
|
||||
TextColor(Color::WHITE)
|
||||
),
|
||||
(
|
||||
Text::new(itemstack.item_type.description()),
|
||||
TextFont::from_font_size(10.0),
|
||||
TextColor(Color::WHITE)
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
)
|
||||
}
|
||||
5
src/features/inventory/ui/mod.rs
Normal file
5
src/features/inventory/ui/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod inventory;
|
||||
pub mod item;
|
||||
|
||||
pub use inventory::*;
|
||||
pub use item::*;
|
||||
@@ -4,6 +4,7 @@ pub mod game_screen;
|
||||
pub mod grid;
|
||||
pub mod hud;
|
||||
pub mod input;
|
||||
pub mod inventory;
|
||||
pub mod phase;
|
||||
pub mod pom;
|
||||
pub mod savegame;
|
||||
@@ -15,6 +16,7 @@ pub use game_screen::GameScreenPlugin;
|
||||
pub use grid::GridPlugin;
|
||||
pub use hud::HudPlugin;
|
||||
pub use input::InputPlugin;
|
||||
pub use inventory::InventoryPlugin;
|
||||
pub use phase::PhasePlugin;
|
||||
pub use pom::PomPlugin;
|
||||
pub use savegame::SavegamePlugin;
|
||||
|
||||
@@ -28,6 +28,7 @@ struct SaveData {
|
||||
session_tracker: SessionTracker,
|
||||
timer_settings: TimerSettings,
|
||||
pom_position: GridPosition,
|
||||
inventory: Vec<ItemStack>,
|
||||
}
|
||||
|
||||
fn dump_savegame(
|
||||
@@ -39,6 +40,8 @@ fn dump_savegame(
|
||||
tracker: Res<SessionTracker>,
|
||||
settings: Res<TimerSettings>,
|
||||
pom_query: Query<&GridPosition, With<Pom>>,
|
||||
inventory: Res<Inventory>,
|
||||
item_query: Query<&ItemStack>,
|
||||
) {
|
||||
for _ in messages.read() {
|
||||
let mut tile_states = Vec::new();
|
||||
@@ -61,6 +64,12 @@ fn dump_savegame(
|
||||
|
||||
let pom_pos = pom_query.single().unwrap();
|
||||
|
||||
let item_stacks: Vec<ItemStack> = inventory
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|entity| item_query.get(*entity).ok().cloned())
|
||||
.collect();
|
||||
|
||||
let save_data = SaveData {
|
||||
grid_width: grid.width,
|
||||
grid_height: grid.height,
|
||||
@@ -69,6 +78,7 @@ fn dump_savegame(
|
||||
session_tracker: tracker.clone(),
|
||||
timer_settings: settings.clone(),
|
||||
pom_position: *pom_pos,
|
||||
inventory: item_stacks,
|
||||
};
|
||||
|
||||
match serde_json::to_string_pretty(&save_data) {
|
||||
@@ -91,6 +101,7 @@ fn dump_savegame(
|
||||
}
|
||||
|
||||
fn load_savegame(
|
||||
mut commands: Commands,
|
||||
mut messages: MessageReader<SavegameLoadMessage>,
|
||||
save_path: Res<SavegamePath>,
|
||||
grid: Res<Grid>,
|
||||
@@ -99,6 +110,7 @@ fn load_savegame(
|
||||
mut tracker: ResMut<SessionTracker>,
|
||||
mut settings: ResMut<TimerSettings>,
|
||||
mut pom_query: Query<(&mut GridPosition, &mut Transform), With<Pom>>,
|
||||
mut inventory: ResMut<Inventory>,
|
||||
) {
|
||||
for _ in messages.read() {
|
||||
if let Ok(mut file) = File::open(&save_path.0) {
|
||||
@@ -136,6 +148,14 @@ fn load_savegame(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stack_entities: Vec<Entity> = save_data
|
||||
.inventory
|
||||
.iter()
|
||||
.map(|stack| commands.spawn(stack.clone()).id())
|
||||
.collect();
|
||||
inventory.items = stack_entities;
|
||||
|
||||
println!("Game loaded from {}", save_path.0.display());
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -32,6 +32,7 @@ fn main() {
|
||||
features::HudPlugin,
|
||||
features::SavegamePlugin,
|
||||
features::UiPlugin,
|
||||
features::InventoryPlugin,
|
||||
))
|
||||
.insert_resource(config)
|
||||
.run();
|
||||
|
||||
@@ -7,6 +7,7 @@ pub use crate::features::{
|
||||
consts::TILE_SIZE,
|
||||
utils::{grid_to_world_coords, world_to_grid_coords},
|
||||
},
|
||||
inventory::components::{Inventory, ItemStack, ItemType},
|
||||
phase::components::{CurrentPhase, Phase},
|
||||
pom::{
|
||||
components::{GridPosition, MovingState, Pom},
|
||||
|
||||
Reference in New Issue
Block a user