diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/protocol/src/lib.rs | 15 | ||||
-rw-r--r-- | server/protocol/src/movement.rs | 13 | ||||
-rw-r--r-- | server/src/entity/customers/mod.rs | 129 | ||||
-rw-r--r-- | server/src/game.rs | 36 | ||||
-rw-r--r-- | server/src/lib.rs | 3 | ||||
-rw-r--r-- | server/src/main.rs | 13 | ||||
-rw-r--r-- | server/src/state.rs | 59 |
7 files changed, 158 insertions, 110 deletions
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index 02c6d0b1..a56b6edb 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -88,8 +88,11 @@ pub enum PacketS { name: String, character: i32, }, - Leave, + Leave { + player: PlayerID, + }, Movement { + player: PlayerID, #[bincode(with_serde)] direction: Vec2, boosting: bool, @@ -97,15 +100,12 @@ pub enum PacketS { pos: Option<Vec2>, }, Interact { - #[bincode(with_serde)] - pos: Option<IVec2>, - }, - Collide { player: PlayerID, #[bincode(with_serde)] - force: Vec2, + pos: Option<IVec2>, }, Communicate { + player: PlayerID, message: Option<Message>, persist: bool, }, @@ -114,6 +114,7 @@ pub enum PacketS { #[bincode(skip)] /// For internal use only ReplaceHand { + player: PlayerID, item: Option<ItemIndex>, }, /// For use in replay sessions only @@ -138,7 +139,7 @@ pub enum PacketC { major: u32, supports_bincode: bool, }, - Init { + Joined { id: PlayerID, }, Data { diff --git a/server/protocol/src/movement.rs b/server/protocol/src/movement.rs index 286c7f6a..5525c5e6 100644 --- a/server/protocol/src/movement.rs +++ b/server/protocol/src/movement.rs @@ -17,7 +17,7 @@ */ use crate::{ glam::{IVec2, Vec2}, - PacketS, + PacketS, PlayerID, }; use std::collections::HashSet; @@ -48,13 +48,7 @@ impl MovementBase { rotation: 0., } } - pub fn update( - &mut self, - map: &HashSet<IVec2>, - direction: Vec2, - mut boost: bool, - dt: f32, - ) -> PacketS { + pub fn update(&mut self, map: &HashSet<IVec2>, direction: Vec2, mut boost: bool, dt: f32) { let direction = direction.clamp_length_max(1.); if direction.length() > 0.1 { self.facing = direction + (self.facing - direction) * (-dt * 10.).exp(); @@ -73,11 +67,14 @@ impl MovementBase { self.position += self.velocity * dt; self.velocity *= (-dt * PLAYER_FRICTION).exp(); collide_player_tiles(self, map); + } + pub fn movement_packet(&self, direction: Vec2, player: PlayerID) -> PacketS { PacketS::Movement { pos: Some(self.position), boosting: self.boosting, direction, + player, } } diff --git a/server/src/entity/customers/mod.rs b/server/src/entity/customers/mod.rs index b5b9fa42..e6067110 100644 --- a/server/src/entity/customers/mod.rs +++ b/server/src/entity/customers/mod.rs @@ -31,7 +31,7 @@ use std::collections::{HashMap, VecDeque}; #[derive(Debug, Clone)] pub struct Customers { demands: Vec<Demand>, - cpackets: VecDeque<(PlayerID, PacketS)>, + cpackets: VecDeque<PacketS>, chairs: HashMap<IVec2, bool>, customer_id_counter: PlayerID, customers: HashMap<PlayerID, CustomerState>, @@ -84,13 +84,10 @@ impl EntityT for Customers { self.spawn_cooldown = 10. + random::<f32>() * 10.; self.customer_id_counter.0 -= 1; let id = self.customer_id_counter; - self.cpackets.push_back(( - id, - PacketS::Join { - name: faker::name::fr_fr::Name().fake(), - character: -1 - (random::<u16>() as i32), - }, - )); + self.cpackets.push_back(PacketS::Join { + name: faker::name::fr_fr::Name().fake(), + character: -1 - (random::<u16>() as i32), + }); 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) @@ -100,24 +97,22 @@ impl EntityT for Customers { .insert(id, CustomerState::Entering { path, chair }); } let mut customers_to_remove = Vec::new(); - for (&id, state) in &mut self.customers { - let Some(player) = game.players.get_mut(&id) else { + for (&player, state) in &mut self.customers { + let Some(playerdata) = game.players.get_mut(&player) else { continue; }; match state { CustomerState::Entering { path, chair } => { - player.direction = path.next_direction(player.position()); + playerdata.direction = path.next_direction(playerdata.position()); if path.is_done() { let demand = DemandIndex(random::<usize>() % self.demands.len()); - self.cpackets.push_back(( - id, - PacketS::Communicate { - message: Some(Message::Item(self.demands[demand.0].from)), - persist: true, - }, - )); - info!("{id:?} -> waiting"); + 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., @@ -130,26 +125,22 @@ impl EntityT for Customers { demand, timeout, } => { - player.direction = (chair.as_vec2() + 0.5) - player.position(); + playerdata.direction = (chair.as_vec2() + 0.5) - playerdata.position(); *timeout -= dt; if *timeout <= 0. { - self.cpackets.push_back(( - id, - PacketS::Communicate { - message: None, - persist: true, - }, - )); - self.cpackets.push_back(( - id, - PacketS::Communicate { - message: Some(Message::Effect("angry".to_string())), - persist: false, - }, - )); + 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, - player.position().as_ivec2(), + playerdata.position().as_ivec2(), game.data.customer_spawn.as_ivec2(), ) .expect("no path to exit"); @@ -157,7 +148,7 @@ impl EntityT for Customers { game.score.demands_failed += 1; game.score.points -= 1; game.score_changed = true; - info!("{id:?} -> exiting"); + info!("{player:?} -> exiting"); *state = CustomerState::Exiting { path } } else { let demand_data = &self.demands[demand.0]; @@ -182,25 +173,23 @@ impl EntityT for Customers { } }); if let Some(pos) = demand_pos { - self.cpackets.push_back(( - id, - PacketS::Communicate { - persist: true, - message: None, - }, - )); - self.cpackets.push_back(( - id, - PacketS::Communicate { - message: Some(Message::Effect("satisfied".to_string())), - persist: false, - }, - )); - self.cpackets - .push_back((id, PacketS::Interact { pos: Some(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((id, PacketS::Interact { pos: None })); - info!("{id:?} -> eating"); + .push_back(PacketS::Interact { pos: None, player }); + info!("{player:?} -> eating"); *state = CustomerState::Eating { demand: *demand, target: pos, @@ -216,21 +205,25 @@ impl EntityT for Customers { progress, chair, } => { - player.direction = (chair.as_vec2() + 0.5) - player.position(); + playerdata.direction = (chair.as_vec2() + 0.5) - playerdata.position(); let demand = &self.demands[demand.0]; *progress += dt / demand.duration; if *progress >= 1. { - self.cpackets - .push_back((id, PacketS::ReplaceHand { item: demand.to })); + 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((id, PacketS::Interact { pos: Some(*target) })); - self.cpackets - .push_back((id, PacketS::Interact { pos: None })); + .push_back(PacketS::Interact { player, pos: None }); } let path = find_path( &game.walkable, - player.position().as_ivec2(), + playerdata.position().as_ivec2(), game.data.customer_spawn.as_ivec2(), ) .ok_or(anyhow!("no path to exit"))?; @@ -238,16 +231,16 @@ impl EntityT for Customers { game.score.demands_completed += 1; game.score.points += demand.points; game.score_changed = true; - info!("{id:?} -> exiting"); + info!("{player:?} -> exiting"); *state = CustomerState::Exiting { path } } } CustomerState::Exiting { path } => { - player.direction = path.next_direction(player.position()); + playerdata.direction = path.next_direction(playerdata.position()); if path.is_done() { - info!("{id:?} -> leave"); - self.cpackets.push_back((id, PacketS::Leave)); - customers_to_remove.push(id); + info!("{player:?} -> leave"); + self.cpackets.push_back(PacketS::Leave { player }); + customers_to_remove.push(player); } } } @@ -255,8 +248,8 @@ impl EntityT for Customers { 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, &mut vec![], packet_out) { + for packet in self.cpackets.drain(..) { + if let Err(err) = game.packet_in(packet, &mut vec![], packet_out) { warn!("demand packet {err}"); } } diff --git a/server/src/game.rs b/server/src/game.rs index e0154c4c..6477d9fa 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -79,6 +79,8 @@ pub struct Game { pub environment_effects: HashSet<String>, pub score_changed: bool, pub score: Score, + + pub player_id_counter: PlayerID, } impl Default for Game { @@ -101,6 +103,7 @@ impl Game { score: Score::default(), environment_effects: HashSet::default(), score_changed: false, + player_id_counter: PlayerID(1), } } @@ -272,23 +275,24 @@ impl Game { pub fn packet_in( &mut self, - player: PlayerID, packet: PacketS, replies: &mut Vec<PacketC>, packet_out: &mut VecDeque<PacketC>, ) -> Result<()> { match packet { PacketS::Join { name, character } => { - if self.players.contains_key(&player) { + let id = self.player_id_counter; + self.player_id_counter.0 += 1; + if self.players.contains_key(&id) { bail!("You already joined.") } - let position = if player.0 < 0 { + let position = if id.0 < 0 { self.data.customer_spawn } else { self.data.chef_spawn }; self.players.insert( - player, + id, Player { item: None, character, @@ -314,13 +318,14 @@ impl Game { ); self.score.players = self.score.players.max(self.players.len()); packet_out.push_back(PacketC::AddPlayer { - id: player, + id, name, position, character, }); + replies.push(PacketC::Joined { id }) } - PacketS::Leave => { + PacketS::Leave { player } => { let p = self .players .remove(&player) @@ -346,6 +351,7 @@ impl Game { pos, boosting, direction, + player, } => { let player = self .players @@ -366,10 +372,7 @@ impl Game { } } } - PacketS::Collide { player, force } => { - packet_out.push_back(PacketC::Collide { player, force }); - } - PacketS::Interact { pos } => { + PacketS::Interact { pos, player } => { let pid = player; let player = self .players @@ -450,7 +453,11 @@ impl Game { ) } } - PacketS::Communicate { message, persist } => { + PacketS::Communicate { + message, + persist, + player, + } => { info!("{player:?} message {message:?}"); if persist { if let Some(player) = self.players.get_mut(&player) { @@ -463,7 +470,7 @@ impl Game { persist, }) } - PacketS::ReplaceHand { item } => { + PacketS::ReplaceHand { item, player } => { let pdata = self .players .get_mut(&player) @@ -592,10 +599,9 @@ impl Game { } } } - for pid in players_auto_release.drain(..) { + for player in players_auto_release.drain(..) { let _ = self.packet_in( - pid, - PacketS::Interact { pos: None }, + PacketS::Interact { pos: None, player }, &mut vec![], packet_out, ); diff --git a/server/src/lib.rs b/server/src/lib.rs index 2cbcc10b..c8f7af8c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -25,6 +25,9 @@ pub mod state; use hurrycurry_protocol::glam::Vec2; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ConnectionID(pub i64); + pub trait InterpolateExt { fn exp_to(&mut self, target: Self, dt: f32); } diff --git a/server/src/main.rs b/server/src/main.rs index f43f668b..bf203b6f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -18,8 +18,8 @@ use anyhow::{anyhow, Result}; use clap::Parser; use futures_util::{SinkExt, StreamExt}; -use hurrycurry_protocol::{PacketC, PacketS, PlayerID, BINCODE_CONFIG, VERSION}; -use hurrycurry_server::{data::DATA_DIR, state::State}; +use hurrycurry_protocol::{PacketC, PacketS, BINCODE_CONFIG, VERSION}; +use hurrycurry_server::{data::DATA_DIR, state::State, ConnectionID}; use log::{debug, info, trace, warn, LevelFilter}; use std::{ net::SocketAddr, @@ -115,7 +115,7 @@ async fn run(addr: SocketAddr) -> anyhow::Result<()> { }); } - for id in (1..).map(PlayerID) { + for id in (1..).map(ConnectionID) { let (sock, addr) = ws_listener.accept().await?; let Ok(sock) = tokio_tungstenite::accept_async(sock).await else { warn!("invalid ws handshake"); @@ -137,7 +137,6 @@ async fn run(addr: SocketAddr) -> anyhow::Result<()> { supports_bincode: true, }, ); - init.insert(1, PacketC::Init { id }); let supports_binary = Arc::new(AtomicBool::new(false)); let supports_binary2 = supports_binary.clone(); @@ -185,7 +184,7 @@ async fn run(addr: SocketAddr) -> anyhow::Result<()> { }); spawn(async move { - info!("{id:?} joined"); + info!("{id:?} connected"); while let Some(Ok(message)) = read.next().await { let packet = match message { Message::Text(line) => match serde_json::from_str(&line) { @@ -230,8 +229,8 @@ async fn run(addr: SocketAddr) -> anyhow::Result<()> { let _ = error_tx.send(packet).await; } } - info!("{id:?} left"); - state.write().await.packet_in(id, PacketS::Leave).await.ok(); + info!("{id:?} disconnected"); + let _ = state.write().await.disconnect(id).await; }); } Ok(()) diff --git a/server/src/state.rs b/server/src/state.rs index 97261fab..43ca29bd 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -15,18 +15,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -use crate::{data::DataIndex, game::Game}; +use crate::{data::DataIndex, game::Game, ConnectionID}; use anyhow::{anyhow, bail, Result}; use clap::{Parser, ValueEnum}; use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID}; use log::{debug, trace}; -use std::{collections::VecDeque, time::Duration}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + time::Duration, +}; use tokio::sync::broadcast::Sender; pub struct State { index: DataIndex, packet_out: VecDeque<PacketC>, tx: Sender<PacketC>, + connections: HashMap<ConnectionID, HashSet<PlayerID>>, pub game: Game, } @@ -89,6 +93,7 @@ impl State { index, tx, packet_out, + connections: HashMap::new(), }) } @@ -110,14 +115,20 @@ impl State { } Ok(()) } - pub async fn packet_in(&mut self, player: PlayerID, packet: PacketS) -> Result<Vec<PacketC>> { + pub async fn packet_in(&mut self, conn: ConnectionID, packet: PacketS) -> Result<Vec<PacketC>> { + if let Some(p) = get_packet_player(&packet) { + if !self.connections.entry(conn).or_default().contains(&p) { + bail!("Packet sent to player that is not owned by this connection."); + } + } let mut replies = Vec::new(); match &packet { PacketS::Communicate { message: Some(Message::Text(text)), persist: false, + player, } if let Some(command) = text.strip_prefix("/") => { - match self.handle_command_parse(player, command).await { + match self.handle_command_parse(*player, command).await { Ok(()) => return Ok(vec![]), Err(e) => { return Ok(vec![PacketC::ServerMessage { @@ -126,10 +137,27 @@ impl State { } } } + PacketS::Leave { player } => { + self.connections.entry(conn).or_default().remove(player); + } + PacketS::Join { .. } => { + if self.connections.entry(conn).or_default().len() > 8 { + bail!("Players per connection limit exceeded.") + } + } _ => (), } self.game - .packet_in(player, packet, &mut replies, &mut self.packet_out)?; + .packet_in(packet, &mut replies, &mut self.packet_out)?; + + for p in &replies { + match p { + PacketC::Joined { id } => { + self.connections.entry(conn).or_default().insert(*id); + } + _ => (), + } + } if self.game.count_chefs() <= 0 && !self.game.lobby { self.tx @@ -146,6 +174,15 @@ impl State { Ok(replies) } + pub async fn disconnect(&mut self, conn: ConnectionID) { + if let Some(players) = self.connections.get(&conn) { + for player in players.to_owned() { + let _ = self.packet_in(conn, PacketS::Leave { player }).await; + } + } + self.connections.remove(&conn); + } + async fn handle_command_parse(&mut self, player: PlayerID, command: &str) -> Result<()> { self.handle_command( player, @@ -239,3 +276,15 @@ impl State { Ok(()) } } + +fn get_packet_player(packet: &PacketS) -> Option<PlayerID> { + match packet { + PacketS::Join { .. } => None, + PacketS::Leave { player } => Some(*player), + PacketS::Movement { player, .. } => Some(*player), + PacketS::Interact { player, .. } => Some(*player), + PacketS::Communicate { player, .. } => Some(*player), + PacketS::ReplaceHand { player, .. } => Some(*player), + PacketS::ReplayTick { .. } => None, + } +} |