aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-08-10 20:57:06 +0200
committermetamuffin <metamuffin@disroot.org>2024-08-10 20:57:06 +0200
commit7dbb34febaf75572f99fee459a77cf917de05d8f (patch)
tree72ba067aa5003d110f2bb707fb3e9c429388505b /server
parent3d13c85ec2e3acbee249b4baf20797cc38a8a121 (diff)
downloadhurrycurry-7dbb34febaf75572f99fee459a77cf917de05d8f.tar
hurrycurry-7dbb34febaf75572f99fee459a77cf917de05d8f.tar.bz2
hurrycurry-7dbb34febaf75572f99fee459a77cf917de05d8f.tar.zst
Change protocol and server to allow multiple players per connection (untested)
Diffstat (limited to 'server')
-rw-r--r--server/protocol/src/lib.rs15
-rw-r--r--server/protocol/src/movement.rs13
-rw-r--r--server/src/entity/customers/mod.rs129
-rw-r--r--server/src/game.rs36
-rw-r--r--server/src/lib.rs3
-rw-r--r--server/src/main.rs13
-rw-r--r--server/src/state.rs59
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,
+ }
+}