aboutsummaryrefslogtreecommitdiff
path: root/server/src
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/src
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/src')
-rw-r--r--server/src/data/demands.rs88
-rw-r--r--server/src/data/mod.rs434
-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
10 files changed, 52 insertions, 718 deletions
diff --git a/server/src/data/demands.rs b/server/src/data/demands.rs
deleted file mode 100644
index 77e187af..00000000
--- a/server/src/data/demands.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- 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::{Demand, ItemIndex, Recipe, TileIndex};
-use std::collections::{HashMap, HashSet};
-
-pub fn generate_demands(
- tiles: &HashSet<TileIndex>,
- items: &HashSet<ItemIndex>,
- raw_demands: &[(ItemIndex, Option<ItemIndex>, f32)],
- recipes: &[Recipe],
-) -> Vec<Demand> {
- let recipes = recipes
- .iter()
- .filter(|r| r.tile().map(|t| tiles.contains(&t)).unwrap_or(true))
- .collect::<Vec<_>>();
-
- let mut producable = HashMap::new();
-
- for i in items {
- producable.insert(*i, 0.0);
- }
-
- loop {
- let prod_count = producable.len();
-
- for r in &recipes {
- let output_count = r.outputs().iter().filter(|o| !items.contains(o)).count();
- let Some(ingred_cost) = r
- .inputs()
- .iter()
- .map(|i| producable.get(i).copied())
- .reduce(|a, b| {
- if let (Some(a), Some(b)) = (a, b) {
- Some(a + b)
- } else {
- None
- }
- })
- .unwrap_or(Some(0.))
- else {
- continue;
- };
-
- let base_cost = match r {
- Recipe::Passive { speed, .. } => 2. + (1. / speed) * 0.1,
- Recipe::Active { speed, .. } => 2. + (1. / speed),
- Recipe::Instant { .. } => 1.,
- };
-
- let output_cost = (ingred_cost + base_cost) / output_count as f32;
- for o in r.outputs() {
- let cost = producable.entry(o).or_insert(f32::INFINITY);
- *cost = cost.min(output_cost);
- }
- }
-
- if prod_count == producable.len() {
- break;
- }
- }
-
- raw_demands
- .iter()
- .filter_map(|(i, o, d)| {
- producable.get(i).map(|cost| Demand {
- input: *i,
- output: *o,
- duration: *d,
- points: *cost as i64,
- })
- })
- .collect()
-}
diff --git a/server/src/data/mod.rs b/server/src/data/mod.rs
deleted file mode 100644
index 74fae62c..00000000
--- a/server/src/data/mod.rs
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- 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/>.
-
-*/
-pub mod demands;
-
-use crate::entity::{construct_entity, Entities, EntityDecl};
-use anyhow::{anyhow, bail, Context, Result};
-use clap::Parser;
-use demands::generate_demands;
-use hurrycurry_bot::algos::ALGO_CONSTRUCTORS;
-use hurrycurry_protocol::{
- 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},
- time::Duration,
-};
-
-#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)]
-#[serde(rename_all = "snake_case")]
-pub enum RecipeDeclAction {
- #[default]
- Never,
- Passive,
- Active,
- Instant,
- Demand,
-}
-
-#[rustfmt::skip]
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct RecipeDecl {
- #[serde(default)] tile: Option<String>,
- #[serde(default)] inputs: Vec<String>,
- #[serde(default)] outputs: Vec<String>,
- #[serde(default)] action: RecipeDeclAction,
- #[serde(default)] warn: bool,
- #[serde(default)] revert_duration: Option<f32>,
- #[serde(default)] duration: Option<f32>,
- #[serde(default)] points: Option<i64>,
-}
-
-#[rustfmt::skip]
-#[derive(Debug, Clone, Deserialize)]
-pub struct MapDecl {
- map: Vec<String>,
- tiles: HashMap<char, String>,
- #[serde(default)] recipes: Option<String>,
- #[serde(default)] hand_count: Option<usize>,
- #[serde(default)] entities: Vec<EntityDecl>,
- #[serde(default)] score_baseline: i64,
- #[serde(default)] default_timer: Option<u64>,
- #[serde(default)] flags: ServerdataFlags,
-}
-
-#[derive(Parser)]
-struct TileArgs {
- tile_name: String,
- #[clap(short = 'c', long)]
- collider: bool,
- #[clap(short = 'x', long)]
- exclusive: bool,
- #[clap(short = 'w', long)]
- walkable: bool,
- #[clap(long)]
- book: bool,
- #[clap(long)]
- chef_spawn: bool,
- #[clap(long)]
- customer_spawn: bool,
- #[clap(short = 'i', long)]
- item: Option<String>,
- #[clap(long)]
- conveyor: Option<String>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct DemandDecl {
- from: String,
- to: Option<String>,
- duration: f32,
- points: i64,
-}
-
-#[derive(Debug, Clone, Default)]
-#[rustfmt::skip]
-pub struct Serverdata {
- pub initial_map: HashMap<IVec2, (TileIndex, Option<ItemIndex>)>,
- pub chef_spawn: Vec2,
- pub customer_spawn: Option<Vec2>,
- pub score_baseline: i64,
- pub default_timer: Option<Duration>,
- pub book: Book,
- pub flags: ServerdataFlags,
-}
-
-#[rustfmt::skip]
-#[derive(Debug, Clone, Default, Deserialize)]
-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(
- maps: &HashMap<String, MapMetadata>,
- map_name: String,
- map_in: MapDecl,
- recipes_in: Vec<RecipeDecl>,
-) -> Result<(Gamedata, Serverdata, Entities)> {
- let reg = ItemTileRegistry::default();
- let mut recipes = Vec::new();
- let mut entities = Vec::new();
- let mut raw_demands = Vec::new();
-
- for mut r in recipes_in {
- #[cfg(feature = "fast_recipes")]
- match r.action {
- RecipeDeclAction::Passive | RecipeDeclAction::Active => {
- if !r.warn {
- r.duration = Some(0.5)
- }
- }
- _ => (),
- }
-
- let r2 = r.clone();
- let mut inputs = r.inputs.into_iter().map(|i| reg.register_item(i));
- let mut outputs = r.outputs.into_iter().map(|o| reg.register_item(o));
- let tile = r.tile.map(|t| reg.register_tile(t));
- match r.action {
- RecipeDeclAction::Never => {}
- RecipeDeclAction::Passive => recipes.push(Recipe::Passive {
- speed: 1. / r.duration.ok_or(anyhow!("duration for passive missing"))?,
- warn: r.warn,
- tile,
- revert_speed: r.revert_duration.map(|d| 1. / d),
- input: inputs
- .next()
- .ok_or(anyhow!("passive recipe without input"))?,
- output: outputs.next(),
- }),
- RecipeDeclAction::Active => recipes.push(Recipe::Active {
- speed: 1. / r.duration.ok_or(anyhow!("duration for active missing"))?,
- tile,
- input: inputs
- .next()
- .ok_or(anyhow!("active recipe without input"))?,
- outputs: [outputs.next(), outputs.next()],
- }),
- RecipeDeclAction::Instant => {
- recipes.push(Recipe::Instant {
- points: r.points.take().unwrap_or(0),
- tile,
- inputs: [inputs.next(), inputs.next()],
- outputs: [outputs.next(), outputs.next()],
- });
- }
- RecipeDeclAction::Demand => raw_demands.push((
- inputs.next().ok_or(anyhow!("demand needs inputs"))?,
- outputs.next(),
- r.duration.unwrap_or(10.),
- )),
- }
- assert_eq!(inputs.next(), None, "{r2:?} inputs left over");
- assert_eq!(outputs.next(), None, "{r2:?} outputs left over");
- assert_eq!(r.points, None, "points specified where not possible")
- }
-
- let mut tile_specs = BTreeMap::new();
- for (char, tile_spec_raw) in map_in.tiles {
- let mut toks = shlex::split(&tile_spec_raw).ok_or(anyhow!("tile spec quoting invalid"))?;
- toks.insert(0, "tile-spec".to_string()); // exe name
- tile_specs.insert(char, TileArgs::try_parse_from(toks)?);
- }
-
- let mut chef_spawn = None;
- let mut customer_spawn = None;
- let mut initial_map = HashMap::new();
- let mut tiles_used = HashSet::new();
- let mut items_used = HashSet::new();
- let mut tile_walkable = HashSet::new();
- let mut exclusive_tiles = BTreeMap::<TileIndex, HashSet<ItemIndex>>::new();
- for (y, line) in map_in.map.iter().enumerate() {
- for (x, char) in line.chars().enumerate() {
- if char == ' ' {
- continue; // space is empty space
- }
- let pos = IVec2::new(x as i32, y as i32);
-
- let tile_spec = tile_specs
- .get(&char)
- .ok_or(anyhow!("tile {char} is undefined"))?;
-
- let tile = reg.register_tile(tile_spec.tile_name.clone());
- tiles_used.insert(tile);
- let item = tile_spec.item.clone().map(|i| reg.register_item(i));
- items_used.extend(item);
- initial_map.insert(pos, (tile, item));
-
- if tile_spec.chef_spawn {
- chef_spawn = Some(pos.as_vec2() + Vec2::splat(0.5));
- }
- if tile_spec.customer_spawn {
- customer_spawn = Some(pos.as_vec2() + Vec2::splat(0.5));
- }
- if tile_spec.walkable {
- tile_walkable.insert(tile);
- }
- if tile_spec.walkable || tile_spec.collider || tile_spec.exclusive {
- exclusive_tiles.entry(tile).or_default().extend(item);
- }
- if tile_spec.book {
- entities.push(construct_entity(Some(pos), &EntityDecl::Book, &reg)?);
- }
- 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,
- )?);
- }
- }
- }
-
- for tile in tile_specs.values() {
- if !tiles_used.contains(&reg.register_tile(tile.tile_name.clone())) {
- bail!("tile {:?} is unused", tile.tile_name)
- }
- }
-
- 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<_>>()?,
- );
-
- 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)
- .map(|(k, v)| (k.to_owned(), v.to_owned()))
- .collect::<Vec<(String, MapMetadata)>>();
- maps.sort_unstable_by_key(|(_, m)| m.difficulty);
- maps.sort_by_key(|(_, m)| m.players);
-
- let mut tile_placeable_items = BTreeMap::new();
- let mut tile_interactable_empty = HashSet::new();
- for (tile, used_items) in exclusive_tiles {
- let whitelist = recipes
- .iter()
- .filter(|r| r.tile() == Some(tile))
- .flat_map(|e| e.inputs())
- .chain(used_items)
- .collect();
- let int_empty = recipes
- .iter()
- .any(|r| r.tile() == Some(tile) && r.inputs().is_empty());
- tile_placeable_items.insert(tile, whitelist);
- if int_empty {
- tile_interactable_empty.insert(tile);
- }
- }
-
- let item_names = reg.items.into_inner().unwrap();
- let tile_names = reg.tiles.into_inner().unwrap();
-
- let default_timer = if map_name.ends_with("lobby") {
- None
- } else {
- Some(Duration::from_secs(map_in.default_timer.unwrap_or(420)))
- };
-
- Ok((
- Gamedata {
- bot_algos,
- current_map: map_name,
- maps,
- tile_walkable,
- tile_placeable_items,
- tile_interactable_empty,
- recipes,
- item_names,
- demands,
- tile_names,
- hand_count: map_in.hand_count.unwrap_or(1),
- },
- Serverdata {
- initial_map,
- chef_spawn,
- flags: map_in.flags,
- customer_spawn,
- default_timer,
- book: Book::default(),
- score_baseline: map_in.score_baseline,
- },
- entities,
- ))
-}
-
-#[derive(Default)]
-pub struct ItemTileRegistry {
- tiles: RwLock<Vec<String>>,
- items: RwLock<Vec<String>>,
-}
-
-impl ItemTileRegistry {
- pub fn register_tile(&self, name: String) -> TileIndex {
- TileIndex(Self::register(&self.tiles, name))
- }
- pub fn register_item(&self, name: String) -> ItemIndex {
- ItemIndex(Self::register(&self.items, name))
- }
- fn register(db: &RwLock<Vec<String>>, name: String) -> usize {
- let mut db = db.write().unwrap();
- if let Some(index) = db.iter().position(|e| e == &name) {
- index
- } else {
- let index = db.len();
- db.push(name);
- index
- }
- }
-}
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,