diff options
author | metamuffin <metamuffin@disroot.org> | 2024-07-18 15:42:11 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-07-18 15:42:11 +0200 |
commit | 5f883c80e7fc63c97910d003c44aea814ab8a5d6 (patch) | |
tree | b73a8c8f78db103671128e686136f08aa276923a | |
parent | efc29c03f7be043ae8d037a93efce8cfa7c384cc (diff) | |
download | hurrycurry-5f883c80e7fc63c97910d003c44aea814ab8a5d6.tar hurrycurry-5f883c80e7fc63c97910d003c44aea814ab8a5d6.tar.bz2 hurrycurry-5f883c80e7fc63c97910d003c44aea814ab8a5d6.tar.zst |
reimplement customers as entity
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, ®)?); - } + 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, + ®, + &tiles_used, + &items_used, + &raw_demands, + &recipes, + &initial_map, + )?); + } + } + } + entities.extend( map_in .entities .iter() - .map(|decl| construct_entity(None, decl, ®)) + .map(|decl| { + construct_entity( + None, + decl, + ®, + &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![]) } |