aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock30
-rw-r--r--pixel-client/src/game.rs6
-rw-r--r--server/Cargo.toml2
-rw-r--r--server/bot/Cargo.toml2
-rw-r--r--server/bot/src/lib.rs17
-rw-r--r--server/bot/src/main.rs2
-rw-r--r--server/client-lib/src/lib.rs35
-rw-r--r--server/protocol/src/lib.rs41
-rw-r--r--server/src/bin/graph.rs2
-rw-r--r--server/src/data.rs363
-rw-r--r--server/src/entity/conveyor.rs29
-rw-r--r--server/src/entity/customers/mod.rs368
-rw-r--r--server/src/entity/environment_effect.rs38
-rw-r--r--server/src/entity/item_portal.rs25
-rw-r--r--server/src/entity/mod.rs16
-rw-r--r--server/src/entity/player_portal.rs24
-rw-r--r--server/src/interaction.rs12
-rw-r--r--server/src/lib.rs2
-rw-r--r--server/src/main.rs2
-rw-r--r--server/src/server.rs (renamed from server/src/game.rs)311
-rw-r--r--server/src/state.rs70
21 files changed, 702 insertions, 695 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 10d5e6b9..d6ba42e0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -277,20 +277,6 @@ dependencies = [
]
[[package]]
-name = "bot"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "clap",
- "env_logger",
- "hurrycurry-client-lib",
- "hurrycurry-protocol",
- "log",
- "rand 0.9.0-alpha.2",
- "rustls",
-]
-
-[[package]]
name = "built"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -816,6 +802,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
+name = "hurrycurry-bot"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "env_logger",
+ "hurrycurry-client-lib",
+ "hurrycurry-protocol",
+ "log",
+ "rand 0.9.0-alpha.2",
+ "rustls",
+]
+
+[[package]]
name = "hurrycurry-client-lib"
version = "0.1.0"
dependencies = [
@@ -868,6 +868,8 @@ dependencies = [
"env_logger",
"fake",
"futures-util",
+ "hurrycurry-bot",
+ "hurrycurry-client-lib",
"hurrycurry-protocol",
"log",
"pollster",
diff --git a/pixel-client/src/game.rs b/pixel-client/src/game.rs
index 29b1ba30..dca84d77 100644
--- a/pixel-client/src/game.rs
+++ b/pixel-client/src/game.rs
@@ -29,7 +29,7 @@ use hurrycurry_client_lib::{network::sync::Network, spatial_index::SpatialIndex}
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
movement::MovementBase,
- ClientGamedata, ItemIndex, ItemLocation, PacketC, PacketS, PlayerID, Score, TileIndex,
+ Gamedata, ItemIndex, ItemLocation, PacketC, PacketS, PlayerID, Score, TileIndex,
};
use log::{info, warn};
use sdl2::{
@@ -41,7 +41,7 @@ use std::collections::{HashMap, HashSet};
pub struct Game {
network: Network,
- data: ClientGamedata,
+ data: Gamedata,
tiles: HashMap<IVec2, Tile>,
tilemap: Tilemap,
walkable: HashSet<IVec2>,
@@ -94,7 +94,7 @@ impl Game {
players: HashMap::new(),
tilemap: Tilemap::default(),
my_id: PlayerID(0),
- data: ClientGamedata::default(),
+ data: Gamedata::default(),
walkable: HashSet::new(),
movement_send_cooldown: 0.,
misc_textures: MiscTextures::init(layout),
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 789bb76a..72e9dc45 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -22,3 +22,5 @@ pollster = "0.3.0"
bincode = "2.0.0-rc.3"
hurrycurry-protocol = { path = "protocol" }
+hurrycurry-client-lib = { path = "client-lib" }
+hurrycurry-bot = { path = "bot" }
diff --git a/server/bot/Cargo.toml b/server/bot/Cargo.toml
index ee9706d8..3143ab55 100644
--- a/server/bot/Cargo.toml
+++ b/server/bot/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "bot"
+name = "hurrycurry-bot"
version = "0.1.0"
edition = "2021"
diff --git a/server/bot/src/lib.rs b/server/bot/src/lib.rs
index e8f05fd6..385e2334 100644
--- a/server/bot/src/lib.rs
+++ b/server/bot/src/lib.rs
@@ -1,3 +1,20 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright 2024 metamuffin
+
+ 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/>.
+
+*/
#![feature(isqrt)]
pub mod algos;
pub mod pathfinding;
diff --git a/server/bot/src/main.rs b/server/bot/src/main.rs
index e9798b75..9ecd0a84 100644
--- a/server/bot/src/main.rs
+++ b/server/bot/src/main.rs
@@ -16,8 +16,8 @@
*/
use anyhow::Result;
-use bot::{algos::ALGO_CONSTRUCTORS, BotAlgo, BotInput};
use clap::Parser;
+use hurrycurry_bot::{algos::ALGO_CONSTRUCTORS, BotAlgo, BotInput};
use hurrycurry_client_lib::{network::sync::Network, Game};
use hurrycurry_protocol::{PacketC, PacketS, PlayerID};
use log::warn;
diff --git a/server/client-lib/src/lib.rs b/server/client-lib/src/lib.rs
index d949ac6e..b04f46f7 100644
--- a/server/client-lib/src/lib.rs
+++ b/server/client-lib/src/lib.rs
@@ -20,8 +20,8 @@ pub mod network;
pub mod spatial_index;
use hurrycurry_protocol::{
- glam::IVec2, movement::MovementBase, ClientGamedata, ItemIndex, ItemLocation, Message, PacketC,
- PlayerID, Score, TileIndex,
+ glam::IVec2, movement::MovementBase, Gamedata, ItemIndex, ItemLocation, Message, PacketC,
+ PlayerID, RecipeIndex, Score, TileIndex,
};
use spatial_index::SpatialIndex;
use std::{
@@ -31,9 +31,17 @@ use std::{
};
#[derive(Debug, PartialEq)]
+pub struct Involvement {
+ pub progress: f32,
+ pub recipe: RecipeIndex,
+ pub working: usize,
+ pub warn: bool,
+}
+
+#[derive(Debug, PartialEq)]
pub struct Item {
pub kind: ItemIndex,
- pub progress: Option<(f32, bool)>,
+ pub active: Option<Involvement>,
}
pub struct Tile {
@@ -52,7 +60,7 @@ pub struct Player {
}
pub struct Game {
- pub data: Arc<ClientGamedata>,
+ pub data: Arc<Gamedata>,
pub tiles: HashMap<IVec2, Tile>,
pub walkable: HashSet<IVec2>,
pub players: HashMap<PlayerID, Player>,
@@ -125,17 +133,20 @@ impl Game {
*self.get_item(to) = self.get_item(from).take();
}
PacketC::SetItem { location, item } => {
- *self.get_item(location) = item.map(|kind| Item {
- kind,
- progress: None,
- });
+ *self.get_item(location) = item.map(|kind| Item { kind, active: None });
}
PacketC::SetProgress {
item,
progress,
warn,
} => {
- self.get_item(item).as_mut().unwrap().progress = progress.map(|s| (s, warn));
+ self.get_item(item).as_mut().unwrap().active =
+ progress.map(|progress| Involvement {
+ working: 1,
+ warn,
+ progress,
+ recipe: RecipeIndex(0),
+ });
}
PacketC::UpdateMap {
tile,
@@ -204,3 +215,9 @@ impl Game {
}
}
}
+
+impl From<TileIndex> for Tile {
+ fn from(kind: TileIndex) -> Self {
+ Self { kind, item: None }
+ }
+}
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs
index 2c165a92..05561a12 100644
--- a/server/protocol/src/lib.rs
+++ b/server/protocol/src/lib.rs
@@ -71,13 +71,12 @@ pub struct MapMetadata {
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)]
#[rustfmt::skip]
-pub struct ClientGamedata {
+pub struct Gamedata {
pub current_map: String,
pub item_names: Vec<String>,
pub tile_names: Vec<String>,
pub tile_collide: Vec<bool>,
pub tile_interact: Vec<bool>,
- pub map_names: HashSet<String>, // for compat with game jam version
pub maps: HashMap<String, MapMetadata>,
pub recipes: Vec<Recipe>,
}
@@ -144,7 +143,7 @@ pub enum PacketC {
id: PlayerID,
},
Data {
- data: ClientGamedata,
+ data: Gamedata,
},
AddPlayer {
id: PlayerID,
@@ -257,6 +256,42 @@ pub enum Recipe {
},
}
+impl Gamedata {
+ pub fn tile_name(&self, index: TileIndex) -> &String {
+ &self.tile_names[index.0]
+ }
+ pub fn is_tile_colliding(&self, index: TileIndex) -> bool {
+ self.tile_collide[index.0]
+ }
+ pub fn is_tile_interactable(&self, index: TileIndex) -> bool {
+ self.tile_interact[index.0]
+ }
+ pub fn item_name(&self, index: ItemIndex) -> &String {
+ &self.item_names[index.0]
+ }
+ pub fn recipe(&self, index: RecipeIndex) -> &Recipe {
+ &self.recipes[index.0]
+ }
+ pub fn get_tile_by_name(&self, name: &str) -> Option<TileIndex> {
+ self.tile_names
+ .iter()
+ .position(|t| t == name)
+ .map(TileIndex)
+ }
+ pub fn get_item_by_name(&self, name: &str) -> Option<ItemIndex> {
+ self.item_names
+ .iter()
+ .position(|t| t == name)
+ .map(ItemIndex)
+ }
+ pub fn recipes(&self) -> impl Iterator<Item = (RecipeIndex, &Recipe)> {
+ self.recipes
+ .iter()
+ .enumerate()
+ .map(|(i, e)| (RecipeIndex(i), e))
+ }
+}
+
impl Recipe {
pub fn tile(&self) -> Option<TileIndex> {
match self {
diff --git a/server/src/bin/graph.rs b/server/src/bin/graph.rs
index 03a59e37..62dc47a2 100644
--- a/server/src/bin/graph.rs
+++ b/server/src/bin/graph.rs
@@ -30,7 +30,7 @@ async fn main() -> Result<()> {
.nth(1)
.ok_or(anyhow!("first arg should be recipe set name"))?;
- let data = index.generate(format!("sushibar-{rn}")).await?;
+ let (data, _) = index.generate(format!("sushibar-{rn}")).await?;
for i in 0..data.item_names.len() {
println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i)))
diff --git a/server/src/data.rs b/server/src/data.rs
index 99cbaf9f..f0a85cca 100644
--- a/server/src/data.rs
+++ b/server/src/data.rs
@@ -20,7 +20,7 @@ use crate::entity::{construct_entity, Entity, EntityDecl};
use anyhow::{anyhow, bail, Result};
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
- ItemIndex, MapMetadata, Recipe, RecipeIndex, TileIndex,
+ Gamedata, ItemIndex, MapMetadata, Recipe, TileIndex,
};
use serde::{Deserialize, Serialize};
use std::{
@@ -99,15 +99,8 @@ pub struct Demand {
#[derive(Debug, Clone, Default)]
#[rustfmt::skip]
-pub struct Gamedata {
+pub struct Serverdata {
pub spec: String,
- pub map_name: String,
- pub item_names: Vec<String>,
- pub tile_names: Vec<String>,
- pub tile_collide: Vec<bool>,
- pub tile_interact: Vec<bool>,
- pub map: HashMap<String, MapMetadata>,
- pub recipes: Vec<Recipe>,
pub initial_map: HashMap<IVec2, (TileIndex, Option<ItemIndex>)>,
pub chef_spawn: Vec2,
pub customer_spawn: Vec2,
@@ -151,182 +144,189 @@ impl DataIndex {
Ok(read_to_string(path).await?)
}
- pub async fn generate(&self, spec: String) -> Result<Gamedata> {
+ pub async fn generate(&self, spec: String) -> Result<(Gamedata, Serverdata)> {
let (map, recipes) = spec.split_once("-").unwrap_or((spec.as_str(), "default"));
let map_in = serde_yml::from_str(&self.read_map(map).await?)?;
let recipes_in = serde_yml::from_str(&self.read_recipes(recipes).await?)?;
- let mut gd = Gamedata::build(spec.clone(), map.to_string(), map_in, recipes_in)?;
- gd.map = self.maps.clone();
- Ok(gd)
+ Ok(build_data(
+ self.maps.clone(),
+ spec.clone(),
+ map.to_string(),
+ map_in,
+ recipes_in,
+ )?)
}
}
-impl Gamedata {
- pub fn build(
- spec: String,
- map_name: String,
- map_in: InitialMap,
- recipes_in: Vec<RecipeDecl>,
- ) -> Result<Self> {
- let reg = ItemTileRegistry::default();
- let mut recipes = Vec::new();
- let mut entities = Vec::new();
- let mut raw_demands = Vec::new();
+pub fn build_data(
+ maps: HashMap<String, MapMetadata>,
+ spec: String,
+ map_name: String,
+ map_in: InitialMap,
+ recipes_in: Vec<RecipeDecl>,
+) -> Result<(Gamedata, Serverdata)> {
+ 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 {
- 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 {
- Action::Never => {}
- Action::Passive => recipes.push(Recipe::Passive {
- duration: r.duration.ok_or(anyhow!("duration for passive missing"))?,
- warn: r.warn,
+ for mut r in recipes_in {
+ 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 {
+ Action::Never => {}
+ Action::Passive => recipes.push(Recipe::Passive {
+ duration: r.duration.ok_or(anyhow!("duration for passive missing"))?,
+ warn: r.warn,
+ tile,
+ revert_duration: r.revert_duration,
+ input: inputs
+ .next()
+ .ok_or(anyhow!("passive recipe without input"))?,
+ output: outputs.next(),
+ }),
+ Action::Active => recipes.push(Recipe::Active {
+ duration: 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()],
+ }),
+ Action::Instant => {
+ recipes.push(Recipe::Instant {
+ points: r.points.take().unwrap_or(0),
tile,
- revert_duration: r.revert_duration,
- input: inputs
- .next()
- .ok_or(anyhow!("passive recipe without input"))?,
- output: outputs.next(),
- }),
- Action::Active => recipes.push(Recipe::Active {
- duration: r.duration.ok_or(anyhow!("duration for active missing"))?,
- tile,
- input: inputs
- .next()
- .ok_or(anyhow!("active recipe without input"))?,
+ inputs: [inputs.next(), inputs.next()],
outputs: [outputs.next(), outputs.next()],
- }),
- Action::Instant => {
- recipes.push(Recipe::Instant {
- points: r.points.take().unwrap_or(0),
- tile,
- inputs: [inputs.next(), inputs.next()],
- outputs: [outputs.next(), outputs.next()],
- });
- }
- Action::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")
+ Action::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")
+ }
- // TODO
- // for d in demands_in {
- // demands.push(Demand {
- // from: reg.register_item(d.from),
- // to: d.to.map(|to| reg.register_item(to)),
- // duration: d.duration,
- // points: d.points,
- // })
- // }
-
- let mut chef_spawn = Vec2::new(0., 0.);
- let mut customer_spawn = Vec2::new(0., 0.);
- let mut initial_map = HashMap::new();
- let mut tiles_used = HashSet::new();
- let mut items_used = HashSet::new();
- for (y, line) in map_in.map.iter().enumerate() {
- for (x, tile) in line.chars().enumerate() {
- if tile == ' ' {
- continue; // space is empty space
- }
- let pos = IVec2::new(x as i32, y as i32);
- if tile == map_in.chef_spawn {
- chef_spawn = pos.as_vec2() + Vec2::splat(0.5);
- }
- if tile == map_in.customer_spawn {
- customer_spawn = pos.as_vec2() + Vec2::splat(0.5);
- }
- let tilename = map_in
- .tiles
- .get(&tile)
- .ok_or(anyhow!("tile {tile} is undefined"))?
- .clone();
+ // TODO
+ // for d in demands_in {
+ // demands.push(Demand {
+ // from: reg.register_item(d.from),
+ // to: d.to.map(|to| reg.register_item(to)),
+ // duration: d.duration,
+ // points: d.points,
+ // })
+ // }
- let itemname = map_in.items.get(&tile).cloned();
- let tile = reg.register_tile(tilename);
- let item = itemname.map(|i| reg.register_item(i));
- tiles_used.insert(tile);
- if let Some(i) = item {
- items_used.insert(i);
- };
- initial_map.insert(pos, (tile, item));
+ let mut chef_spawn = Vec2::new(0., 0.);
+ let mut customer_spawn = Vec2::new(0., 0.);
+ let mut initial_map = HashMap::new();
+ let mut tiles_used = HashSet::new();
+ let mut items_used = HashSet::new();
+ for (y, line) in map_in.map.iter().enumerate() {
+ for (x, tile) in line.chars().enumerate() {
+ if tile == ' ' {
+ continue; // space is empty space
+ }
+ let pos = IVec2::new(x as i32, y as i32);
+ if tile == map_in.chef_spawn {
+ chef_spawn = pos.as_vec2() + Vec2::splat(0.5);
}
+ if tile == map_in.customer_spawn {
+ customer_spawn = pos.as_vec2() + Vec2::splat(0.5);
+ }
+ let tilename = map_in
+ .tiles
+ .get(&tile)
+ .ok_or(anyhow!("tile {tile} is undefined"))?
+ .clone();
+
+ let itemname = map_in.items.get(&tile).cloned();
+ let tile = reg.register_tile(tilename);
+ let item = itemname.map(|i| reg.register_item(i));
+ tiles_used.insert(tile);
+ if let Some(i) = item {
+ items_used.insert(i);
+ };
+ initial_map.insert(pos, (tile, item));
}
+ }
- for (y, line) in map_in.map.iter().enumerate() {
- for (x, tile) in line.trim().chars().enumerate() {
- let pos = IVec2::new(x as i32, y as i32);
- if let Some(ent) = map_in.tile_entities.get(&tile) {
- entities.push(construct_entity(
- Some(pos),
- ent,
- &reg,
- &tiles_used,
- &items_used,
- &raw_demands,
- &recipes,
- &initial_map,
- )?);
- }
+ for (y, line) in map_in.map.iter().enumerate() {
+ for (x, tile) in line.trim().chars().enumerate() {
+ let pos = IVec2::new(x as i32, y as i32);
+ if let Some(ent) = map_in.tile_entities.get(&tile) {
+ entities.push(construct_entity(
+ Some(pos),
+ ent,
+ &reg,
+ &tiles_used,
+ &items_used,
+ &raw_demands,
+ &recipes,
+ &initial_map,
+ )?);
}
}
+ }
- entities.extend(
- map_in
- .entities
- .iter()
- .map(|decl| {
- construct_entity(
- None,
- decl,
- &reg,
- &tiles_used,
- &items_used,
- &raw_demands,
- &recipes,
- &initial_map,
- )
- })
- .try_collect::<Vec<_>>()?,
- );
-
- let item_names = reg.items.into_inner().unwrap();
- let tile_names = reg.tiles.into_inner().unwrap();
- let tile_collide = tile_names
- .iter()
- .map(|i| !map_in.walkable.contains(i))
- .collect();
- let tile_interact = tile_names
+ entities.extend(
+ map_in
+ .entities
.iter()
- .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i))
- .collect();
+ .map(|decl| {
+ construct_entity(
+ None,
+ decl,
+ &reg,
+ &tiles_used,
+ &items_used,
+ &raw_demands,
+ &recipes,
+ &initial_map,
+ )
+ })
+ .try_collect::<Vec<_>>()?,
+ );
- Ok(Gamedata {
- spec,
+ let item_names = reg.items.into_inner().unwrap();
+ let tile_names = reg.tiles.into_inner().unwrap();
+ let tile_collide = tile_names
+ .iter()
+ .map(|i| !map_in.walkable.contains(i))
+ .collect();
+ let tile_interact = tile_names
+ .iter()
+ .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i))
+ .collect();
+
+ Ok((
+ Gamedata {
+ current_map: map_name,
+ maps,
tile_collide,
- map_name,
tile_interact,
recipes,
- score_baseline: map_in.score_baseline,
- map: HashMap::new(),
- initial_map,
item_names,
- entities,
tile_names,
+ },
+ Serverdata {
+ spec,
+ initial_map,
chef_spawn,
customer_spawn,
- })
- }
+ score_baseline: map_in.score_baseline,
+ entities,
+ },
+ ))
}
#[derive(Default)]
@@ -353,56 +353,3 @@ impl ItemTileRegistry {
}
}
}
-
-impl Gamedata {
- pub fn tile_name(&self, index: TileIndex) -> &String {
- &self.tile_names[index.0]
- }
- pub fn is_tile_colliding(&self, index: TileIndex) -> bool {
- self.tile_collide[index.0]
- }
- pub fn is_tile_interactable(&self, index: TileIndex) -> bool {
- self.tile_interact[index.0]
- }
- pub fn item_name(&self, index: ItemIndex) -> &String {
- &self.item_names[index.0]
- }
- pub fn recipe(&self, index: RecipeIndex) -> &Recipe {
- &self.recipes[index.0]
- }
- pub fn get_tile_by_name(&self, name: &str) -> Option<TileIndex> {
- self.tile_names
- .iter()
- .position(|t| t == name)
- .map(TileIndex)
- }
- pub fn get_item_by_name(&self, name: &str) -> Option<ItemIndex> {
- self.item_names
- .iter()
- .position(|t| t == name)
- .map(ItemIndex)
- }
- pub fn recipes(&self) -> impl Iterator<Item = (RecipeIndex, &Recipe)> {
- self.recipes
- .iter()
- .enumerate()
- .map(|(i, e)| (RecipeIndex(i), e))
- }
-}
-/*
- Hurry Curry! - a game about cooking
- Copyright 2024 metamuffin
-
- 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/>.
-
-*/
diff --git a/server/src/entity/conveyor.rs b/server/src/entity/conveyor.rs
index b370356c..f7f091c7 100644
--- a/server/src/entity/conveyor.rs
+++ b/server/src/entity/conveyor.rs
@@ -1,5 +1,3 @@
-use std::collections::VecDeque;
-
/*
Hurry Curry! - a game about cooking
Copyright 2024 metamuffin
@@ -17,10 +15,10 @@ use std::collections::VecDeque;
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use super::EntityT;
-use crate::game::{interact_effect, Game};
+use super::{EntityContext, EntityT};
+use crate::server::interact_effect;
use anyhow::{anyhow, Result};
-use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation, PacketC};
+use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation};
#[derive(Debug, Clone)]
pub struct Conveyor {
@@ -33,15 +31,17 @@ pub struct Conveyor {
}
impl EntityT for Conveyor {
- fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()> {
- let from = game
+ fn tick(&mut self, c: EntityContext) -> Result<()> {
+ let from = c
+ .game
.tiles
.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 = game
+ let filter_tile = c
+ .game
.tiles
.get(t)
.ok_or(anyhow!("conveyor filter missing"))?;
@@ -56,28 +56,29 @@ impl EntityT for Conveyor {
}
}
- self.cooldown += dt;
+ self.cooldown += c.dt;
if self.cooldown < self.max_cooldown {
return Ok(());
}
self.cooldown = 0.;
- let [from, to] = game
+ let [from, to] = c
+ .game
.tiles
.get_many_mut([&self.from, &self.to])
.ok_or(anyhow!("conveyor does ends in itself"))?;
interact_effect(
- &game.data,
+ &c.game.data,
true,
&mut to.item,
ItemLocation::Tile(self.to),
&mut from.item,
ItemLocation::Tile(self.from),
Some(to.kind),
- packet_out,
- &mut game.score,
- &mut game.score_changed,
+ c.packet_out,
+ &mut c.game.score,
+ c.score_changed,
true,
);
}
diff --git a/server/src/entity/customers/mod.rs b/server/src/entity/customers/mod.rs
index 221351a5..85da2c07 100644
--- a/server/src/entity/customers/mod.rs
+++ b/server/src/entity/customers/mod.rs
@@ -18,8 +18,8 @@
pub mod demands;
mod pathfinding;
-use super::EntityT;
-use crate::{data::Demand, game::Game};
+use super::{EntityContext, EntityT};
+use crate::{data::Demand, server::Server};
use anyhow::{anyhow, bail, Result};
use fake::{faker, Fake};
use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketC, PacketS, PlayerID};
@@ -75,190 +75,190 @@ impl Customers {
}
impl EntityT for Customers {
- fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()> {
- self.spawn_cooldown -= dt;
- self.spawn_cooldown = self.spawn_cooldown.max(0.);
- if self.customers.len() < 5 && self.spawn_cooldown <= 0. {
- self.spawn_cooldown = 10. + random::<f32>() * 10.;
- let id = game.join_player(
- faker::name::fr_fr::Name().fake(),
- -1 - (random::<u16>() as i32),
- packet_out,
- );
+ fn tick(&mut self, c: EntityContext) -> Result<()> {
+ // self.spawn_cooldown -= dt;
+ // self.spawn_cooldown = self.spawn_cooldown.max(0.);
+ // if self.customers.len() < 5 && self.spawn_cooldown <= 0. {
+ // self.spawn_cooldown = 10. + random::<f32>() * 10.;
+ // let id = game.join_player(
+ // faker::name::fr_fr::Name().fake(),
+ // -1 - (random::<u16>() as i32),
+ // packet_out,
+ // );
- let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?;
- let from = game.data.customer_spawn.as_ivec2();
- let path = find_path(&game.walkable, from, chair)
- .ok_or(anyhow!("no path from {from} to {chair}"))?;
- info!("{id:?} -> entering");
- self.customers
- .insert(id, CustomerState::Entering { path, chair });
- }
- let mut customers_to_remove = Vec::new();
- for (&player, state) in &mut self.customers {
- let Some(playerdata) = game.players.get_mut(&player) else {
- continue;
- };
+ // let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?;
+ // let from = game.data.customer_spawn.as_ivec2();
+ // let path = find_path(&game.walkable, from, chair)
+ // .ok_or(anyhow!("no path from {from} to {chair}"))?;
+ // info!("{id:?} -> entering");
+ // self.customers
+ // .insert(id, CustomerState::Entering { path, chair });
+ // }
+ // let mut customers_to_remove = Vec::new();
+ // for (&player, state) in &mut self.customers {
+ // let Some(playerdata) = game.players.get_mut(&player) else {
+ // continue;
+ // };
- match state {
- CustomerState::Entering { path, chair } => {
- playerdata
- .movement
- .input(path.next_direction(playerdata.position()), false);
- if path.is_done() {
- let demand = DemandIndex(random::<u32>() as usize % self.demands.len());
- self.cpackets.push_back(PacketS::Communicate {
- message: Some(Message::Item(self.demands[demand.0].from)),
- persist: true,
- player,
- });
- info!("{player:?} -> waiting");
- *state = CustomerState::Waiting {
- chair: *chair,
- timeout: 90. + random::<f32>() * 60.,
- demand,
- };
- }
- }
- CustomerState::Waiting {
- chair,
- demand,
- timeout,
- } => {
- playerdata
- .movement
- .input((chair.as_vec2() + 0.5) - playerdata.position(), false);
- *timeout -= dt;
- if *timeout <= 0. {
- self.cpackets.push_back(PacketS::Communicate {
- message: None,
- persist: true,
- player,
- });
- self.cpackets.push_back(PacketS::Communicate {
- message: Some(Message::Effect("angry".to_string())),
- persist: false,
- player,
- });
- let path = find_path(
- &game.walkable,
- playerdata.position().as_ivec2(),
- game.data.customer_spawn.as_ivec2(),
- )
- .expect("no path to exit");
- *self.chairs.get_mut(chair).unwrap() = true;
- game.score.demands_failed += 1;
- game.score.points -= 1;
- game.score_changed = true;
- info!("{player:?} -> exiting");
- *state = CustomerState::Exiting { path }
- } else {
- let demand_data = &self.demands[demand.0];
- let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y]
- .into_iter()
- .find_map(|off| {
- let pos = *chair + off;
- if game
- .tiles
- .get(&pos)
- .map(|t| {
- t.item
- .as_ref()
- .map(|i| i.kind == demand_data.from)
- .unwrap_or_default()
- })
- .unwrap_or_default()
- {
- Some(pos)
- } else {
- None
- }
- });
- if let Some(pos) = demand_pos {
- self.cpackets.push_back(PacketS::Communicate {
- persist: true,
- message: None,
- player,
- });
- self.cpackets.push_back(PacketS::Communicate {
- message: Some(Message::Effect("satisfied".to_string())),
- persist: false,
- player,
- });
- self.cpackets.push_back(PacketS::Interact {
- pos: Some(pos),
- player,
- });
- self.cpackets
- .push_back(PacketS::Interact { pos: None, player });
- info!("{player:?} -> eating");
- *state = CustomerState::Eating {
- demand: *demand,
- target: pos,
- progress: 0.,
- chair: *chair,
- }
- }
- }
- }
- CustomerState::Eating {
- demand,
- target,
- progress,
- chair,
- } => {
- playerdata
- .movement
- .input((chair.as_vec2() + 0.5) - playerdata.position(), false);
- let demand = &self.demands[demand.0];
- *progress += dt / demand.duration;
- if *progress >= 1. {
- self.cpackets.push_back(PacketS::ReplaceHand {
- player,
- item: demand.to,
- });
- if demand.to.is_some() {
- self.cpackets.push_back(PacketS::Interact {
- player,
- pos: Some(*target),
- });
- self.cpackets
- .push_back(PacketS::Interact { player, pos: None });
- }
- let path = find_path(
- &game.walkable,
- playerdata.position().as_ivec2(),
- game.data.customer_spawn.as_ivec2(),
- )
- .ok_or(anyhow!("no path to exit"))?;
- *self.chairs.get_mut(chair).unwrap() = true;
- game.score.demands_completed += 1;
- game.score.points += demand.points;
- game.score_changed = true;
- info!("{player:?} -> exiting");
- *state = CustomerState::Exiting { path }
- }
- }
- CustomerState::Exiting { path } => {
- playerdata
- .movement
- .input(path.next_direction(playerdata.position()), false);
- if path.is_done() {
- info!("{player:?} -> leave");
- self.cpackets.push_back(PacketS::Leave { player });
- customers_to_remove.push(player);
- }
- }
- }
- }
- for c in customers_to_remove {
- self.customers.remove(&c).unwrap();
- }
- for packet in self.cpackets.drain(..) {
- if let Err(err) = game.packet_in(packet, &mut vec![], packet_out) {
- warn!("demand packet {err}");
- }
- }
+ // match state {
+ // CustomerState::Entering { path, chair } => {
+ // playerdata
+ // .movement
+ // .input(path.next_direction(playerdata.position()), false);
+ // if path.is_done() {
+ // let demand = DemandIndex(random::<u32>() as usize % self.demands.len());
+ // self.cpackets.push_back(PacketS::Communicate {
+ // message: Some(Message::Item(self.demands[demand.0].from)),
+ // persist: true,
+ // player,
+ // });
+ // info!("{player:?} -> waiting");
+ // *state = CustomerState::Waiting {
+ // chair: *chair,
+ // timeout: 90. + random::<f32>() * 60.,
+ // demand,
+ // };
+ // }
+ // }
+ // CustomerState::Waiting {
+ // chair,
+ // demand,
+ // timeout,
+ // } => {
+ // playerdata
+ // .movement
+ // .input((chair.as_vec2() + 0.5) - playerdata.position(), false);
+ // *timeout -= dt;
+ // if *timeout <= 0. {
+ // self.cpackets.push_back(PacketS::Communicate {
+ // message: None,
+ // persist: true,
+ // player,
+ // });
+ // self.cpackets.push_back(PacketS::Communicate {
+ // message: Some(Message::Effect("angry".to_string())),
+ // persist: false,
+ // player,
+ // });
+ // let path = find_path(
+ // &game.walkable,
+ // playerdata.position().as_ivec2(),
+ // game.data.customer_spawn.as_ivec2(),
+ // )
+ // .expect("no path to exit");
+ // *self.chairs.get_mut(chair).unwrap() = true;
+ // game.score.demands_failed += 1;
+ // game.score.points -= 1;
+ // game.score_changed = true;
+ // info!("{player:?} -> exiting");
+ // *state = CustomerState::Exiting { path }
+ // } else {
+ // let demand_data = &self.demands[demand.0];
+ // let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y]
+ // .into_iter()
+ // .find_map(|off| {
+ // let pos = *chair + off;
+ // if game
+ // .tiles
+ // .get(&pos)
+ // .map(|t| {
+ // t.item
+ // .as_ref()
+ // .map(|i| i.kind == demand_data.from)
+ // .unwrap_or_default()
+ // })
+ // .unwrap_or_default()
+ // {
+ // Some(pos)
+ // } else {
+ // None
+ // }
+ // });
+ // if let Some(pos) = demand_pos {
+ // self.cpackets.push_back(PacketS::Communicate {
+ // persist: true,
+ // message: None,
+ // player,
+ // });
+ // self.cpackets.push_back(PacketS::Communicate {
+ // message: Some(Message::Effect("satisfied".to_string())),
+ // persist: false,
+ // player,
+ // });
+ // self.cpackets.push_back(PacketS::Interact {
+ // pos: Some(pos),
+ // player,
+ // });
+ // self.cpackets
+ // .push_back(PacketS::Interact { pos: None, player });
+ // info!("{player:?} -> eating");
+ // *state = CustomerState::Eating {
+ // demand: *demand,
+ // target: pos,
+ // progress: 0.,
+ // chair: *chair,
+ // }
+ // }
+ // }
+ // }
+ // CustomerState::Eating {
+ // demand,
+ // target,
+ // progress,
+ // chair,
+ // } => {
+ // playerdata
+ // .movement
+ // .input((chair.as_vec2() + 0.5) - playerdata.position(), false);
+ // let demand = &self.demands[demand.0];
+ // *progress += dt / demand.duration;
+ // if *progress >= 1. {
+ // self.cpackets.push_back(PacketS::ReplaceHand {
+ // player,
+ // item: demand.to,
+ // });
+ // if demand.to.is_some() {
+ // self.cpackets.push_back(PacketS::Interact {
+ // player,
+ // pos: Some(*target),
+ // });
+ // self.cpackets
+ // .push_back(PacketS::Interact { player, pos: None });
+ // }
+ // let path = find_path(
+ // &game.walkable,
+ // playerdata.position().as_ivec2(),
+ // game.data.customer_spawn.as_ivec2(),
+ // )
+ // .ok_or(anyhow!("no path to exit"))?;
+ // *self.chairs.get_mut(chair).unwrap() = true;
+ // game.score.demands_completed += 1;
+ // game.score.points += demand.points;
+ // game.score_changed = true;
+ // info!("{player:?} -> exiting");
+ // *state = CustomerState::Exiting { path }
+ // }
+ // }
+ // CustomerState::Exiting { path } => {
+ // playerdata
+ // .movement
+ // .input(path.next_direction(playerdata.position()), false);
+ // if path.is_done() {
+ // info!("{player:?} -> leave");
+ // self.cpackets.push_back(PacketS::Leave { player });
+ // customers_to_remove.push(player);
+ // }
+ // }
+ // }
+ // }
+ // for c in customers_to_remove {
+ // self.customers.remove(&c).unwrap();
+ // }
+ // for packet in self.cpackets.drain(..) {
+ // if let Err(err) = game.packet_in(packet, &mut vec![], packet_out) {
+ // warn!("demand packet {err}");
+ // }
+ // }
Ok(())
}
}
diff --git a/server/src/entity/environment_effect.rs b/server/src/entity/environment_effect.rs
index f369bce4..8f54d29c 100644
--- a/server/src/entity/environment_effect.rs
+++ b/server/src/entity/environment_effect.rs
@@ -15,15 +15,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use super::EntityT;
-use crate::game::Game;
+use super::{EntityContext, EntityT};
use hurrycurry_protocol::PacketC;
use rand::random;
use serde::{Deserialize, Serialize};
-use std::{
- collections::VecDeque,
- time::{Duration, Instant},
-};
+use std::time::{Duration, Instant};
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct EnvironmentEffect {
@@ -54,26 +50,21 @@ impl EnvironmentEffectController {
}
}
impl EntityT for EnvironmentEffectController {
- fn tick(
- &mut self,
- game: &mut Game,
- packet_out: &mut VecDeque<PacketC>,
- _dt: f32,
- ) -> anyhow::Result<()> {
+ fn tick(&mut self, c: EntityContext) -> anyhow::Result<()> {
if self.next_transition < Instant::now() {
if self.active {
self.next_transition +=
Duration::from_secs_f32(self.config.on + (0.5 + random::<f32>()));
self.active = false;
- game.environment_effects.remove(&self.config.name);
+ c.game.environment_effects.remove(&self.config.name);
} else {
self.next_transition +=
Duration::from_secs_f32(self.config.off * (0.5 + random::<f32>()));
self.active = true;
- game.environment_effects.insert(self.config.name.clone());
+ c.game.environment_effects.insert(self.config.name.clone());
}
- packet_out.push_back(PacketC::Environment {
- effects: game.environment_effects.clone(),
+ c.packet_out.push_back(PacketC::Environment {
+ effects: c.game.environment_effects.clone(),
})
}
Ok(())
@@ -83,16 +74,11 @@ impl EntityT for EnvironmentEffectController {
#[derive(Debug, Clone)]
pub struct EnvironmentController(pub Vec<String>);
impl EntityT for EnvironmentController {
- fn tick(
- &mut self,
- game: &mut Game,
- packet_out: &mut VecDeque<PacketC>,
- _dt: f32,
- ) -> anyhow::Result<()> {
- if game.environment_effects.is_empty() {
- game.environment_effects.extend(self.0.clone());
- packet_out.push_back(PacketC::Environment {
- effects: game.environment_effects.clone(),
+ fn tick(&mut self, c: EntityContext) -> anyhow::Result<()> {
+ if c.game.environment_effects.is_empty() {
+ c.game.environment_effects.extend(self.0.clone());
+ c.packet_out.push_back(PacketC::Environment {
+ effects: c.game.environment_effects.clone(),
})
}
Ok(())
diff --git a/server/src/entity/item_portal.rs b/server/src/entity/item_portal.rs
index f63ff274..be6acd06 100644
--- a/server/src/entity/item_portal.rs
+++ b/server/src/entity/item_portal.rs
@@ -15,11 +15,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use super::EntityT;
-use crate::game::{interact_effect, Game};
+use super::{EntityContext, EntityT};
+use crate::server::interact_effect;
use anyhow::{anyhow, Result};
-use hurrycurry_protocol::{glam::IVec2, ItemLocation, PacketC};
-use std::collections::VecDeque;
+use hurrycurry_protocol::{glam::IVec2, ItemLocation};
#[derive(Debug, Default, Clone)]
pub struct ItemPortal {
@@ -28,29 +27,25 @@ pub struct ItemPortal {
}
impl EntityT for ItemPortal {
- fn tick(
- &mut self,
- game: &mut Game,
- packet_out: &mut VecDeque<PacketC>,
- _dt: f32,
- ) -> Result<()> {
- let [from, to] = game
+ fn tick(&mut self, c: EntityContext) -> Result<()> {
+ let [from, to] = c
+ .game
.tiles
.get_many_mut([&self.from, &self.to])
.ok_or(anyhow!("conveyor does ends in itself"))?;
if from.item.is_some() {
interact_effect(
- &game.data,
+ &c.game.data,
true,
&mut to.item,
ItemLocation::Tile(self.to),
&mut from.item,
ItemLocation::Tile(self.from),
Some(to.kind),
- packet_out,
- &mut game.score,
- &mut game.score_changed,
+ c.packet_out,
+ &mut c.game.score,
+ c.score_changed,
true,
);
}
diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs
index 94718dd1..10d0c155 100644
--- a/server/src/entity/mod.rs
+++ b/server/src/entity/mod.rs
@@ -21,11 +21,12 @@ pub mod environment_effect;
pub mod item_portal;
pub mod player_portal;
-use crate::{data::ItemTileRegistry, game::Game};
+use crate::data::ItemTileRegistry;
use anyhow::{anyhow, Result};
use conveyor::Conveyor;
use customers::{demands::generate_demands, Customers};
use environment_effect::{EnvironmentController, EnvironmentEffect, EnvironmentEffectController};
+use hurrycurry_client_lib::Game;
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
ItemIndex, PacketC, Recipe, TileIndex,
@@ -35,8 +36,15 @@ use player_portal::PlayerPortal;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet, VecDeque};
+pub struct EntityContext<'a> {
+ pub game: &'a mut Game,
+ pub packet_out: &'a mut VecDeque<PacketC>,
+ pub score_changed: &'a mut bool,
+ pub dt: f32,
+}
+
pub trait EntityT: Clone {
- fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()>;
+ fn tick(&mut self, c: EntityContext<'_>) -> Result<()>;
}
macro_rules! entities {
@@ -44,8 +52,8 @@ macro_rules! entities {
#[derive(Debug, Clone)]
pub enum Entity { $($e($e)),* }
impl EntityT for Entity {
- fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()> {
- match self { $(Entity::$e(x) => x.tick(game, packet_out, dt)),*, }
+ fn tick(&mut self, c: EntityContext<'_>) -> Result<()> {
+ match self { $(Entity::$e(x) => x.tick(c)),*, }
}
}
};
diff --git a/server/src/entity/player_portal.rs b/server/src/entity/player_portal.rs
index 6ee04f15..11442677 100644
--- a/server/src/entity/player_portal.rs
+++ b/server/src/entity/player_portal.rs
@@ -15,11 +15,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use super::EntityT;
-use crate::game::Game;
+use super::{EntityContext, EntityT};
use anyhow::{anyhow, Result};
use hurrycurry_protocol::{glam::Vec2, PacketC};
-use std::collections::VecDeque;
#[derive(Debug, Default, Clone)]
pub struct PlayerPortal {
@@ -28,19 +26,21 @@ pub struct PlayerPortal {
}
impl EntityT for PlayerPortal {
- fn tick(
- &mut self,
- game: &mut Game,
- _packet_out: &mut VecDeque<PacketC>,
- _dt: f32,
- ) -> Result<()> {
+ fn tick(&mut self, c: EntityContext) -> Result<()> {
let mut players = Vec::new();
- game.players_spatial_index
+ c.game
+ .players_spatial_index
.query(self.from, 0.5, |pid, _| players.push(pid));
- for p in players {
- let p = game.players.get_mut(&p).ok_or(anyhow!("player missing"))?;
+ for pid in players {
+ let p = c
+ .game
+ .players
+ .get_mut(&pid)
+ .ok_or(anyhow!("player missing"))?;
p.movement.position = self.to;
+ c.packet_out
+ .push_back(PacketC::MovementSync { player: pid });
}
Ok(())
diff --git a/server/src/interaction.rs b/server/src/interaction.rs
index 4630b536..2f58ed8f 100644
--- a/server/src/interaction.rs
+++ b/server/src/interaction.rs
@@ -1,3 +1,4 @@
+use hurrycurry_client_lib::{Involvement, Item};
/*
Hurry Curry! - a game about cooking
Copyright 2024 metamuffin
@@ -15,11 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::{
- data::Gamedata,
- game::{Involvement, Item},
-};
-use hurrycurry_protocol::{Recipe, Score, TileIndex};
+use hurrycurry_protocol::{Gamedata, Recipe, Score, TileIndex};
use log::info;
pub enum InteractEffect {
@@ -80,6 +77,7 @@ pub fn interact(
recipe: ri,
working: 1,
progress: 0.,
+ warn: false,
});
}
}
@@ -96,6 +94,7 @@ pub fn interact(
recipe: ri,
working: 1,
progress: 0.,
+ warn: false,
});
}
*this = Some(item);
@@ -180,11 +179,12 @@ pub fn tick_slot(
} else {
for (ri, recipe) in data.recipes() {
if recipe.supports_tile(tile) {
- if let Recipe::Passive { input, .. } = recipe {
+ if let Recipe::Passive { input, warn, .. } = recipe {
if *input == item.kind {
item.active = Some(Involvement {
recipe: ri,
progress: 0.,
+ warn: *warn,
working: 1,
});
return Some(TickEffect::Progress(recipe.warn()));
diff --git a/server/src/lib.rs b/server/src/lib.rs
index c8f7af8c..b87f1cb9 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -18,7 +18,7 @@
#![feature(if_let_guard, map_many_mut, let_chains, iterator_try_collect, isqrt)]
pub mod data;
pub mod entity;
-pub mod game;
+pub mod server;
pub mod interaction;
pub mod spatial_index;
pub mod state;
diff --git a/server/src/main.rs b/server/src/main.rs
index 0f38aa2b..40ea3433 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -19,7 +19,7 @@ use anyhow::{anyhow, Result};
use clap::Parser;
use futures_util::{SinkExt, StreamExt};
use hurrycurry_protocol::{PacketC, PacketS, BINCODE_CONFIG, VERSION};
-use hurrycurry_server::{data::DATA_DIR, state::State, ConnectionID};
+use hurrycurry_server::{data::DATA_DIR, server::GameServerExt, state::State, ConnectionID};
use log::{debug, info, trace, warn, LevelFilter};
use std::{
net::SocketAddr,
diff --git a/server/src/game.rs b/server/src/server.rs
index 39cd61dc..f4ccbf35 100644
--- a/server/src/game.rs
+++ b/server/src/server.rs
@@ -16,95 +16,55 @@
*/
use crate::{
- data::Gamedata,
- entity::{Entity, EntityT},
+ data::Serverdata,
+ entity::{Entity, EntityContext, EntityT},
interaction::{interact, tick_slot, InteractEffect, TickEffect},
- spatial_index::SpatialIndex,
};
use anyhow::{anyhow, bail, Result};
+use hurrycurry_client_lib::{Game, Item, Player, Tile};
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
movement::MovementBase,
- ClientGamedata, ItemIndex, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID,
- RecipeIndex, Score, TileIndex,
+ Gamedata, ItemLocation, Menu, PacketC, PacketS, PlayerID, Score, TileIndex,
};
use log::{info, warn};
use std::{
- collections::{HashMap, HashSet, VecDeque},
- sync::{Arc, RwLock},
+ collections::{HashMap, VecDeque},
+ sync::Arc,
time::{Duration, Instant},
};
-#[derive(Debug, PartialEq)]
-pub struct Involvement {
- pub recipe: RecipeIndex,
- pub progress: f32,
- pub working: usize,
-}
-
-#[derive(Debug, PartialEq)]
-pub struct Item {
- pub kind: ItemIndex,
- pub active: Option<Involvement>,
-}
-
-pub struct Tile {
- pub kind: TileIndex,
- pub item: Option<Item>,
-}
-
-pub struct Player {
- pub name: String,
- pub character: i32,
- pub interacting: Option<IVec2>,
- pub item: Option<Item>,
- pub communicate_persist: Option<Message>,
-
- pub movement: MovementBase,
- pub last_position_update: Instant,
-}
-
-pub struct Game {
- pub data: Arc<Gamedata>,
- pub tiles: HashMap<IVec2, Tile>,
- pub walkable: HashSet<IVec2>,
- pub players: HashMap<PlayerID, Player>,
- pub players_spatial_index: SpatialIndex<PlayerID>,
- entities: Arc<RwLock<Vec<Entity>>>,
- end: Option<Instant>,
+pub struct ServerState {
+ pub data: Arc<Serverdata>,
+ entities: Vec<Entity>,
pub lobby: bool,
-
- pub environment_effects: HashSet<String>,
+ pub player_id_counter: PlayerID,
pub score_changed: bool,
- pub score: Score,
+}
- pub player_id_counter: PlayerID,
+pub struct Server<'a> {
+ pub game: &'a mut Game,
+ pub state: &'a mut ServerState,
}
-impl Default for Game {
+impl Default for ServerState {
fn default() -> Self {
Self::new()
}
}
-impl Game {
- pub fn new() -> Self {
- Self {
- lobby: false,
- data: Gamedata::default().into(),
- players: HashMap::new(),
- tiles: HashMap::new(),
- walkable: HashSet::new(),
- end: None,
- entities: Arc::new(RwLock::new(vec![])),
- players_spatial_index: SpatialIndex::default(),
- score: Score::default(),
- environment_effects: HashSet::default(),
- score_changed: false,
- player_id_counter: PlayerID(1),
- }
- }
-
+pub trait GameServerExt {
+ fn unload(&mut self, packet_out: &mut VecDeque<PacketC>);
+ fn load(
+ &mut self,
+ gamedata: Gamedata,
+ serverdata: &Serverdata,
+ timer: Option<Duration>,
+ packet_out: &mut VecDeque<PacketC>,
+ );
+ fn prime_client(&self) -> Vec<PacketC>;
+}
+impl GameServerExt for Game {
fn unload(&mut self, packet_out: &mut VecDeque<PacketC>) {
packet_out.push_back(PacketC::SetIngame {
state: false,
@@ -125,9 +85,10 @@ impl Game {
self.environment_effects.clear();
self.walkable.clear();
}
- pub fn load(
+ fn load(
&mut self,
gamedata: Gamedata,
+ serverdata: &Serverdata,
timer: Option<Duration>,
packet_out: &mut VecDeque<PacketC>,
) {
@@ -140,16 +101,15 @@ impl Game {
self.unload(packet_out);
- self.lobby = gamedata.map_name == "lobby";
+ self.lobby = gamedata.current_map == "lobby";
self.data = gamedata.into();
self.score = Score {
time_remaining: timer.map(|dur| dur.as_secs_f64()).unwrap_or(0.),
..Default::default()
};
self.end = timer.map(|dur| Instant::now() + dur);
- self.entities = Arc::new(RwLock::new(self.data.entities.clone()));
- for (&p, (tile, item)) in &self.data.initial_map {
+ for (&p, (tile, item)) in &serverdata.initial_map {
self.tiles.insert(
p,
Tile {
@@ -172,9 +132,9 @@ impl Game {
character,
movement: MovementBase {
position: if character < 0 {
- self.data.customer_spawn
+ serverdata.customer_spawn
} else {
- self.data.chef_spawn
+ serverdata.chef_spawn
},
input_direction: Vec2::ZERO,
input_boost: false,
@@ -184,7 +144,6 @@ impl Game {
boosting: false,
stamina: 0.,
},
- last_position_update: Instant::now(),
communicate_persist: None,
interacting: None,
name: name.clone(),
@@ -195,32 +154,10 @@ impl Game {
packet_out.extend(self.prime_client());
}
- pub fn prime_client(&self) -> Vec<PacketC> {
+ fn prime_client(&self) -> Vec<PacketC> {
let mut out = Vec::new();
out.push(PacketC::Data {
- data: ClientGamedata {
- recipes: self.data.recipes.clone(),
- item_names: self.data.item_names.clone(),
- tile_names: self.data.tile_names.clone(),
- tile_collide: self.data.tile_collide.clone(),
- tile_interact: self.data.tile_interact.clone(),
- current_map: self.data.map_name.clone(),
- map_names: self
- .data
- .map
- .clone()
- .keys()
- .filter(|n| n.as_str() != "lobby")
- .map(|s| s.to_owned())
- .collect(),
- maps: self
- .data
- .map
- .clone()
- .into_iter()
- .filter(|(n, _)| n != "lobby")
- .collect(),
- },
+ data: self.data.as_ref().to_owned(),
});
out.push(PacketC::Environment {
effects: self.environment_effects.clone(),
@@ -271,6 +208,29 @@ impl Game {
});
out
}
+}
+
+impl ServerState {
+ pub fn new() -> Self {
+ Self {
+ lobby: false,
+ data: Serverdata::default().into(),
+ entities: vec![],
+ player_id_counter: PlayerID(1),
+ score_changed: false,
+ }
+ }
+}
+impl Server<'_> {
+ pub fn load(
+ &mut self,
+ (gamedata, serverdata): (Gamedata, Serverdata),
+ timer: Option<Duration>,
+ packet_out: &mut VecDeque<PacketC>,
+ ) {
+ self.game.load(gamedata, &serverdata, timer, packet_out);
+ self.state.data = serverdata.into();
+ }
pub fn join_player(
&mut self,
@@ -278,24 +238,20 @@ impl Game {
character: i32,
packet_out: &mut VecDeque<PacketC>,
) -> PlayerID {
- let id = self.player_id_counter;
- self.player_id_counter.0 += 1;
- let position = if id.0 < 0 {
- self.data.customer_spawn
+ let id = self.state.player_id_counter;
+ self.state.player_id_counter.0 += 1;
+ let position = if character < 0 {
+ self.state.data.customer_spawn
} else {
- self.data.chef_spawn
+ self.state.data.chef_spawn
};
- self.players.insert(
+ self.game.players.insert(
id,
Player {
item: None,
character,
movement: MovementBase {
- position: if character < 0 {
- self.data.customer_spawn
- } else {
- self.data.chef_spawn
- },
+ position,
input_direction: Vec2::ZERO,
input_boost: false,
facing: Vec2::X,
@@ -304,13 +260,13 @@ impl Game {
boosting: false,
stamina: 0.,
},
- last_position_update: Instant::now(),
+
communicate_persist: None,
interacting: None,
name: name.clone(),
},
);
- self.score.players = self.score.players.max(self.players.len());
+ self.game.score.players = self.game.score.players.max(self.game.players.len());
packet_out.push_back(PacketC::AddPlayer {
id,
name,
@@ -333,15 +289,16 @@ impl Game {
}
PacketS::Leave { player } => {
let p = self
+ .game
.players
.remove(&player)
.ok_or(anyhow!("player does not exist"))?;
- self.players_spatial_index.remove_entry(player);
+ self.game.players_spatial_index.remove_entry(player);
if let Some(item) = p.item {
let pos = p.movement.position.floor().as_ivec2();
- if let Some(tile) = self.tiles.get_mut(&pos) {
+ if let Some(tile) = self.game.tiles.get_mut(&pos) {
if tile.item.is_none() {
packet_out.push_back(PacketC::SetItem {
location: ItemLocation::Tile(pos),
@@ -360,26 +317,30 @@ impl Game {
player,
} => {
let pd = self
+ .game
.players
.get_mut(&player)
.ok_or(anyhow!("player does not exist"))?;
pd.movement.input(direction, boost);
- if let Some(pos) = pos {
- let dt = pd.last_position_update.elapsed();
- pd.last_position_update += dt;
- let diff = pos - pd.movement.position;
- pd.movement.position += diff.clamp_length_max(dt.as_secs_f32());
+ let _ = pos; // TODO
- if diff.length() > 1. {
- replies.push(PacketC::MovementSync { player });
- }
- }
+ // if let Some(pos) = pos {
+ // let dt = pd.last_position_update.elapsed();
+ // pd.last_position_update += dt;
+ // let diff = pos - pd.movement.position;
+ // pd.movement.position += diff.clamp_length_max(dt.as_secs_f32());
+
+ // if diff.length() > 1. {
+ // replies.push(PacketC::MovementSync { player });
+ // }
+ // }
}
PacketS::Interact { pos, player } => {
let pid = player;
let player = self
+ .game
.players
.get_mut(&pid)
.ok_or(anyhow!("player does not exist"))?;
@@ -397,6 +358,7 @@ impl Game {
}
let tile = self
+ .game
.tiles
.get_mut(&pos)
.ok_or(anyhow!("tile does not exist"))?;
@@ -405,8 +367,9 @@ impl Game {
player.interacting = if edge { Some(pos) } else { None };
- let other_pid = if !self.data.is_tile_interactable(tile.kind) {
- self.players
+ let other_pid = if !self.game.data.is_tile_interactable(tile.kind) {
+ self.game
+ .players
.iter()
.find(|(id, p)| **id != pid && p.movement.position.distance(entpos) < 0.7)
.map(|(&id, _)| id)
@@ -416,6 +379,7 @@ impl Game {
if let Some(base_pid) = other_pid {
let [other, this] = self
+ .game
.players
.get_many_mut([&pid, &base_pid])
.ok_or(anyhow!("interacting with yourself. this is impossible"))?;
@@ -425,7 +389,7 @@ impl Game {
}
interact_effect(
- &self.data,
+ &self.game.data,
edge,
&mut this.item,
ItemLocation::Player(base_pid),
@@ -433,18 +397,19 @@ impl Game {
ItemLocation::Player(pid),
None,
packet_out,
- &mut self.score,
- &mut self.score_changed,
+ &mut self.game.score,
+ &mut self.state.score_changed,
false,
)
} else {
let player = self
+ .game
.players
.get_mut(&pid)
.ok_or(anyhow!("player does not exist"))?;
interact_effect(
- &self.data,
+ &self.game.data,
edge,
&mut tile.item,
ItemLocation::Tile(pos),
@@ -452,8 +417,8 @@ impl Game {
ItemLocation::Player(pid),
Some(tile.kind),
packet_out,
- &mut self.score,
- &mut self.score_changed,
+ &mut self.game.score,
+ &mut self.state.score_changed,
false,
)
}
@@ -465,7 +430,7 @@ impl Game {
} => {
info!("{player:?} message {message:?}");
if persist {
- if let Some(player) = self.players.get_mut(&player) {
+ if let Some(player) = self.game.players.get_mut(&player) {
player.communicate_persist = message.clone()
}
}
@@ -477,6 +442,7 @@ impl Game {
}
PacketS::ReplaceHand { item, player } => {
let pdata = self
+ .game
.players
.get_mut(&player)
.ok_or(anyhow!("player does not exist"))?;
@@ -496,18 +462,18 @@ impl Game {
/// Returns true if the game should end
pub fn tick(&mut self, dt: f32, packet_out: &mut VecDeque<PacketC>) -> bool {
- if self.score_changed {
- self.score_changed = false;
- packet_out.push_back(PacketC::Score(self.score.clone()));
+ if self.state.score_changed {
+ self.state.score_changed = false;
+ packet_out.push_back(PacketC::Score(self.game.score.clone()));
}
- for (&pos, tile) in &mut self.tiles {
+ for (&pos, tile) in &mut self.game.tiles {
if let Some(effect) = tick_slot(
dt,
- &self.data,
+ &self.game.data,
Some(tile.kind),
&mut tile.item,
- &mut self.score,
+ &mut self.game.score,
) {
match effect {
TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress {
@@ -536,22 +502,25 @@ impl Game {
}
}
- for (&pid, player) in &mut self.players {
- player.movement.update(&self.walkable, dt);
+ for (&pid, player) in &mut self.game.players {
+ player.movement.update(&self.game.walkable, dt);
- self.players_spatial_index
+ self.game
+ .players_spatial_index
.update_entry(pid, player.movement.position);
}
- self.players_spatial_index.all(|p1, pos1| {
- self.players_spatial_index.query(pos1, 2., |p2, _pos2| {
- if let Some([a, b]) = self.players.get_many_mut([&p1, &p2]) {
- a.movement.collide(&mut b.movement, dt)
- }
- })
+ self.game.players_spatial_index.all(|p1, pos1| {
+ self.game
+ .players_spatial_index
+ .query(pos1, 2., |p2, _pos2| {
+ if let Some([a, b]) = self.game.players.get_many_mut([&p1, &p2]) {
+ a.movement.collide(&mut b.movement, dt)
+ }
+ })
});
- for (&pid, player) in &mut self.players {
+ for (&pid, player) in &mut self.game.players {
packet_out.push_back(PacketC::Movement {
player: pid,
pos: player.movement.position,
@@ -560,8 +529,13 @@ impl Game {
rot: player.movement.rotation,
});
- if let Some(effect) = tick_slot(dt, &self.data, None, &mut player.item, &mut self.score)
- {
+ if let Some(effect) = tick_slot(
+ dt,
+ &self.game.data,
+ None,
+ &mut player.item,
+ &mut self.game.score,
+ ) {
match effect {
TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress {
warn,
@@ -590,9 +564,9 @@ impl Game {
}
let mut players_auto_release = Vec::new();
- for (pid, player) in &mut self.players {
+ for (pid, player) in &mut self.game.players {
if let Some(pos) = player.interacting {
- if let Some(tile) = self.tiles.get(&pos) {
+ if let Some(tile) = self.game.tiles.get(&pos) {
if let Some(item) = &tile.item {
if let Some(involvement) = &item.active {
if involvement.progress >= 1. {
@@ -611,25 +585,31 @@ impl Game {
);
}
- for entity in self.entities.clone().write().unwrap().iter_mut() {
- if let Err(e) = entity.tick(self, packet_out, dt) {
+ for entity in self.state.entities.iter_mut() {
+ if let Err(e) = entity.tick(EntityContext {
+ game: self.game,
+ packet_out,
+ score_changed: &mut self.state.score_changed,
+ dt,
+ }) {
warn!("entity tick failed: {e}")
}
}
let now = Instant::now();
- if let Some(end) = self.end {
- self.score.time_remaining = (end - now).as_secs_f64();
+ if let Some(end) = self.game.end {
+ self.game.score.time_remaining = (end - now).as_secs_f64();
if end < now {
- let relative_score = (self.score.points * 100) / self.data.score_baseline.max(1);
- self.score.stars = match relative_score {
+ let relative_score =
+ (self.game.score.points * 100) / self.state.data.score_baseline.max(1);
+ self.game.score.stars = match relative_score {
100.. => 3,
70.. => 2,
40.. => 1,
_ => 0,
};
- packet_out.push_back(PacketC::Menu(Menu::Score(self.score.clone())));
+ packet_out.push_back(PacketC::Menu(Menu::Score(self.game.score.clone())));
true
} else {
false
@@ -640,19 +620,14 @@ impl Game {
}
pub fn count_chefs(&self) -> usize {
- self.players
+ self.game
+ .players
.values()
.map(|p| if p.character >= 0 { 1 } else { 0 })
.sum()
}
}
-impl From<TileIndex> for Tile {
- fn from(kind: TileIndex) -> Self {
- Self { kind, item: None }
- }
-}
-
pub fn interact_effect(
data: &Gamedata,
edge: bool,
@@ -729,9 +704,3 @@ pub fn interact_effect(
}
}
}
-
-impl Player {
- pub fn position(&self) -> Vec2 {
- self.movement.position
- }
-}
diff --git a/server/src/state.rs b/server/src/state.rs
index 526f70aa..ecfa0fcb 100644
--- a/server/src/state.rs
+++ b/server/src/state.rs
@@ -15,9 +15,14 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::{data::DataIndex, game::Game, ConnectionID};
+use crate::{
+ data::DataIndex,
+ server::{Server, ServerState},
+ ConnectionID,
+};
use anyhow::{anyhow, bail, Result};
use clap::{Parser, ValueEnum};
+use hurrycurry_client_lib::Game;
use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID};
use log::{debug, trace};
use std::{
@@ -31,6 +36,7 @@ pub struct State {
packet_out: VecDeque<PacketC>,
tx: Sender<PacketC>,
connections: HashMap<ConnectionID, HashSet<PlayerID>>,
+ pub server: ServerState,
pub game: Game,
}
@@ -81,15 +87,24 @@ impl State {
index.reload()?;
let mut packet_out = VecDeque::new();
- let mut game = Game::new();
- game.load(
- index.generate("lobby-none".to_string()).await?,
- None,
- &mut packet_out,
- );
+ let mut game = Game::default();
+ let mut server = ServerState::default();
+
+ {
+ Server {
+ game: &mut game,
+ state: &mut server,
+ }
+ .load(
+ index.generate("lobby-none".to_string()).await?,
+ None,
+ &mut packet_out,
+ );
+ }
Ok(Self {
game,
+ server,
index,
tx,
packet_out,
@@ -98,8 +113,12 @@ impl State {
}
pub async fn tick(&mut self, dt: f32) -> anyhow::Result<()> {
- if self.game.tick(dt, &mut self.packet_out) {
- self.game.load(
+ let mut server = Server {
+ game: &mut self.game,
+ state: &mut self.server,
+ };
+ if server.tick(dt, &mut self.packet_out) {
+ server.load(
self.index.generate("lobby-none".to_string()).await?,
None,
&mut self.packet_out,
@@ -147,8 +166,11 @@ impl State {
}
_ => (),
}
- self.game
- .packet_in(packet, &mut replies, &mut self.packet_out)?;
+ let mut server = Server {
+ game: &mut self.game,
+ state: &mut self.server,
+ };
+ server.packet_in(packet, &mut replies, &mut self.packet_out)?;
for p in &replies {
match p {
@@ -159,13 +181,13 @@ impl State {
}
}
- if self.game.count_chefs() <= 0 && !self.game.lobby {
+ if server.count_chefs() <= 0 && !server.state.lobby {
self.tx
.send(PacketC::ServerMessage {
text: "Game was aborted automatically due to a lack of players".to_string(),
})
.ok();
- self.game.load(
+ server.load(
self.index.generate("lobby-none".to_string()).await?,
None,
&mut self.packet_out,
@@ -197,18 +219,22 @@ impl State {
}
async fn handle_command(&mut self, player: PlayerID, command: Command) -> Result<()> {
+ let mut server = Server {
+ game: &mut self.game,
+ state: &mut self.server,
+ };
match command {
Command::Start { spec, timer } => {
let data = self.index.generate(spec).await?;
- self.game
- .load(data, Some(Duration::from_secs(timer)), &mut self.packet_out);
+ server.load(data, Some(Duration::from_secs(timer)), &mut self.packet_out);
}
Command::End => {
self.tx
.send(PacketC::ServerMessage {
text: format!(
"Game was aborted by {}.",
- self.game
+ server
+ .game
.players
.get(&player)
.ok_or(anyhow!("player missing"))?
@@ -216,18 +242,20 @@ impl State {
),
})
.ok();
- self.game.load(
+ server.load(
self.index.generate("lobby-none".to_string()).await?,
None,
&mut self.packet_out,
);
}
Command::Reload => {
- if self.game.count_chefs() > 1 {
+ if server.count_chefs() > 1 {
bail!("must be at most one player to reload");
}
- self.game.load(
- self.index.generate(self.game.data.spec.to_string()).await?,
+ server.load(
+ self.index
+ .generate(server.state.data.spec.to_string())
+ .await?,
None,
&mut self.packet_out,
);
@@ -259,7 +287,7 @@ impl State {
.ok();
}
Command::Item { name } => {
- let item = self
+ let item = server
.game
.data
.get_item_by_name(&name)