From 30a878a563d2e6e06adf01612207994f5446397d Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Wed, 13 Nov 2024 18:52:13 -0500 Subject: [PATCH 01/18] Adding crafting system infrastructure --- .../cosmos/blocks/basic_fabricator.json | 24 +++ .../images/blocks/basic_fabricator_sides.png | Bin 0 -> 160 bytes .../blocks/basic_fabricator_top_bottom.png | Bin 0 -> 240 bytes .../assets/cosmos/images/items/iron_bar.png | Bin 0 -> 205 bytes cosmos_client/src/crafting/mod.rs | 7 + .../src/crafting/recipes/basic_fabricator.rs | 21 +++ cosmos_client/src/crafting/recipes/mod.rs | 7 + cosmos_client/src/main.rs | 2 + cosmos_core/src/block/blocks/mod.rs | 6 + cosmos_core/src/crafting/mod.rs | 7 + .../src/crafting/recipes/basic_fabricator.rs | 74 +++++++++ cosmos_core/src/crafting/recipes/mod.rs | 14 ++ cosmos_core/src/item/items/mod.rs | 2 + cosmos_core/src/lib.rs | 1 + cosmos_core/src/netty/client_registry.rs | 19 +++ cosmos_core/src/plugin/cosmos_core_plugin.rs | 5 +- .../basic_fabricator/grey_ship_hull.json | 1 + cosmos_server/src/crafting/mod.rs | 6 + .../src/crafting/recipes/basic_fabricator.rs | 108 +++++++++++++ cosmos_server/src/crafting/recipes/mod.rs | 14 ++ cosmos_server/src/main.rs | 1 + cosmos_server/src/plugin/server_plugin.rs | 3 +- mockups/fabricator_mockup.html | 143 ++++++++++++++++++ shop_mockup.html => mockups/shop_mockup.html | 0 24 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 cosmos_client/assets/cosmos/blocks/basic_fabricator.json create mode 100644 cosmos_client/assets/cosmos/images/blocks/basic_fabricator_sides.png create mode 100644 cosmos_client/assets/cosmos/images/blocks/basic_fabricator_top_bottom.png create mode 100644 cosmos_client/assets/cosmos/images/items/iron_bar.png create mode 100644 cosmos_client/src/crafting/mod.rs create mode 100644 cosmos_client/src/crafting/recipes/basic_fabricator.rs create mode 100644 cosmos_client/src/crafting/recipes/mod.rs create mode 100644 cosmos_core/src/crafting/mod.rs create mode 100644 cosmos_core/src/crafting/recipes/basic_fabricator.rs create mode 100644 cosmos_core/src/crafting/recipes/mod.rs create mode 100644 cosmos_core/src/netty/client_registry.rs create mode 100644 cosmos_server/assets/cosmos/recipes/basic_fabricator/grey_ship_hull.json create mode 100644 cosmos_server/src/crafting/mod.rs create mode 100644 cosmos_server/src/crafting/recipes/basic_fabricator.rs create mode 100644 cosmos_server/src/crafting/recipes/mod.rs create mode 100644 mockups/fabricator_mockup.html rename shop_mockup.html => mockups/shop_mockup.html (100%) diff --git a/cosmos_client/assets/cosmos/blocks/basic_fabricator.json b/cosmos_client/assets/cosmos/blocks/basic_fabricator.json new file mode 100644 index 000000000..bfdd103e6 --- /dev/null +++ b/cosmos_client/assets/cosmos/blocks/basic_fabricator.json @@ -0,0 +1,24 @@ +{ + "texture": { + "Sides": { + "right": { + "Single": "cosmos:basic_fabricator_sides" + }, + "left": { + "Single": "cosmos:basic_fabricator_sides" + }, + "front": { + "Single": "cosmos:basic_fabricator_sides" + }, + "back": { + "Single": "cosmos:basic_fabricator_sides" + }, + "top": { + "Single": "cosmos:basic_fabricator_top_bottom" + }, + "bottom": { + "Single": "cosmos:basic_fabricator_top_bottom" + } + } + } +} diff --git a/cosmos_client/assets/cosmos/images/blocks/basic_fabricator_sides.png b/cosmos_client/assets/cosmos/images/blocks/basic_fabricator_sides.png new file mode 100644 index 0000000000000000000000000000000000000000..3050277e9b4335e31ba6a75ff765bbed1e604195 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf3hhZu6^vYd#ul_r|lW*PJsQtQ~dCEsf`*K!XKV$RblFv8!E50mPa$CFc@hqMhRUI$Ywtx68AwT;^*~BA;UN==^ lRD1roF>) { + for ev in nevr.read() { + let recipes = ev.0.clone(); + info!("Received basic fabricator recipes from server {recipes:?}"); + commands.insert_resource(recipes); + } +} + +pub(super) fn register(app: &mut App) { + app.add_systems(Update, sync_recipes.in_set(NetworkingSystemsSet::Between)); +} diff --git a/cosmos_client/src/crafting/recipes/mod.rs b/cosmos_client/src/crafting/recipes/mod.rs new file mode 100644 index 000000000..6336b82bd --- /dev/null +++ b/cosmos_client/src/crafting/recipes/mod.rs @@ -0,0 +1,7 @@ +use bevy::prelude::App; + +mod basic_fabricator; + +pub(super) fn register(app: &mut App) { + basic_fabricator::register(app); +} diff --git a/cosmos_client/src/main.rs b/cosmos_client/src/main.rs index c222907a5..3ab2e1350 100644 --- a/cosmos_client/src/main.rs +++ b/cosmos_client/src/main.rs @@ -8,6 +8,7 @@ pub mod audio; pub mod block; pub mod camera; pub mod chat; +pub mod crafting; pub mod debug; pub mod economy; pub mod ecs; @@ -186,6 +187,7 @@ fn main() { item::register(&mut app); debug::register(&mut app); chat::register(&mut app); + crafting::register(&mut app); if cfg!(feature = "print-schedule") { println!( diff --git a/cosmos_core/src/block/blocks/mod.rs b/cosmos_core/src/block/blocks/mod.rs index fddf24941..29bf199fa 100644 --- a/cosmos_core/src/block/blocks/mod.rs +++ b/cosmos_core/src/block/blocks/mod.rs @@ -480,6 +480,12 @@ fn add_cosmos_blocks( .create(), ); + blocks.register( + BlockBuilder::new("cosmos:basic_fabricator", 2.0, 20.0, 5.0) + .add_property(BlockProperty::Full) + .create(), + ); + loading.finish_loading(id, &mut end_writer); } diff --git a/cosmos_core/src/crafting/mod.rs b/cosmos_core/src/crafting/mod.rs new file mode 100644 index 000000000..613ef3269 --- /dev/null +++ b/cosmos_core/src/crafting/mod.rs @@ -0,0 +1,7 @@ +use bevy::prelude::App; + +pub mod recipes; + +pub(super) fn register(app: &mut App) { + recipes::register(app); +} diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs new file mode 100644 index 000000000..e76af88fa --- /dev/null +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -0,0 +1,74 @@ +use bevy::prelude::{App, Commands, Event, Res, Resource}; +use serde::{Deserialize, Serialize}; + +use crate::{ + item::Item, + netty::sync::events::netty_event::{IdentifiableEvent, NettyEvent, SyncedEventImpl}, + registry::{identifiable::Identifiable, Registry}, +}; + +use super::RecipeItem; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FabricatorItemInput { + quantity: u16, + item: RecipeItem, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FabricatorItemOutput { + quantity: u16, + item: u16, +} + +impl FabricatorItemInput { + pub fn new(item: RecipeItem, quantity: u16) -> Self { + Self { item, quantity } + } +} + +impl FabricatorItemOutput { + pub fn new(item: &Item, quantity: u16) -> Self { + Self { item: item.id(), quantity } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BasicFabricatorRecipe { + inputs: Vec, + output: FabricatorItemOutput, +} + +impl BasicFabricatorRecipe { + pub fn new(output: FabricatorItemOutput, inputs: Vec) -> Self { + Self { output, inputs } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, Resource)] +pub struct BasicFabricatorRecipes(Vec); + +impl BasicFabricatorRecipes { + pub fn add_recipe(&mut self, recipe: BasicFabricatorRecipe) { + self.0.push(recipe); + } +} + +#[derive(Event, Serialize, Deserialize, Debug)] +pub struct SyncBasicFabricatorRecipesEvent(pub BasicFabricatorRecipes); + +impl IdentifiableEvent for SyncBasicFabricatorRecipesEvent { + fn unlocalized_name() -> &'static str { + "cosmos:sync_basic_fabricator_recipes" + } +} + +impl NettyEvent for SyncBasicFabricatorRecipesEvent { + fn event_receiver() -> crate::netty::sync::events::netty_event::EventReceiver { + crate::netty::sync::events::netty_event::EventReceiver::Client + } +} + +pub(super) fn register(app: &mut App) { + app.add_netty_event::(); +} diff --git a/cosmos_core/src/crafting/recipes/mod.rs b/cosmos_core/src/crafting/recipes/mod.rs new file mode 100644 index 000000000..2d9d72c87 --- /dev/null +++ b/cosmos_core/src/crafting/recipes/mod.rs @@ -0,0 +1,14 @@ +use bevy::prelude::App; +use serde::{Deserialize, Serialize}; + +pub mod basic_fabricator; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RecipeItem { + Item(u16), + Category(u16), +} + +pub(super) fn register(app: &mut App) { + basic_fabricator::register(app); +} diff --git a/cosmos_core/src/item/items/mod.rs b/cosmos_core/src/item/items/mod.rs index c09b02e40..56e1db6af 100644 --- a/cosmos_core/src/item/items/mod.rs +++ b/cosmos_core/src/item/items/mod.rs @@ -19,6 +19,8 @@ fn add_cosmos_items( items.register(Item::new("cosmos:fluid_cell", DEFAULT_MAX_STACK_SIZE)); items.register(Item::new("cosmos:fluid_cell_filled", 1)); + items.register(Item::new("cosmos:iron_bar", DEFAULT_MAX_STACK_SIZE)); + loading.finish_loading(id, &mut end_writer); } diff --git a/cosmos_core/src/lib.rs b/cosmos_core/src/lib.rs index 4f74c7642..42ed00747 100644 --- a/cosmos_core/src/lib.rs +++ b/cosmos_core/src/lib.rs @@ -6,6 +6,7 @@ pub mod block; pub mod blockitems; pub mod chat; +pub mod crafting; pub mod debug; pub mod economy; pub mod ecs; diff --git a/cosmos_core/src/netty/client_registry.rs b/cosmos_core/src/netty/client_registry.rs new file mode 100644 index 000000000..28cdceb61 --- /dev/null +++ b/cosmos_core/src/netty/client_registry.rs @@ -0,0 +1,19 @@ +//! Used to sync registries from server -> client + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +/// Used to sync registries from server -> client +/// +/// For this to work, both the client and server must call their own versions of `sync_registry` for the registry type. +pub enum RegistrySyncing { + /// The # of registries the client must received before starting the game + RegistryCount(u64), + /// A registry the client must use before starting the game + Registry { + /// The serialized form of this registry (serialized via `cosmos_encoder::serialize`) + serialized: Vec, + /// The unlocalized name of this registry + registry_name: String, + }, +} diff --git a/cosmos_core/src/plugin/cosmos_core_plugin.rs b/cosmos_core/src/plugin/cosmos_core_plugin.rs index 77eedf36f..506c20037 100644 --- a/cosmos_core/src/plugin/cosmos_core_plugin.rs +++ b/cosmos_core/src/plugin/cosmos_core_plugin.rs @@ -1,7 +1,9 @@ //! This should contain everything needed for a cosmos application to run use crate::netty::sync::registry::RegistrySyncInit; -use crate::{block, chat, debug, economy, ecs, entities, fluid, inventory, logic, netty, persistence, projectiles, shop, universe, utils}; +use crate::{ + block, chat, crafting, debug, economy, ecs, entities, fluid, inventory, logic, netty, persistence, projectiles, shop, universe, utils, +}; use crate::{blockitems, structure}; use crate::{events, loader}; use crate::{item, physics}; @@ -117,6 +119,7 @@ impl Plugin for CosmosCorePlugin< utils::register(app); chat::register(app); entities::register(app); + crafting::register(app); } } diff --git a/cosmos_server/assets/cosmos/recipes/basic_fabricator/grey_ship_hull.json b/cosmos_server/assets/cosmos/recipes/basic_fabricator/grey_ship_hull.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/cosmos_server/assets/cosmos/recipes/basic_fabricator/grey_ship_hull.json @@ -0,0 +1 @@ +{} diff --git a/cosmos_server/src/crafting/mod.rs b/cosmos_server/src/crafting/mod.rs new file mode 100644 index 000000000..174397bab --- /dev/null +++ b/cosmos_server/src/crafting/mod.rs @@ -0,0 +1,6 @@ +use bevy::prelude::App; +mod recipes; + +pub(super) fn register(app: &mut App) { + recipes::register(app); +} diff --git a/cosmos_server/src/crafting/recipes/basic_fabricator.rs b/cosmos_server/src/crafting/recipes/basic_fabricator.rs new file mode 100644 index 000000000..00a1200a5 --- /dev/null +++ b/cosmos_server/src/crafting/recipes/basic_fabricator.rs @@ -0,0 +1,108 @@ +use std::ffi::OsStr; + +use bevy::{ + app::Update, + log::warn, + prelude::{in_state, resource_exists_and_changed, App, Commands, EventReader, IntoSystemConfigs, OnEnter, Res, ResMut}, +}; +use serde::{Deserialize, Serialize}; + +use cosmos_core::{ + crafting::recipes::{ + basic_fabricator::{ + BasicFabricatorRecipe, BasicFabricatorRecipes, FabricatorItemInput, FabricatorItemOutput, SyncBasicFabricatorRecipesEvent, + }, + RecipeItem, + }, + item::Item, + netty::{sync::events::server_event::NettyEventWriter, system_sets::NetworkingSystemsSet}, + registry::{identifiable::Identifiable, Registry}, + state::GameState, +}; +use walkdir::WalkDir; + +use crate::netty::server_events::PlayerConnectedEvent; + +use super::RawRecipeItem; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RawFabricatorItem { + quantity: u16, + item: RawRecipeItem, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RawBasicFabricatorRecipe { + inputs: Vec, + output: RawFabricatorItem, +} + +fn load_recipes(items: Res>, mut commands: Commands) { + // for entry in WalkDir::new("assets/cosmos/recipes/basic_fabricator").max_depth(0) { + // let Ok(entry) = entry else { + // continue; + // }; + // + // let path = entry.path(); + // if path.is_dir() || path.extension().and_then(OsStr::to_str) != Some("json") { + // continue; + // } + // } + // + let mut recipes = BasicFabricatorRecipes::default(); + + if let Some(iron_bar) = items.from_id("cosmos:iron_bar") { + if let Some(iron_ore) = items.from_id("cosmos:iron_ore") { + recipes.add_recipe(BasicFabricatorRecipe::new( + FabricatorItemOutput::new(iron_bar, 1), + vec![FabricatorItemInput::new(RecipeItem::Item(iron_ore.id()), 2)], + )); + } else { + warn!("Missing iron ore!"); + } + } else { + warn!("Missing iron bar!"); + } + + if let Some(grey_hull) = items.from_id("cosmos:ship_hull_grey") { + if let Some(iron_bar) = items.from_id("cosmos:iron_bar") { + recipes.add_recipe(BasicFabricatorRecipe::new( + FabricatorItemOutput::new(grey_hull, 1), + vec![FabricatorItemInput::new(RecipeItem::Item(iron_bar.id()), 1)], + )); + } else { + warn!("Missing iron bar!"); + } + } else { + warn!("Missing grey ship hull!"); + } + + commands.insert_resource(recipes); +} + +fn sync_recipes_on_change(recipes: Res, mut nevw_sync_recipes: NettyEventWriter) { + nevw_sync_recipes.broadcast(SyncBasicFabricatorRecipesEvent(recipes.clone())); +} + +fn sync_recipes_on_join( + recipes: Res, + mut evr_player_join: EventReader, + mut nevw_sync_recipes: NettyEventWriter, +) { + for ev in evr_player_join.read() { + nevw_sync_recipes.send(SyncBasicFabricatorRecipesEvent(recipes.clone()), ev.client_id); + } +} + +pub(super) fn register(app: &mut App) { + app.add_systems(OnEnter(GameState::PostLoading), load_recipes).add_systems( + Update, + ( + sync_recipes_on_join, + sync_recipes_on_change.run_if(resource_exists_and_changed::), + ) + .chain() + .in_set(NetworkingSystemsSet::SyncComponents) + .run_if(in_state(GameState::Playing)), + ); +} diff --git a/cosmos_server/src/crafting/recipes/mod.rs b/cosmos_server/src/crafting/recipes/mod.rs new file mode 100644 index 000000000..020b1a622 --- /dev/null +++ b/cosmos_server/src/crafting/recipes/mod.rs @@ -0,0 +1,14 @@ +use bevy::prelude::App; +use serde::{Deserialize, Serialize}; + +mod basic_fabricator; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum RawRecipeItem { + Item(String), + Category(String), +} + +pub(super) fn register(app: &mut App) { + basic_fabricator::register(app); +} diff --git a/cosmos_server/src/main.rs b/cosmos_server/src/main.rs index f39c5b674..798d14d33 100644 --- a/cosmos_server/src/main.rs +++ b/cosmos_server/src/main.rs @@ -31,6 +31,7 @@ pub mod ai; pub mod blocks; pub mod chat; pub mod commands; +pub mod crafting; mod debug; pub mod entities; pub mod fluid; diff --git a/cosmos_server/src/plugin/server_plugin.rs b/cosmos_server/src/plugin/server_plugin.rs index 8e344e32c..ecb28cfdb 100644 --- a/cosmos_server/src/plugin/server_plugin.rs +++ b/cosmos_server/src/plugin/server_plugin.rs @@ -3,7 +3,7 @@ use bevy::{log::info, prelude::Plugin}; use crate::{ - ai, blocks, chat, commands, debug, fluid, + ai, blocks, chat, commands, crafting, debug, fluid, init::{self, init_server}, inventory, items, logic, netty, persistence, physics, projectiles, shop, structure, universe, utility_runs, }; @@ -39,6 +39,7 @@ impl Plugin for ServerPlugin { logic::register(app); debug::register(app); chat::register(app); + crafting::register(app); info!("Done setting up server!"); } diff --git a/mockups/fabricator_mockup.html b/mockups/fabricator_mockup.html new file mode 100644 index 000000000..00215bd9c --- /dev/null +++ b/mockups/fabricator_mockup.html @@ -0,0 +1,143 @@ + + + + + + Document + + + + + +
+
+

Fabricator

+

X

+
+
+

+
+
+ + diff --git a/shop_mockup.html b/mockups/shop_mockup.html similarity index 100% rename from shop_mockup.html rename to mockups/shop_mockup.html From bf9786709661c2e24535fd274ea4db67589b5abb Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Thu, 14 Nov 2024 13:43:51 -0500 Subject: [PATCH 02/18] Recipes are now synced to client and displayed --- .../assets/cosmos/lang/items/en_us.lang | 3 +- .../crafting/blocks/basic_fabricator/mod.rs | 51 +++++ .../crafting/blocks/basic_fabricator/ui.rs | 213 ++++++++++++++++++ cosmos_client/src/crafting/blocks/mod.rs | 7 + cosmos_client/src/crafting/mod.rs | 2 + .../src/crafting/blocks/basic_fabricator.rs | 25 ++ cosmos_core/src/crafting/blocks/mod.rs | 7 + cosmos_core/src/crafting/mod.rs | 2 + .../src/crafting/recipes/basic_fabricator.rs | 16 +- cosmos_core/src/netty/client_registry.rs | 15 +- cosmos_core/src/netty/mod.rs | 11 + cosmos_core/src/netty/sync/registry/client.rs | 19 +- .../src/crafting/blocks/basic_fabricator.rs | 60 +++++ cosmos_server/src/crafting/blocks/mod.rs | 7 + cosmos_server/src/crafting/mod.rs | 3 + .../src/crafting/recipes/basic_fabricator.rs | 8 +- cosmos_server/src/netty/sync/mod.rs | 2 + cosmos_server/src/netty/sync/registry.rs | 43 ++++ 18 files changed, 465 insertions(+), 29 deletions(-) create mode 100644 cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs create mode 100644 cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs create mode 100644 cosmos_client/src/crafting/blocks/mod.rs create mode 100644 cosmos_core/src/crafting/blocks/basic_fabricator.rs create mode 100644 cosmos_core/src/crafting/blocks/mod.rs create mode 100644 cosmos_server/src/crafting/blocks/basic_fabricator.rs create mode 100644 cosmos_server/src/crafting/blocks/mod.rs create mode 100644 cosmos_server/src/netty/sync/registry.rs diff --git a/cosmos_client/assets/cosmos/lang/items/en_us.lang b/cosmos_client/assets/cosmos/lang/items/en_us.lang index b8f4f0e2d..153823b27 100644 --- a/cosmos_client/assets/cosmos/lang/items/en_us.lang +++ b/cosmos_client/assets/cosmos/lang/items/en_us.lang @@ -1 +1,2 @@ -cosmos:test_crystal=Test Crystal \ No newline at end of file +cosmos:test_crystal=Test Crystal +cosmos:iron_bar=Iron Bar diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs new file mode 100644 index 000000000..505b88869 --- /dev/null +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs @@ -0,0 +1,51 @@ +use bevy::{ + app::Update, + core::Name, + prelude::{App, Commands, Component, Entity, EventReader, IntoSystemConfigs, IntoSystemSetConfigs, Query, SystemSet, With}, + reflect::Reflect, +}; +use cosmos_core::{ + crafting::blocks::basic_fabricator::OpenBasicFabricatorEvent, + ecs::NeedsDespawned, + netty::{sync::events::client_event::NettyEventReceived, system_sets::NetworkingSystemsSet}, + prelude::StructureBlock, +}; + +mod ui; + +#[derive(Component, Debug, Reflect)] +struct OpenBasicFabricatorMenu(StructureBlock); + +fn open_menu( + q_open_menu: Query>, + mut commands: Commands, + mut nevr: EventReader>, +) { + let Some(ev) = nevr.read().last() else { + return; + }; + + if let Ok(ent) = q_open_menu.get_single() { + commands.entity(ent).insert(NeedsDespawned); + } + + commands.spawn((OpenBasicFabricatorMenu(ev.0), Name::new("Open Basic Fabricator Menu"))); +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] +enum FabricatorMenuSet { + OpenMenu, + PopulateMenu, +} + +pub(super) fn register(app: &mut App) { + ui::register(app); + + app.configure_sets(Update, (FabricatorMenuSet::OpenMenu, FabricatorMenuSet::PopulateMenu).chain()); + + app.add_systems( + Update, + open_menu.in_set(NetworkingSystemsSet::Between).in_set(FabricatorMenuSet::OpenMenu), + ) + .register_type::(); +} diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs new file mode 100644 index 000000000..7d3fd6e99 --- /dev/null +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -0,0 +1,213 @@ +use bevy::{ + app::Update, + color::{Color, Srgba}, + core::Name, + prelude::{ + in_state, resource_exists, Added, App, BuildChildren, Commands, Entity, IntoSystemConfigs, NodeBundle, Query, Res, TextBundle, + }, + text::{Text, TextStyle}, + ui::{FlexDirection, JustifyContent, Style, UiRect, Val}, +}; +use cosmos_core::{ + crafting::recipes::{basic_fabricator::BasicFabricatorRecipes, RecipeItem}, + item::Item, + netty::system_sets::NetworkingSystemsSet, + registry::{identifiable::Identifiable, Registry}, + state::GameState, +}; + +use crate::{ + lang::Lang, + ui::{ + components::{ + scollable_container::ScrollBundle, + window::{GuiWindow, WindowBundle}, + }, + font::DefaultFont, + item_renderer::RenderItem, + }, +}; + +use super::{FabricatorMenuSet, OpenBasicFabricatorMenu}; + +fn populate_menu( + mut commands: Commands, + q_added_menu: Query>, + font: Res, + crafting_recipes: Res, + items: Res>, + lang: Res>, +) { + for ent in q_added_menu.iter() { + let mut ecmds = commands.entity(ent); + + let text_style = TextStyle { + font: font.0.clone_weak(), + font_size: 24.0, + color: Color::WHITE, + }; + + ecmds.insert( + (WindowBundle { + node_bundle: NodeBundle { + background_color: Srgba::hex("2D2D2D").unwrap().into(), + style: Style { + width: Val::Px(400.0), + height: Val::Px(800.0), + margin: UiRect { + // Centers it vertically + top: Val::Auto, + bottom: Val::Auto, + // Aligns it 100px from the right + left: Val::Auto, + right: Val::Px(100.0), + }, + ..Default::default() + }, + ..Default::default() + }, + window: GuiWindow { + title: "Basic Fabricator".into(), + body_styles: Style { + flex_direction: FlexDirection::Column, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }), + ); + + ecmds.with_children(|p| { + p.spawn( + (ScrollBundle { + node_bundle: NodeBundle { + style: Style { + flex_grow: 1.0, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }), + ) + .with_children(|p| { + for recipe in crafting_recipes.iter() { + p.spawn( + (NodeBundle { + style: Style { + height: Val::Px(100.0), + width: Val::Percent(100.0), + justify_content: JustifyContent::SpaceBetween, + ..Default::default() + }, + ..Default::default() + }), + ) + .with_children(|p| { + p.spawn(( + NodeBundle { + style: Style { + width: Val::Px(64.0), + height: Val::Px(64.0), + margin: UiRect::all(Val::Auto), + ..Default::default() + }, + ..Default::default() + }, + RenderItem { + item_id: recipe.output.item, + }, + )); + + let item = items.from_numeric_id(recipe.output.item); + let name = lang.get_name_from_id(item.unlocalized_name()).unwrap_or(item.unlocalized_name()); + + p.spawn(( + Name::new("Item name + inputs display"), + NodeBundle { + style: Style { + width: Val::Percent(80.0), + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::SpaceEvenly, + ..Default::default() + }, + ..Default::default() + }, + )) + .with_children(|p| { + p.spawn(TextBundle { + style: Style { + width: Val::Percent(100.0), + // margin: UiRect::vertical(Val::Auto), + ..Default::default() + }, + text: Text::from_section(format!("{}x {}", recipe.output.quantity, name), text_style.clone()), + ..Default::default() + }); + + p.spawn( + (NodeBundle { + style: Style { + flex_direction: FlexDirection::Row, + width: Val::Percent(100.0), + ..Default::default() + }, + ..Default::default() + }), + ) + .with_children(|p| { + for item in recipe.inputs.iter() { + p.spawn(( + NodeBundle { + style: Style { + width: Val::Px(64.0), + height: Val::Px(64.0), + ..Default::default() + }, + ..Default::default() + }, + RenderItem { + item_id: match item.item { + RecipeItem::Item(i) => i, + RecipeItem::Category(_) => todo!("Categories"), + }, + }, + )); + } + }); + }); + }); + } + }); + + p.spawn( + (NodeBundle { + style: Style { + height: Val::Px(200.0), + width: Val::Percent(100.0), + ..Default::default() + }, + ..Default::default() + }), + ) + .with_children(|p| { + p.spawn(TextBundle { + text: Text::from_section("Item details", text_style.clone()), + ..Default::default() + }); + }); + }); + } +} + +pub(super) fn register(app: &mut App) { + app.add_systems( + Update, + populate_menu + .in_set(NetworkingSystemsSet::Between) + .in_set(FabricatorMenuSet::PopulateMenu) + .run_if(in_state(GameState::Playing)) + .run_if(resource_exists::), + ); +} diff --git a/cosmos_client/src/crafting/blocks/mod.rs b/cosmos_client/src/crafting/blocks/mod.rs new file mode 100644 index 000000000..6336b82bd --- /dev/null +++ b/cosmos_client/src/crafting/blocks/mod.rs @@ -0,0 +1,7 @@ +use bevy::prelude::App; + +mod basic_fabricator; + +pub(super) fn register(app: &mut App) { + basic_fabricator::register(app); +} diff --git a/cosmos_client/src/crafting/mod.rs b/cosmos_client/src/crafting/mod.rs index 7cbba9421..7a4365310 100644 --- a/cosmos_client/src/crafting/mod.rs +++ b/cosmos_client/src/crafting/mod.rs @@ -1,7 +1,9 @@ use bevy::prelude::App; +mod blocks; mod recipes; pub(super) fn register(app: &mut App) { recipes::register(app); + blocks::register(app); } diff --git a/cosmos_core/src/crafting/blocks/basic_fabricator.rs b/cosmos_core/src/crafting/blocks/basic_fabricator.rs new file mode 100644 index 000000000..5000340bd --- /dev/null +++ b/cosmos_core/src/crafting/blocks/basic_fabricator.rs @@ -0,0 +1,25 @@ +use bevy::prelude::{App, Event}; +use serde::{Deserialize, Serialize}; + +use crate::{ + netty::sync::events::netty_event::{IdentifiableEvent, NettyEvent, SyncedEventImpl}, + prelude::StructureBlock, +}; + +#[derive(Event, Debug, Clone, Copy, Serialize, Deserialize)] +pub struct OpenBasicFabricatorEvent(pub StructureBlock); + +impl IdentifiableEvent for OpenBasicFabricatorEvent { + fn unlocalized_name() -> &'static str { + "cosmos:open_basic_fabricator" + } +} + +impl NettyEvent for OpenBasicFabricatorEvent { + fn event_receiver() -> crate::netty::sync::events::netty_event::EventReceiver { + crate::netty::sync::events::netty_event::EventReceiver::Client + } +} +pub(super) fn register(app: &mut App) { + app.add_netty_event::(); +} diff --git a/cosmos_core/src/crafting/blocks/mod.rs b/cosmos_core/src/crafting/blocks/mod.rs new file mode 100644 index 000000000..060a4ee7a --- /dev/null +++ b/cosmos_core/src/crafting/blocks/mod.rs @@ -0,0 +1,7 @@ +use bevy::prelude::App; + +pub mod basic_fabricator; + +pub(super) fn register(app: &mut App) { + basic_fabricator::register(app); +} diff --git a/cosmos_core/src/crafting/mod.rs b/cosmos_core/src/crafting/mod.rs index 613ef3269..891154353 100644 --- a/cosmos_core/src/crafting/mod.rs +++ b/cosmos_core/src/crafting/mod.rs @@ -1,7 +1,9 @@ use bevy::prelude::App; +pub mod blocks; pub mod recipes; pub(super) fn register(app: &mut App) { recipes::register(app); + blocks::register(app); } diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs index e76af88fa..383514884 100644 --- a/cosmos_core/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -11,14 +11,14 @@ use super::RecipeItem; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FabricatorItemInput { - quantity: u16, - item: RecipeItem, + pub quantity: u16, + pub item: RecipeItem, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FabricatorItemOutput { - quantity: u16, - item: u16, + pub quantity: u16, + pub item: u16, } impl FabricatorItemInput { @@ -35,8 +35,8 @@ impl FabricatorItemOutput { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BasicFabricatorRecipe { - inputs: Vec, - output: FabricatorItemOutput, + pub inputs: Vec, + pub output: FabricatorItemOutput, } impl BasicFabricatorRecipe { @@ -52,6 +52,10 @@ impl BasicFabricatorRecipes { pub fn add_recipe(&mut self, recipe: BasicFabricatorRecipe) { self.0.push(recipe); } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } } #[derive(Event, Serialize, Deserialize, Debug)] diff --git a/cosmos_core/src/netty/client_registry.rs b/cosmos_core/src/netty/client_registry.rs index 28cdceb61..87cb04736 100644 --- a/cosmos_core/src/netty/client_registry.rs +++ b/cosmos_core/src/netty/client_registry.rs @@ -1,19 +1,12 @@ -//! Used to sync registries from server -> client +//! Used to sync registries from client -> server use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] -/// Used to sync registries from server -> client +/// Used to inform the server that the client has received all necessary registries /// /// For this to work, both the client and server must call their own versions of `sync_registry` for the registry type. pub enum RegistrySyncing { - /// The # of registries the client must received before starting the game - RegistryCount(u64), - /// A registry the client must use before starting the game - Registry { - /// The serialized form of this registry (serialized via `cosmos_encoder::serialize`) - serialized: Vec, - /// The unlocalized name of this registry - registry_name: String, - }, + /// The client has received all necessary registries + FinishedReceivingRegistries, } diff --git a/cosmos_core/src/netty/mod.rs b/cosmos_core/src/netty/mod.rs index bbf12921a..486a2343c 100644 --- a/cosmos_core/src/netty/mod.rs +++ b/cosmos_core/src/netty/mod.rs @@ -2,6 +2,7 @@ #[cfg(feature = "client")] pub mod client; +pub mod client_registry; pub mod client_reliable_messages; pub mod client_unreliable_messages; pub mod cosmos_encoder; @@ -76,6 +77,8 @@ pub enum NettyChannelClient { ComponentReplication, /// Automatic syncing of events NettyEvent, + /// Automatic syncing of registries + Registry, } impl From for u8 { @@ -87,6 +90,7 @@ impl From for u8 { NettyChannelClient::Shop => 3, NettyChannelClient::ComponentReplication => 4, NettyChannelClient::NettyEvent => 5, + NettyChannelClient::Registry => 6, } } } @@ -138,6 +142,13 @@ impl NettyChannelClient { resend_time: Duration::from_millis(200), }, }, + ChannelConfig { + channel_id: Self::Registry.into(), + max_memory_usage_bytes: 1 * MB, + send_type: SendType::ReliableOrdered { + resend_time: Duration::from_millis(200), + }, + }, ] } } diff --git a/cosmos_core/src/netty/sync/registry/client.rs b/cosmos_core/src/netty/sync/registry/client.rs index 4714e67cb..c4b7e57dd 100644 --- a/cosmos_core/src/netty/sync/registry/client.rs +++ b/cosmos_core/src/netty/sync/registry/client.rs @@ -1,7 +1,7 @@ //! Handles client-side registry syncing logic use crate::{ - netty::{cosmos_encoder, server_registry::RegistrySyncing, system_sets::NetworkingSystemsSet, NettyChannelServer}, + netty::{cosmos_encoder, server_registry::RegistrySyncing, system_sets::NetworkingSystemsSet, NettyChannelClient, NettyChannelServer}, registry::{identifiable::Identifiable, Registry}, }; use bevy::{ @@ -118,12 +118,17 @@ pub(super) fn register( app.configure_sets(Update, TransitionStateSet::TransitionState); - let transition_state = move |mut state_changer: ResMut>, loading_registries: Res| { - if loading_registries.0.is_some_and(|x| x == 0) { - info!("Got all registries from server - loading world!"); - state_changer.set(loading_world_state); - } - }; + let transition_state = + move |mut client: ResMut, mut state_changer: ResMut>, loading_registries: Res| { + if loading_registries.0.is_some_and(|x| x == 0) { + info!("Got all registries from server - loading world!"); + state_changer.set(loading_world_state); + client.send_message( + NettyChannelClient::Registry, + cosmos_encoder::serialize(&crate::netty::client_registry::RegistrySyncing::FinishedReceivingRegistries), + ) + } + }; app.add_systems( Update, diff --git a/cosmos_server/src/crafting/blocks/basic_fabricator.rs b/cosmos_server/src/crafting/blocks/basic_fabricator.rs new file mode 100644 index 000000000..1d6b36c15 --- /dev/null +++ b/cosmos_server/src/crafting/blocks/basic_fabricator.rs @@ -0,0 +1,60 @@ +use bevy::{ + app::Update, + prelude::{in_state, App, Event, EventReader, IntoSystemConfigs, Query, Res}, +}; +use serde::{Deserialize, Serialize}; + +use cosmos_core::{ + block::{ + block_events::{BlockEventsSet, BlockInteractEvent}, + Block, + }, + crafting::blocks::basic_fabricator::OpenBasicFabricatorEvent, + entities::player::Player, + netty::{ + sync::events::{ + netty_event::{IdentifiableEvent, NettyEvent, SyncedEventImpl}, + server_event::NettyEventWriter, + }, + system_sets::NetworkingSystemsSet, + }, + prelude::{Structure, StructureBlock}, + registry::{identifiable::Identifiable, Registry}, + state::GameState, +}; + +fn monitor_basic_fabricator_interactions( + mut evr_block_interact: EventReader, + mut nevw_open_basic_fabricator: NettyEventWriter, + q_player: Query<&Player>, + q_structure: Query<&Structure>, + blocks: Res>, +) { + for ev in evr_block_interact.read() { + let Some(block) = ev.block else { + continue; + }; + let Ok(structure) = q_structure.get(block.structure()) else { + continue; + }; + if structure.block_at(block.coords(), &blocks).unlocalized_name() != "cosmos:basic_fabricator" { + continue; + } + let Ok(player) = q_player.get(ev.interactor) else { + continue; + }; + + nevw_open_basic_fabricator.send(OpenBasicFabricatorEvent(block), player.id()); + } +} + +pub(super) fn register(app: &mut App) { + app.add_systems( + Update, + monitor_basic_fabricator_interactions + .in_set(NetworkingSystemsSet::Between) + .in_set(BlockEventsSet::ProcessEvents) + .run_if(in_state(GameState::Playing)), + ) + .add_netty_event::(); +} diff --git a/cosmos_server/src/crafting/blocks/mod.rs b/cosmos_server/src/crafting/blocks/mod.rs new file mode 100644 index 000000000..6336b82bd --- /dev/null +++ b/cosmos_server/src/crafting/blocks/mod.rs @@ -0,0 +1,7 @@ +use bevy::prelude::App; + +mod basic_fabricator; + +pub(super) fn register(app: &mut App) { + basic_fabricator::register(app); +} diff --git a/cosmos_server/src/crafting/mod.rs b/cosmos_server/src/crafting/mod.rs index 174397bab..7a4365310 100644 --- a/cosmos_server/src/crafting/mod.rs +++ b/cosmos_server/src/crafting/mod.rs @@ -1,6 +1,9 @@ use bevy::prelude::App; + +mod blocks; mod recipes; pub(super) fn register(app: &mut App) { recipes::register(app); + blocks::register(app); } diff --git a/cosmos_server/src/crafting/recipes/basic_fabricator.rs b/cosmos_server/src/crafting/recipes/basic_fabricator.rs index 00a1200a5..dfbd6bc0a 100644 --- a/cosmos_server/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_server/src/crafting/recipes/basic_fabricator.rs @@ -21,7 +21,7 @@ use cosmos_core::{ }; use walkdir::WalkDir; -use crate::netty::server_events::PlayerConnectedEvent; +use crate::netty::{server_events::PlayerConnectedEvent, sync::registry::ClientFinishedReceivingRegistriesEvent}; use super::RawRecipeItem; @@ -86,11 +86,11 @@ fn sync_recipes_on_change(recipes: Res, mut nevw_sync_re fn sync_recipes_on_join( recipes: Res, - mut evr_player_join: EventReader, + mut evr_loaded_registries: EventReader, mut nevw_sync_recipes: NettyEventWriter, ) { - for ev in evr_player_join.read() { - nevw_sync_recipes.send(SyncBasicFabricatorRecipesEvent(recipes.clone()), ev.client_id); + for ev in evr_loaded_registries.read() { + nevw_sync_recipes.send(SyncBasicFabricatorRecipesEvent(recipes.clone()), ev.0); } } diff --git a/cosmos_server/src/netty/sync/mod.rs b/cosmos_server/src/netty/sync/mod.rs index c109f22c0..af071c5af 100644 --- a/cosmos_server/src/netty/sync/mod.rs +++ b/cosmos_server/src/netty/sync/mod.rs @@ -2,8 +2,10 @@ use bevy::prelude::App; +pub mod registry; pub mod sync_bodies; pub(super) fn register(app: &mut App) { sync_bodies::register(app); + registry::register(app); } diff --git a/cosmos_server/src/netty/sync/registry.rs b/cosmos_server/src/netty/sync/registry.rs new file mode 100644 index 000000000..649348e47 --- /dev/null +++ b/cosmos_server/src/netty/sync/registry.rs @@ -0,0 +1,43 @@ +use bevy::{ + app::Update, + log::warn, + prelude::{in_state, App, Event, EventWriter, IntoSystemConfigs, ResMut}, +}; +use cosmos_core::{ + netty::{client_registry::RegistrySyncing, cosmos_encoder, system_sets::NetworkingSystemsSet, NettyChannelClient}, + state::GameState, +}; +use renet2::{ClientId, RenetServer}; + +#[derive(Debug, Event)] +pub struct ClientFinishedReceivingRegistriesEvent(pub ClientId); + +fn listen_for_done_syncing( + mut server: ResMut, + mut evw_finished_receiving_registries: EventWriter, +) { + for client_id in server.clients_id().into_iter() { + while let Some(message) = server.receive_message(client_id, NettyChannelClient::ComponentReplication) { + let Ok(msg) = cosmos_encoder::deserialize::(&message) else { + warn!("Bad deserialization"); + continue; + }; + + match msg { + RegistrySyncing::FinishedReceivingRegistries => { + evw_finished_receiving_registries.send(ClientFinishedReceivingRegistriesEvent(client_id)); + } + } + } + } +} + +pub(super) fn register(app: &mut App) { + app.add_systems( + Update, + listen_for_done_syncing + .run_if(in_state(GameState::Playing)) + .in_set(NetworkingSystemsSet::ReceiveMessages), + ) + .add_event::(); +} From 947577861b07d8078455fbd17c83964e80a1be51 Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Thu, 14 Nov 2024 16:12:59 -0500 Subject: [PATCH 03/18] Adding iron ore --- .../assets/cosmos/images/blocks/iron_ore.png | Bin 0 -> 809 bytes .../assets/cosmos/lang/blocks/en_us.lang | 4 +++- cosmos_core/src/block/blocks/mod.rs | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 cosmos_client/assets/cosmos/images/blocks/iron_ore.png diff --git a/cosmos_client/assets/cosmos/images/blocks/iron_ore.png b/cosmos_client/assets/cosmos/images/blocks/iron_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..1203eea8281d745a047d143d3b030e593d2854a2 GIT binary patch literal 809 zcmV+^1J?YBP)?H@2oh%)5OMxE#g{|Qs@GoGYd>2$J3E`Is>=KOJBdUB(=>5i z7g?536a~-oXf~Vl`+ef^ILTxZAq354lR}|@EXy*jR*PINM=qD6TCJie3ejkkZnul; zx@5Ci*4EaD#bO*B9FR_@DHe-#IvwPXj}KH;Wp{U%ot+(ykB@nLd_)LAGMPk@BwW{} z)9Iin3b(hnl*?rt$6YjahOad)a!Ks+U+($5a7Blk|eRezt8seHeX*~EG;bo;JPlcSPTJ> zOeV1`i}84j@B74JF(gR>U_2hrHY=CQ0Gywnqw6}2Mg!^e^mJ;PCc3WUI1b5V63_GK z^?GD78AhWKpP!%P^Lggx=5Sq?cs!2dIQYJgFin&3c#IGNP19(#TCA+BpzAt{qL9gC z&~=?`HcO||0bn>B5{4m}Oa@t!BoswqJRV~h1}7&c?CtF_91eMYer7Zp;rl+Xudk$1 zDSY2Y(=7?*Ny0FU*)ISDL4XhfS(XuzNQ73a#q#p<% Date: Tue, 19 Nov 2024 00:00:22 -0500 Subject: [PATCH 04/18] Crafting netty events --- .../crafting/blocks/basic_fabricator/ui.rs | 207 ++++++++++++++++-- .../src/crafting/blocks/basic_fabricator.rs | 23 +- cosmos_core/src/inventory/mod.rs | 10 + .../src/netty/sync/events/netty_event.rs | 6 +- .../src/crafting/blocks/basic_fabricator.rs | 10 +- .../src/crafting/recipes/basic_fabricator.rs | 7 +- 6 files changed, 224 insertions(+), 39 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index 7d3fd6e99..8f12bfdda 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -1,17 +1,22 @@ use bevy::{ app::Update, - color::{Color, Srgba}, + color::{palettes::css, Color, Srgba}, core::Name, prelude::{ - in_state, resource_exists, Added, App, BuildChildren, Commands, Entity, IntoSystemConfigs, NodeBundle, Query, Res, TextBundle, + in_state, resource_exists, Added, App, BuildChildren, Changed, Commands, Component, DespawnRecursiveExt, Entity, Event, + EventReader, IntoSystemConfigs, NodeBundle, Parent, Query, Res, TextBundle, With, }, text::{Text, TextStyle}, ui::{FlexDirection, JustifyContent, Style, UiRect, Val}, }; use cosmos_core::{ - crafting::recipes::{basic_fabricator::BasicFabricatorRecipes, RecipeItem}, + crafting::recipes::{ + basic_fabricator::{BasicFabricatorRecipe, BasicFabricatorRecipes}, + RecipeItem, + }, + inventory::Inventory, item::Item, - netty::system_sets::NetworkingSystemsSet, + netty::{client::LocalPlayer, system_sets::NetworkingSystemsSet}, registry::{identifiable::Identifiable, Registry}, state::GameState, }; @@ -20,16 +25,30 @@ use crate::{ lang::Lang, ui::{ components::{ + button::{register_button, Button, ButtonBundle, ButtonEvent}, scollable_container::ScrollBundle, window::{GuiWindow, WindowBundle}, }, font::DefaultFont, item_renderer::RenderItem, + UiSystemSet, }, }; use super::{FabricatorMenuSet, OpenBasicFabricatorMenu}; +#[derive(Event, Debug)] +struct SelectItemEvent(Entity); + +impl ButtonEvent for SelectItemEvent { + fn create_event(btn_entity: Entity) -> Self { + Self(btn_entity) + } +} + +#[derive(Component, Debug)] +struct Recipe(BasicFabricatorRecipe); + fn populate_menu( mut commands: Commands, q_added_menu: Query>, @@ -93,17 +112,21 @@ fn populate_menu( ) .with_children(|p| { for recipe in crafting_recipes.iter() { - p.spawn( - (NodeBundle { - style: Style { - height: Val::Px(100.0), - width: Val::Percent(100.0), - justify_content: JustifyContent::SpaceBetween, + p.spawn(( + ButtonBundle { + node_bundle: NodeBundle { + style: Style { + height: Val::Px(100.0), + width: Val::Percent(100.0), + justify_content: JustifyContent::SpaceBetween, + ..Default::default() + }, ..Default::default() }, - ..Default::default() - }), - ) + button: Button::::default(), + }, + Recipe(recipe.clone()), + )) .with_children(|p| { p.spawn(( NodeBundle { @@ -181,30 +204,166 @@ fn populate_menu( } }); - p.spawn( - (NodeBundle { + p.spawn(( + SelectedRecipeDisplay, + NodeBundle { style: Style { height: Val::Px(200.0), width: Val::Percent(100.0), ..Default::default() }, ..Default::default() - }), - ) - .with_children(|p| { - p.spawn(TextBundle { - text: Text::from_section("Item details", text_style.clone()), - ..Default::default() - }); - }); + }, + )); }); } } +#[derive(Debug, Component)] +struct SelectedRecipeDisplay; + +#[derive(Component, Debug)] +struct InventoryCount { + recipe_amt: u16, + item_id: u16, +} + +fn on_select_item( + mut commands: Commands, + q_selected_recipe_display: Query<(Entity, &Parent), With>, + q_inventory: Query<&Inventory, With>, + mut evr_select_item: EventReader, + q_recipe: Query<&Recipe>, + font: Res, +) { + for ev in evr_select_item.read() { + let Ok(recipe) = q_recipe.get(ev.0) else { + continue; + }; + + let Ok((selected_recipe_display, parent)) = q_selected_recipe_display.get_single() else { + return; + }; + + commands.entity(selected_recipe_display).despawn_recursive(); + + let text_style_enough = TextStyle { + font: font.0.clone_weak(), + font_size: 24.0, + color: Color::WHITE, + }; + let text_style_not_enough = TextStyle { + font: font.0.clone_weak(), + font_size: 24.0, + color: css::RED.into(), + }; + + let Ok(inventory) = q_inventory.get_single() else { + continue; + }; + + commands + .spawn(( + SelectedRecipeDisplay, + NodeBundle { + style: Style { + height: Val::Px(200.0), + width: Val::Percent(100.0), + ..Default::default() + }, + ..Default::default() + }, + )) + .with_children(|p| { + for item in recipe.0.inputs.iter() { + let item_id = match item.item { + RecipeItem::Item(i) => i, + RecipeItem::Category(_) => todo!("Categories"), + }; + + p.spawn(( + NodeBundle { + style: Style { + width: Val::Px(64.0), + height: Val::Px(64.0), + ..Default::default() + }, + ..Default::default() + }, + RenderItem { item_id }, + )); + + let inventory_count = inventory.total_quantity_of_item(item_id); + + let text_style = if inventory_count >= item.quantity as u64 { + text_style_enough.clone() + } else { + text_style_not_enough.clone() + }; + + p.spawn(( + Name::new("Item recipe qty"), + InventoryCount { + item_id, + recipe_amt: item.quantity, + }, + TextBundle { + text: Text::from_section(format!("{}/{}", inventory_count, item.quantity), text_style.clone()), + ..Default::default() + }, + )); + } + }) + .set_parent(parent.get()); + + println!("{recipe:?}"); + } +} + +fn update_inventory_counts( + font: Res, + mut q_inventory_counts: Query<(&mut Text, &InventoryCount)>, + q_changed_inventory: Query<&Inventory, (With, Changed)>, +) { + let Ok(inventory) = q_changed_inventory.get_single() else { + return; + }; + + let text_style_enough = TextStyle { + font: font.0.clone_weak(), + font_size: 24.0, + color: Color::WHITE, + }; + let text_style_not_enough = TextStyle { + font: font.0.clone_weak(), + font_size: 24.0, + color: css::RED.into(), + }; + + for (mut text, recipe_info) in q_inventory_counts.iter_mut() { + let inventory_count = inventory.total_quantity_of_item(recipe_info.item_id); + + let text_style = if inventory_count >= recipe_info.recipe_amt as u64 { + text_style_enough.clone() + } else { + text_style_not_enough.clone() + }; + + text.sections[0].style = text_style; + text.sections[0].value = format!("{}/{}", inventory_count, recipe_info.recipe_amt); + } +} + pub(super) fn register(app: &mut App) { + register_button::(app); + app.add_systems( Update, - populate_menu + ( + populate_menu, + (on_select_item, update_inventory_counts).chain().in_set(UiSystemSet::DoUi), + ) + .chain() .in_set(NetworkingSystemsSet::Between) .in_set(FabricatorMenuSet::PopulateMenu) .run_if(in_state(GameState::Playing)) diff --git a/cosmos_core/src/crafting/blocks/basic_fabricator.rs b/cosmos_core/src/crafting/blocks/basic_fabricator.rs index 5000340bd..5a8d5d025 100644 --- a/cosmos_core/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_core/src/crafting/blocks/basic_fabricator.rs @@ -2,6 +2,7 @@ use bevy::prelude::{App, Event}; use serde::{Deserialize, Serialize}; use crate::{ + crafting::recipes::basic_fabricator::BasicFabricatorRecipe, netty::sync::events::netty_event::{IdentifiableEvent, NettyEvent, SyncedEventImpl}, prelude::StructureBlock, }; @@ -20,6 +21,26 @@ impl NettyEvent for OpenBasicFabricatorEvent { crate::netty::sync::events::netty_event::EventReceiver::Client } } + +#[derive(Event, Debug, Clone, Serialize, Deserialize)] +pub struct CraftBasicFabricatorRecipeEvent { + pub block: StructureBlock, + pub recipe: BasicFabricatorRecipe, +} + +impl IdentifiableEvent for CraftBasicFabricatorRecipeEvent { + fn unlocalized_name() -> &'static str { + "cosmos:craft_basic_fabricator" + } +} + +impl NettyEvent for CraftBasicFabricatorRecipeEvent { + fn event_receiver() -> crate::netty::sync::events::netty_event::EventReceiver { + crate::netty::sync::events::netty_event::EventReceiver::Server + } +} + pub(super) fn register(app: &mut App) { - app.add_netty_event::(); + app.add_netty_event::() + .add_netty_event::(); } diff --git a/cosmos_core/src/inventory/mod.rs b/cosmos_core/src/inventory/mod.rs index 493545509..3e1b1f10f 100644 --- a/cosmos_core/src/inventory/mod.rs +++ b/cosmos_core/src/inventory/mod.rs @@ -822,6 +822,16 @@ impl Inventory { pub fn iter_mut(&mut self) -> std::slice::IterMut> { self.items.iter_mut() } + + /// Returns the total quantity of this item within the inventory. This does NOT respect + /// any item data that may make it unique + pub fn total_quantity_of_item(&self, item_id: u16) -> u64 { + self.iter() + .flatten() + .filter(|x| x.item_id() == item_id) + .map(|x| x.quantity() as u64) + .sum::() + } } pub(super) fn register(app: &mut App, playing_state: T) { diff --git a/cosmos_core/src/netty/sync/events/netty_event.rs b/cosmos_core/src/netty/sync/events/netty_event.rs index 6505b2932..962d96f1a 100644 --- a/cosmos_core/src/netty/sync/events/netty_event.rs +++ b/cosmos_core/src/netty/sync/events/netty_event.rs @@ -49,7 +49,7 @@ pub(super) enum NettyEventMessage { /// `app.add_netty_event` implementation. pub trait SyncedEventImpl { /// Adds a netty-synced event. See [`NettyEvent`]. - fn add_netty_event(&mut self); + fn add_netty_event(&mut self) -> &mut Self; } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -73,12 +73,14 @@ impl Identifiable for RegisteredNettyEvent { } impl SyncedEventImpl for App { - fn add_netty_event(&mut self) { + fn add_netty_event(&mut self) -> &mut Self { #[cfg(feature = "client")] client_event::register_event::(self); #[cfg(feature = "server")] server_event::register_event::(self); + + self } } diff --git a/cosmos_server/src/crafting/blocks/basic_fabricator.rs b/cosmos_server/src/crafting/blocks/basic_fabricator.rs index 1d6b36c15..21c95add3 100644 --- a/cosmos_server/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_server/src/crafting/blocks/basic_fabricator.rs @@ -1,8 +1,7 @@ use bevy::{ app::Update, - prelude::{in_state, App, Event, EventReader, IntoSystemConfigs, Query, Res}, + prelude::{in_state, App, EventReader, IntoSystemConfigs, Query, Res}, }; -use serde::{Deserialize, Serialize}; use cosmos_core::{ block::{ @@ -12,13 +11,10 @@ use cosmos_core::{ crafting::blocks::basic_fabricator::OpenBasicFabricatorEvent, entities::player::Player, netty::{ - sync::events::{ - netty_event::{IdentifiableEvent, NettyEvent, SyncedEventImpl}, - server_event::NettyEventWriter, - }, + sync::events::{netty_event::SyncedEventImpl, server_event::NettyEventWriter}, system_sets::NetworkingSystemsSet, }, - prelude::{Structure, StructureBlock}, + prelude::Structure, registry::{identifiable::Identifiable, Registry}, state::GameState, }; diff --git a/cosmos_server/src/crafting/recipes/basic_fabricator.rs b/cosmos_server/src/crafting/recipes/basic_fabricator.rs index dfbd6bc0a..4d4b32261 100644 --- a/cosmos_server/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_server/src/crafting/recipes/basic_fabricator.rs @@ -1,9 +1,7 @@ -use std::ffi::OsStr; - use bevy::{ app::Update, log::warn, - prelude::{in_state, resource_exists_and_changed, App, Commands, EventReader, IntoSystemConfigs, OnEnter, Res, ResMut}, + prelude::{in_state, resource_exists_and_changed, App, Commands, EventReader, IntoSystemConfigs, OnEnter, Res}, }; use serde::{Deserialize, Serialize}; @@ -19,9 +17,8 @@ use cosmos_core::{ registry::{identifiable::Identifiable, Registry}, state::GameState, }; -use walkdir::WalkDir; -use crate::netty::{server_events::PlayerConnectedEvent, sync::registry::ClientFinishedReceivingRegistriesEvent}; +use crate::netty::sync::registry::ClientFinishedReceivingRegistriesEvent; use super::RawRecipeItem; From be6513ddbea472e01730254e06e9df30e4eaac98 Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Tue, 19 Nov 2024 00:06:36 -0500 Subject: [PATCH 05/18] Added basic fabricate button --- .../crafting/blocks/basic_fabricator/ui.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index 8f12bfdda..ca5a3486f 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -316,6 +316,31 @@ fn on_select_item( }) .set_parent(parent.get()); + commands + .spawn(( + bevy::prelude::ButtonBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Px(100.0), + ..Default::default() + }, + background_color: css::AQUA.into(), + + ..Default::default() + }, + Name::new("Craft button"), + )) + .with_children(|p| { + p.spawn(( + Name::new("Fabricate Button"), + TextBundle { + text: Text::from_section("FABRICATE", text_style_enough), + ..Default::default() + }, + )); + }) + .set_parent(parent.get()); + println!("{recipe:?}"); } } From eaaa135f6a2635fa7770a69dffa5b19020328935 Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Tue, 19 Nov 2024 19:22:16 -0500 Subject: [PATCH 06/18] Renamed InventoryNeedsDisplayed --- .../crafting/blocks/basic_fabricator/mod.rs | 27 +++- .../crafting/blocks/basic_fabricator/ui.rs | 20 ++- .../src/events/block/block_events.rs | 8 +- cosmos_client/src/inventory/mod.rs | 27 ++-- cosmos_core/src/netty/sync/mapping.rs | 40 ++++- .../src/blocks/data/basic_fabricator.rs | 140 ++++++++++++++++++ cosmos_server/src/blocks/data/mod.rs | 2 + 7 files changed, 242 insertions(+), 22 deletions(-) create mode 100644 cosmos_server/src/blocks/data/basic_fabricator.rs diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs index 505b88869..0226196a2 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/mod.rs @@ -1,14 +1,24 @@ use bevy::{ app::Update, core::Name, - prelude::{App, Commands, Component, Entity, EventReader, IntoSystemConfigs, IntoSystemSetConfigs, Query, SystemSet, With}, + log::error, + prelude::{ + in_state, App, Commands, Component, Entity, EventReader, IntoSystemConfigs, IntoSystemSetConfigs, Query, Res, SystemSet, With, + }, reflect::Reflect, }; use cosmos_core::{ crafting::blocks::basic_fabricator::OpenBasicFabricatorEvent, ecs::NeedsDespawned, - netty::{sync::events::client_event::NettyEventReceived, system_sets::NetworkingSystemsSet}, + netty::{ + sync::{ + events::client_event::NettyEventReceived, + mapping::{Mappable, NetworkMapping}, + }, + system_sets::NetworkingSystemsSet, + }, prelude::StructureBlock, + state::GameState, }; mod ui; @@ -20,6 +30,7 @@ fn open_menu( q_open_menu: Query>, mut commands: Commands, mut nevr: EventReader>, + network_mapping: Res, ) { let Some(ev) = nevr.read().last() else { return; @@ -29,7 +40,12 @@ fn open_menu( commands.entity(ent).insert(NeedsDespawned); } - commands.spawn((OpenBasicFabricatorMenu(ev.0), Name::new("Open Basic Fabricator Menu"))); + let Ok(s_block) = ev.0.map(&network_mapping) else { + error!("Bad network mapping - {:?}", ev.0); + return; + }; + + commands.spawn((OpenBasicFabricatorMenu(s_block), Name::new("Open Basic Fabricator Menu"))); } #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] @@ -45,7 +61,10 @@ pub(super) fn register(app: &mut App) { app.add_systems( Update, - open_menu.in_set(NetworkingSystemsSet::Between).in_set(FabricatorMenuSet::OpenMenu), + open_menu + .in_set(NetworkingSystemsSet::Between) + .in_set(FabricatorMenuSet::OpenMenu) + .run_if(in_state(GameState::Playing)), ) .register_type::(); } diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index ca5a3486f..ea0005b71 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -2,6 +2,7 @@ use bevy::{ app::Update, color::{palettes::css, Color, Srgba}, core::Name, + log::error, prelude::{ in_state, resource_exists, Added, App, BuildChildren, Changed, Commands, Component, DespawnRecursiveExt, Entity, Event, EventReader, IntoSystemConfigs, NodeBundle, Parent, Query, Res, TextBundle, With, @@ -17,6 +18,7 @@ use cosmos_core::{ inventory::Inventory, item::Item, netty::{client::LocalPlayer, system_sets::NetworkingSystemsSet}, + prelude::Structure, registry::{identifiable::Identifiable, Registry}, state::GameState, }; @@ -51,13 +53,27 @@ struct Recipe(BasicFabricatorRecipe); fn populate_menu( mut commands: Commands, - q_added_menu: Query>, + q_added_menu: Query<(Entity, &OpenBasicFabricatorMenu), Added>, font: Res, crafting_recipes: Res, items: Res>, lang: Res>, + q_structure: Query<&Structure>, + q_inventory: Query<&Inventory>, ) { - for ent in q_added_menu.iter() { + for (ent, fab_menu) in q_added_menu.iter() { + let Ok(structure) = q_structure.get(fab_menu.0.structure()) else { + error!("No structure for basic_fabricator!"); + continue; + }; + + let Some(inventory) = structure.query_block_data(fab_menu.0.coords(), &q_inventory) else { + error!("No inventory in basic_fabricator!"); + continue; + }; + + println!("{inventory:?}"); + let mut ecmds = commands.entity(ent); let text_style = TextStyle { diff --git a/cosmos_client/src/events/block/block_events.rs b/cosmos_client/src/events/block/block_events.rs index 99730bfd1..df2078523 100644 --- a/cosmos_client/src/events/block/block_events.rs +++ b/cosmos_client/src/events/block/block_events.rs @@ -46,7 +46,7 @@ fn handle_block_break( network_mapping: Res, ) { for ev in event_reader.read() { - let Ok(sb) = ev.block.map(&network_mapping) else { + let Ok(sb) = ev.block.map_to_server(&network_mapping) else { continue; }; @@ -63,7 +63,7 @@ fn handle_block_place( network_mapping: Res, ) { for ev in event_reader.read() { - let Ok(sb) = ev.block.map(&network_mapping) else { + let Ok(sb) = ev.block.map_to_server(&network_mapping) else { continue; }; @@ -85,7 +85,7 @@ fn handle_block_interact( network_mapping: Res, ) { for ev in event_reader.read() { - let Ok(server_structure_block) = ev.block_including_fluids.map(&network_mapping) else { + let Ok(server_structure_block) = ev.block_including_fluids.map_to_server(&network_mapping) else { continue; }; @@ -93,7 +93,7 @@ fn handle_block_interact( NettyChannelClient::Reliable, cosmos_encoder::serialize(&ClientReliableMessages::InteractWithBlock { block_including_fluids: server_structure_block, - block: ev.block.and_then(|b| b.map(&network_mapping).ok()), + block: ev.block.and_then(|b| b.map_to_server(&network_mapping).ok()), alternate: ev.alternate, }), ); diff --git a/cosmos_client/src/inventory/mod.rs b/cosmos_client/src/inventory/mod.rs index 582ba52a9..222efd897 100644 --- a/cosmos_client/src/inventory/mod.rs +++ b/cosmos_client/src/inventory/mod.rs @@ -62,23 +62,25 @@ struct RenderedInventory { fn toggle_inventory( mut commands: Commands, player_inventory: Query>, - open_inventories: Query>, + open_inventories: Query>, open_menus: Query<(), With>, inputs: InputChecker, ) { if inputs.check_just_pressed(CosmosInputs::ToggleInventory) { if !open_inventories.is_empty() { open_inventories.iter().for_each(|ent| { - commands.entity(ent).remove::(); + commands.entity(ent).remove::(); }); } else if let Ok(player_inventory_ent) = player_inventory.get_single() { if open_menus.is_empty() { - commands.entity(player_inventory_ent).insert(NeedsDisplayed(InventorySide::Left)); + commands + .entity(player_inventory_ent) + .insert(InventoryNeedsDisplayed(InventorySide::Left)); } } } else if inputs.check_just_pressed(CosmosInputs::Interact) && !open_inventories.is_empty() { open_inventories.iter().for_each(|ent| { - commands.entity(ent).remove::(); + commands.entity(ent).remove::(); }); } } @@ -86,13 +88,13 @@ fn toggle_inventory( fn close_button_system( mut commands: Commands, q_close_inventory: Query<&RenderedInventory, With>, - open_inventories: Query>, + open_inventories: Query>, ) { for rendered_inventory in q_close_inventory.iter() { // TODO: fix inventory closing to only close the one open if let Some(mut _ecmds) = commands.get_entity(rendered_inventory.inventory_holder) { open_inventories.iter().for_each(|ent| { - commands.entity(ent).remove::(); + commands.entity(ent).remove::(); }); } } @@ -100,7 +102,7 @@ fn close_button_system( #[derive(Default, Component)] /// Add this to an inventory you want displayed, and remove this component when you want to hide the inventory -pub struct NeedsDisplayed(InventorySide); +pub struct InventoryNeedsDisplayed(InventorySide); #[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] /// The side of the screen the inventory will be rendered @@ -134,12 +136,15 @@ struct InventoryRenderedItem; fn toggle_inventory_rendering( mut commands: Commands, asset_server: Res, - added_inventories: Query<(Entity, &Inventory, &NeedsDisplayed, Option<&OpenInventoryEntity>), Added>, - mut without_needs_displayed_inventories: Query<(Entity, &mut Inventory, Option<&OpenInventoryEntity>), Without>, + added_inventories: Query<(Entity, &Inventory, &InventoryNeedsDisplayed, Option<&OpenInventoryEntity>), Added>, + mut without_needs_displayed_inventories: Query< + (Entity, &mut Inventory, Option<&OpenInventoryEntity>), + Without, + >, mut holding_item: Query<(Entity, &DisplayedItemFromInventory, &mut HeldItemStack), With>, mut client: ResMut, mapping: Res, - mut removed_components: RemovedComponents, + mut removed_components: RemovedComponents, q_block_data: Query<&BlockData>, q_middle_root: Query>, q_bottom_root: Query>, @@ -674,7 +679,7 @@ fn handle_interactions( mapping: Res, q_block_data: Query<&BlockData>, asset_server: Res, - open_inventories: Query>, + open_inventories: Query>, ) { let lmb = input_handler.mouse_inputs().just_pressed(MouseButton::Left); let rmb = input_handler.mouse_inputs().just_pressed(MouseButton::Right); diff --git a/cosmos_core/src/netty/sync/mapping.rs b/cosmos_core/src/netty/sync/mapping.rs index 5680709b9..aab4920fa 100644 --- a/cosmos_core/src/netty/sync/mapping.rs +++ b/cosmos_core/src/netty/sync/mapping.rs @@ -20,7 +20,7 @@ use bevy::{ #[derive(Component, Reflect, PartialEq, Eq)] pub struct ServerEntity(pub Entity); -#[derive(Default, Resource)] +#[derive(Default, Resource, Debug)] /// Used to map server entities to client entities and client entities to server entities. pub struct NetworkMapping { server_to_client: HashMap, @@ -83,6 +83,13 @@ pub trait Mappable { fn map(self, network_mapping: &NetworkMapping) -> Result> where Self: Sized; + + /// Converts all instances of server entities into their respective client entities based off the mapping. + /// + /// Returns Err if it is unable to find the proper mapping + fn map_to_server(self, network_mapping: &NetworkMapping) -> Result> + where + Self: Sized; } impl Mappable for NettyRigidBody { @@ -102,10 +109,41 @@ impl Mappable for NettyRigidBody { NettyRigidBodyLocation::Absolute(_) => Ok(self), } } + + fn map_to_server(self, network_mapping: &NetworkMapping) -> Result> + where + Self: Sized, + { + match self.location { + NettyRigidBodyLocation::Relative(rel_pos, parent_ent) => { + let Some(client_ent) = network_mapping.server_from_client(&parent_ent) else { + return Err(MappingError::MissingRecord(self)); + }; + + Ok(NettyRigidBody { + body_vel: self.body_vel, + location: NettyRigidBodyLocation::Relative(rel_pos, client_ent), + rotation: self.rotation, + }) + } + NettyRigidBodyLocation::Absolute(_) => Ok(self), + } + } } impl Mappable for StructureBlock { fn map(self, network_mapping: &NetworkMapping) -> Result> { + if let Some(e) = network_mapping.client_from_server(&self.structure()) { + Ok(Self::new(self.coords(), e)) + } else { + Err(MappingError::MissingRecord(self)) + } + } + + fn map_to_server(self, network_mapping: &NetworkMapping) -> Result> + where + Self: Sized, + { if let Some(e) = network_mapping.server_from_client(&self.structure()) { Ok(Self::new(self.coords(), e)) } else { diff --git a/cosmos_server/src/blocks/data/basic_fabricator.rs b/cosmos_server/src/blocks/data/basic_fabricator.rs new file mode 100644 index 000000000..fc4ba4f86 --- /dev/null +++ b/cosmos_server/src/blocks/data/basic_fabricator.rs @@ -0,0 +1,140 @@ +//! Handles basic fabricator inventory + +use bevy::{ + app::{App, Update}, + ecs::{ + entity::Entity, + event::{EventReader, EventWriter}, + query::With, + schedule::IntoSystemConfigs, + system::{Query, Res}, + }, + prelude::Event, +}; +use cosmos_core::{ + block::{block_events::BlockEventsSet, data::BlockData, Block}, + events::block_events::{BlockChangedEvent, BlockDataSystemParams}, + inventory::Inventory, + netty::system_sets::NetworkingSystemsSet, + registry::{identifiable::Identifiable, Registry}, + structure::{structure_block::StructureBlock, Structure}, +}; + +use crate::{ + fluid::interact_fluid::FluidInteractionSet, + persistence::loading::{LoadingBlueprintSystemSet, NeedsBlueprintLoaded, LOADING_SCHEDULE}, +}; + +#[derive(Event, Debug)] +/// Sent whenever an entity needs an inventory populated. +/// +/// This should be populated by reading the block data on disk or creating a new inventory. +struct PopulateBasicFabricatorInventoryEvent { + /// The block + pub block: StructureBlock, +} + +/// Used to process the addition/removal of storage blocks to a structure. +/// +/// Sends out the `PopulateBlockInventoryEvent` event when needed. +fn on_add_basic_fabricator( + mut q_structure: Query<&mut Structure>, + blocks: Res>, + mut evr_block_changed: EventReader, + mut ev_writer: EventWriter, + mut q_block_data: Query<&mut BlockData>, + mut params: BlockDataSystemParams, + q_has_data: Query<(), With>, +) { + if evr_block_changed.is_empty() { + return; + } + + let Some(block) = blocks.from_id("cosmos:basic_fabricator") else { + return; + }; + + for ev in evr_block_changed.read() { + if ev.new_block == ev.old_block { + continue; + } + + let Ok(mut structure) = q_structure.get_mut(ev.block.structure()) else { + continue; + }; + + if blocks.from_numeric_id(ev.old_block) == block { + let coords = ev.block.coords(); + + structure.remove_block_data::(coords, &mut params, &mut q_block_data, &q_has_data); + } + + if blocks.from_numeric_id(ev.new_block) == block { + ev_writer.send(PopulateBasicFabricatorInventoryEvent { block: ev.block }); + } + } +} + +fn on_load_blueprint_storage( + needs_blueprint_loaded_structure: Query<(Entity, &Structure), With>, + blocks: Res>, + mut ev_writer: EventWriter, +) { + for (structure_entity, structure) in needs_blueprint_loaded_structure.iter() { + let Some(storage_block) = blocks.from_id("cosmos:basic_fabricator") else { + return; + }; + + for block in structure.all_blocks_iter(false) { + if structure.block_id_at(block) == storage_block.id() { + ev_writer.send(PopulateBasicFabricatorInventoryEvent { + block: StructureBlock::new(block, structure_entity), + }); + } + } + } +} + +fn populate_inventory( + mut q_structure: Query<&mut Structure>, + mut q_block_data: Query<&mut BlockData>, + q_has_inventory: Query<(), With>, + mut params: BlockDataSystemParams, + mut ev_reader: EventReader, +) { + for ev in ev_reader.read() { + let coords = ev.block.coords(); + + let Ok(mut structure) = q_structure.get_mut(ev.block.structure()) else { + continue; + }; + + structure.insert_block_data_with_entity( + coords, + |e| Inventory::new("Basic Fabricator", 9, None, e), + &mut params, + &mut q_block_data, + &q_has_inventory, + ); + } +} + +pub(super) fn register(app: &mut App) { + app.add_systems( + Update, + ( + on_add_basic_fabricator + .in_set(BlockEventsSet::ProcessEvents) + .ambiguous_with(FluidInteractionSet::InteractWithFluidBlocks), + populate_inventory.in_set(BlockEventsSet::SendEventsForNextFrame), + ) + .chain() + .in_set(NetworkingSystemsSet::Between), + ) + .add_systems( + LOADING_SCHEDULE, + // Need structure to be populated first, thus `DoneLoadingBlueprints` instead of `DoLoadingBlueprints`` + on_load_blueprint_storage.in_set(LoadingBlueprintSystemSet::DoneLoadingBlueprints), + ) + .add_event::(); +} diff --git a/cosmos_server/src/blocks/data/mod.rs b/cosmos_server/src/blocks/data/mod.rs index 1209fe55e..59a818b12 100644 --- a/cosmos_server/src/blocks/data/mod.rs +++ b/cosmos_server/src/blocks/data/mod.rs @@ -1,7 +1,9 @@ use bevy::app::App; +mod basic_fabricator; mod storage; pub(super) fn register(app: &mut App) { storage::register(app); + basic_fabricator::register(app); } From 9fb2d1d2bd39dc5886699c29130225e2aca16e8a Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Tue, 19 Nov 2024 19:28:38 -0500 Subject: [PATCH 07/18] Refactoring inventory rendering to support custom slot locations --- cosmos_client/src/inventory/mod.rs | 31 ++++++++++++++++++++++++---- cosmos_client/src/inventory/netty.rs | 10 +++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/cosmos_client/src/inventory/mod.rs b/cosmos_client/src/inventory/mod.rs index 222efd897..61952e7ca 100644 --- a/cosmos_client/src/inventory/mod.rs +++ b/cosmos_client/src/inventory/mod.rs @@ -75,7 +75,7 @@ fn toggle_inventory( if open_menus.is_empty() { commands .entity(player_inventory_ent) - .insert(InventoryNeedsDisplayed(InventorySide::Left)); + .insert(InventoryNeedsDisplayed::Normal(InventorySide::Left)); } } } else if inputs.check_just_pressed(CosmosInputs::Interact) && !open_inventories.is_empty() { @@ -100,9 +100,29 @@ fn close_button_system( } } -#[derive(Default, Component)] +#[derive(Debug, Clone)] +pub struct CustomInventoryRender { + slots: Vec<(usize, Entity)>, +} + +impl CustomInventoryRender { + pub fn new(slots: Vec<(usize, Entity)>) -> Self { + Self { slots } + } +} + +#[derive(Component, Debug, Clone)] /// Add this to an inventory you want displayed, and remove this component when you want to hide the inventory -pub struct InventoryNeedsDisplayed(InventorySide); +pub enum InventoryNeedsDisplayed { + Normal(InventorySide), + Custom(CustomInventoryRender), +} + +impl Default for InventoryNeedsDisplayed { + fn default() -> Self { + Self::Normal(Default::default()) + } +} #[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] /// The side of the screen the inventory will be rendered @@ -219,6 +239,9 @@ fn toggle_inventory_rendering( if open_inventory_entity.is_some() { continue; } + let InventoryNeedsDisplayed::Normal(needs_displayed_side) = needs_displayed else { + continue; + }; let font = asset_server.load("fonts/PixeloidSans.ttf"); @@ -233,7 +256,7 @@ fn toggle_inventory_rendering( let slot_size = 64.0; let scrollbar_width = 15.0; - let (left, right) = if needs_displayed.0 == InventorySide::Right { + let (left, right) = if *needs_displayed_side == InventorySide::Right { (Val::Auto, Val::Px(100.0)) } else { (Val::Px(100.0), Val::Auto) diff --git a/cosmos_client/src/inventory/netty.rs b/cosmos_client/src/inventory/netty.rs index a7b6fe8b5..a91d6a82a 100644 --- a/cosmos_client/src/inventory/netty.rs +++ b/cosmos_client/src/inventory/netty.rs @@ -17,7 +17,7 @@ use cosmos_core::{ structure::Structure, }; -use super::{HeldItemStack, InventorySide, NeedsDisplayed}; +use super::{HeldItemStack, InventoryNeedsDisplayed, InventorySide}; fn sync( mut client: ResMut, @@ -58,7 +58,7 @@ fn sync( InventoryIdentifier::Entity(owner) => { if let Some(client_entity) = network_mapping.client_from_server(&owner) { if let Some(mut ecmds) = commands.get_entity(client_entity) { - ecmds.insert(NeedsDisplayed::default()); + ecmds.insert(InventoryNeedsDisplayed::default()); } } else { warn!("Error: unrecognized entity {owner:?} received from server when trying to sync up inventories!"); @@ -90,11 +90,13 @@ fn sync( continue; } - commands.entity(data_entity).insert(NeedsDisplayed::default()); + commands.entity(data_entity).insert(InventoryNeedsDisplayed::default()); } } - commands.entity(local_player.single()).insert(NeedsDisplayed(InventorySide::Left)); + commands + .entity(local_player.single()) + .insert(InventoryNeedsDisplayed::Normal(InventorySide::Left)); } } } From ddf95e9173699b4dd9959f2d85edc6058392a4bd Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Sat, 23 Nov 2024 14:47:35 -0500 Subject: [PATCH 08/18] Adding inventory to basic fabricator --- .../crafting/blocks/basic_fabricator/ui.rs | 122 +++++++++++++++--- cosmos_client/src/inventory/mod.rs | 18 ++- .../src/blocks/data/basic_fabricator.rs | 2 +- 3 files changed, 121 insertions(+), 21 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index ea0005b71..cac7c5c28 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -8,7 +8,7 @@ use bevy::{ EventReader, IntoSystemConfigs, NodeBundle, Parent, Query, Res, TextBundle, With, }, text::{Text, TextStyle}, - ui::{FlexDirection, JustifyContent, Style, UiRect, Val}, + ui::{FlexDirection, JustifyContent, Style, TargetCamera, UiRect, Val}, }; use cosmos_core::{ crafting::recipes::{ @@ -24,16 +24,18 @@ use cosmos_core::{ }; use crate::{ + inventory::{CustomInventoryRender, InventoryNeedsDisplayed, InventorySide}, lang::Lang, + rendering::MainCamera, ui::{ components::{ - button::{register_button, Button, ButtonBundle, ButtonEvent}, + button::{register_button, Button, ButtonBundle, ButtonEvent, ButtonStyles}, scollable_container::ScrollBundle, window::{GuiWindow, WindowBundle}, }, font::DefaultFont, item_renderer::RenderItem, - UiSystemSet, + OpenMenu, UiSystemSet, }, }; @@ -41,27 +43,40 @@ use super::{FabricatorMenuSet, OpenBasicFabricatorMenu}; #[derive(Event, Debug)] struct SelectItemEvent(Entity); - impl ButtonEvent for SelectItemEvent { fn create_event(btn_entity: Entity) -> Self { Self(btn_entity) } } +#[derive(Event, Debug)] +struct CreateClickedEvent; +impl ButtonEvent for CreateClickedEvent { + fn create_event(btn_entity: Entity) -> Self { + Self + } +} + #[derive(Component, Debug)] struct Recipe(BasicFabricatorRecipe); fn populate_menu( mut commands: Commands, q_added_menu: Query<(Entity, &OpenBasicFabricatorMenu), Added>, + q_player: Query>, font: Res, crafting_recipes: Res, items: Res>, lang: Res>, q_structure: Query<&Structure>, q_inventory: Query<&Inventory>, + q_cam: Query>, ) { for (ent, fab_menu) in q_added_menu.iter() { + let Ok(cam) = q_cam.get_single() else { + return; + }; + let Ok(structure) = q_structure.get(fab_menu.0.structure()) else { error!("No structure for basic_fabricator!"); continue; @@ -71,8 +86,13 @@ fn populate_menu( error!("No inventory in basic_fabricator!"); continue; }; + let Ok(player_ent) = q_player.get_single() else { + return; + }; - println!("{inventory:?}"); + commands + .entity(player_ent) + .insert(InventoryNeedsDisplayed::Normal(InventorySide::Left)); let mut ecmds = commands.entity(ent); @@ -82,12 +102,16 @@ fn populate_menu( color: Color::WHITE, }; - ecmds.insert( - (WindowBundle { + let item_slot_size = 64.0; + + ecmds.insert(( + TargetCamera(cam), + OpenMenu::new(0), + WindowBundle { node_bundle: NodeBundle { background_color: Srgba::hex("2D2D2D").unwrap().into(), style: Style { - width: Val::Px(400.0), + width: Val::Px(item_slot_size * 6.0), height: Val::Px(800.0), margin: UiRect { // Centers it vertically @@ -110,8 +134,10 @@ fn populate_menu( ..Default::default() }, ..Default::default() - }), - ); + }, + )); + + let mut slot_ents = vec![]; ecmds.with_children(|p| { p.spawn( @@ -220,18 +246,71 @@ fn populate_menu( } }); - p.spawn(( - SelectedRecipeDisplay, - NodeBundle { + p.spawn( + (NodeBundle { style: Style { - height: Val::Px(200.0), + flex_direction: FlexDirection::Column, width: Val::Percent(100.0), + height: Val::Px(item_slot_size * 2.0), ..Default::default() }, ..Default::default() - }, - )); + }), + ) + .with_children(|p| { + p.spawn(( + SelectedRecipeDisplay, + NodeBundle { + style: Style { + width: Val::Px(item_slot_size * 6.0), + height: Val::Px(item_slot_size), + ..Default::default() + }, + ..Default::default() + }, + )) + .with_children(|p| { + for (slot, _) in inventory.iter().enumerate() { + let ent = p + .spawn(( + Name::new("Rendered Item"), + NodeBundle { + style: Style { + width: Val::Px(64.0), + height: Val::Px(64.0), + ..Default::default() + }, + ..Default::default() + }, + )) + .id(); + slot_ents.push((slot, ent)); + } + }); + + p.spawn(( + Name::new("Fabricate Button"), + ButtonBundle { + button: Button:: { + text: Some(("Fabricate".into(), text_style)), + button_styles: Some(ButtonStyles { ..Default::default() }), + ..Default::default() + }, + node_bundle: NodeBundle { + style: Style { + flex_grow: 1.0, + ..Default::default() + }, + ..Default::default() + }, + }, + )); + }); }); + + commands + .entity(structure.block_data(fab_menu.0.coords()).expect("Must expect from above")) + .insert(InventoryNeedsDisplayed::Custom(CustomInventoryRender::new(slot_ents))); } } @@ -395,14 +474,23 @@ fn update_inventory_counts( } } +fn listen_create(mut evr_create: EventReader) { + for ev in evr_create.read() { + println!("Create!"); + } +} + pub(super) fn register(app: &mut App) { register_button::(app); + register_button::(app); app.add_systems( Update, ( populate_menu, - (on_select_item, update_inventory_counts).chain().in_set(UiSystemSet::DoUi), + (on_select_item, listen_create, update_inventory_counts) + .chain() + .in_set(UiSystemSet::DoUi), ) .chain() .in_set(NetworkingSystemsSet::Between) diff --git a/cosmos_client/src/inventory/mod.rs b/cosmos_client/src/inventory/mod.rs index 61952e7ca..db5232a11 100644 --- a/cosmos_client/src/inventory/mod.rs +++ b/cosmos_client/src/inventory/mod.rs @@ -239,9 +239,6 @@ fn toggle_inventory_rendering( if open_inventory_entity.is_some() { continue; } - let InventoryNeedsDisplayed::Normal(needs_displayed_side) = needs_displayed else { - continue; - }; let font = asset_server.load("fonts/PixeloidSans.ttf"); @@ -251,6 +248,21 @@ fn toggle_inventory_rendering( font: font.clone(), }; + let needs_displayed_side = match needs_displayed { + InventoryNeedsDisplayed::Custom(slots) => { + for &(slot_number, slot) in slots.slots.iter() { + commands.entity(slot).with_children(|p| { + let slot = inventory.itemstack_at(slot_number); + + create_inventory_slot(inventory_holder, slot_number, p, slot, text_style.clone(), ItemRenderLayer::Middle); + }); + } + + continue; + } + InventoryNeedsDisplayed::Normal(needs_displayed_side) => needs_displayed_side, + }; + let inventory_border_size = 2.0; let n_slots_per_row: usize = 9; let slot_size = 64.0; diff --git a/cosmos_server/src/blocks/data/basic_fabricator.rs b/cosmos_server/src/blocks/data/basic_fabricator.rs index fc4ba4f86..8b639a136 100644 --- a/cosmos_server/src/blocks/data/basic_fabricator.rs +++ b/cosmos_server/src/blocks/data/basic_fabricator.rs @@ -111,7 +111,7 @@ fn populate_inventory( structure.insert_block_data_with_entity( coords, - |e| Inventory::new("Basic Fabricator", 9, None, e), + |e| Inventory::new("Basic Fabricator", 6, None, e), &mut params, &mut q_block_data, &q_has_inventory, From c9693a2defd4fcc61cbc5326c926f6122c083212 Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Sat, 23 Nov 2024 15:31:02 -0500 Subject: [PATCH 09/18] Better item rendering --- .../crafting/blocks/basic_fabricator/ui.rs | 224 ++++++++---------- cosmos_client/src/inventory/mod.rs | 2 +- .../src/crafting/recipes/basic_fabricator.rs | 28 ++- 3 files changed, 123 insertions(+), 131 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index cac7c5c28..6c79f5939 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -4,17 +4,18 @@ use bevy::{ core::Name, log::error, prelude::{ - in_state, resource_exists, Added, App, BuildChildren, Changed, Commands, Component, DespawnRecursiveExt, Entity, Event, + in_state, resource_exists, Added, App, BuildChildren, Changed, Children, Commands, Component, DespawnRecursiveExt, Entity, Event, EventReader, IntoSystemConfigs, NodeBundle, Parent, Query, Res, TextBundle, With, }, text::{Text, TextStyle}, - ui::{FlexDirection, JustifyContent, Style, TargetCamera, UiRect, Val}, + ui::{AlignItems, FlexDirection, JustifyContent, Style, TargetCamera, UiRect, Val}, }; use cosmos_core::{ crafting::recipes::{ basic_fabricator::{BasicFabricatorRecipe, BasicFabricatorRecipes}, RecipeItem, }, + ecs::NeedsDespawned, inventory::Inventory, item::Item, netty::{client::LocalPlayer, system_sets::NetworkingSystemsSet}, @@ -24,7 +25,7 @@ use cosmos_core::{ }; use crate::{ - inventory::{CustomInventoryRender, InventoryNeedsDisplayed, InventorySide}, + inventory::{CustomInventoryRender, InventoryNeedsDisplayed, InventorySide, TextNeedsTopRoot}, lang::Lang, rendering::MainCamera, ui::{ @@ -52,12 +53,12 @@ impl ButtonEvent for SelectItemEvent { #[derive(Event, Debug)] struct CreateClickedEvent; impl ButtonEvent for CreateClickedEvent { - fn create_event(btn_entity: Entity) -> Self { + fn create_event(_: Entity) -> Self { Self } } -#[derive(Component, Debug)] +#[derive(Component, Debug, Clone)] struct Recipe(BasicFabricatorRecipe); fn populate_menu( @@ -246,20 +247,35 @@ fn populate_menu( } }); - p.spawn( - (NodeBundle { + p.spawn(( + Name::new("Footer"), + NodeBundle { style: Style { flex_direction: FlexDirection::Column, width: Val::Percent(100.0), - height: Val::Px(item_slot_size * 2.0), + height: Val::Px(item_slot_size * 3.0), ..Default::default() }, ..Default::default() - }), - ) + }, + )) .with_children(|p| { p.spawn(( SelectedRecipeDisplay, + Name::new("Selected Recipe Display"), + NodeBundle { + style: Style { + align_items: AlignItems::Center, + width: Val::Px(item_slot_size * 6.0), + height: Val::Px(item_slot_size), + ..Default::default() + }, + ..Default::default() + }, + )); + + p.spawn(( + Name::new("Rendered Inventory"), NodeBundle { style: Style { width: Val::Px(item_slot_size * 6.0), @@ -325,8 +341,7 @@ struct InventoryCount { fn on_select_item( mut commands: Commands, - q_selected_recipe_display: Query<(Entity, &Parent), With>, - q_inventory: Query<&Inventory, With>, + q_selected_recipe_display: Query<(Entity, Option<&Children>), With>, mut evr_select_item: EventReader, q_recipe: Query<&Recipe>, font: Res, @@ -336,147 +351,103 @@ fn on_select_item( continue; }; - let Ok((selected_recipe_display, parent)) = q_selected_recipe_display.get_single() else { + let Ok((selected_recipe_display, children)) = q_selected_recipe_display.get_single() else { return; }; - commands.entity(selected_recipe_display).despawn_recursive(); + if let Some(children) = children { + for &child in children.iter() { + commands.entity(child).despawn_recursive(); + } + } - let text_style_enough = TextStyle { + let text_style = TextStyle { font: font.0.clone_weak(), font_size: 24.0, color: Color::WHITE, }; - let text_style_not_enough = TextStyle { - font: font.0.clone_weak(), - font_size: 24.0, - color: css::RED.into(), - }; - - let Ok(inventory) = q_inventory.get_single() else { - continue; - }; - - commands - .spawn(( - SelectedRecipeDisplay, - NodeBundle { - style: Style { - height: Val::Px(200.0), - width: Val::Percent(100.0), - ..Default::default() - }, - ..Default::default() - }, - )) - .with_children(|p| { - for item in recipe.0.inputs.iter() { - let item_id = match item.item { - RecipeItem::Item(i) => i, - RecipeItem::Category(_) => todo!("Categories"), - }; + // let text_style_not_enough = TextStyle { + // font: font.0.clone_weak(), + // font_size: 24.0, + // color: css::RED.into(), + // }; + // + // let Ok(inventory) = q_inventory.get_single() else { + // continue; + // }; + + commands.entity(selected_recipe_display).insert(recipe.clone()).with_children(|p| { + for item in recipe.0.inputs.iter() { + let item_id = match item.item { + RecipeItem::Item(i) => i, + RecipeItem::Category(_) => todo!("Categories"), + }; - p.spawn(( - NodeBundle { - style: Style { - width: Val::Px(64.0), - height: Val::Px(64.0), - ..Default::default() - }, + p.spawn(( + NodeBundle { + style: Style { + width: Val::Px(64.0), + height: Val::Px(64.0), + flex_direction: FlexDirection::Column, + align_items: AlignItems::End, + justify_content: JustifyContent::End, ..Default::default() }, - RenderItem { item_id }, - )); - - let inventory_count = inventory.total_quantity_of_item(item_id); - - let text_style = if inventory_count >= item.quantity as u64 { - text_style_enough.clone() - } else { - text_style_not_enough.clone() - }; - + ..Default::default() + }, + RenderItem { item_id }, + )) + .with_children(|p| { p.spawn(( Name::new("Item recipe qty"), + TextNeedsTopRoot, InventoryCount { item_id, recipe_amt: item.quantity, }, TextBundle { - text: Text::from_section(format!("{}/{}", inventory_count, item.quantity), text_style.clone()), + text: Text::from_section(format!("{}", item.quantity), text_style.clone()), ..Default::default() }, )); - } - }) - .set_parent(parent.get()); - - commands - .spawn(( - bevy::prelude::ButtonBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Px(100.0), - ..Default::default() - }, - background_color: css::AQUA.into(), - - ..Default::default() - }, - Name::new("Craft button"), - )) - .with_children(|p| { - p.spawn(( - Name::new("Fabricate Button"), - TextBundle { - text: Text::from_section("FABRICATE", text_style_enough), - ..Default::default() - }, - )); - }) - .set_parent(parent.get()); + }); + } + }); println!("{recipe:?}"); } } -fn update_inventory_counts( - font: Res, - mut q_inventory_counts: Query<(&mut Text, &InventoryCount)>, - q_changed_inventory: Query<&Inventory, (With, Changed)>, +fn listen_create( + q_structure: Query<&Structure>, + q_inventory: Query<&Inventory>, + q_open_fab_menu: Query<&OpenBasicFabricatorMenu>, + q_selected_recipe: Query<&Recipe, With>, + mut evr_create: EventReader, ) { - let Ok(inventory) = q_changed_inventory.get_single() else { - return; - }; - - let text_style_enough = TextStyle { - font: font.0.clone_weak(), - font_size: 24.0, - color: Color::WHITE, - }; - let text_style_not_enough = TextStyle { - font: font.0.clone_weak(), - font_size: 24.0, - color: css::RED.into(), - }; - - for (mut text, recipe_info) in q_inventory_counts.iter_mut() { - let inventory_count = inventory.total_quantity_of_item(recipe_info.item_id); - - let text_style = if inventory_count >= recipe_info.recipe_amt as u64 { - text_style_enough.clone() - } else { - text_style_not_enough.clone() + for _ in evr_create.read() { + let Ok(fab_menu) = q_open_fab_menu.get_single() else { + return; }; - text.sections[0].style = text_style; - text.sections[0].value = format!("{}/{}", inventory_count, recipe_info.recipe_amt); - } -} + let Ok(structure) = q_structure.get(fab_menu.0.structure()) else { + continue; + }; + let Some(block_inv) = structure.query_block_data(fab_menu.0.coords(), &q_inventory) else { + continue; + }; + + let Ok(recipe) = q_selected_recipe.get_single() else { + return; + }; + + let max_can_create = recipe.0.max_can_create(block_inv.iter().flatten()); + + if max_can_create == 0 { + continue; + } -fn listen_create(mut evr_create: EventReader) { - for ev in evr_create.read() { - println!("Create!"); + println!("Create {max_can_create}!"); } } @@ -486,12 +457,7 @@ pub(super) fn register(app: &mut App) { app.add_systems( Update, - ( - populate_menu, - (on_select_item, listen_create, update_inventory_counts) - .chain() - .in_set(UiSystemSet::DoUi), - ) + (populate_menu, (on_select_item, listen_create).chain().in_set(UiSystemSet::DoUi)) .chain() .in_set(NetworkingSystemsSet::Between) .in_set(FabricatorMenuSet::PopulateMenu) diff --git a/cosmos_client/src/inventory/mod.rs b/cosmos_client/src/inventory/mod.rs index db5232a11..a24131649 100644 --- a/cosmos_client/src/inventory/mod.rs +++ b/cosmos_client/src/inventory/mod.rs @@ -868,7 +868,7 @@ fn handle_interactions( */ #[derive(Component)] -struct TextNeedsTopRoot; +pub struct TextNeedsTopRoot; fn create_item_stack_slot_data( item_stack: &ItemStack, diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs index 383514884..f1785a679 100644 --- a/cosmos_core/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -1,7 +1,11 @@ -use bevy::prelude::{App, Commands, Event, Res, Resource}; +use bevy::{ + prelude::{App, Commands, Event, Res, Resource}, + utils::{HashMap, HashSet}, +}; use serde::{Deserialize, Serialize}; use crate::{ + inventory::itemstack::ItemStack, item::Item, netty::sync::events::netty_event::{IdentifiableEvent, NettyEvent, SyncedEventImpl}, registry::{identifiable::Identifiable, Registry}, @@ -43,6 +47,28 @@ impl BasicFabricatorRecipe { pub fn new(output: FabricatorItemOutput, inputs: Vec) -> Self { Self { output, inputs } } + + pub fn max_can_create<'a>(&self, items: impl Iterator) -> u32 { + let mut unique_item_counts = HashMap::new(); + for item in items { + *unique_item_counts.entry(item.item_id()).or_insert(0) += item.quantity() as u32; + } + + unique_item_counts + .into_iter() + .flat_map(|(item_id, quantity)| { + let Some(input) = self.inputs.iter().find(|x| match x.item { + RecipeItem::Item(id) => id == item_id, + RecipeItem::Category(_) => todo!(), + }) else { + return None; + }; + + Some(quantity / input.quantity as u32) + }) + .min() + .unwrap_or(0) + } } #[derive(Debug, Clone, Serialize, Deserialize, Default, Resource)] From b869e4ca5a1adac8699e4a9012ed8e15f1cf99fa Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Sat, 23 Nov 2024 16:03:06 -0500 Subject: [PATCH 10/18] Adding target camera to top-level entities --- cosmos_client/src/inventory/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cosmos_client/src/inventory/mod.rs b/cosmos_client/src/inventory/mod.rs index a24131649..649aa791f 100644 --- a/cosmos_client/src/inventory/mod.rs +++ b/cosmos_client/src/inventory/mod.rs @@ -976,6 +976,8 @@ enum InventorySet { fn make_render_middle_camera( q_mid_cam: Query>, mut q_target_cam: Query<&mut TargetCamera, (Changed, With)>, + q_needs_target_cam: Query, With)>, + mut commands: Commands, ) { for mut target_camera in q_target_cam.iter_mut() { let middle_camera_entity = q_mid_cam.single(); @@ -983,6 +985,11 @@ fn make_render_middle_camera( target_camera.0 = middle_camera_entity; } } + + for ent in q_needs_target_cam.iter() { + let middle_camera_entity = q_mid_cam.single(); + commands.entity(ent).insert(TargetCamera(middle_camera_entity)); + } } pub(super) fn register(app: &mut App) { From fee3af8b548fab898dfbf9548adae0fdf9dbdeee Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Sat, 23 Nov 2024 22:27:56 -0500 Subject: [PATCH 11/18] Basic crafting working --- .../crafting/blocks/basic_fabricator/ui.rs | 29 ++++- .../src/crafting/blocks/basic_fabricator.rs | 1 + .../src/crafting/recipes/basic_fabricator.rs | 11 +- cosmos_core/src/crafting/recipes/mod.rs | 2 +- cosmos_core/src/inventory/itemstack.rs | 4 + cosmos_core/src/inventory/mod.rs | 18 +++ .../src/crafting/blocks/basic_fabricator.rs | 106 +++++++++++++++++- 7 files changed, 159 insertions(+), 12 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index 6c79f5939..308846be7 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -11,14 +11,24 @@ use bevy::{ ui::{AlignItems, FlexDirection, JustifyContent, Style, TargetCamera, UiRect, Val}, }; use cosmos_core::{ - crafting::recipes::{ - basic_fabricator::{BasicFabricatorRecipe, BasicFabricatorRecipes}, - RecipeItem, + crafting::{ + blocks::basic_fabricator::CraftBasicFabricatorRecipeEvent, + recipes::{ + basic_fabricator::{BasicFabricatorRecipe, BasicFabricatorRecipes}, + RecipeItem, + }, }, ecs::NeedsDespawned, inventory::Inventory, item::Item, - netty::{client::LocalPlayer, system_sets::NetworkingSystemsSet}, + netty::{ + client::LocalPlayer, + sync::{ + events::client_event::NettyEventWriter, + mapping::{Mappable, NetworkMapping}, + }, + system_sets::NetworkingSystemsSet, + }, prelude::Structure, registry::{identifiable::Identifiable, Registry}, state::GameState, @@ -424,6 +434,8 @@ fn listen_create( q_open_fab_menu: Query<&OpenBasicFabricatorMenu>, q_selected_recipe: Query<&Recipe, With>, mut evr_create: EventReader, + mut nevw_craft_event: NettyEventWriter, + network_mapping: Res, ) { for _ in evr_create.read() { let Ok(fab_menu) = q_open_fab_menu.get_single() else { @@ -448,6 +460,15 @@ fn listen_create( } println!("Create {max_can_create}!"); + + if let Ok(block) = fab_menu.0.map_to_server(&network_mapping) { + println!("Sending craft event!"); + nevw_craft_event.send(CraftBasicFabricatorRecipeEvent { + block, + recipe: recipe.0.clone(), + quantity: max_can_create, + }); + } } } diff --git a/cosmos_core/src/crafting/blocks/basic_fabricator.rs b/cosmos_core/src/crafting/blocks/basic_fabricator.rs index 5a8d5d025..c6299c31c 100644 --- a/cosmos_core/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_core/src/crafting/blocks/basic_fabricator.rs @@ -26,6 +26,7 @@ impl NettyEvent for OpenBasicFabricatorEvent { pub struct CraftBasicFabricatorRecipeEvent { pub block: StructureBlock, pub recipe: BasicFabricatorRecipe, + pub quantity: u32, } impl IdentifiableEvent for CraftBasicFabricatorRecipeEvent { diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs index f1785a679..4724fdd61 100644 --- a/cosmos_core/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -13,13 +13,13 @@ use crate::{ use super::RecipeItem; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct FabricatorItemInput { pub quantity: u16, pub item: RecipeItem, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct FabricatorItemOutput { pub quantity: u16, pub item: u16, @@ -37,7 +37,7 @@ impl FabricatorItemOutput { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct BasicFabricatorRecipe { pub inputs: Vec, pub output: FabricatorItemOutput, @@ -68,6 +68,7 @@ impl BasicFabricatorRecipe { }) .min() .unwrap_or(0) + * self.output.quantity as u32 } } @@ -75,6 +76,10 @@ impl BasicFabricatorRecipe { pub struct BasicFabricatorRecipes(Vec); impl BasicFabricatorRecipes { + pub fn contains(&self, recipe: &BasicFabricatorRecipe) -> bool { + self.iter().any(|x| x == recipe) + } + pub fn add_recipe(&mut self, recipe: BasicFabricatorRecipe) { self.0.push(recipe); } diff --git a/cosmos_core/src/crafting/recipes/mod.rs b/cosmos_core/src/crafting/recipes/mod.rs index 2d9d72c87..990621698 100644 --- a/cosmos_core/src/crafting/recipes/mod.rs +++ b/cosmos_core/src/crafting/recipes/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; pub mod basic_fabricator; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum RecipeItem { Item(u16), Category(u16), diff --git a/cosmos_core/src/inventory/itemstack.rs b/cosmos_core/src/inventory/itemstack.rs index 6c2b61a05..7c1e8d1a0 100644 --- a/cosmos_core/src/inventory/itemstack.rs +++ b/cosmos_core/src/inventory/itemstack.rs @@ -393,6 +393,10 @@ impl ItemStack { } } + pub fn max_quantity_can_be_inserted(&self) -> u16 { + self.max_stack_size() - self.quantity + } + #[inline] /// Returns true if the ItemStack is at or above the max stack size. pub fn is_full(&self) -> bool { diff --git a/cosmos_core/src/inventory/mod.rs b/cosmos_core/src/inventory/mod.rs index 3e1b1f10f..d057a61ce 100644 --- a/cosmos_core/src/inventory/mod.rs +++ b/cosmos_core/src/inventory/mod.rs @@ -395,6 +395,24 @@ impl Inventory { (qty, new_slot) } + /// Returns the maximum amount of this item that could be inserted into this inventory. + pub fn max_quantity_can_be_inserted(&mut self, item: &Item) -> u32 { + self.items + .iter() + .map(|x| { + if let Some(x) = x { + if x.item_id() == item.id() && x.data_entity().is_none() { + x.max_quantity_can_be_inserted() as u32 + } else { + 0 + } + } else { + item.max_stack_size() as u32 + } + }) + .sum() + } + /// Returns the overflow that could not fit in any slot. The second item in the tuple will be Some /// if some or all of the ItemStack got its own slot. If it did, then this will represent the /// new slot in use. diff --git a/cosmos_server/src/crafting/blocks/basic_fabricator.rs b/cosmos_server/src/crafting/blocks/basic_fabricator.rs index 21c95add3..0de89dc1a 100644 --- a/cosmos_server/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_server/src/crafting/blocks/basic_fabricator.rs @@ -1,6 +1,9 @@ +use std::{cell::RefCell, rc::Rc}; + use bevy::{ app::Update, - prelude::{in_state, App, EventReader, IntoSystemConfigs, Query, Res}, + log::{error, warn}, + prelude::{in_state, App, Commands, EventReader, IntoSystemConfigs, Query, Res, With, Without}, }; use cosmos_core::{ @@ -8,10 +11,20 @@ use cosmos_core::{ block_events::{BlockEventsSet, BlockInteractEvent}, Block, }, - crafting::blocks::basic_fabricator::OpenBasicFabricatorEvent, + crafting::{ + blocks::basic_fabricator::{CraftBasicFabricatorRecipeEvent, OpenBasicFabricatorEvent}, + recipes::{basic_fabricator::BasicFabricatorRecipes, RecipeItem}, + }, entities::player::Player, + events::block_events::BlockDataSystemParams, + inventory::{itemstack::ItemShouldHaveData, Inventory}, + item::Item, netty::{ - sync::events::{netty_event::SyncedEventImpl, server_event::NettyEventWriter}, + server::ServerLobby, + sync::events::{ + netty_event::SyncedEventImpl, + server_event::{NettyEventReceived, NettyEventWriter}, + }, system_sets::NetworkingSystemsSet, }, prelude::Structure, @@ -44,10 +57,95 @@ fn monitor_basic_fabricator_interactions( } } +fn monitor_craft_event( + mut nevr_craft_event: EventReader>, + q_structure: Query<&Structure>, + // Separate queries to please borrow checker + mut q_player_inventory: Query<&mut Inventory, With>, + mut q_not_player_inventory: Query<&mut Inventory, Without>, + lobby: Res, + blocks: Res>, + bd_params: BlockDataSystemParams, + recipes: Res, + mut commands: Commands, + needs_data: Res, + items: Res>, +) { + let bd_params = Rc::new(RefCell::new(bd_params)); + for ev in nevr_craft_event.read() { + let Some(player_ent) = lobby.player_from_id(ev.client_id) else { + warn!("Bad player - cid: {}", ev.client_id); + continue; + }; + + if !recipes.contains(&ev.recipe) { + warn!("Invalid recipe from client {:?}", player_ent); + continue; + } + + let Ok(mut player_inv) = q_player_inventory.get_mut(player_ent) else { + error!("Player {player_ent:?} missing inventory component"); + continue; + }; + + let Ok(structure) = q_structure.get(ev.block.structure()) else { + warn!("Invalid structure entity - {:?}.", ev.block); + continue; + }; + + if structure.block_at(ev.block.coords(), &blocks).unlocalized_name() != "cosmos:basic_fabricator" { + warn!("Block here is not fabricator."); + continue; + } + + let Some(mut fab_inv) = structure.query_block_data_mut(ev.block.coords(), &mut q_not_player_inventory, bd_params.clone()) else { + error!("Fabricator @ {:?} missing inventory block data!", ev.block); + continue; + }; + + let max_qty = ev.recipe.max_can_create(fab_inv.iter().flatten()); + if ev.quantity > max_qty { + warn!("Invalid quantity requested."); + continue; + } + + let item = items.from_numeric_id(ev.recipe.output.item); + + let max_can_be_inserted = player_inv.max_quantity_can_be_inserted(item); + let leftover = if max_can_be_inserted < ev.quantity { + ev.quantity - max_can_be_inserted + } else { + 0 + }; + + let qty_crafted = ev.quantity - leftover as u32; + // Enures always a whole amount is crafted + let qty_crafted = (qty_crafted / ev.recipe.output.quantity as u32) * ev.recipe.output.quantity as u32; + let input_multiplier = qty_crafted / ev.recipe.output.quantity as u32; + + for input in ev.recipe.inputs.iter() { + let item = match input.item { + RecipeItem::Item(item_id) => item_id, + RecipeItem::Category(_) => todo!(), + }; + let item = items.from_numeric_id(item); + let (leftover, _) = fab_inv.take_and_remove_item(&item, input.quantity as usize * input_multiplier as usize, &mut commands); + assert_eq!(leftover, 0, "Invalid crafting occurred! Input Leftover ({leftover}) != 0"); + } + + let (leftover, _) = player_inv.insert_item(item, qty_crafted as u16, &mut commands, &needs_data); + assert_eq!( + leftover, 0, + "Invalid crafting occured! Unable to insert all products! ({} leftover)", + leftover + ); + } +} + pub(super) fn register(app: &mut App) { app.add_systems( Update, - monitor_basic_fabricator_interactions + (monitor_basic_fabricator_interactions, monitor_craft_event) .in_set(NetworkingSystemsSet::Between) .in_set(BlockEventsSet::ProcessEvents) .run_if(in_state(GameState::Playing)), From 73c3e570c1b58098aeedf627be253f55f15c501f Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Sat, 23 Nov 2024 22:32:50 -0500 Subject: [PATCH 12/18] Adding laser cannon recipe --- .../src/crafting/recipes/basic_fabricator.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cosmos_server/src/crafting/recipes/basic_fabricator.rs b/cosmos_server/src/crafting/recipes/basic_fabricator.rs index 4d4b32261..67f69f619 100644 --- a/cosmos_server/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_server/src/crafting/recipes/basic_fabricator.rs @@ -74,6 +74,26 @@ fn load_recipes(items: Res>, mut commands: Commands) { warn!("Missing grey ship hull!"); } + if let Some(laser_cannon) = items.from_id("cosmos:laser_cannon") { + if let Some(iron_bar) = items.from_id("cosmos:iron_bar") { + if let Some(crystal) = items.from_id("cosmos:test_crystal") { + recipes.add_recipe(BasicFabricatorRecipe::new( + FabricatorItemOutput::new(laser_cannon, 1), + vec![ + FabricatorItemInput::new(RecipeItem::Item(crystal.id()), 5), + FabricatorItemInput::new(RecipeItem::Item(iron_bar.id()), 1), + ], + )); + } else { + warn!("Missing crystal!"); + } + } else { + warn!("Missing iron bar!"); + } + } else { + warn!("Missing grey ship hull!"); + } + commands.insert_resource(recipes); } From ca09d506f32f415937e53bee7b91a0aec458250f Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Wed, 27 Nov 2024 12:01:32 -0500 Subject: [PATCH 13/18] Highlighting selected recipe --- .../crafting/blocks/basic_fabricator/ui.rs | 148 +++++++----------- cosmos_client/src/input/inputs.rs | 5 + 2 files changed, 59 insertions(+), 94 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index 308846be7..6bc46e643 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -2,13 +2,13 @@ use bevy::{ app::Update, color::{palettes::css, Color, Srgba}, core::Name, - log::error, + log::{error, info}, prelude::{ in_state, resource_exists, Added, App, BuildChildren, Changed, Children, Commands, Component, DespawnRecursiveExt, Entity, Event, EventReader, IntoSystemConfigs, NodeBundle, Parent, Query, Res, TextBundle, With, }, text::{Text, TextStyle}, - ui::{AlignItems, FlexDirection, JustifyContent, Style, TargetCamera, UiRect, Val}, + ui::{AlignItems, BackgroundColor, FlexDirection, Interaction, JustifyContent, Style, TargetCamera, UiRect, Val}, }; use cosmos_core::{ crafting::{ @@ -35,6 +35,7 @@ use cosmos_core::{ }; use crate::{ + input::inputs::{CosmosInputHandler, CosmosInputs, InputChecker, InputHandler}, inventory::{CustomInventoryRender, InventoryNeedsDisplayed, InventorySide, TextNeedsTopRoot}, lang::Lang, rendering::MainCamera, @@ -71,6 +72,9 @@ impl ButtonEvent for CreateClickedEvent { #[derive(Component, Debug, Clone)] struct Recipe(BasicFabricatorRecipe); +#[derive(Component, Debug)] +struct SelectedRecipe; + fn populate_menu( mut commands: Commands, q_added_menu: Query<(Entity, &OpenBasicFabricatorMenu), Added>, @@ -234,22 +238,39 @@ fn populate_menu( ) .with_children(|p| { for item in recipe.inputs.iter() { + let item_id = match item.item { + RecipeItem::Item(i) => i, + RecipeItem::Category(_) => todo!("Categories"), + }; + p.spawn(( NodeBundle { style: Style { width: Val::Px(64.0), height: Val::Px(64.0), + flex_direction: FlexDirection::Column, + align_items: AlignItems::End, + justify_content: JustifyContent::End, ..Default::default() }, ..Default::default() }, - RenderItem { - item_id: match item.item { - RecipeItem::Item(i) => i, - RecipeItem::Category(_) => todo!("Categories"), + RenderItem { item_id }, + )) + .with_children(|p| { + p.spawn(( + Name::new("Item recipe qty"), + TextNeedsTopRoot, + InventoryCount { + item_id, + recipe_amt: item.quantity, }, - }, - )); + TextBundle { + text: Text::from_section(format!("{}", item.quantity), text_style.clone()), + ..Default::default() + }, + )); + }); } }); }); @@ -263,27 +284,13 @@ fn populate_menu( style: Style { flex_direction: FlexDirection::Column, width: Val::Percent(100.0), - height: Val::Px(item_slot_size * 3.0), + height: Val::Px(item_slot_size * 2.0), ..Default::default() }, ..Default::default() }, )) .with_children(|p| { - p.spawn(( - SelectedRecipeDisplay, - Name::new("Selected Recipe Display"), - NodeBundle { - style: Style { - align_items: AlignItems::Center, - width: Val::Px(item_slot_size * 6.0), - height: Val::Px(item_slot_size), - ..Default::default() - }, - ..Default::default() - }, - )); - p.spawn(( Name::new("Rendered Inventory"), NodeBundle { @@ -340,9 +347,6 @@ fn populate_menu( } } -#[derive(Debug, Component)] -struct SelectedRecipeDisplay; - #[derive(Component, Debug)] struct InventoryCount { recipe_amt: u16, @@ -351,78 +355,28 @@ struct InventoryCount { fn on_select_item( mut commands: Commands, - q_selected_recipe_display: Query<(Entity, Option<&Children>), With>, mut evr_select_item: EventReader, q_recipe: Query<&Recipe>, - font: Res, + q_selected_recipe: Query>, + mut q_bg_col: Query<&mut BackgroundColor>, ) { for ev in evr_select_item.read() { let Ok(recipe) = q_recipe.get(ev.0) else { continue; }; - let Ok((selected_recipe_display, children)) = q_selected_recipe_display.get_single() else { - return; - }; - - if let Some(children) = children { - for &child in children.iter() { - commands.entity(child).despawn_recursive(); - } + if let Ok(selected_recipe) = q_selected_recipe.get_single() { + commands.entity(selected_recipe).remove::(); + q_bg_col.get_mut(selected_recipe).expect("Must be ui node").0 = Color::NONE; } - - let text_style = TextStyle { - font: font.0.clone_weak(), - font_size: 24.0, - color: Color::WHITE, - }; - // let text_style_not_enough = TextStyle { - // font: font.0.clone_weak(), - // font_size: 24.0, - // color: css::RED.into(), - // }; - // - // let Ok(inventory) = q_inventory.get_single() else { - // continue; - // }; - - commands.entity(selected_recipe_display).insert(recipe.clone()).with_children(|p| { - for item in recipe.0.inputs.iter() { - let item_id = match item.item { - RecipeItem::Item(i) => i, - RecipeItem::Category(_) => todo!("Categories"), - }; - - p.spawn(( - NodeBundle { - style: Style { - width: Val::Px(64.0), - height: Val::Px(64.0), - flex_direction: FlexDirection::Column, - align_items: AlignItems::End, - justify_content: JustifyContent::End, - ..Default::default() - }, - ..Default::default() - }, - RenderItem { item_id }, - )) - .with_children(|p| { - p.spawn(( - Name::new("Item recipe qty"), - TextNeedsTopRoot, - InventoryCount { - item_id, - recipe_amt: item.quantity, - }, - TextBundle { - text: Text::from_section(format!("{}", item.quantity), text_style.clone()), - ..Default::default() - }, - )); - }); - } - }); + commands.entity(ev.0).insert(SelectedRecipe); + q_bg_col.get_mut(ev.0).expect("Must be ui node").0 = Srgba { + red: 1.0, + green: 1.0, + blue: 1.0, + alpha: 0.1, + } + .into(); println!("{recipe:?}"); } @@ -432,10 +386,11 @@ fn listen_create( q_structure: Query<&Structure>, q_inventory: Query<&Inventory>, q_open_fab_menu: Query<&OpenBasicFabricatorMenu>, - q_selected_recipe: Query<&Recipe, With>, + q_selected_recipe: Query<&Recipe, With>, mut evr_create: EventReader, mut nevw_craft_event: NettyEventWriter, network_mapping: Res, + input_handler: InputChecker, ) { for _ in evr_create.read() { let Ok(fab_menu) = q_open_fab_menu.get_single() else { @@ -459,14 +414,19 @@ fn listen_create( continue; } - println!("Create {max_can_create}!"); - if let Ok(block) = fab_menu.0.map_to_server(&network_mapping) { - println!("Sending craft event!"); + let quantity = if input_handler.check_pressed(CosmosInputs::BulkCraft) { + max_can_create + } else { + 1 + }; + + info!("Sending craft {quantity} event!"); + nevw_craft_event.send(CraftBasicFabricatorRecipeEvent { block, recipe: recipe.0.clone(), - quantity: max_can_create, + quantity, }); } } diff --git a/cosmos_client/src/input/inputs.rs b/cosmos_client/src/input/inputs.rs index 162deaa96..3a6ca0815 100644 --- a/cosmos_client/src/input/inputs.rs +++ b/cosmos_client/src/input/inputs.rs @@ -127,6 +127,9 @@ pub enum CosmosInputs { ToggleChat, /// Sends the chat message the user has typed - does not close the chat window SendChatMessage, + + /// Instead of crafting 1, the maximum amount will be crafted + BulkCraft, } fn init_input(mut input_handler: ResMut) { @@ -196,6 +199,8 @@ fn init_input(mut input_handler: ResMut) { input_handler.set_keycode(CosmosInputs::ToggleChat, KeyCode::Enter); input_handler.set_keycode(CosmosInputs::SendChatMessage, KeyCode::Enter); + + input_handler.set_keycode(CosmosInputs::BulkCraft, KeyCode::ShiftLeft); } #[derive(Resource, Default, Debug)] From 94b08c1c0c12202f77987744fd0298713b9f7474 Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Fri, 29 Nov 2024 02:58:36 -0500 Subject: [PATCH 14/18] Prepping for button coloring --- cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index 6bc46e643..8462fc034 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -75,6 +75,9 @@ struct Recipe(BasicFabricatorRecipe); #[derive(Component, Debug)] struct SelectedRecipe; +#[derive(Component)] +struct FabricateButton; + fn populate_menu( mut commands: Commands, q_added_menu: Query<(Entity, &OpenBasicFabricatorMenu), Added>, @@ -323,6 +326,7 @@ fn populate_menu( p.spawn(( Name::new("Fabricate Button"), + FabricateButton, ButtonBundle { button: Button:: { text: Some(("Fabricate".into(), text_style)), @@ -432,6 +436,8 @@ fn listen_create( } } +fn color_fabricate_button(q_fab_button: Query<&mut ButtonStyles, With>) {} + pub(super) fn register(app: &mut App) { register_button::(app); register_button::(app); From 20cf16fa53bf5b6b178adec6c8066ed9761459b4 Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Tue, 3 Dec 2024 20:23:36 -0500 Subject: [PATCH 15/18] Adding coloring to button when recipe is craftable --- .../crafting/blocks/basic_fabricator/ui.rs | 48 ++++++++++++++++++- .../src/crafting/recipes/basic_fabricator.rs | 8 ++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index 8462fc034..d0c81acd9 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -436,7 +436,46 @@ fn listen_create( } } -fn color_fabricate_button(q_fab_button: Query<&mut ButtonStyles, With>) {} +fn color_fabricate_button( + q_open_fab_menu: Query<&OpenBasicFabricatorMenu>, + q_structure: Query<&Structure>, + q_selected_recipe: Query<&Recipe, With>, + q_inventory: Query<&Inventory>, + mut q_fab_button: Query<&mut Button, With>, +) { + let Ok(mut btn) = q_fab_button.get_single_mut() else { + return; + }; + + let Ok(fab_menu) = q_open_fab_menu.get_single() else { + return; + }; + + let Ok(structure) = q_structure.get(fab_menu.0.structure()) else { + return; + }; + + let Some(inventory) = structure.query_block_data(fab_menu.0.coords(), &q_inventory) else { + return; + }; + + let Ok(recipe) = q_selected_recipe.get_single() else { + return; + }; + + if recipe.0.max_can_create(inventory.iter().flatten()) == 0 { + btn.button_styles = Some(ButtonStyles::default()); + } else { + btn.button_styles = Some(ButtonStyles { + background_color: css::GREEN.into(), + foreground_color: css::WHITE.into(), + hover_background_color: css::GREEN.into(), + hover_foreground_color: css::WHITE.into(), + press_background_color: css::GREEN.into(), + press_foreground_color: css::WHITE.into(), + }); + } +} pub(super) fn register(app: &mut App) { register_button::(app); @@ -444,7 +483,12 @@ pub(super) fn register(app: &mut App) { app.add_systems( Update, - (populate_menu, (on_select_item, listen_create).chain().in_set(UiSystemSet::DoUi)) + ( + populate_menu, + (on_select_item, listen_create, color_fabricate_button) + .chain() + .in_set(UiSystemSet::DoUi), + ) .chain() .in_set(NetworkingSystemsSet::Between) .in_set(FabricatorMenuSet::PopulateMenu) diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs index 4724fdd61..e427b5cf7 100644 --- a/cosmos_core/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -54,6 +54,14 @@ impl BasicFabricatorRecipe { *unique_item_counts.entry(item.item_id()).or_insert(0) += item.quantity() as u32; } + for input in &self.inputs { + let id = match input.item { + RecipeItem::Item(id) => id, + RecipeItem::Category(_) => todo!(), + }; + unique_item_counts.entry(id).or_insert(0); + } + unique_item_counts .into_iter() .flat_map(|(item_id, quantity)| { From aebc10acf977fbfd35cd0f8fddba8dc2defb6e9a Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Wed, 4 Dec 2024 14:43:10 -0500 Subject: [PATCH 16/18] cargo clippy --fix --- .../src/crafting/blocks/basic_fabricator/ui.rs | 17 ++++++++--------- .../src/crafting/recipes/basic_fabricator.rs | 6 +++--- cosmos_core/src/netty/mod.rs | 2 +- .../src/crafting/blocks/basic_fabricator.rs | 4 ++-- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index d0c81acd9..ab3db573c 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -4,11 +4,11 @@ use bevy::{ core::Name, log::{error, info}, prelude::{ - in_state, resource_exists, Added, App, BuildChildren, Changed, Children, Commands, Component, DespawnRecursiveExt, Entity, Event, - EventReader, IntoSystemConfigs, NodeBundle, Parent, Query, Res, TextBundle, With, + in_state, resource_exists, Added, App, BuildChildren, Commands, Component, Entity, Event, + EventReader, IntoSystemConfigs, NodeBundle, Query, Res, TextBundle, With, }, text::{Text, TextStyle}, - ui::{AlignItems, BackgroundColor, FlexDirection, Interaction, JustifyContent, Style, TargetCamera, UiRect, Val}, + ui::{AlignItems, BackgroundColor, FlexDirection, JustifyContent, Style, TargetCamera, UiRect, Val}, }; use cosmos_core::{ crafting::{ @@ -18,7 +18,6 @@ use cosmos_core::{ RecipeItem, }, }, - ecs::NeedsDespawned, inventory::Inventory, item::Item, netty::{ @@ -35,7 +34,7 @@ use cosmos_core::{ }; use crate::{ - input::inputs::{CosmosInputHandler, CosmosInputs, InputChecker, InputHandler}, + input::inputs::{CosmosInputs, InputChecker, InputHandler}, inventory::{CustomInventoryRender, InventoryNeedsDisplayed, InventorySide, TextNeedsTopRoot}, lang::Lang, rendering::MainCamera, @@ -159,7 +158,7 @@ fn populate_menu( ecmds.with_children(|p| { p.spawn( - (ScrollBundle { + ScrollBundle { node_bundle: NodeBundle { style: Style { flex_grow: 1.0, @@ -168,7 +167,7 @@ fn populate_menu( ..Default::default() }, ..Default::default() - }), + }, ) .with_children(|p| { for recipe in crafting_recipes.iter() { @@ -230,14 +229,14 @@ fn populate_menu( }); p.spawn( - (NodeBundle { + NodeBundle { style: Style { flex_direction: FlexDirection::Row, width: Val::Percent(100.0), ..Default::default() }, ..Default::default() - }), + }, ) .with_children(|p| { for item in recipe.inputs.iter() { diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs index e427b5cf7..b8fdba62a 100644 --- a/cosmos_core/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -1,6 +1,6 @@ use bevy::{ - prelude::{App, Commands, Event, Res, Resource}, - utils::{HashMap, HashSet}, + prelude::{App, Event, Resource}, + utils::HashMap, }; use serde::{Deserialize, Serialize}; @@ -8,7 +8,7 @@ use crate::{ inventory::itemstack::ItemStack, item::Item, netty::sync::events::netty_event::{IdentifiableEvent, NettyEvent, SyncedEventImpl}, - registry::{identifiable::Identifiable, Registry}, + registry::identifiable::Identifiable, }; use super::RecipeItem; diff --git a/cosmos_core/src/netty/mod.rs b/cosmos_core/src/netty/mod.rs index 486a2343c..b538cde2a 100644 --- a/cosmos_core/src/netty/mod.rs +++ b/cosmos_core/src/netty/mod.rs @@ -144,7 +144,7 @@ impl NettyChannelClient { }, ChannelConfig { channel_id: Self::Registry.into(), - max_memory_usage_bytes: 1 * MB, + max_memory_usage_bytes: MB, send_type: SendType::ReliableOrdered { resend_time: Duration::from_millis(200), }, diff --git a/cosmos_server/src/crafting/blocks/basic_fabricator.rs b/cosmos_server/src/crafting/blocks/basic_fabricator.rs index 0de89dc1a..ec363a709 100644 --- a/cosmos_server/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_server/src/crafting/blocks/basic_fabricator.rs @@ -118,7 +118,7 @@ fn monitor_craft_event( 0 }; - let qty_crafted = ev.quantity - leftover as u32; + let qty_crafted = ev.quantity - leftover; // Enures always a whole amount is crafted let qty_crafted = (qty_crafted / ev.recipe.output.quantity as u32) * ev.recipe.output.quantity as u32; let input_multiplier = qty_crafted / ev.recipe.output.quantity as u32; @@ -129,7 +129,7 @@ fn monitor_craft_event( RecipeItem::Category(_) => todo!(), }; let item = items.from_numeric_id(item); - let (leftover, _) = fab_inv.take_and_remove_item(&item, input.quantity as usize * input_multiplier as usize, &mut commands); + let (leftover, _) = fab_inv.take_and_remove_item(item, input.quantity as usize * input_multiplier as usize, &mut commands); assert_eq!(leftover, 0, "Invalid crafting occurred! Input Leftover ({leftover}) != 0"); } From 65e6e065dcd683476cd0e5be4cac8e8e849c44df Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Wed, 4 Dec 2024 14:55:57 -0500 Subject: [PATCH 17/18] Docs --- .../crafting/blocks/basic_fabricator/ui.rs | 43 ++++++------------- cosmos_client/src/crafting/mod.rs | 2 + cosmos_client/src/inventory/mod.rs | 11 +++++ .../src/crafting/blocks/basic_fabricator.rs | 9 ++++ cosmos_core/src/crafting/blocks/mod.rs | 2 + cosmos_core/src/crafting/mod.rs | 2 + .../src/crafting/recipes/basic_fabricator.rs | 30 ++++++++++++- cosmos_core/src/crafting/recipes/mod.rs | 6 ++- cosmos_core/src/inventory/itemstack.rs | 2 + .../src/crafting/blocks/basic_fabricator.rs | 1 - 10 files changed, 75 insertions(+), 33 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index ab3db573c..c50607706 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -4,8 +4,8 @@ use bevy::{ core::Name, log::{error, info}, prelude::{ - in_state, resource_exists, Added, App, BuildChildren, Commands, Component, Entity, Event, - EventReader, IntoSystemConfigs, NodeBundle, Query, Res, TextBundle, With, + in_state, resource_exists, Added, App, BuildChildren, Commands, Component, Entity, Event, EventReader, IntoSystemConfigs, + NodeBundle, Query, Res, TextBundle, With, }, text::{Text, TextStyle}, ui::{AlignItems, BackgroundColor, FlexDirection, JustifyContent, Style, TargetCamera, UiRect, Val}, @@ -157,18 +157,16 @@ fn populate_menu( let mut slot_ents = vec![]; ecmds.with_children(|p| { - p.spawn( - ScrollBundle { - node_bundle: NodeBundle { - style: Style { - flex_grow: 1.0, - ..Default::default() - }, + p.spawn(ScrollBundle { + node_bundle: NodeBundle { + style: Style { + flex_grow: 1.0, ..Default::default() }, ..Default::default() }, - ) + ..Default::default() + }) .with_children(|p| { for recipe in crafting_recipes.iter() { p.spawn(( @@ -228,21 +226,18 @@ fn populate_menu( ..Default::default() }); - p.spawn( - NodeBundle { - style: Style { - flex_direction: FlexDirection::Row, - width: Val::Percent(100.0), - ..Default::default() - }, + p.spawn(NodeBundle { + style: Style { + flex_direction: FlexDirection::Row, + width: Val::Percent(100.0), ..Default::default() }, - ) + ..Default::default() + }) .with_children(|p| { for item in recipe.inputs.iter() { let item_id = match item.item { RecipeItem::Item(i) => i, - RecipeItem::Category(_) => todo!("Categories"), }; p.spawn(( @@ -263,10 +258,6 @@ fn populate_menu( p.spawn(( Name::new("Item recipe qty"), TextNeedsTopRoot, - InventoryCount { - item_id, - recipe_amt: item.quantity, - }, TextBundle { text: Text::from_section(format!("{}", item.quantity), text_style.clone()), ..Default::default() @@ -350,12 +341,6 @@ fn populate_menu( } } -#[derive(Component, Debug)] -struct InventoryCount { - recipe_amt: u16, - item_id: u16, -} - fn on_select_item( mut commands: Commands, mut evr_select_item: EventReader, diff --git a/cosmos_client/src/crafting/mod.rs b/cosmos_client/src/crafting/mod.rs index 7a4365310..df1952517 100644 --- a/cosmos_client/src/crafting/mod.rs +++ b/cosmos_client/src/crafting/mod.rs @@ -1,3 +1,5 @@ +//! Client crafting logic + use bevy::prelude::App; mod blocks; diff --git a/cosmos_client/src/inventory/mod.rs b/cosmos_client/src/inventory/mod.rs index 649aa791f..f1f96d92e 100644 --- a/cosmos_client/src/inventory/mod.rs +++ b/cosmos_client/src/inventory/mod.rs @@ -101,11 +101,18 @@ fn close_button_system( } #[derive(Debug, Clone)] +/// Instructions on how to render this inventory. pub struct CustomInventoryRender { slots: Vec<(usize, Entity)>, } impl CustomInventoryRender { + /// The slots should be a Vec<(slot_index, slot_entity)>. + /// + /// Each `slot_index` should be based off the slots in the inventory you wish to render. + /// + /// Each `slot_entity` should be a UI node that will be filled in to be an interactable item + /// slot. pub fn new(slots: Vec<(usize, Entity)>) -> Self { Self { slots } } @@ -114,7 +121,11 @@ impl CustomInventoryRender { #[derive(Component, Debug, Clone)] /// Add this to an inventory you want displayed, and remove this component when you want to hide the inventory pub enum InventoryNeedsDisplayed { + /// A standard inventory rendering with no custom rendering. This Will be rendered like a chest + /// or player's inventory. Normal(InventorySide), + /// You dictate where and which inventory slots should be rendered. See + /// [`CustomInventoryRender::new`] Custom(CustomInventoryRender), } diff --git a/cosmos_core/src/crafting/blocks/basic_fabricator.rs b/cosmos_core/src/crafting/blocks/basic_fabricator.rs index c6299c31c..387759351 100644 --- a/cosmos_core/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_core/src/crafting/blocks/basic_fabricator.rs @@ -1,3 +1,5 @@ +//! Contains logic for the basic fabricator block + use bevy::prelude::{App, Event}; use serde::{Deserialize, Serialize}; @@ -8,6 +10,7 @@ use crate::{ }; #[derive(Event, Debug, Clone, Copy, Serialize, Deserialize)] +/// Sent by the server to the client to instruct them to open a basic fabricator. pub struct OpenBasicFabricatorEvent(pub StructureBlock); impl IdentifiableEvent for OpenBasicFabricatorEvent { @@ -23,9 +26,15 @@ impl NettyEvent for OpenBasicFabricatorEvent { } #[derive(Event, Debug, Clone, Serialize, Deserialize)] +/// Sent by the client to the server to request crafting a specific recipe. pub struct CraftBasicFabricatorRecipeEvent { + /// The block that contains the fabricator the client is using pub block: StructureBlock, + /// The recipe to use. Note that this MUST match one of the recipes the server contains or it + /// will be ignored by the server. pub recipe: BasicFabricatorRecipe, + /// The quantity they wish to craft. If more is requested than can be crafted, the maximum + /// amount that can be fabricated will be created. pub quantity: u32, } diff --git a/cosmos_core/src/crafting/blocks/mod.rs b/cosmos_core/src/crafting/blocks/mod.rs index 060a4ee7a..cd652ffef 100644 --- a/cosmos_core/src/crafting/blocks/mod.rs +++ b/cosmos_core/src/crafting/blocks/mod.rs @@ -1,3 +1,5 @@ +//! Contains logic for blocks that are used for crafting + use bevy::prelude::App; pub mod basic_fabricator; diff --git a/cosmos_core/src/crafting/mod.rs b/cosmos_core/src/crafting/mod.rs index 891154353..a5ef99ab0 100644 --- a/cosmos_core/src/crafting/mod.rs +++ b/cosmos_core/src/crafting/mod.rs @@ -1,3 +1,5 @@ +//! Shared logic for all forms of crafting + use bevy::prelude::App; pub mod blocks; diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs index b8fdba62a..fb7ee3874 100644 --- a/cosmos_core/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -1,3 +1,5 @@ +//! Shared logic for Basic Fabricator recipes. + use bevy::{ prelude::{App, Event, Resource}, utils::HashMap, @@ -14,40 +16,55 @@ use crate::{ use super::RecipeItem; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +/// An item that can be used as an input for a basic fabricator recipe pub struct FabricatorItemInput { + /// The amount of this item required pub quantity: u16, + /// The type of item required pub item: RecipeItem, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +/// The output of the fabricator recipe pub struct FabricatorItemOutput { + /// The quantity output pub quantity: u16, + /// The item output pub item: u16, } impl FabricatorItemInput { + /// Creates a new fabricator item input pub fn new(item: RecipeItem, quantity: u16) -> Self { Self { item, quantity } } } impl FabricatorItemOutput { + /// Creates a new fabricator item output pub fn new(item: &Item, quantity: u16) -> Self { Self { item: item.id(), quantity } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +/// A recipe for the basic fabricator. pub struct BasicFabricatorRecipe { + /// All inputs for this recipe pub inputs: Vec, + /// The output for this recipe pub output: FabricatorItemOutput, } impl BasicFabricatorRecipe { + /// Creates a new recipe pub fn new(output: FabricatorItemOutput, inputs: Vec) -> Self { Self { output, inputs } } + /// Computes the maximum amount of items this recipe can prodce, with the given items. + /// + /// The `items` iterator can contain items unrelated to the recipe. pub fn max_can_create<'a>(&self, items: impl Iterator) -> u32 { let mut unique_item_counts = HashMap::new(); for item in items { @@ -57,7 +74,6 @@ impl BasicFabricatorRecipe { for input in &self.inputs { let id = match input.item { RecipeItem::Item(id) => id, - RecipeItem::Category(_) => todo!(), }; unique_item_counts.entry(id).or_insert(0); } @@ -67,7 +83,6 @@ impl BasicFabricatorRecipe { .flat_map(|(item_id, quantity)| { let Some(input) = self.inputs.iter().find(|x| match x.item { RecipeItem::Item(id) => id == item_id, - RecipeItem::Category(_) => todo!(), }) else { return None; }; @@ -81,23 +96,34 @@ impl BasicFabricatorRecipe { } #[derive(Debug, Clone, Serialize, Deserialize, Default, Resource)] +/// Contains all the Basic Fabricator recipes. +/// +/// Recipes should be registered with this to be considered active. pub struct BasicFabricatorRecipes(Vec); impl BasicFabricatorRecipes { + /// Returns true if this is a valid recipe contained in this registry pub fn contains(&self, recipe: &BasicFabricatorRecipe) -> bool { self.iter().any(|x| x == recipe) } + /// Adds a recipe to the registry. This will not add duplicates. pub fn add_recipe(&mut self, recipe: BasicFabricatorRecipe) { + if self.contains(&recipe) { + return; + } self.0.push(recipe); } + /// Iterates over every recipe pub fn iter(&self) -> impl Iterator { self.0.iter() } } #[derive(Event, Serialize, Deserialize, Debug)] +/// Used to sync all recipes to the connecting clients. Sent when a client joins after they have +/// loaded all the recipes. pub struct SyncBasicFabricatorRecipesEvent(pub BasicFabricatorRecipes); impl IdentifiableEvent for SyncBasicFabricatorRecipesEvent { diff --git a/cosmos_core/src/crafting/recipes/mod.rs b/cosmos_core/src/crafting/recipes/mod.rs index 990621698..cc6ba91ec 100644 --- a/cosmos_core/src/crafting/recipes/mod.rs +++ b/cosmos_core/src/crafting/recipes/mod.rs @@ -1,12 +1,16 @@ +//! Contains logic for the different types of recipes + use bevy::prelude::App; use serde::{Deserialize, Serialize}; pub mod basic_fabricator; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +/// An item that is used in a recipe pub enum RecipeItem { + /// A single item's numberic id Item(u16), - Category(u16), + // Category(u16), } pub(super) fn register(app: &mut App) { diff --git a/cosmos_core/src/inventory/itemstack.rs b/cosmos_core/src/inventory/itemstack.rs index 7c1e8d1a0..7b11060f1 100644 --- a/cosmos_core/src/inventory/itemstack.rs +++ b/cosmos_core/src/inventory/itemstack.rs @@ -393,6 +393,8 @@ impl ItemStack { } } + /// Returns the maximum amount of items that can be inserted into this itemstack without going + /// over the maximum stack size. pub fn max_quantity_can_be_inserted(&self) -> u16 { self.max_stack_size() - self.quantity } diff --git a/cosmos_server/src/crafting/blocks/basic_fabricator.rs b/cosmos_server/src/crafting/blocks/basic_fabricator.rs index ec363a709..8c0161ebc 100644 --- a/cosmos_server/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_server/src/crafting/blocks/basic_fabricator.rs @@ -126,7 +126,6 @@ fn monitor_craft_event( for input in ev.recipe.inputs.iter() { let item = match input.item { RecipeItem::Item(item_id) => item_id, - RecipeItem::Category(_) => todo!(), }; let item = items.from_numeric_id(item); let (leftover, _) = fab_inv.take_and_remove_item(item, input.quantity as usize * input_multiplier as usize, &mut commands); From f814ec85ce7f73105191348822d7b4e5ec6315f6 Mon Sep 17 00:00:00 2001 From: Anthony Tornetta Date: Wed, 4 Dec 2024 15:00:10 -0500 Subject: [PATCH 18/18] docs + clippy --- .../src/crafting/blocks/basic_fabricator/ui.rs | 5 +---- cosmos_core/src/crafting/recipes/basic_fabricator.rs | 10 +++------- cosmos_server/src/crafting/blocks/basic_fabricator.rs | 4 +--- cosmos_server/src/crafting/mod.rs | 2 ++ cosmos_server/src/netty/sync/registry.rs | 6 ++++++ 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs index c50607706..a0ec302e5 100644 --- a/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs +++ b/cosmos_client/src/crafting/blocks/basic_fabricator/ui.rs @@ -148,7 +148,6 @@ fn populate_menu( flex_direction: FlexDirection::Column, ..Default::default() }, - ..Default::default() }, ..Default::default() }, @@ -236,9 +235,7 @@ fn populate_menu( }) .with_children(|p| { for item in recipe.inputs.iter() { - let item_id = match item.item { - RecipeItem::Item(i) => i, - }; + let RecipeItem::Item(item_id) = item.item; p.spawn(( NodeBundle { diff --git a/cosmos_core/src/crafting/recipes/basic_fabricator.rs b/cosmos_core/src/crafting/recipes/basic_fabricator.rs index fb7ee3874..5fbe5b492 100644 --- a/cosmos_core/src/crafting/recipes/basic_fabricator.rs +++ b/cosmos_core/src/crafting/recipes/basic_fabricator.rs @@ -72,20 +72,16 @@ impl BasicFabricatorRecipe { } for input in &self.inputs { - let id = match input.item { - RecipeItem::Item(id) => id, - }; + let RecipeItem::Item(id) = input.item; unique_item_counts.entry(id).or_insert(0); } unique_item_counts .into_iter() .flat_map(|(item_id, quantity)| { - let Some(input) = self.inputs.iter().find(|x| match x.item { + let input = self.inputs.iter().find(|x| match x.item { RecipeItem::Item(id) => id == item_id, - }) else { - return None; - }; + })?; Some(quantity / input.quantity as u32) }) diff --git a/cosmos_server/src/crafting/blocks/basic_fabricator.rs b/cosmos_server/src/crafting/blocks/basic_fabricator.rs index 8c0161ebc..e6f4a3142 100644 --- a/cosmos_server/src/crafting/blocks/basic_fabricator.rs +++ b/cosmos_server/src/crafting/blocks/basic_fabricator.rs @@ -124,9 +124,7 @@ fn monitor_craft_event( let input_multiplier = qty_crafted / ev.recipe.output.quantity as u32; for input in ev.recipe.inputs.iter() { - let item = match input.item { - RecipeItem::Item(item_id) => item_id, - }; + let RecipeItem::Item(item) = input.item; let item = items.from_numeric_id(item); let (leftover, _) = fab_inv.take_and_remove_item(item, input.quantity as usize * input_multiplier as usize, &mut commands); assert_eq!(leftover, 0, "Invalid crafting occurred! Input Leftover ({leftover}) != 0"); diff --git a/cosmos_server/src/crafting/mod.rs b/cosmos_server/src/crafting/mod.rs index 7a4365310..cecfdffa0 100644 --- a/cosmos_server/src/crafting/mod.rs +++ b/cosmos_server/src/crafting/mod.rs @@ -1,3 +1,5 @@ +//! Server-related crafting logic + use bevy::prelude::App; mod blocks; diff --git a/cosmos_server/src/netty/sync/registry.rs b/cosmos_server/src/netty/sync/registry.rs index 649348e47..6ac2c2db7 100644 --- a/cosmos_server/src/netty/sync/registry.rs +++ b/cosmos_server/src/netty/sync/registry.rs @@ -1,3 +1,5 @@ +//! Server registry syncing + use bevy::{ app::Update, log::warn, @@ -10,6 +12,10 @@ use cosmos_core::{ use renet2::{ClientId, RenetServer}; #[derive(Debug, Event)] +/// This event is sent when the client has received every registry from the server. +/// +/// This will be sent in their initial connecting phase, and anything that relies on a registry +/// must be sent after this is received. pub struct ClientFinishedReceivingRegistriesEvent(pub ClientId); fn listen_for_done_syncing(