diff options
author | metamuffin <metamuffin@disroot.org> | 2025-10-06 23:03:32 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-10-06 23:03:40 +0200 |
commit | 176e6bc6c4c29bea3be2aceca99743b997c76c97 (patch) | |
tree | 1161e7a966843324756340da4b6452492902fa07 /server | |
parent | ea86b11b682500160f37b35ea8f06b081cd05036 (diff) | |
download | hurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar hurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar.bz2 hurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar.zst |
Move data code to own crate + general data refactor
Diffstat (limited to 'server')
22 files changed, 322 insertions, 329 deletions
diff --git a/server/Cargo.toml b/server/Cargo.toml index e14bb84d..f72cb7fa 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -13,7 +13,6 @@ tokio = { version = "1.47.1", features = ["full"] } serde_json = "1.0.145" tokio-tungstenite = "0.27.0" futures-util = "0.3.31" -serde_yml = "0.0.12" rand = "0.9.2" rand_distr = "0.5.1" shlex = "1.3.0" @@ -34,6 +33,7 @@ hurrycurry-locale = { path = "locale" } hurrycurry-protocol = { path = "protocol" } hurrycurry-client-lib = { path = "client-lib" } hurrycurry-bot = { path = "bot" } +hurrycurry-data = { path = "data" } [target.'cfg(windows)'.dependencies] windows-registry = "0.6" @@ -44,4 +44,4 @@ mdns = ["dep:mdns-sd", "dep:get_if_addrs"] register = ["dep:reqwest"] upnp = ["dep:igd", "dep:get_if_addrs"] -fast_recipes = [] +fast_recipes = ["hurrycurry-data/fast_recipes"] diff --git a/server/data/Cargo.toml b/server/data/Cargo.toml new file mode 100644 index 00000000..5ba266a8 --- /dev/null +++ b/server/data/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hurrycurry-data" +version = "0.1.0" +edition = "2024" + +[dependencies] +hurrycurry-protocol = { path = "../protocol" } +hurrycurry-locale = { path = "../locale" } +anyhow = "1.0.100" +serde_json = "1.0.145" +serde = { version = "1.0.225", features = ["derive"] } +shlex = "1.3.0" +clap = { version = "4.5.47", features = ["derive"] } +serde_yml = "0.0.12" + +[features] +fast_recipes = [] diff --git a/server/tools/src/diagram_layout.rs b/server/data/src/book/diagram_layout.rs index 0ea26a69..0ea26a69 100644 --- a/server/tools/src/diagram_layout.rs +++ b/server/data/src/book/diagram_layout.rs diff --git a/server/tools/src/book.rs b/server/data/src/book/mod.rs index bffbe836..b52779f3 100644 --- a/server/tools/src/book.rs +++ b/server/data/src/book/mod.rs @@ -16,14 +16,19 @@ */ -use crate::{diagram_layout::diagram_layout, recipe_diagram::recipe_diagram}; +pub mod diagram_layout; +pub mod recipe_diagram; + +use crate::{ + Serverdata, + book::{diagram_layout::diagram_layout, recipe_diagram::recipe_diagram}, +}; use anyhow::Result; use hurrycurry_locale::trm; use hurrycurry_protocol::{ Gamedata, Message, book::{Book, BookPage}, }; -use hurrycurry_server::data::Serverdata; struct RecipePageParams<'a> { name: &'a str, diff --git a/server/tools/src/recipe_diagram.rs b/server/data/src/book/recipe_diagram.rs index 0be75433..2ec92b68 100644 --- a/server/tools/src/recipe_diagram.rs +++ b/server/data/src/book/recipe_diagram.rs @@ -16,19 +16,19 @@ */ +use crate::Serverdata; use anyhow::Result; use hurrycurry_protocol::{ Gamedata, ItemIndex, Message, Recipe, RecipeIndex, book::{Diagram, DiagramEdge, DiagramNode, NodeStyle}, glam::Vec2, }; -use hurrycurry_server::data::Serverdata; use std::{ cmp::Reverse, collections::{BTreeMap, BTreeSet, HashSet}, }; -pub(crate) fn recipe_diagram( +pub fn recipe_diagram( data: &Gamedata, serverdata: &Serverdata, target_items: &[&str], diff --git a/server/src/data/demands.rs b/server/data/src/demands.rs index 77e187af..77e187af 100644 --- a/server/src/data/demands.rs +++ b/server/data/src/demands.rs diff --git a/server/data/src/entities.rs b/server/data/src/entities.rs new file mode 100644 index 00000000..68dbe479 --- /dev/null +++ b/server/data/src/entities.rs @@ -0,0 +1,99 @@ +/* + Hurry Curry! - a game about cooking + Copyright (C) 2025 Hurry Curry! Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ + +use hurrycurry_protocol::glam::{IVec2, Vec2}; +use serde::{Deserialize, Serialize}; + +use crate::ItemTileRegistry; + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum EntityDecl { + Conveyor { + from: IVec2, + to: IVec2, + speed: Option<f32>, + }, + ItemPortal { + from: IVec2, + to: IVec2, + }, + PlayerPortal { + from: Vec2, + to: Vec2, + }, + Customers { + scaling_factor: Option<f32>, + }, + Map { + name: String, + pos: Vec2, + }, + EnvironmentEffect(EnvironmentEffect), + Environment(Vec<String>), + Gate { + condition: GateCondition, + pos: IVec2, + }, + Tram { + length: usize, + color: Option<i32>, + points: Vec<Vec2>, + spacing: f32, + smoothing: f32, + }, + Book { + pos: IVec2, + }, + Pedestrians { + spawn_delay: f32, + spawn_delay_stdev: Option<f32>, + speed: Option<f32>, + points: Vec<Vec2>, + }, +} + +impl EntityDecl { + pub(crate) fn run_register(&self, reg: &ItemTileRegistry) { + match self { + Self::Gate { .. } => drop(reg.register_tile("fence".into())), + Self::Customers { .. } => drop(reg.register_item("unknown-order".into())), + _ => (), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum GateCondition { + All(Vec<GateCondition>), + Any(Vec<GateCondition>), + Stars(String, u8), +} + +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +pub struct EnvironmentEffect { + pub name: String, + #[serde(default = "default_onoff")] + pub on: f32, + #[serde(default = "default_onoff")] + pub off: f32, +} +fn default_onoff() -> f32 { + 40. +} diff --git a/server/data/src/index.rs b/server/data/src/index.rs new file mode 100644 index 00000000..a5ec8d97 --- /dev/null +++ b/server/data/src/index.rs @@ -0,0 +1,94 @@ +/* + Hurry Curry! - a game about cooking + Copyright (C) 2025 Hurry Curry! Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ + +use anyhow::{Context, Result, anyhow, bail}; +use hurrycurry_protocol::{Gamedata, MapMetadata}; +use serde::Deserialize; +use std::{ + collections::{HashMap, HashSet}, + fs::{File, read_to_string}, + path::PathBuf, + str::FromStr, + sync::Mutex, +}; + +use crate::{MapDecl, Serverdata, book::book, build_data}; + +#[derive(Debug, Deserialize, Default)] +pub struct DataIndex { + pub maps: HashMap<String, MapMetadata>, + pub recipes: HashSet<String>, +} + +pub static DATA_DIR: Mutex<Option<PathBuf>> = Mutex::new(None); +fn data_dir() -> PathBuf { + DATA_DIR + .lock() + .unwrap() + .to_owned() + .unwrap_or_else(|| PathBuf::from_str("data").unwrap()) +} + +impl DataIndex { + pub fn load() -> Result<Self> { + let mut s = Self::default(); + s.reload()?; + Ok(s) + } + pub fn reload(&mut self) -> Result<()> { + *self = serde_yml::from_reader(File::open(data_dir().join("index.yaml"))?)?; + Ok(()) + } + + pub fn read_map(&self, name: &str) -> Result<String> { + // Scary! + if name.contains("..") || name.starts_with("/") || name.contains("//") { + bail!("illegal map path"); + } + let path = data_dir().join(format!("maps/{name}.yaml")); + Ok(read_to_string(path)?) + } + pub fn read_recipes(&self, name: &str) -> Result<String> { + if !self.recipes.contains(name) { + bail!("unknown recipes: {name:?}"); + } + let path = data_dir().join(format!("recipes/{name}.yaml")); + Ok(read_to_string(path)?) + } + pub fn generate(&self, map: &str) -> Result<(Gamedata, Serverdata)> { + let map_in: MapDecl = serde_yml::from_str( + &self + .read_map(map) + .context(anyhow!("Failed to read map file ({map})"))?, + ) + .context(anyhow!("Failed to parse map file ({map})"))?; + let recipes_in = serde_yml::from_str( + &self + .read_recipes(map_in.recipes.as_deref().unwrap_or("default")) + .context("Failed read recipe file")?, + ) + .context("Failed to parse recipe file")?; + + build_data(&self.maps, map.to_string(), map_in, recipes_in) + } + pub fn generate_with_book(&self, map: &str) -> Result<(Gamedata, Serverdata)> { + let (gd, mut sd) = self.generate(map)?; + sd.book = book(&gd, &sd).context("within book")?; + Ok((gd, sd)) + } +} diff --git a/server/src/data/mod.rs b/server/data/src/lib.rs index 74fae62c..822d6997 100644 --- a/server/src/data/mod.rs +++ b/server/data/src/lib.rs @@ -15,28 +15,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +pub mod book; pub mod demands; +pub mod entities; +pub mod index; -use crate::entity::{construct_entity, Entities, EntityDecl}; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Result, anyhow, bail}; use clap::Parser; use demands::generate_demands; -use hurrycurry_bot::algos::ALGO_CONSTRUCTORS; use hurrycurry_protocol::{ + Gamedata, ItemIndex, MapMetadata, Recipe, TileIndex, book::Book, glam::{IVec2, Vec2}, - Gamedata, ItemIndex, MapMetadata, Recipe, TileIndex, }; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, HashMap, HashSet}, - fs::{read_to_string, File}, - path::PathBuf, - str::FromStr, - sync::{Mutex, RwLock}, + sync::RwLock, time::Duration, }; +use crate::entities::EntityDecl; + #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)] #[serde(rename_all = "snake_case")] pub enum RecipeDeclAction { @@ -113,6 +114,7 @@ pub struct Serverdata { pub default_timer: Option<Duration>, pub book: Book, pub flags: ServerdataFlags, + pub entity_decls: Vec<EntityDecl> } #[rustfmt::skip] @@ -121,82 +123,12 @@ pub struct ServerdataFlags { #[serde(default)] pub disable_unknown_orders: bool, } -#[derive(Debug, Deserialize, Default)] -pub struct DataIndex { - pub maps: HashMap<String, MapMetadata>, - pub recipes: HashSet<String>, -} - -pub static DATA_DIR: Mutex<Option<PathBuf>> = Mutex::new(None); -fn data_dir() -> PathBuf { - DATA_DIR - .lock() - .unwrap() - .to_owned() - .unwrap_or_else(|| PathBuf::from_str("data").unwrap()) -} - -impl DataIndex { - pub fn load() -> Result<Self> { - let mut s = Self::default(); - s.reload()?; - Ok(s) - } - pub fn reload(&mut self) -> Result<()> { - *self = serde_yml::from_reader(File::open(data_dir().join("index.yaml"))?)?; - Ok(()) - } - - pub fn read_map(&self, name: &str) -> Result<String> { - // Scary! - if name.contains("..") || name.starts_with("/") || name.contains("//") { - bail!("illegal map path"); - } - let path = data_dir().join(format!("maps/{name}.yaml")); - Ok(read_to_string(path)?) - } - pub fn read_recipes(&self, name: &str) -> Result<String> { - if !self.recipes.contains(name) { - bail!("unknown recipes: {name:?}"); - } - let path = data_dir().join(format!("recipes/{name}.yaml")); - Ok(read_to_string(path)?) - } - pub fn generate(&self, map: &str) -> Result<(Gamedata, Serverdata, Entities)> { - let map_in: MapDecl = serde_yml::from_str( - &self - .read_map(map) - .context(anyhow!("Failed to read map file ({map})"))?, - ) - .context(anyhow!("Failed to parse map file ({map})"))?; - let recipes_in = serde_yml::from_str( - &self - .read_recipes(map_in.recipes.as_deref().unwrap_or("default")) - .context("Failed read recipe file")?, - ) - .context("Failed to parse recipe file")?; - - build_data(&self.maps, map.to_string(), map_in, recipes_in) - } - pub fn generate_with_book(&self, map: &str) -> Result<(Gamedata, Serverdata, Entities)> { - let (gd, mut sd, es) = self.generate(map)?; - sd.book = self.read_book()?; - Ok((gd, sd, es)) - } - pub fn read_book(&self) -> Result<Book> { - serde_json::from_str( - &read_to_string(data_dir().join("book.json")).context("Failed to read book file")?, - ) - .context("Failed to parse book file") - } -} - -pub fn build_data( +fn build_data( maps: &HashMap<String, MapMetadata>, map_name: String, map_in: MapDecl, recipes_in: Vec<RecipeDecl>, -) -> Result<(Gamedata, Serverdata, Entities)> { +) -> Result<(Gamedata, Serverdata)> { let reg = ItemTileRegistry::default(); let mut recipes = Vec::new(); let mut entities = Vec::new(); @@ -300,25 +232,18 @@ pub fn build_data( exclusive_tiles.entry(tile).or_default().extend(item); } if tile_spec.book { - entities.push(construct_entity(Some(pos), &EntityDecl::Book, ®)?); + entities.push(EntityDecl::Book { pos }); } if let Some(off) = &tile_spec.conveyor { let (x, y) = off .split_once(",") .ok_or(anyhow!("conveyor offset invalid format"))?; let dir = IVec2::new(x.parse()?, y.parse()?); - entities.push(construct_entity( - Some(pos), - &EntityDecl::Conveyor { - dir: Some(dir), - filter: None, - filter_dir: None, - from: None, - speed: None, - to: None, - }, - ®, - )?); + entities.push(EntityDecl::Conveyor { + from: pos, + speed: None, + to: pos + dir, + }); } } } @@ -331,21 +256,10 @@ pub fn build_data( let chef_spawn = chef_spawn.ok_or(anyhow!("map has no chef spawn"))?; - entities.extend( - map_in - .entities - .iter() - .map(|decl| construct_entity(None, decl, ®)) - .try_collect::<Vec<_>>()?, - ); + entities.extend(map_in.entities.clone()); let demands = generate_demands(&tiles_used, &items_used, &raw_demands, &recipes); - let bot_algos = ALGO_CONSTRUCTORS - .iter() - .map(|(name, _)| (*name).to_owned()) - .collect::<Vec<String>>(); - let mut maps = maps .iter() .filter(|(_, v)| v.players > 0) @@ -372,6 +286,10 @@ pub fn build_data( } } + for e in &entities { + e.run_register(®); + } + let item_names = reg.items.into_inner().unwrap(); let tile_names = reg.tiles.into_inner().unwrap(); @@ -383,7 +301,6 @@ pub fn build_data( Ok(( Gamedata { - bot_algos, current_map: map_name, maps, tile_walkable, @@ -393,6 +310,12 @@ pub fn build_data( item_names, demands, tile_names, + bot_algos: vec![ + "waiter".to_string(), + "simple".to_string(), + "dishwasher".to_string(), + "frank".to_string(), + ], hand_count: map_in.hand_count.unwrap_or(1), }, Serverdata { @@ -403,13 +326,13 @@ pub fn build_data( default_timer, book: Book::default(), score_baseline: map_in.score_baseline, + entity_decls: entities, }, - entities, )) } #[derive(Default)] -pub struct ItemTileRegistry { +pub(crate) struct ItemTileRegistry { tiles: RwLock<Vec<String>>, items: RwLock<Vec<String>>, } diff --git a/server/src/entity/campaign.rs b/server/src/entity/campaign.rs index 53ea6582..fdc169d1 100644 --- a/server/src/entity/campaign.rs +++ b/server/src/entity/campaign.rs @@ -18,16 +18,16 @@ use super::{Entity, EntityContext}; use crate::{scoreboard::ScoreboardStore, server::GameServerExt}; use anyhow::Result; +use hurrycurry_data::entities::GateCondition; use hurrycurry_locale::{trm, TrError}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, Message, PacketC, PlayerID, TileIndex, }; -use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone)] pub struct Map { - pub location: Vec2, + pub pos: Vec2, pub name: String, } @@ -36,7 +36,7 @@ impl Entity for Map { let mut activate = false; c.game .players_spatial_index - .query(self.location, 0.5, |_, _| activate = true); + .query(self.pos, 0.5, |_, _| activate = true); if activate { *c.load_map = Some(self.name.clone()); @@ -46,19 +46,11 @@ impl Entity for Map { } } -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum GateCondition { - All(Vec<GateCondition>), - Any(Vec<GateCondition>), - Stars(String, u8), -} - #[derive(Debug, Clone)] pub struct Gate { pub active: bool, pub unlocked: bool, - pub location: IVec2, + pub pos: IVec2, pub blocker_tile: TileIndex, pub condition: GateCondition, } @@ -69,7 +61,7 @@ impl Entity for Gate { self.unlocked = self.condition.check(c.scoreboard); if !self.unlocked { c.game - .set_tile(self.location, Some(self.blocker_tile), c.packet_out); + .set_tile(self.pos, Some(self.blocker_tile), c.packet_out); c.packet_out.push_back(PacketC::FlushMap); // TODO dont send too often } } @@ -81,7 +73,7 @@ impl Entity for Gate { pos: Option<IVec2>, _player: PlayerID, ) -> Result<bool, TrError> { - if !self.unlocked && pos == Some(self.location) { + if !self.unlocked && pos == Some(self.pos) { c.packet_out.push_back(PacketC::ServerMessage { message: trm!( "s.campaign.unlock_condition", @@ -95,7 +87,11 @@ impl Entity for Gate { } } -impl GateCondition { +trait GateConditionExt { + fn check(&self, scoreboard: &ScoreboardStore) -> bool; + fn show(&self, scoreboard: &ScoreboardStore) -> Message; +} +impl GateConditionExt for GateCondition { fn check(&self, scoreboard: &ScoreboardStore) -> bool { match self { GateCondition::All(cs) => cs.iter().all(|c| c.check(scoreboard)), @@ -105,7 +101,7 @@ impl GateCondition { .is_some_and(|s| s.best.first().is_some_and(|b| b.score.stars >= *thres)), } } - pub fn show(&self, scoreboard: &ScoreboardStore) -> Message { + fn show(&self, scoreboard: &ScoreboardStore) -> Message { match self { GateCondition::All(cs) => cs .iter() diff --git a/server/src/entity/conveyor.rs b/server/src/entity/conveyor.rs index 9c7f5e4d..e31410e3 100644 --- a/server/src/entity/conveyor.rs +++ b/server/src/entity/conveyor.rs @@ -18,14 +18,12 @@ use super::{Entity, EntityContext}; use crate::interaction::interact; use anyhow::{anyhow, bail, Result}; -use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation}; +use hurrycurry_protocol::{glam::IVec2, ItemLocation}; #[derive(Debug, Clone)] pub struct Conveyor { pub(super) from: IVec2, pub(super) to: IVec2, - pub(super) filter_tile: Option<IVec2>, - pub(super) filter_item: Option<ItemIndex>, pub(super) cooldown: f32, pub(super) max_cooldown: f32, } @@ -38,24 +36,7 @@ impl Entity for Conveyor { .get(&self.from) .ok_or(anyhow!("conveyor from missing"))?; - if let Some(from_item) = from.item.as_ref() { - let filter = if let Some(t) = &self.filter_tile { - let filter_tile = c - .game - .tiles - .get(t) - .ok_or(anyhow!("conveyor filter missing"))?; - filter_tile.item.as_ref().map(|e| e.kind) - } else { - self.filter_item.as_ref().map(|i| *i) - }; - - if let Some(filter) = filter { - if from_item.kind != filter { - return Ok(()); - } - } - + if from.item.is_some() { self.cooldown += c.dt; if self.cooldown < self.max_cooldown { return Ok(()); diff --git a/server/src/entity/customers.rs b/server/src/entity/customers.rs index e3a23830..22522e4e 100644 --- a/server/src/entity/customers.rs +++ b/server/src/entity/customers.rs @@ -29,13 +29,13 @@ pub struct Customers { } impl Customers { - pub fn new(scaling_factor: f32) -> Result<Self> { - Ok(Self { + pub fn new(scaling_factor: f32) -> Self { + Self { customers: Default::default(), spawn_cooldown: 0., chair_count: None, scaling_factor, - }) + } } } diff --git a/server/src/entity/environment_effect.rs b/server/src/entity/environment_effect.rs index 95040954..ba2c395e 100644 --- a/server/src/entity/environment_effect.rs +++ b/server/src/entity/environment_effect.rs @@ -16,23 +16,11 @@ */ use super::{Entity, EntityContext}; +use hurrycurry_data::entities::EnvironmentEffect; use hurrycurry_protocol::PacketC; use rand::random; -use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; -#[derive(Clone, Debug, Deserialize, Serialize, Default)] -pub struct EnvironmentEffect { - name: String, - #[serde(default = "default_onoff")] - on: f32, - #[serde(default = "default_onoff")] - off: f32, -} -fn default_onoff() -> f32 { - 40. -} - #[derive(Clone, Debug)] pub struct EnvironmentEffectController { config: EnvironmentEffect, diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 928910bc..47d37f3d 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -27,26 +27,19 @@ pub mod player_portal; pub mod tram; pub mod tutorial; -use crate::{ - data::{ItemTileRegistry, Serverdata}, - entity::pedestrians::Pedestrians, - scoreboard::ScoreboardStore, -}; -use anyhow::{anyhow, Result}; +use crate::{entity::pedestrians::Pedestrians, scoreboard::ScoreboardStore}; +use anyhow::Result; use book::Book; -use campaign::{Gate, GateCondition, Map}; +use campaign::{Gate, Map}; use conveyor::Conveyor; use customers::Customers; -use environment_effect::{EnvironmentController, EnvironmentEffect, EnvironmentEffectController}; +use environment_effect::{EnvironmentController, EnvironmentEffectController}; use hurrycurry_client_lib::Game; +use hurrycurry_data::{entities::EntityDecl, Serverdata}; use hurrycurry_locale::TrError; -use hurrycurry_protocol::{ - glam::{IVec2, Vec2}, - Character, PacketC, PacketS, PlayerID, -}; +use hurrycurry_protocol::{glam::IVec2, Character, Gamedata, PacketC, PacketS, PlayerID}; use item_portal::ItemPortal; use player_portal::PlayerPortal; -use serde::{Deserialize, Serialize}; use std::{ any::Any, collections::{HashMap, VecDeque}, @@ -87,135 +80,29 @@ pub trait Entity: Any { } } -// macro_rules! entities { -// ($($e:ident),*) => { -// pub enum DynEntity { $($e($e)),* } -// impl Entity for DynEntity { -// fn tick(&mut self, c: EntityContext<'_>) -> Result<()> { -// match self { $(DynEntity::$e(x) => x.tick(c)),*, } -// } -// fn destructor(&mut self, c: EntityContext<'_>) { -// match self { $(DynEntity::$e(x) => x.destructor(c)),*, } -// } -// } -// }; -// } -// entities!( -// Conveyor, -// ItemPortal, -// PlayerPortal, -// Customers, -// EnvironmentEffectController, -// EnvironmentController -// ); - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum EntityDecl { - Conveyor { - from: Option<IVec2>, - to: Option<IVec2>, - filter_dir: Option<IVec2>, - filter: Option<String>, - dir: Option<IVec2>, - speed: Option<f32>, - }, - ItemPortal { - from: Option<IVec2>, - to: IVec2, - }, - PlayerPortal { - from: Option<Vec2>, - to: Vec2, - }, - Customers { - scaling_factor: Option<f32>, - }, - Map { - name: String, - location: Option<Vec2>, - }, - EnvironmentEffect(EnvironmentEffect), - Environment(Vec<String>), - Gate { - location: Option<IVec2>, - condition: GateCondition, - }, - Tram { - length: usize, - color: Option<i32>, - points: Vec<Vec2>, - spacing: f32, - smoothing: f32, - }, - Book, - Pedestrians { - spawn_delay: f32, - spawn_delay_stdev: Option<f32>, - speed: Option<f32>, - points: Vec<Vec2>, - }, -} - -pub fn construct_entity( - pos: Option<IVec2>, - decl: &EntityDecl, - reg: &ItemTileRegistry, -) -> Result<DynEntity> { - Ok(match decl.to_owned() { - EntityDecl::Book => Box::new(Book(pos.ok_or(anyhow!("book is tile entity"))?)), - EntityDecl::ItemPortal { from, to } => Box::new(ItemPortal { - from: from - .or(pos) - .ok_or(anyhow!("Item portal start without start"))?, - to, - }), - EntityDecl::PlayerPortal { from, to } => Box::new(PlayerPortal { - from: from - .or(pos.map(|v| v.as_vec2())) - .ok_or(anyhow!("Player portal without start"))?, - to, - }), - EntityDecl::Conveyor { +pub fn construct_entity(decl: &EntityDecl, data: &Gamedata) -> DynEntity { + match decl.to_owned() { + EntityDecl::Book { pos } => Box::new(Book(pos)), + EntityDecl::ItemPortal { from, to } => Box::new(ItemPortal { from, to }), + EntityDecl::PlayerPortal { from, to } => Box::new(PlayerPortal { from, to }), + EntityDecl::Conveyor { from, to, speed } => Box::new(Conveyor { from, to, - speed, - dir, - filter, - filter_dir, - } => { - let from = from.or(pos).ok_or(anyhow!("Conveyor has no start"))?; - let to = to - .or(dir.map(|s| s + from)) - .ok_or(anyhow!("Conveyor has no destination"))?; - Box::new(Conveyor { - from, - to, - max_cooldown: 1. / speed.unwrap_or(2.), - filter_tile: filter_dir.map(|o| to + o), - filter_item: filter.map(|name| reg.register_item(name)), - cooldown: 0., - }) - } - EntityDecl::Map { name, location } => Box::new(Map { - location: location - .or(pos.map(|p| p.as_vec2() + 0.5)) - .ok_or(anyhow!("no location"))?, - name, + max_cooldown: 1. / speed.unwrap_or(2.), + cooldown: 0., }), - EntityDecl::Gate { - condition, - location, - } => Box::new(Gate { + EntityDecl::Map { name, pos } => Box::new(Map { pos, name }), + EntityDecl::Gate { condition, pos } => Box::new(Gate { condition, unlocked: false, - location: location.or(pos).ok_or(anyhow!("no location"))?, - blocker_tile: reg.register_tile("fence".to_string()), + pos, + blocker_tile: data + .get_tile_by_name("fence") + .expect("asserted earlier (tm)"), active: true, }), EntityDecl::Customers { scaling_factor } => { - reg.register_item("unknown-order".to_owned()); - Box::new(Customers::new(scaling_factor.unwrap_or(0.5))?) + Box::new(Customers::new(scaling_factor.unwrap_or(0.5))) } EntityDecl::EnvironmentEffect(config) => Box::new(EnvironmentEffectController::new(config)), EntityDecl::Environment(names) => Box::new(EnvironmentController(names)), @@ -254,5 +141,5 @@ pub fn construct_entity( cooldown: 0., speed: speed.unwrap_or(0.6), }), - }) + } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 3e01ba36..da49f85d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -17,7 +17,6 @@ */ #![feature(if_let_guard, let_chains, iterator_try_collect, stmt_expr_attributes)] pub mod commands; -pub mod data; pub mod entity; pub mod interaction; pub mod network; diff --git a/server/src/main.rs b/server/src/main.rs index 5514e7d0..b1265420 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -18,9 +18,10 @@ use anyhow::{bail, Result}; use clap::Parser; use futures_util::{SinkExt, StreamExt}; +use hurrycurry_data::index::DATA_DIR; use hurrycurry_locale::trm; use hurrycurry_protocol::{PacketC, PacketS}; -use hurrycurry_server::{data::DATA_DIR, server::Server, ConnectionID}; +use hurrycurry_server::{server::Server, ConnectionID}; use log::{debug, info, trace, warn, LevelFilter}; use std::{ env::var, net::SocketAddr, path::PathBuf, process::exit, str::FromStr, sync::Arc, @@ -314,8 +315,9 @@ async fn run(args: Args) -> anyhow::Result<()> { #[cfg(test)] mod test { + use hurrycurry_data::index::DATA_DIR; use hurrycurry_protocol::{Character, PacketS, PlayerClass, PlayerID}; - use hurrycurry_server::{data::DATA_DIR, server::Server, ConnectionID}; + use hurrycurry_server::{server::Server, ConnectionID}; use std::future::Future; use tokio::sync::broadcast; diff --git a/server/src/server.rs b/server/src/server.rs index e16fdb61..c370f3c4 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -16,14 +16,14 @@ */ use crate::{ - data::{DataIndex, Serverdata}, - entity::{Entities, EntityContext}, + entity::{construct_entity, Entities, EntityContext}, interaction::{interact, tick_slot}, scoreboard::ScoreboardStore, ConnectionID, }; use anyhow::{Context, Result}; use hurrycurry_client_lib::{gamedata_index::GamedataIndex, Game, Involvement, Item, Player, Tile}; +use hurrycurry_data::{index::DataIndex, Serverdata}; use hurrycurry_locale::{tre, TrError}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, @@ -353,7 +353,7 @@ impl Server { impl Server { pub fn load( &mut self, - (gamedata, serverdata, entities): (Gamedata, Serverdata, Entities), + (gamedata, serverdata): (Gamedata, Serverdata), timer: Option<Duration>, ) { for mut e in self.entities.drain(..) { @@ -369,7 +369,7 @@ impl Server { load_map: &mut None, }); } - self.tick(0.); + self.tick(0.); // TODO ? self.game.load( gamedata, &serverdata, @@ -377,8 +377,11 @@ impl Server { &mut self.packet_out, ); self.gamedata_index.update(&self.game.data); + for ed in &serverdata.entity_decls { + self.entities.push(construct_entity(ed, &self.game.data)); + } self.data = serverdata.into(); - self.entities = entities; + self.entities.clear(); for e in &mut self.entities { e.constructor(EntityContext { game: &mut self.game, diff --git a/server/tools/Cargo.toml b/server/tools/Cargo.toml index db1c5ebf..1c427c81 100644 --- a/server/tools/Cargo.toml +++ b/server/tools/Cargo.toml @@ -11,6 +11,7 @@ clap = { version = "4.5.47", features = ["derive"] } hurrycurry-protocol = { path = "../protocol" } hurrycurry-server = { path = ".." } hurrycurry-locale = { path = "../locale" } +hurrycurry-data = { path = "../data" } serde_json = "1.0.145" serde = { version = "1.0.225", features = ["derive"] } markup = "0.15.0" diff --git a/server/tools/src/graph.rs b/server/tools/src/graph.rs index 53f70d99..a65ffc97 100644 --- a/server/tools/src/graph.rs +++ b/server/tools/src/graph.rs @@ -16,8 +16,8 @@ */ use anyhow::Result; +use hurrycurry_data::index::DataIndex; use hurrycurry_protocol::{Demand, ItemIndex, Recipe, RecipeIndex}; -use hurrycurry_server::data::DataIndex; pub(crate) fn graph() -> Result<()> { let mut index = DataIndex::default(); @@ -25,7 +25,7 @@ pub(crate) fn graph() -> Result<()> { println!("digraph {{"); - let (data, _, _) = index.generate("5star")?; + let (data, _) = index.generate("5star")?; for i in 0..data.item_names.len() { println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i))) } diff --git a/server/tools/src/graph_summary.rs b/server/tools/src/graph_summary.rs index be53e768..bfdcc955 100644 --- a/server/tools/src/graph_summary.rs +++ b/server/tools/src/graph_summary.rs @@ -15,9 +15,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ + use anyhow::Result; +use hurrycurry_data::index::DataIndex; use hurrycurry_protocol::{ItemIndex, Recipe, TileIndex}; -use hurrycurry_server::data::DataIndex; use std::collections::HashSet; pub(crate) fn graph_summary() -> Result<()> { @@ -26,7 +27,7 @@ pub(crate) fn graph_summary() -> Result<()> { println!("digraph {{"); - let (data, sdata, _) = index.generate("5star")?; + let (data, sdata) = index.generate("5star")?; struct Node { inputs: Vec<ItemIndex>, diff --git a/server/tools/src/main.rs b/server/tools/src/main.rs index b550dabb..f70c5755 100644 --- a/server/tools/src/main.rs +++ b/server/tools/src/main.rs @@ -16,31 +16,28 @@ */ -pub mod book; pub mod book_html; pub mod diagram_dot; -pub mod diagram_layout; pub mod diagram_svg; pub mod graph; pub mod graph_summary; pub mod map_linter; -pub mod recipe_diagram; use crate::{ - book::{book, print_book}, book_html::render_html_book, diagram_dot::{diagram_dot, diagram_dot_svg}, - diagram_layout::diagram_layout, diagram_svg::diagram_svg, graph::graph, graph_summary::graph_summary, map_linter::check_map, - recipe_diagram::recipe_diagram, }; use anyhow::Result; use clap::Parser; +use hurrycurry_data::{ + book::{book, diagram_layout::diagram_layout, print_book, recipe_diagram::recipe_diagram}, + index::DataIndex, +}; use hurrycurry_locale::FALLBACK_LOCALE; -use hurrycurry_server::data::DataIndex; #[derive(Parser)] enum Action { @@ -83,7 +80,7 @@ fn main() -> Result<()> { } => { let mut index = DataIndex::default(); index.reload()?; - let (data, serverdata, _) = index.generate("5star")?; + let (data, serverdata) = index.generate("5star")?; let mut diagram = recipe_diagram(&data, &serverdata, &[&out])?; let out = if dot_out { diagram_dot(&data, &diagram, false)? @@ -99,20 +96,20 @@ fn main() -> Result<()> { Action::Book => { let mut index = DataIndex::default(); index.reload()?; - let (data, serverdata, _) = index.generate("5star")?; + let (data, serverdata) = index.generate("5star")?; print_book(&data, &serverdata)? } Action::BookHtml => { let mut index = DataIndex::default(); index.reload()?; - let (data, serverdata, _) = index.generate("5star")?; + let (data, serverdata) = index.generate("5star")?; let book = book(&data, &serverdata)?; println!("{}", render_html_book(&data, &book, &FALLBACK_LOCALE)); } Action::MapDemands { map } => { let mut index = DataIndex::default(); index.reload()?; - let (data, _, _) = index.generate(&map)?; + let (data, _) = index.generate(&map)?; for demand in &data.demands { println!("{}", data.item_name(demand.input)) } @@ -120,7 +117,7 @@ fn main() -> Result<()> { Action::MapItems { map } => { let mut index = DataIndex::default(); index.reload()?; - let (data, _, _) = index.generate(&map)?; + let (data, _) = index.generate(&map)?; for name in &data.item_names { println!("{name}") } @@ -128,7 +125,7 @@ fn main() -> Result<()> { Action::MapTiles { map } => { let mut index = DataIndex::default(); index.reload()?; - let (data, _, _) = index.generate(&map)?; + let (data, _) = index.generate(&map)?; for name in &data.tile_names { println!("{name}") } diff --git a/server/tools/src/map_linter.rs b/server/tools/src/map_linter.rs index 738a9e10..678f2930 100644 --- a/server/tools/src/map_linter.rs +++ b/server/tools/src/map_linter.rs @@ -17,6 +17,7 @@ */ use anyhow::Result; +use hurrycurry_data::{Serverdata, index::DataIndex}; use hurrycurry_locale::{ FALLBACK_LOCALE, message::{COLORED, MessageDisplayExt}, @@ -26,7 +27,6 @@ use hurrycurry_protocol::{ Gamedata, TileIndex, glam::{IVec2, ivec2}, }; -use hurrycurry_server::data::{DataIndex, Serverdata}; use std::{ collections::{BTreeSet, HashMap, HashSet}, sync::LazyLock, @@ -148,7 +148,7 @@ pub fn check_map(map: &str) -> Result<()> { let locale = &*FALLBACK_LOCALE; let mut index = DataIndex::default(); index.reload()?; - let (data, serverdata, _) = index.generate(map)?; + let (data, serverdata) = index.generate(map)?; let mut warnings = Vec::new(); |