summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/maps/5star.yaml3
-rw-r--r--data/maps/auto_sushi.yaml3
-rw-r--r--data/maps/bar.yaml3
-rw-r--r--data/maps/bbq.yaml3
-rw-r--r--data/maps/bus.yaml3
-rw-r--r--data/maps/conveyors_dot_com.yaml3
-rw-r--r--data/maps/debug.yaml3
-rw-r--r--data/maps/depot.yaml3
-rw-r--r--data/maps/duplex.yaml3
-rw-r--r--data/maps/junior.yaml3
-rw-r--r--data/maps/line.yaml3
-rw-r--r--data/maps/lobby.yaml3
-rw-r--r--data/maps/rivalry.yaml3
-rw-r--r--data/maps/senior.yaml3
-rw-r--r--data/maps/smallest.yaml3
-rw-r--r--data/maps/sophomore.yaml3
-rw-r--r--data/maps/station.yaml3
-rw-r--r--data/maps/sushibar.yaml3
-rw-r--r--data/maps/teeny.yaml3
-rw-r--r--data/maps/village.yaml3
-rw-r--r--data/maps/zigzag.yaml3
-rw-r--r--pixel-client/src/game.rs6
-rw-r--r--server/src/bin/graph.rs44
-rw-r--r--server/src/data.rs (renamed from server/src/data/mod.rs)49
-rw-r--r--server/src/entity/conveyor.rs34
-rw-r--r--server/src/entity/customers/demands.rs (renamed from server/src/data/demands.rs)6
-rw-r--r--server/src/entity/customers/mod.rs (renamed from server/src/customer/mod.rs)174
-rw-r--r--server/src/entity/customers/pathfinding.rs (renamed from server/src/customer/pathfinding.rs)23
-rw-r--r--server/src/entity/mod.rs43
-rw-r--r--server/src/entity/portal.rs26
-rw-r--r--server/src/game.rs105
-rw-r--r--server/src/lib.rs1
-rw-r--r--server/src/spatial_index.rs17
-rw-r--r--server/src/state.rs9
34 files changed, 330 insertions, 270 deletions
diff --git a/data/maps/5star.yaml b/data/maps/5star.yaml
index 87d8d00d..f74b518f 100644
--- a/data/maps/5star.yaml
+++ b/data/maps/5star.yaml
@@ -81,6 +81,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/auto_sushi.yaml b/data/maps/auto_sushi.yaml
index 85384e42..0635b762 100644
--- a/data/maps/auto_sushi.yaml
+++ b/data/maps/auto_sushi.yaml
@@ -82,6 +82,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/bar.yaml b/data/maps/bar.yaml
index 3c0ffdcf..82b20538 100644
--- a/data/maps/bar.yaml
+++ b/data/maps/bar.yaml
@@ -65,6 +65,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/bbq.yaml b/data/maps/bbq.yaml
index cc9b5429..b3e8e0a0 100644
--- a/data/maps/bbq.yaml
+++ b/data/maps/bbq.yaml
@@ -62,6 +62,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "~"
diff --git a/data/maps/bus.yaml b/data/maps/bus.yaml
index 041c7d25..2fb04afb 100644
--- a/data/maps/bus.yaml
+++ b/data/maps/bus.yaml
@@ -92,6 +92,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "~"
diff --git a/data/maps/conveyors_dot_com.yaml b/data/maps/conveyors_dot_com.yaml
index 7953505e..c8bae912 100644
--- a/data/maps/conveyors_dot_com.yaml
+++ b/data/maps/conveyors_dot_com.yaml
@@ -85,6 +85,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/debug.yaml b/data/maps/debug.yaml
index 8581e1a6..74e445ad 100644
--- a/data/maps/debug.yaml
+++ b/data/maps/debug.yaml
@@ -63,6 +63,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/depot.yaml b/data/maps/depot.yaml
index 622a318f..ed307f4b 100644
--- a/data/maps/depot.yaml
+++ b/data/maps/depot.yaml
@@ -86,6 +86,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/duplex.yaml b/data/maps/duplex.yaml
index 23a2f685..ebbd54e2 100644
--- a/data/maps/duplex.yaml
+++ b/data/maps/duplex.yaml
@@ -86,6 +86,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "~"
diff --git a/data/maps/junior.yaml b/data/maps/junior.yaml
index d3ed31c7..5807eb70 100644
--- a/data/maps/junior.yaml
+++ b/data/maps/junior.yaml
@@ -68,6 +68,9 @@ items:
chef_spawn: "~"
customer_spawn: "!"
+entities:
+ - !customers
+
walkable:
- door
- floor
diff --git a/data/maps/line.yaml b/data/maps/line.yaml
index acc596e9..0980b741 100644
--- a/data/maps/line.yaml
+++ b/data/maps/line.yaml
@@ -56,6 +56,9 @@ items:
chef_spawn: "~"
customer_spawn: "!"
+entities:
+ - !customers
+
walkable:
- door
- floor
diff --git a/data/maps/lobby.yaml b/data/maps/lobby.yaml
index 36153bfa..ead5b182 100644
--- a/data/maps/lobby.yaml
+++ b/data/maps/lobby.yaml
@@ -47,6 +47,9 @@ items:
"t": tomato-soup-plate
"T": bread-slice-plate
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/rivalry.yaml b/data/maps/rivalry.yaml
index 35fc82b9..3d68b2f8 100644
--- a/data/maps/rivalry.yaml
+++ b/data/maps/rivalry.yaml
@@ -61,6 +61,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "~"
diff --git a/data/maps/senior.yaml b/data/maps/senior.yaml
index d4e626e7..a9b142d5 100644
--- a/data/maps/senior.yaml
+++ b/data/maps/senior.yaml
@@ -65,6 +65,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/smallest.yaml b/data/maps/smallest.yaml
index 5c1153f3..793540f8 100644
--- a/data/maps/smallest.yaml
+++ b/data/maps/smallest.yaml
@@ -50,6 +50,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "~"
diff --git a/data/maps/sophomore.yaml b/data/maps/sophomore.yaml
index b376019f..8e0888aa 100644
--- a/data/maps/sophomore.yaml
+++ b/data/maps/sophomore.yaml
@@ -62,6 +62,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/station.yaml b/data/maps/station.yaml
index 0e08c6c6..b4c6f2a0 100644
--- a/data/maps/station.yaml
+++ b/data/maps/station.yaml
@@ -68,6 +68,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/sushibar.yaml b/data/maps/sushibar.yaml
index 3dee515a..846e756a 100644
--- a/data/maps/sushibar.yaml
+++ b/data/maps/sushibar.yaml
@@ -71,6 +71,9 @@ items:
"g": glass
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/teeny.yaml b/data/maps/teeny.yaml
index 2254c5a7..934eae00 100644
--- a/data/maps/teeny.yaml
+++ b/data/maps/teeny.yaml
@@ -63,6 +63,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "~"
diff --git a/data/maps/village.yaml b/data/maps/village.yaml
index b04a6a20..66040d6d 100644
--- a/data/maps/village.yaml
+++ b/data/maps/village.yaml
@@ -74,6 +74,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/data/maps/zigzag.yaml b/data/maps/zigzag.yaml
index 2445c15a..43612ac9 100644
--- a/data/maps/zigzag.yaml
+++ b/data/maps/zigzag.yaml
@@ -77,6 +77,9 @@ items:
"p": plate
"f": foodprocessor
+entities:
+ - !customers
+
chef_spawn: "~"
customer_spawn: "!"
diff --git a/pixel-client/src/game.rs b/pixel-client/src/game.rs
index 7e43bdc9..57f419e2 100644
--- a/pixel-client/src/game.rs
+++ b/pixel-client/src/game.rs
@@ -45,6 +45,7 @@ pub struct Game {
tilemap: Tilemap,
collision_map: HashSet<IVec2>,
players: HashMap<PlayerID, Player>,
+ player_ids: HashSet<PlayerID>,
items_removed: Vec<Item>,
my_id: PlayerID,
@@ -107,6 +108,7 @@ impl Game {
items_removed: Vec::new(),
interacting: false,
score: Score::default(),
+ player_ids: HashSet::new(),
camera_center: Vec2::ZERO,
}
}
@@ -255,9 +257,11 @@ impl Game {
},
},
);
+ self.player_ids.insert(id);
}
PacketC::RemovePlayer { id } => {
info!("remove player {}", id.0);
+ self.player_ids.remove(&id);
self.players.remove(&id);
}
PacketC::Position {
@@ -355,6 +359,8 @@ impl Game {
self.tilemap.draw(ctx);
+
+
if let Some(me) = self.players.get(&self.my_id) {
let t = me.movement.get_interact_target();
ctx.draw_world(
diff --git a/server/src/bin/graph.rs b/server/src/bin/graph.rs
index 49ad4716..58cc1763 100644
--- a/server/src/bin/graph.rs
+++ b/server/src/bin/graph.rs
@@ -17,10 +17,7 @@
*/
use anyhow::{anyhow, Result};
use hurrycurry_protocol::{ItemIndex, RecipeIndex};
-use hurrycurry_server::{
- data::{DataIndex, Demand},
- interaction::Recipe,
-};
+use hurrycurry_server::{data::DataIndex, interaction::Recipe};
#[tokio::main]
async fn main() -> Result<()> {
@@ -60,25 +57,26 @@ async fn main() -> Result<()> {
}
}
- for (
- di,
- Demand {
- duration,
- from: ItemIndex(from),
- to,
- points,
- },
- ) in data.demands.iter().enumerate()
- {
- let color = "#c4422b";
- println!(
- "d{di} [label=\"Demand\\ntakes {duration}s\\n{points} points\" shape=box color={color:?} fillcolor={color:?} style=filled]",
- );
- println!("i{from} -> d{di}");
- if let Some(ItemIndex(to)) = to {
- println!("d{di} -> i{to}");
- }
- }
+ // TODO
+ // for (
+ // di,
+ // Demand {
+ // duration,
+ // from: ItemIndex(from),
+ // to,
+ // points,
+ // },
+ // ) in data.demands.iter().enumerate()
+ // {
+ // let color = "#c4422b";
+ // println!(
+ // "d{di} [label=\"Demand\\ntakes {duration}s\\n{points} points\" shape=box color={color:?} fillcolor={color:?} style=filled]",
+ // );
+ // println!("i{from} -> d{di}");
+ // if let Some(ItemIndex(to)) = to {
+ // println!("d{di} -> i{to}");
+ // }
+ // }
println!("}}");
Ok(())
diff --git a/server/src/data/mod.rs b/server/src/data.rs
index 28347a25..2d190f3b 100644
--- a/server/src/data/mod.rs
+++ b/server/src/data.rs
@@ -21,10 +21,9 @@ use crate::{
interaction::Recipe,
};
use anyhow::{anyhow, bail, Result};
-use demands::generate_demands;
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
- DemandIndex, ItemIndex, MapMetadata, RecipeIndex, TileIndex,
+ ItemIndex, MapMetadata, RecipeIndex, TileIndex,
};
use serde::{Deserialize, Serialize};
use std::{
@@ -36,8 +35,6 @@ use std::{
};
use tokio::fs::read_to_string;
-pub mod demands;
-
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")]
pub enum Action {
@@ -111,7 +108,6 @@ pub struct Gamedata {
pub tile_interact: Vec<bool>,
pub map: HashMap<String, MapMetadata>,
#[serde(skip)] pub recipes: Vec<Recipe>,
- #[serde(skip)] pub demands: Vec<Demand>,
#[serde(skip)] pub initial_map: HashMap<IVec2, (TileIndex, Option<ItemIndex>)>,
#[serde(skip)] pub chef_spawn: Vec2,
#[serde(skip)] pub customer_spawn: Vec2,
@@ -254,9 +250,7 @@ impl Gamedata {
.get(&tile)
.ok_or(anyhow!("tile {tile} is undefined"))?
.clone();
- if let Some(ent) = map_in.tile_entities.get(&tile) {
- entities.push(construct_entity(Some(pos), ent, &reg)?);
- }
+
let itemname = map_in.items.get(&tile).cloned();
let tile = reg.register_tile(tilename);
let item = itemname.map(|i| reg.register_item(i));
@@ -268,16 +262,43 @@ impl Gamedata {
}
}
+ 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))
+ .map(|decl| {
+ construct_entity(
+ None,
+ decl,
+ &reg,
+ &tiles_used,
+ &items_used,
+ &raw_demands,
+ &recipes,
+ &initial_map,
+ )
+ })
.try_collect::<Vec<_>>()?,
);
- let demands = generate_demands(tiles_used, items_used, &raw_demands, &recipes);
-
let item_names = reg.items.into_inner().unwrap();
let tile_names = reg.tiles.into_inner().unwrap();
let tile_collide = tile_names
@@ -291,7 +312,6 @@ impl Gamedata {
Ok(Gamedata {
spec,
- demands,
tile_collide,
tile_interact,
recipes,
@@ -347,9 +367,6 @@ impl Gamedata {
pub fn recipe(&self, index: RecipeIndex) -> &Recipe {
&self.recipes[index.0]
}
- pub fn demand(&self, index: DemandIndex) -> &Demand {
- &self.demands[index.0]
- }
pub fn get_tile_by_name(&self, name: &str) -> Option<TileIndex> {
self.tile_names
.iter()
@@ -385,4 +402,4 @@ impl Gamedata {
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/>.
-*/ \ No newline at end of file
+*/
diff --git a/server/src/entity/conveyor.rs b/server/src/entity/conveyor.rs
index 2d56c144..d1594ce7 100644
--- a/server/src/entity/conveyor.rs
+++ b/server/src/entity/conveyor.rs
@@ -16,13 +16,9 @@
*/
use super::EntityT;
-use crate::{
- data::Gamedata,
- game::{interact_effect, Tile},
-};
+use crate::game::{interact_effect, Game};
use anyhow::{anyhow, Result};
-use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation, PacketC};
-use std::collections::{HashMap, VecDeque};
+use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation};
#[derive(Debug, Clone)]
pub struct Conveyor {
@@ -35,21 +31,18 @@ pub struct Conveyor {
}
impl EntityT for Conveyor {
- fn tick(
- &mut self,
- data: &Gamedata,
- points: &mut i64,
- packet_out: &mut VecDeque<PacketC>,
- tiles: &mut HashMap<IVec2, Tile>,
- dt: f32,
- ) -> Result<()> {
- let from = tiles
+ fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()> {
+ let from = 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 = tiles.get(t).ok_or(anyhow!("conveyor filter missing"))?;
+ let filter_tile = game
+ .tiles
+ .get(t)
+ .ok_or(anyhow!("conveyor filter missing"))?;
filter_tile.item.as_ref().map(|e| e.kind)
} else if let Some(i) = &self.filter_item {
Some(*i)
@@ -69,20 +62,21 @@ impl EntityT for Conveyor {
}
self.cooldown = 0.;
- let [from, to] = tiles
+ let [from, to] = game
+ .tiles
.get_many_mut([&self.from, &self.to])
.ok_or(anyhow!("conveyor does ends in itself"))?;
interact_effect(
- data,
+ &game.data,
true,
&mut to.item,
ItemLocation::Tile(self.to),
&mut from.item,
ItemLocation::Tile(self.from),
Some(to.kind),
- packet_out,
- points,
+ &mut game.packet_out,
+ &mut game.points,
true,
);
}
diff --git a/server/src/data/demands.rs b/server/src/entity/customers/demands.rs
index 2501e225..fa7e0dbf 100644
--- a/server/src/data/demands.rs
+++ b/server/src/entity/customers/demands.rs
@@ -21,8 +21,8 @@ use hurrycurry_protocol::{ItemIndex, TileIndex};
use std::collections::{HashMap, HashSet};
pub fn generate_demands(
- tiles: HashSet<TileIndex>,
- items: HashSet<ItemIndex>,
+ tiles: &HashSet<TileIndex>,
+ items: &HashSet<ItemIndex>,
raw_demands: &[(ItemIndex, Option<ItemIndex>, f32)],
recipes: &[Recipe],
) -> Vec<Demand> {
@@ -33,7 +33,7 @@ pub fn generate_demands(
let mut producable = HashMap::new();
- for i in &items {
+ for i in items {
producable.insert(*i, 0.0);
}
diff --git a/server/src/customer/mod.rs b/server/src/entity/customers/mod.rs
index bf385927..7f0b0c22 100644
--- a/server/src/customer/mod.rs
+++ b/server/src/entity/customers/mod.rs
@@ -15,35 +15,30 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+pub mod demands;
mod pathfinding;
-use crate::{data::Gamedata, game::Tile};
+use super::EntityT;
+use crate::{data::Demand, game::Game};
use anyhow::{anyhow, Result};
use fake::{faker, Fake};
-use hurrycurry_protocol::{
- glam::IVec2, movement::MovementBase, DemandIndex, Message, PacketS, PlayerID,
-};
-use log::info;
+use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketS, PlayerID};
+use log::{info, warn};
use pathfinding::{find_path, Path};
use rand::{random, thread_rng};
-use std::{
- collections::{HashMap, HashSet},
- sync::Arc,
-};
+use std::collections::{HashMap, VecDeque};
-pub struct DemandState {
- data: Arc<Gamedata>,
- walkable: HashSet<IVec2>,
+#[derive(Debug, Clone)]
+pub struct Customers {
+ demands: Vec<Demand>,
+ cpackets: VecDeque<(PlayerID, PacketS)>,
chairs: HashMap<IVec2, bool>,
customer_id_counter: PlayerID,
- customers: HashMap<PlayerID, Customer>,
+ customers: HashMap<PlayerID, CustomerState>,
spawn_cooldown: f32,
-
- pub completed: usize,
- pub failed: usize,
- pub score_changed: bool,
}
+#[derive(Debug, Clone)]
enum CustomerState {
Entering {
path: Path,
@@ -65,52 +60,28 @@ enum CustomerState {
},
}
-pub struct Customer {
- movement: MovementBase,
- state: CustomerState,
-}
-
-impl DemandState {
- pub fn new(data: Arc<Gamedata>, map: &HashMap<IVec2, Tile>) -> Self {
- let chair = data.get_tile_by_name("chair");
+impl Customers {
+ pub fn new(chairs: HashMap<IVec2, bool>, demands: Vec<Demand>) -> Self {
Self {
- score_changed: true,
- completed: 0,
- failed: 0,
- walkable: map
- .iter()
- .filter(|(_, v)| !data.is_tile_colliding(v.kind))
- .map(|(e, _)| *e)
- .collect(),
- chairs: map
- .iter()
- .filter(|(_, v)| Some(v.kind) == chair)
- .map(|(e, _)| (*e, true))
- .collect(),
+ chairs,
customer_id_counter: PlayerID(0),
customers: Default::default(),
- data,
+ demands,
spawn_cooldown: 0.,
+ cpackets: VecDeque::new(),
}
}
}
-impl DemandState {
- pub fn tick(
- &mut self,
- packets_out: &mut Vec<(PlayerID, PacketS)>,
- tiles: &mut HashMap<IVec2, Tile>,
- data: &Gamedata,
- dt: f32,
- points: &mut i64,
- ) -> Result<()> {
+impl EntityT for Customers {
+ fn tick(&mut self, game: &mut Game, 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.;
self.customer_id_counter.0 -= 1;
let id = self.customer_id_counter;
- packets_out.push((
+ self.cpackets.push_back((
id,
PacketS::Join {
name: faker::name::fr_fr::Name().fake(),
@@ -118,34 +89,33 @@ impl DemandState {
},
));
let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?;
- let from = data.customer_spawn.as_ivec2();
- let path = find_path(&self.walkable, from, chair)
+ 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,
- Customer {
- movement: MovementBase::new(data.customer_spawn),
- state: CustomerState::Entering { path, chair },
- },
- );
+ self.customers
+ .insert(id, CustomerState::Entering { path, chair });
}
let mut customers_to_remove = Vec::new();
- for (&id, p) in &mut self.customers {
- match &mut p.state {
+ for (&id, state) in &mut self.customers {
+ let Some(player) = game.players.get_mut(&id) else {
+ continue;
+ };
+
+ match state {
CustomerState::Entering { path, chair } => {
- packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt)));
+ player.direction = path.next_direction(player.position());
if path.is_done() {
- let demand = DemandIndex(random::<usize>() % self.data.demands.len());
- packets_out.push((
+ let demand = DemandIndex(random::<usize>() % self.demands.len());
+ self.cpackets.push_back((
id,
PacketS::Communicate {
- message: Some(Message::Item(data.demand(demand).from)),
+ message: Some(Message::Item(self.demands[demand.0].from)),
persist: true,
},
));
info!("{id:?} -> waiting");
- p.state = CustomerState::Waiting {
+ *state = CustomerState::Waiting {
chair: *chair,
timeout: 90. + random::<f32>() * 60.,
demand,
@@ -157,16 +127,17 @@ impl DemandState {
demand,
timeout,
} => {
+ player.direction *= 0.;
*timeout -= dt;
if *timeout <= 0. {
- packets_out.push((
+ self.cpackets.push_back((
id,
PacketS::Communicate {
message: None,
persist: true,
},
));
- packets_out.push((
+ self.cpackets.push_back((
id,
PacketS::Communicate {
message: Some(Message::Effect("angry".to_string())),
@@ -174,24 +145,25 @@ impl DemandState {
},
));
let path = find_path(
- &self.walkable,
- p.movement.position.as_ivec2(),
- data.customer_spawn.as_ivec2(),
+ &game.walkable,
+ player.position().as_ivec2(),
+ game.data.customer_spawn.as_ivec2(),
)
.expect("no path to exit");
*self.chairs.get_mut(&chair).unwrap() = true;
- self.failed += 1;
- *points -= 1;
- self.score_changed = true;
+ game.demands_failed += 1;
+ game.points -= 1;
+ game.score_changed = true;
info!("{id:?} -> exiting");
- p.state = CustomerState::Exiting { path }
+ *state = CustomerState::Exiting { path }
} else {
- let demand_data = &data.demand(*demand);
+ 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 tiles
+ if game
+ .tiles
.get(&pos)
.map(|t| {
t.item
@@ -207,24 +179,26 @@ impl DemandState {
}
});
if let Some(pos) = demand_pos {
- packets_out.push((
+ self.cpackets.push_back((
id,
PacketS::Communicate {
persist: true,
message: None,
},
));
- packets_out.push((
+ self.cpackets.push_back((
id,
PacketS::Communicate {
message: Some(Message::Effect("satisfied".to_string())),
persist: false,
},
));
- packets_out.push((id, PacketS::Interact { pos: Some(pos) }));
- packets_out.push((id, PacketS::Interact { pos: None }));
+ self.cpackets
+ .push_back((id, PacketS::Interact { pos: Some(pos) }));
+ self.cpackets
+ .push_back((id, PacketS::Interact { pos: None }));
info!("{id:?} -> eating");
- p.state = CustomerState::Eating {
+ *state = CustomerState::Eating {
demand: *demand,
target: pos,
progress: 0.,
@@ -239,33 +213,37 @@ impl DemandState {
progress,
chair,
} => {
- let demand = data.demand(*demand);
+ player.direction *= 0.;
+ let demand = &self.demands[demand.0];
*progress += dt / demand.duration;
if *progress >= 1. {
- packets_out.push((id, PacketS::ReplaceHand { item: demand.to }));
+ self.cpackets
+ .push_back((id, PacketS::ReplaceHand { item: demand.to }));
if demand.to.is_some() {
- packets_out.push((id, PacketS::Interact { pos: Some(*target) }));
- packets_out.push((id, PacketS::Interact { pos: None }));
+ self.cpackets
+ .push_back((id, PacketS::Interact { pos: Some(*target) }));
+ self.cpackets
+ .push_back((id, PacketS::Interact { pos: None }));
}
let path = find_path(
- &self.walkable,
- p.movement.position.as_ivec2(),
- data.customer_spawn.as_ivec2(),
+ &game.walkable,
+ player.position().as_ivec2(),
+ game.data.customer_spawn.as_ivec2(),
)
.ok_or(anyhow!("no path to exit"))?;
*self.chairs.get_mut(&chair).unwrap() = true;
- self.completed += 1;
- *points += demand.points;
- self.score_changed = true;
+ game.demands_completed += 1;
+ game.points += demand.points;
+ game.score_changed = true;
info!("{id:?} -> exiting");
- p.state = CustomerState::Exiting { path }
+ *state = CustomerState::Exiting { path }
}
}
CustomerState::Exiting { path } => {
- packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt)));
+ player.direction = path.next_direction(player.position());
if path.is_done() {
info!("{id:?} -> leave");
- packets_out.push((id, PacketS::Leave));
+ self.cpackets.push_back((id, PacketS::Leave));
customers_to_remove.push(id);
}
}
@@ -274,9 +252,15 @@ impl DemandState {
for c in customers_to_remove {
self.customers.remove(&c).unwrap();
}
+ for (player, packet) in self.cpackets.drain(..) {
+ if let Err(err) = game.packet_in(player, packet) {
+ warn!("demand packet {err}");
+ }
+ }
Ok(())
}
-
+}
+impl Customers {
fn select_chair(&mut self) -> Option<IVec2> {
use rand::seq::IteratorRandom;
let (chosen, free) = self
diff --git a/server/src/customer/pathfinding.rs b/server/src/entity/customers/pathfinding.rs
index d1e1e997..97bd8328 100644
--- a/server/src/customer/pathfinding.rs
+++ b/server/src/entity/customers/pathfinding.rs
@@ -15,37 +15,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use hurrycurry_protocol::{
- glam::{IVec2, Vec2}, movement::MovementBase, PacketS
-};
+use hurrycurry_protocol::glam::{IVec2, Vec2};
use log::trace;
use std::{
cmp::Ordering,
collections::{BinaryHeap, HashMap, HashSet},
};
+#[derive(Debug, Clone)]
pub struct Path(Vec<Vec2>);
impl Path {
- pub fn execute_tick(
- &mut self,
- player: &mut MovementBase,
- walkable: &HashSet<IVec2>,
- dt: f32,
- ) -> PacketS {
+ pub fn next_direction(&mut self, position: Vec2) -> Vec2 {
if let Some(next) = self.0.last().copied() {
trace!("next {next}");
- if next.distance(player.position) < if self.0.len() == 1 { 0.1 } else { 0.6 } {
+ if next.distance(position) < if self.0.len() == 1 { 0.1 } else { 0.6 } {
self.0.pop();
}
- player.update(
- &walkable,
- (next - player.position).normalize_or_zero() * 0.5,
- false,
- dt,
- )
+ (next - position).normalize_or_zero() * 0.5
} else {
- player.update(&walkable, Vec2::ZERO, false, dt)
+ Vec2::ZERO
}
}
pub fn is_done(&self) -> bool {
diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs
index a1f690a3..beee9309 100644
--- a/server/src/entity/mod.rs
+++ b/server/src/entity/mod.rs
@@ -16,27 +16,20 @@
*/
pub mod conveyor;
+pub mod customers;
pub mod portal;
-use crate::{
- data::{Gamedata, ItemTileRegistry},
- game::Tile,
-};
+use std::collections::{HashMap, HashSet};
+
+use crate::{data::ItemTileRegistry, game::Game, interaction::Recipe};
use anyhow::{anyhow, Result};
use conveyor::Conveyor;
-use hurrycurry_protocol::{glam::IVec2, PacketC};
+use customers::{demands::generate_demands, Customers};
+use hurrycurry_protocol::{glam::IVec2, ItemIndex, TileIndex};
use portal::Portal;
use serde::{Deserialize, Serialize};
-use std::collections::{HashMap, VecDeque};
pub trait EntityT: Clone {
- fn tick(
- &mut self,
- data: &Gamedata,
- points: &mut i64,
- packet_out: &mut VecDeque<PacketC>,
- tiles: &mut HashMap<IVec2, Tile>,
- dt: f32,
- ) -> Result<()>;
+ fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()>;
}
macro_rules! entities {
@@ -44,14 +37,14 @@ macro_rules! entities {
#[derive(Debug, Clone)]
pub enum Entity { $($e($e)),* }
impl EntityT for Entity {
- fn tick(&mut self, data: &Gamedata, points: &mut i64, packet_out: &mut VecDeque<PacketC>, tiles: &mut HashMap<IVec2, Tile>, dt: f32) -> Result<()> {
- match self { $(Entity::$e(x) => x.tick(data, points, packet_out, tiles, dt)),*, }
+ fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()> {
+ match self { $(Entity::$e(x) => x.tick(game, dt)),*, }
}
}
};
}
-entities!(Conveyor, Portal);
+entities!(Conveyor, Portal, Customers);
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
@@ -68,12 +61,18 @@ pub enum EntityDecl {
from: Option<IVec2>,
to: IVec2,
},
+ Customers {},
}
pub fn construct_entity(
pos: Option<IVec2>,
decl: &EntityDecl,
reg: &ItemTileRegistry,
+ tiles_used: &HashSet<TileIndex>,
+ items_used: &HashSet<ItemIndex>,
+ raw_demands: &[(ItemIndex, Option<ItemIndex>, f32)],
+ recipes: &[Recipe],
+ initial_map: &HashMap<IVec2, (TileIndex, Option<ItemIndex>)>,
) -> Result<Entity> {
Ok(match decl.to_owned() {
EntityDecl::Portal { from, to } => Entity::Portal(Portal {
@@ -101,5 +100,15 @@ pub fn construct_entity(
cooldown: 0.,
})
}
+ EntityDecl::Customers {} => {
+ let demands = generate_demands(tiles_used, items_used, &raw_demands, &recipes);
+ let chair = reg.register_tile("chair".to_string());
+ let chairs = initial_map
+ .iter()
+ .filter(|(_, (tile, _))| *tile == chair)
+ .map(|(e, _)| (*e, true))
+ .collect();
+ Entity::Customers(Customers::new(chairs, demands))
+ }
})
}
diff --git a/server/src/entity/portal.rs b/server/src/entity/portal.rs
index 092a8da5..3aed35ac 100644
--- a/server/src/entity/portal.rs
+++ b/server/src/entity/portal.rs
@@ -16,13 +16,9 @@
*/
use super::EntityT;
-use crate::{
- data::Gamedata,
- game::{interact_effect, Tile},
-};
+use crate::game::{interact_effect, Game};
use anyhow::{anyhow, Result};
-use hurrycurry_protocol::{glam::IVec2, ItemLocation, PacketC};
-use std::collections::{HashMap, VecDeque};
+use hurrycurry_protocol::{glam::IVec2, ItemLocation};
#[derive(Debug, Default, Clone)]
pub struct Portal {
@@ -31,29 +27,23 @@ pub struct Portal {
}
impl EntityT for Portal {
- fn tick(
- &mut self,
- data: &Gamedata,
- points: &mut i64,
- packet_out: &mut VecDeque<PacketC>,
- tiles: &mut HashMap<IVec2, Tile>,
- _dt: f32,
- ) -> Result<()> {
- let [from, to] = tiles
+ fn tick(&mut self, game: &mut Game, _dt: f32) -> Result<()> {
+ let [from, to] = game
+ .tiles
.get_many_mut([&self.from, &self.to])
.ok_or(anyhow!("conveyor does ends in itself"))?;
if from.item.is_some() {
interact_effect(
- data,
+ &game.data,
true,
&mut to.item,
ItemLocation::Tile(self.to),
&mut from.item,
ItemLocation::Tile(self.from),
Some(to.kind),
- packet_out,
- points,
+ &mut game.packet_out,
+ &mut game.points,
true,
);
}
diff --git a/server/src/game.rs b/server/src/game.rs
index b3b23ce0..370c2e8f 100644
--- a/server/src/game.rs
+++ b/server/src/game.rs
@@ -16,7 +16,6 @@
*/
use crate::{
- customer::DemandState,
data::Gamedata,
entity::{Entity, EntityT},
interaction::{interact, tick_slot, InteractEffect, TickEffect},
@@ -32,7 +31,7 @@ use hurrycurry_protocol::{
use log::{info, warn};
use std::{
collections::{HashMap, HashSet, VecDeque},
- sync::Arc,
+ sync::{Arc, RwLock},
time::{Duration, Instant},
};
@@ -62,37 +61,44 @@ pub struct Player {
pub communicate_persist: Option<Message>,
movement: MovementBase,
- direction: Vec2,
- boost: bool,
- last_position_update: Instant,
+ pub direction: Vec2,
+ pub boost: bool,
+ pub last_position_update: Instant,
}
pub struct Game {
pub data: Arc<Gamedata>,
- tiles: HashMap<IVec2, Tile>,
- walkable: HashSet<IVec2>,
+ pub tiles: HashMap<IVec2, Tile>,
+ pub walkable: HashSet<IVec2>,
pub players: HashMap<PlayerID, Player>,
players_spatial_index: SpatialIndex<PlayerID>,
- packet_out: VecDeque<PacketC>,
- demand: Option<DemandState>,
- pub points: i64,
- entities: Vec<Entity>,
+ pub packet_out: VecDeque<PacketC>,
+ entities: Arc<RwLock<Vec<Entity>>>,
end: Option<Instant>,
+ pub lobby: bool,
+
+ pub score_changed: bool,
+ pub points: i64,
+ pub demands_failed: usize,
+ pub demands_completed: usize,
}
impl Game {
pub fn new() -> Self {
Self {
+ lobby: false,
data: Gamedata::default().into(),
packet_out: Default::default(),
players: HashMap::new(),
tiles: HashMap::new(),
walkable: HashSet::new(),
- demand: None,
end: None,
- entities: vec![],
+ entities: Arc::new(RwLock::new(vec![])),
players_spatial_index: SpatialIndex::default(),
points: 0,
+ demands_failed: 0,
+ demands_completed: 0,
+ score_changed: false,
}
}
@@ -111,7 +117,7 @@ impl Game {
neighbors: [None, None, None, None],
})
}
- self.demand = None;
+ self.walkable.clear();
}
pub fn load(&mut self, gamedata: Gamedata, timer: Option<Duration>) {
let players = self
@@ -126,7 +132,7 @@ impl Game {
self.data = gamedata.into();
self.points = 0;
self.end = timer.map(|dur| Instant::now() + dur);
- self.entities = self.data.entities.clone();
+ self.entities = Arc::new(RwLock::new(self.data.entities.clone()));
for (&p, (tile, item)) in &self.data.initial_map {
self.tiles.insert(
@@ -150,7 +156,11 @@ impl Game {
item: None,
character,
movement: MovementBase {
- position: self.data.chef_spawn,
+ position: if character < 0 {
+ self.data.customer_spawn
+ } else {
+ self.data.chef_spawn
+ },
facing: Vec2::X,
rotation: 0.,
velocity: Vec2::ZERO,
@@ -167,17 +177,9 @@ impl Game {
);
}
- if !self.data.demands.is_empty() {
- self.demand = Some(DemandState::new(self.data.clone(), &self.tiles))
- }
-
self.packet_out.extend(self.prime_client());
}
- pub fn tiles(&self) -> &HashMap<IVec2, Tile> {
- &self.tiles
- }
-
pub fn packet_out(&mut self) -> Option<PacketC> {
self.packet_out.pop_front()
}
@@ -249,7 +251,7 @@ impl Game {
out.push(self.score());
out.push(PacketC::SetIngame {
state: true,
- lobby: self.demand.is_none(),
+ lobby: self.lobby,
});
out
}
@@ -258,12 +260,8 @@ impl Game {
PacketC::Score {
time_remaining: self.end.map(|t| (t - Instant::now()).as_secs_f32()),
points: self.points,
- demands_failed: self.demand.as_ref().map(|d| d.failed).unwrap_or_default(),
- demands_completed: self
- .demand
- .as_ref()
- .map(|d| d.completed)
- .unwrap_or_default(),
+ demands_failed: self.demands_failed,
+ demands_completed: self.demands_completed,
}
}
pub fn packet_in(&mut self, player: PlayerID, packet: PacketS) -> Result<()> {
@@ -285,7 +283,11 @@ impl Game {
item: None,
character,
movement: MovementBase {
- position: self.data.chef_spawn,
+ position: if character < 0 {
+ self.data.customer_spawn
+ } else {
+ self.data.chef_spawn
+ },
facing: Vec2::X,
rotation: 0.,
velocity: Vec2::ZERO,
@@ -471,26 +473,9 @@ impl Game {
/// Returns true if the game should end
pub fn tick(&mut self, dt: f32) -> bool {
- if let Some(demand) = &mut self.demand {
- let mut packet_out = Vec::new();
- if let Err(err) = demand.tick(
- &mut packet_out,
- &mut self.tiles,
- &self.data,
- dt,
- &mut self.points,
- ) {
- warn!("demand tick {err}");
- }
- if demand.score_changed {
- demand.score_changed = false;
- self.packet_out.push_back(self.score());
- }
- for (player, packet) in packet_out {
- if let Err(err) = self.packet_in(player, packet) {
- warn!("demand packet {err}");
- }
- }
+ if self.score_changed {
+ self.score_changed = false;
+ self.packet_out.push_back(self.score());
}
for (&pos, tile) in &mut self.tiles {
@@ -583,14 +568,8 @@ impl Game {
let _ = self.packet_in(pid, PacketS::Interact { pos: None });
}
- for entity in &mut self.entities {
- if let Err(e) = entity.tick(
- &self.data,
- &mut self.points,
- &mut self.packet_out,
- &mut self.tiles,
- dt,
- ) {
+ for entity in self.entities.clone().write().unwrap().iter_mut() {
+ if let Err(e) = entity.tick(self, dt) {
warn!("entity tick failed: {e}")
}
}
@@ -686,3 +665,9 @@ pub fn interact_effect(
}
}
}
+
+impl Player {
+ pub fn position(&self) -> Vec2 {
+ self.movement.position
+ }
+}
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 0339b535..a59aad11 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -16,7 +16,6 @@
*/
#![feature(if_let_guard, map_many_mut, let_chains, iterator_try_collect, isqrt)]
-pub mod customer;
pub mod data;
pub mod entity;
pub mod game;
diff --git a/server/src/spatial_index.rs b/server/src/spatial_index.rs
index 4395f0f5..a62c80e0 100644
--- a/server/src/spatial_index.rs
+++ b/server/src/spatial_index.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/>.
+
+*/
use hurrycurry_protocol::glam::Vec2;
use std::{collections::HashMap, hash::Hash};
diff --git a/server/src/state.rs b/server/src/state.rs
index 347e2a92..e637b323 100644
--- a/server/src/state.rs
+++ b/server/src/state.rs
@@ -118,6 +118,15 @@ impl State {
_ => (),
}
self.game.packet_in(player, packet)?;
+ if self.game.players.is_empty() && !self.game.lobby {
+ self.tx
+ .send(PacketC::ServerMessage {
+ text: "Game was aborted automatically due to a lack of players".to_string(),
+ })
+ .ok();
+ self.game
+ .load(self.index.generate("lobby-none".to_string()).await?, None);
+ }
Ok(vec![])
}