aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-10-06 23:03:32 +0200
committermetamuffin <metamuffin@disroot.org>2025-10-06 23:03:40 +0200
commit176e6bc6c4c29bea3be2aceca99743b997c76c97 (patch)
tree1161e7a966843324756340da4b6452492902fa07 /server
parentea86b11b682500160f37b35ea8f06b081cd05036 (diff)
downloadhurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar
hurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar.bz2
hurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar.zst
Move data code to own crate + general data refactor
Diffstat (limited to 'server')
-rw-r--r--server/Cargo.toml4
-rw-r--r--server/data/Cargo.toml17
-rw-r--r--server/data/src/book/diagram_layout.rs (renamed from server/tools/src/diagram_layout.rs)0
-rw-r--r--server/data/src/book/mod.rs (renamed from server/tools/src/book.rs)9
-rw-r--r--server/data/src/book/recipe_diagram.rs (renamed from server/tools/src/recipe_diagram.rs)4
-rw-r--r--server/data/src/demands.rs (renamed from server/src/data/demands.rs)0
-rw-r--r--server/data/src/entities.rs99
-rw-r--r--server/data/src/index.rs94
-rw-r--r--server/data/src/lib.rs (renamed from server/src/data/mod.rs)139
-rw-r--r--server/src/entity/campaign.rs28
-rw-r--r--server/src/entity/conveyor.rs23
-rw-r--r--server/src/entity/customers.rs6
-rw-r--r--server/src/entity/environment_effect.rs14
-rw-r--r--server/src/entity/mod.rs157
-rw-r--r--server/src/lib.rs1
-rw-r--r--server/src/main.rs6
-rw-r--r--server/src/server.rs13
-rw-r--r--server/tools/Cargo.toml1
-rw-r--r--server/tools/src/graph.rs4
-rw-r--r--server/tools/src/graph_summary.rs5
-rw-r--r--server/tools/src/main.rs23
-rw-r--r--server/tools/src/map_linter.rs4
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, &reg)?);
+ 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,
- },
- &reg,
- )?);
+ 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, &reg))
- .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(&reg);
+ }
+
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();