diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/protocol/src/lib.rs | 28 | ||||
-rw-r--r-- | server/src/entity/conveyor.rs | 14 | ||||
-rw-r--r-- | server/src/entity/customers/mod.rs | 14 | ||||
-rw-r--r-- | server/src/entity/mod.rs | 10 | ||||
-rw-r--r-- | server/src/entity/portal.rs | 15 | ||||
-rw-r--r-- | server/src/game.rs | 128 | ||||
-rw-r--r-- | server/src/interaction.rs | 10 | ||||
-rw-r--r-- | server/src/state.rs | 56 |
8 files changed, 163 insertions, 112 deletions
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index cf422761..fcccb107 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -193,12 +193,7 @@ pub enum PacketC { ServerMessage { text: String, }, - Score { - points: i64, - demands_failed: usize, - demands_completed: usize, - time_remaining: Option<f32>, - }, + Score(Score), SetIngame { state: bool, lobby: bool, @@ -206,12 +201,33 @@ pub enum PacketC { Error { message: String, }, + Menu(Menu), MovementSync, /// For use in replay sessions only ReplayStart, } +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] +#[serde(rename_all = "snake_case", tag = "menu", content = "data")] +pub enum Menu { + Book, + Score(Score), +} + +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] +pub struct Score { + pub time_remaining: f64, + pub stars: u8, + pub points: i64, + pub demands_failed: usize, + pub demands_completed: usize, + pub players: usize, + pub active_recipes: usize, + pub passive_recipes: usize, + pub instant_recipes: usize, +} + #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Copy, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum ItemLocation { diff --git a/server/src/entity/conveyor.rs b/server/src/entity/conveyor.rs index 6067c679..7f3d8688 100644 --- a/server/src/entity/conveyor.rs +++ b/server/src/entity/conveyor.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + /* Hurry Curry! - a game about cooking Copyright 2024 metamuffin @@ -18,7 +20,7 @@ use super::EntityT; use crate::game::{interact_effect, Game}; use anyhow::{anyhow, Result}; -use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation}; +use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation, PacketC}; #[derive(Debug, Clone)] pub struct Conveyor { @@ -31,7 +33,7 @@ pub struct Conveyor { } impl EntityT for Conveyor { - fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()> { + fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()> { let from = game .tiles .get(&self.from) @@ -44,7 +46,9 @@ impl EntityT for Conveyor { .get(t) .ok_or(anyhow!("conveyor filter missing"))?; filter_tile.item.as_ref().map(|e| e.kind) - } else { self.filter_item.as_ref().map(|i| *i) }; + } else { + self.filter_item.as_ref().map(|i| *i) + }; if let Some(filter) = filter { if from_item.kind != filter { @@ -71,8 +75,8 @@ impl EntityT for Conveyor { &mut from.item, ItemLocation::Tile(self.from), Some(to.kind), - &mut game.packet_out, - &mut game.points, + packet_out, + &mut game.score, true, ); } diff --git a/server/src/entity/customers/mod.rs b/server/src/entity/customers/mod.rs index 7a8280bc..974ae686 100644 --- a/server/src/entity/customers/mod.rs +++ b/server/src/entity/customers/mod.rs @@ -22,7 +22,7 @@ use super::EntityT; use crate::{data::Demand, game::Game}; use anyhow::{anyhow, Result}; use fake::{faker, Fake}; -use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketS, PlayerID}; +use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketC, PacketS, PlayerID}; use log::{info, warn}; use pathfinding::{find_path, Path}; use rand::{random, thread_rng}; @@ -74,7 +74,7 @@ impl Customers { } impl EntityT for Customers { - fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()> { + 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. { @@ -151,8 +151,8 @@ impl EntityT for Customers { ) .expect("no path to exit"); *self.chairs.get_mut(chair).unwrap() = true; - game.demands_failed += 1; - game.points -= 1; + game.score.demands_failed += 1; + game.score.points -= 1; game.score_changed = true; info!("{id:?} -> exiting"); *state = CustomerState::Exiting { path } @@ -232,8 +232,8 @@ impl EntityT for Customers { ) .ok_or(anyhow!("no path to exit"))?; *self.chairs.get_mut(chair).unwrap() = true; - game.demands_completed += 1; - game.points += demand.points; + game.score.demands_completed += 1; + game.score.points += demand.points; game.score_changed = true; info!("{id:?} -> exiting"); *state = CustomerState::Exiting { path } @@ -253,7 +253,7 @@ impl EntityT for Customers { self.customers.remove(&c).unwrap(); } for (player, packet) in self.cpackets.drain(..) { - if let Err(err) = game.packet_in(player, packet, &mut vec![]) { + if let Err(err) = game.packet_in(player, packet, &mut vec![], packet_out) { warn!("demand packet {err}"); } } diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index c471a6d4..ec5ec744 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -18,18 +18,18 @@ pub mod conveyor; pub mod customers; pub mod portal; -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap, HashSet, VecDeque}; use crate::{data::ItemTileRegistry, game::Game, interaction::Recipe}; use anyhow::{anyhow, Result}; use conveyor::Conveyor; use customers::{demands::generate_demands, Customers}; -use hurrycurry_protocol::{glam::IVec2, ItemIndex, TileIndex}; +use hurrycurry_protocol::{glam::IVec2, ItemIndex, PacketC, TileIndex}; use portal::Portal; use serde::{Deserialize, Serialize}; pub trait EntityT: Clone { - fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()>; + fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()>; } macro_rules! entities { @@ -37,8 +37,8 @@ macro_rules! entities { #[derive(Debug, Clone)] pub enum Entity { $($e($e)),* } impl EntityT for Entity { - fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()> { - match self { $(Entity::$e(x) => x.tick(game, dt)),*, } + 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)),*, } } } }; diff --git a/server/src/entity/portal.rs b/server/src/entity/portal.rs index 3aed35ac..e0ec353f 100644 --- a/server/src/entity/portal.rs +++ b/server/src/entity/portal.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + /* Hurry Curry! - a game about cooking Copyright 2024 metamuffin @@ -18,7 +20,7 @@ use super::EntityT; use crate::game::{interact_effect, Game}; use anyhow::{anyhow, Result}; -use hurrycurry_protocol::{glam::IVec2, ItemLocation}; +use hurrycurry_protocol::{glam::IVec2, ItemLocation, PacketC}; #[derive(Debug, Default, Clone)] pub struct Portal { @@ -27,7 +29,12 @@ pub struct Portal { } impl EntityT for Portal { - fn tick(&mut self, game: &mut Game, _dt: f32) -> Result<()> { + fn tick( + &mut self, + game: &mut Game, + packet_out: &mut VecDeque<PacketC>, + _dt: f32, + ) -> Result<()> { let [from, to] = game .tiles .get_many_mut([&self.from, &self.to]) @@ -42,8 +49,8 @@ impl EntityT for Portal { &mut from.item, ItemLocation::Tile(self.from), Some(to.kind), - &mut game.packet_out, - &mut game.points, + packet_out, + &mut game.score, true, ); } diff --git a/server/src/game.rs b/server/src/game.rs index 9a01e4a3..21bb5f33 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -25,8 +25,8 @@ use anyhow::{anyhow, bail, Result}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, movement::MovementBase, - ClientGamedata, ItemIndex, ItemLocation, Message, PacketC, PacketS, PlayerID, RecipeIndex, - TileIndex, + ClientGamedata, ItemIndex, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID, + RecipeIndex, Score, TileIndex, }; use log::{info, warn}; use std::{ @@ -72,15 +72,12 @@ pub struct Game { pub walkable: HashSet<IVec2>, pub players: HashMap<PlayerID, Player>, players_spatial_index: SpatialIndex<PlayerID>, - 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, + pub score: Score, } impl Default for Game { @@ -94,30 +91,27 @@ impl Game { Self { lobby: false, data: Gamedata::default().into(), - packet_out: Default::default(), players: HashMap::new(), tiles: HashMap::new(), walkable: HashSet::new(), end: None, entities: Arc::new(RwLock::new(vec![])), players_spatial_index: SpatialIndex::default(), - points: 0, - demands_failed: 0, - demands_completed: 0, + score: Score::default(), score_changed: false, } } - fn unload(&mut self) { - self.packet_out.push_back(PacketC::SetIngame { + fn unload(&mut self, packet_out: &mut VecDeque<PacketC>) { + packet_out.push_back(PacketC::SetIngame { state: false, lobby: false, }); for (id, _) in self.players.drain() { - self.packet_out.push_back(PacketC::RemovePlayer { id }) + packet_out.push_back(PacketC::RemovePlayer { id }) } for (pos, _) in self.tiles.drain() { - self.packet_out.push_back(PacketC::UpdateMap { + packet_out.push_back(PacketC::UpdateMap { tile: pos, kind: None, neighbors: [None, None, None, None], @@ -125,7 +119,12 @@ impl Game { } self.walkable.clear(); } - pub fn load(&mut self, gamedata: Gamedata, timer: Option<Duration>) { + pub fn load( + &mut self, + gamedata: Gamedata, + timer: Option<Duration>, + packet_out: &mut VecDeque<PacketC>, + ) { let players = self .players .iter() @@ -133,11 +132,11 @@ impl Game { .map(|(id, p)| (*id, (p.name.to_owned(), p.character))) .collect::<HashMap<_, _>>(); - self.unload(); + self.unload(packet_out); self.lobby = gamedata.map_name == "lobby"; self.data = gamedata.into(); - self.points = 0; + self.score = Score::default(); self.end = timer.map(|dur| Instant::now() + dur); self.entities = Arc::new(RwLock::new(self.data.entities.clone())); @@ -184,11 +183,7 @@ impl Game { ); } - self.packet_out.extend(self.prime_client()); - } - - pub fn packet_out(&mut self) -> Option<PacketC> { - self.packet_out.pop_front() + packet_out.extend(self.prime_client()); } pub fn prime_client(&self) -> Vec<PacketC> { @@ -256,7 +251,7 @@ impl Game { }) } } - out.push(self.score()); + out.push(PacketC::Score(self.score.clone())); out.push(PacketC::SetIngame { state: true, lobby: self.lobby, @@ -264,22 +259,13 @@ impl Game { out } - pub fn score(&self) -> PacketC { - PacketC::Score { - time_remaining: self.end.map(|t| (t - Instant::now()).as_secs_f32()), - points: self.points, - demands_failed: self.demands_failed, - demands_completed: self.demands_completed, - } - } pub fn packet_in( &mut self, player: PlayerID, packet: PacketS, replies: &mut Vec<PacketC>, + packet_out: &mut VecDeque<PacketC>, ) -> Result<()> { - let points_before = self.points; - match packet { PacketS::Join { name, character } => { if self.players.contains_key(&player) { @@ -315,7 +301,7 @@ impl Game { name: name.clone(), }, ); - self.packet_out.push_back(PacketC::AddPlayer { + packet_out.push_back(PacketC::AddPlayer { id: player, name, position, @@ -334,7 +320,7 @@ impl Game { let pos = p.movement.position.floor().as_ivec2(); if let Some(tile) = self.tiles.get_mut(&pos) { if tile.item.is_none() { - self.packet_out.push_back(PacketC::SetItem { + packet_out.push_back(PacketC::SetItem { location: ItemLocation::Tile(pos), item: Some(item.kind), }); @@ -342,8 +328,7 @@ impl Game { } } } - self.packet_out - .push_back(PacketC::RemovePlayer { id: player }) + packet_out.push_back(PacketC::RemovePlayer { id: player }) } PacketS::Movement { pos, @@ -370,8 +355,7 @@ impl Game { } } PacketS::Collide { player, force } => { - self.packet_out - .push_back(PacketC::Collide { player, force }); + packet_out.push_back(PacketC::Collide { player, force }); } PacketS::Interact { pos } => { let pid = player; @@ -428,8 +412,8 @@ impl Game { &mut other.item, ItemLocation::Player(pid), None, - &mut self.packet_out, - &mut self.points, + packet_out, + &mut self.score, false, ) } else { @@ -446,8 +430,8 @@ impl Game { &mut player.item, ItemLocation::Player(pid), Some(tile.kind), - &mut self.packet_out, - &mut self.points, + packet_out, + &mut self.score, false, ) } @@ -459,7 +443,7 @@ impl Game { player.communicate_persist = message.clone() } } - self.packet_out.push_back(PacketC::Communicate { + packet_out.push_back(PacketC::Communicate { player, message, persist, @@ -474,31 +458,33 @@ impl Game { kind: i, active: None, }); - self.packet_out.push_back(PacketC::SetItem { + packet_out.push_back(PacketC::SetItem { location: ItemLocation::Player(player), item, }) } PacketS::ReplayTick { .. } => bail!("packet not supported in this session"), } - - if self.points != points_before { - self.packet_out.push_back(self.score()) - } Ok(()) } /// Returns true if the game should end - pub fn tick(&mut self, dt: f32) -> bool { + pub fn tick(&mut self, dt: f32, packet_out: &mut VecDeque<PacketC>) -> bool { if self.score_changed { self.score_changed = false; - self.packet_out.push_back(self.score()); + packet_out.push_back(PacketC::Score(self.score.clone())); } for (&pos, tile) in &mut self.tiles { - if let Some(effect) = tick_slot(dt, &self.data, Some(tile.kind), &mut tile.item) { + if let Some(effect) = tick_slot( + dt, + &self.data, + Some(tile.kind), + &mut tile.item, + &mut self.score, + ) { match effect { - TickEffect::Progress(warn) => self.packet_out.push_back(PacketC::SetProgress { + TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress { warn, item: ItemLocation::Tile(pos), progress: tile @@ -510,7 +496,7 @@ impl Game { .map(|i| i.progress), }), TickEffect::Produce => { - self.packet_out.push_back(PacketC::SetItem { + packet_out.push_back(PacketC::SetItem { location: ItemLocation::Tile(pos), item: tile.item.as_ref().map(|i| i.kind), }); @@ -537,16 +523,17 @@ impl Game { }); for (&pid, player) in &mut self.players { - self.packet_out.push_back(PacketC::Position { + packet_out.push_back(PacketC::Position { player: pid, pos: player.movement.position, boosting: player.movement.boosting, rot: player.movement.rotation, }); - if let Some(effect) = tick_slot(dt, &self.data, None, &mut player.item) { + if let Some(effect) = tick_slot(dt, &self.data, None, &mut player.item, &mut self.score) + { match effect { - TickEffect::Progress(warn) => self.packet_out.push_back(PacketC::SetProgress { + TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress { warn, item: ItemLocation::Player(pid), progress: player @@ -558,7 +545,7 @@ impl Game { .map(|i| i.progress), }), TickEffect::Produce => { - self.packet_out.push_back(PacketC::SetItem { + packet_out.push_back(PacketC::SetItem { location: ItemLocation::Player(pid), item: player.item.as_ref().map(|i| i.kind), }); @@ -582,16 +569,31 @@ impl Game { } } for pid in players_auto_release.drain(..) { - let _ = self.packet_in(pid, PacketS::Interact { pos: None }, &mut vec![]); + let _ = self.packet_in( + pid, + PacketS::Interact { pos: None }, + &mut vec![], + packet_out, + ); } for entity in self.entities.clone().write().unwrap().iter_mut() { - if let Err(e) = entity.tick(self, dt) { + if let Err(e) = entity.tick(self, packet_out, dt) { warn!("entity tick failed: {e}") } } - self.end.map(|t| t < Instant::now()).unwrap_or_default() + if let Some(end) = self.end { + self.score.time_remaining = (end - Instant::now()).as_secs_f64(); + if end < Instant::now() { + packet_out.push_back(PacketC::Menu(Menu::Score(self.score.clone()))); + true + } else { + false + } + } else { + false + } } pub fn count_chefs(&self) -> usize { @@ -617,13 +619,13 @@ pub fn interact_effect( other_loc: ItemLocation, this_tile_kind: Option<TileIndex>, packet_out: &mut VecDeque<PacketC>, - points: &mut i64, + score: &mut Score, automated: bool, ) { let this_had_item = this.is_some(); let other_had_item = other.is_some(); - if let Some(effect) = interact(data, edge, this_tile_kind, this, other, points, automated) { + if let Some(effect) = interact(data, edge, this_tile_kind, this, other, score, automated) { match effect { InteractEffect::Put => { info!("put {this_loc} <- {other_loc}"); diff --git a/server/src/interaction.rs b/server/src/interaction.rs index 2f6c940a..71125ac4 100644 --- a/server/src/interaction.rs +++ b/server/src/interaction.rs @@ -19,7 +19,7 @@ use crate::{ data::Gamedata, game::{Involvement, Item}, }; -use hurrycurry_protocol::{ItemIndex, TileIndex}; +use hurrycurry_protocol::{ItemIndex, Score, TileIndex}; use log::info; use serde::{Deserialize, Serialize}; @@ -115,7 +115,7 @@ pub fn interact( tile: Option<TileIndex>, this: &mut Option<Item>, other: &mut Option<Item>, - points: &mut i64, + score: &mut Score, automated: bool, ) -> Option<InteractEffect> { let interactable = automated @@ -180,6 +180,7 @@ pub fn interact( }); } *this = Some(item); + score.active_recipes += 1; return Some(InteractEffect::Put); } } @@ -200,7 +201,8 @@ pub fn interact( let ok_rev = ok_rev as usize; *other = outputs[1 - ok_rev].map(|kind| Item { kind, active: None }); *this = outputs[ok_rev].map(|kind| Item { kind, active: None }); - *points += pd; + score.points += pd; + score.instant_recipes += 1; return Some(InteractEffect::Produce); } } @@ -234,6 +236,7 @@ pub fn tick_slot( data: &Gamedata, tile: Option<TileIndex>, slot: &mut Option<Item>, + score: &mut Score, ) -> Option<TickEffect> { if let Some(item) = slot { if let Some(a) = &mut item.active { @@ -246,6 +249,7 @@ pub fn tick_slot( if a.progress >= 1. { if let Recipe::Passive { output, .. } = &data.recipe(a.recipe) { *slot = output.map(|kind| Item { kind, active: None }); + score.passive_recipes += 1; return Some(TickEffect::Produce); }; a.progress = 1.; diff --git a/server/src/state.rs b/server/src/state.rs index 61795da4..82540fd6 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -20,11 +20,12 @@ use anyhow::{anyhow, bail, Result}; use clap::{Parser, ValueEnum}; use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID}; use log::{debug, trace}; -use std::time::Duration; +use std::{collections::VecDeque, time::Duration}; use tokio::sync::broadcast::Sender; pub struct State { index: DataIndex, + packet_out: VecDeque<PacketC>, tx: Sender<PacketC>, pub game: Game, } @@ -74,29 +75,37 @@ impl State { let mut index = DataIndex::default(); index.reload()?; + let mut packet_out = VecDeque::new(); let mut game = Game::new(); - game.load(index.generate("lobby-none".to_string()).await?, None); + game.load( + index.generate("lobby-none".to_string()).await?, + None, + &mut packet_out, + ); - Ok(Self { game, index, tx }) + Ok(Self { + game, + index, + tx, + packet_out, + }) } pub async fn tick(&mut self, dt: f32) -> anyhow::Result<()> { - if self.game.tick(dt) { - self.tx - .send(PacketC::ServerMessage { - text: format!("Game finished. You reached {} points.", self.game.points), - }) - .ok(); - self.game - .load(self.index.generate("lobby-none".to_string()).await?, None); + if self.game.tick(dt, &mut self.packet_out) { + self.game.load( + self.index.generate("lobby-none".to_string()).await?, + None, + &mut self.packet_out, + ); } - while let Some(p) = self.game.packet_out() { + while let Some(p) = self.packet_out.pop_front() { if matches!(p, PacketC::UpdateMap { .. } | PacketC::Position { .. }) { trace!("-> {p:?}"); } else { debug!("-> {p:?}"); } - let _ = self.tx.send(p); + self.tx.send(p).unwrap(); } Ok(()) } @@ -118,15 +127,19 @@ impl State { } _ => (), } - self.game.packet_in(player, packet, &mut replies)?; + self.game + .packet_in(player, packet, &mut replies, &mut self.packet_out)?; 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); + self.game.load( + self.index.generate("lobby-none".to_string()).await?, + None, + &mut self.packet_out, + ); } Ok(replies) } @@ -148,7 +161,8 @@ impl State { match command { Command::Start { spec, timer } => { let data = self.index.generate(spec).await?; - self.game.load(data, Some(Duration::from_secs(timer))); + self.game + .load(data, Some(Duration::from_secs(timer)), &mut self.packet_out); } Command::End => { self.tx @@ -163,8 +177,11 @@ impl State { ), }) .ok(); - self.game - .load(self.index.generate("lobby-none".to_string()).await?, None); + self.game.load( + self.index.generate("lobby-none".to_string()).await?, + None, + &mut self.packet_out, + ); } Command::Reload => { if self.game.count_chefs() > 1 { @@ -173,6 +190,7 @@ impl State { self.game.load( self.index.generate(self.game.data.spec.to_string()).await?, None, + &mut self.packet_out, ); } Command::ReloadIndex => { |