diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/examples/client.rs | 70 | ||||
-rw-r--r-- | server/protocol/src/lib.rs | 7 | ||||
-rw-r--r-- | server/protocol/src/movement.rs | 21 | ||||
-rw-r--r-- | server/src/game.rs | 119 | ||||
-rw-r--r-- | server/src/lib.rs | 1 | ||||
-rw-r--r-- | server/src/main.rs | 2 | ||||
-rw-r--r-- | server/src/spatial_index.rs | 31 |
7 files changed, 135 insertions, 116 deletions
diff --git a/server/examples/client.rs b/server/examples/client.rs deleted file mode 100644 index 70b1bb00..00000000 --- a/server/examples/client.rs +++ /dev/null @@ -1,70 +0,0 @@ -/* - 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, PacketC, PacketS}; -use std::{ - io::{stdin, BufRead, BufReader, Write}, - net::TcpStream, - thread, -}; - -fn main() { - let mut sock = TcpStream::connect("127.0.0.1:27031").unwrap(); - - let sock2 = BufReader::new(sock.try_clone().unwrap()); - thread::spawn(move || { - for line in sock2.lines() { - let line = line.unwrap(); - let packet: PacketC = serde_json::from_str(&line).unwrap(); - eprintln!("{packet:?}") - } - }); - - for line in stdin().lines() { - let line = line.unwrap(); - let mut toks = line.split(" "); - let packet = match toks.next().unwrap() { - "j" => PacketS::Join { - character: 0, - name: "test".to_string(), - }, - "p" => PacketS::Position { - pos: Vec2::new( - toks.next().unwrap().parse().unwrap(), - toks.next().unwrap().parse().unwrap(), - ), - boosting: false, - rot: 0., - }, - "i" => PacketS::Position { - pos: Vec2::new( - toks.next().unwrap().parse().unwrap(), - toks.next().unwrap().parse().unwrap(), - ), - boosting: false, - rot: toks.next().unwrap_or("0").parse().unwrap(), - }, - _ => { - println!("unknown"); - continue; - } - }; - sock.write_all(serde_json::to_string(&packet).unwrap().as_bytes()) - .unwrap(); - sock.write_all(b"\n").unwrap(); - } -} diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index f2a6dd27..14c410b3 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -88,11 +88,12 @@ pub enum PacketS { character: i32, }, Leave, - Position { + Movement { #[bincode(with_serde)] - pos: Vec2, - rot: f32, + direction: Vec2, boosting: bool, + #[bincode(with_serde)] + pos: Option<Vec2>, }, Interact { #[bincode(with_serde)] diff --git a/server/protocol/src/movement.rs b/server/protocol/src/movement.rs index 9abff295..ab4eb818 100644 --- a/server/protocol/src/movement.rs +++ b/server/protocol/src/movement.rs @@ -72,21 +72,32 @@ impl MovementBase { self.velocity += direction * dt * speed; self.position += self.velocity * dt; self.velocity = self.velocity * (-dt * PLAYER_FRICTION).exp(); - collide_player(self, map); + collide_player_tiles(self, map); - PacketS::Position { - pos: self.position, + PacketS::Movement { + pos: Some(self.position), boosting: self.boosting, - rot: self.rotation, + direction, } } + pub fn collide(&mut self, other: &mut Self, dt: f32) { + let diff = self.position - other.position; + let d = diff.length(); + if d < 0.01 || d > PLAYER_SIZE * 2. { + return; + } + let norm = diff.normalize(); + let f = 100. / (1. + d); + self.velocity += norm * f * dt + } + pub fn get_interact_target(&self) -> IVec2 { (self.position + Vec2::new(self.rotation.sin(), self.rotation.cos())).as_ivec2() } } -pub fn collide_player(p: &mut MovementBase, map: &HashSet<IVec2>) { +fn collide_player_tiles(p: &mut MovementBase, map: &HashSet<IVec2>) { for xo in -1..=1 { for yo in -1..=1 { let tile = IVec2::new(xo, yo) + p.position.as_ivec2(); diff --git a/server/src/game.rs b/server/src/game.rs index 1c50c7c2..b3b23ce0 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -20,16 +20,18 @@ use crate::{ data::Gamedata, entity::{Entity, EntityT}, interaction::{interact, tick_slot, InteractEffect, TickEffect}, + spatial_index::SpatialIndex, }; use anyhow::{anyhow, bail, Result}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, + movement::MovementBase, ClientGamedata, ItemIndex, ItemLocation, Message, PacketC, PacketS, PlayerID, RecipeIndex, TileIndex, }; use log::{info, warn}; use std::{ - collections::{HashMap, VecDeque}, + collections::{HashMap, HashSet, VecDeque}, sync::Arc, time::{Duration, Instant}, }; @@ -55,17 +57,22 @@ pub struct Tile { pub struct Player { pub name: String, pub character: i32, - pub position: Vec2, - pub last_position_ts: Instant, pub interacting: Option<IVec2>, pub item: Option<Item>, pub communicate_persist: Option<Message>, + + movement: MovementBase, + direction: Vec2, + boost: bool, + last_position_update: Instant, } pub struct Game { pub data: Arc<Gamedata>, tiles: HashMap<IVec2, Tile>, + walkable: HashSet<IVec2>, pub players: HashMap<PlayerID, Player>, + players_spatial_index: SpatialIndex<PlayerID>, packet_out: VecDeque<PacketC>, demand: Option<DemandState>, pub points: i64, @@ -80,9 +87,11 @@ impl Game { packet_out: Default::default(), players: HashMap::new(), tiles: HashMap::new(), + walkable: HashSet::new(), demand: None, end: None, entities: vec![], + players_spatial_index: SpatialIndex::default(), points: 0, } } @@ -130,15 +139,27 @@ impl Game { }), }, ); + if !self.data.tile_collide[tile.0] { + self.walkable.insert(p); + } } for (id, (name, character)) in players { self.players.insert( id, Player { item: None, - last_position_ts: Instant::now(), character, - position: self.data.chef_spawn, + movement: MovementBase { + position: self.data.chef_spawn, + facing: Vec2::X, + rotation: 0., + velocity: Vec2::ZERO, + boosting: false, + stamina: 0., + }, + last_position_update: Instant::now(), + boost: false, + direction: Vec2::ZERO, communicate_persist: None, interacting: None, name: name.clone(), @@ -189,7 +210,7 @@ impl Game { for (&id, player) in &self.players { out.push(PacketC::AddPlayer { id, - position: player.position, + position: player.movement.position, character: player.character, name: player.name.clone(), }); @@ -262,9 +283,18 @@ impl Game { player, Player { item: None, - last_position_ts: Instant::now(), character, - position, + movement: MovementBase { + position: self.data.chef_spawn, + facing: Vec2::X, + rotation: 0., + velocity: Vec2::ZERO, + boosting: false, + stamina: 0., + }, + last_position_update: Instant::now(), + boost: false, + direction: Vec2::ZERO, communicate_persist: None, interacting: None, name: name.clone(), @@ -282,8 +312,11 @@ impl Game { .players .remove(&player) .ok_or(anyhow!("player does not exist"))?; + + self.players_spatial_index.remove_entry(player); + if let Some(item) = p.item { - let pos = p.position.floor().as_ivec2(); + 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 { @@ -297,37 +330,25 @@ impl Game { self.packet_out .push_back(PacketC::RemovePlayer { id: player }) } - PacketS::Position { pos, rot, boosting } => { - let pid = player; + PacketS::Movement { + pos, + boosting, + direction, + } => { let player = self .players .get_mut(&player) .ok_or(anyhow!("player does not exist"))?; - // let dt = player.last_position_ts.elapsed().as_secs_f32(); - // let dist = pos.distance(player.position); - // let speed = dist / dt; - // let interact_dist = player - // .interacting - // .map(|p| (p.as_vec2() + Vec2::splat(0.5)).distance(player.position)) - // .unwrap_or_default(); - // let movement_ok = speed < PLAYER_SPEED_LIMIT && dist < 1. && interact_dist < 2.; - // if movement_ok { - player.position = pos; - player.last_position_ts = Instant::now(); - // } - self.packet_out.push_back(PacketC::Position { - player: pid, - pos: player.position, - rot, - boosting, - }); - // if !movement_ok { - // bail!( - // "{:?} moved to quickly. speed={speed:.02} dist={dist:.02}", - // player.name - // ) - // } + player.direction = direction; + player.boost = boosting; + + if let Some(pos) = pos { + let dt = player.last_position_update.elapsed(); + player.last_position_update += dt; + player.movement.position += + (pos - player.movement.position).clamp_length_max(dt.as_secs_f32()); + } } PacketS::Collide { player, force } => { self.packet_out @@ -348,7 +369,7 @@ impl Game { }; let entpos = pos.as_vec2() + Vec2::splat(0.5); - if edge && entpos.distance(player.position) > 2. { + if edge && entpos.distance(player.movement.position) > 2. { bail!("interacting too far from player"); } @@ -364,7 +385,7 @@ impl Game { let other_pid = if !self.data.is_tile_interactable(tile.kind) { self.players .iter() - .find(|(id, p)| **id != pid && p.position.distance(entpos) < 0.7) + .find(|(id, p)| **id != pid && p.movement.position.distance(entpos) < 0.7) .map(|(&id, _)| id) } else { None @@ -497,6 +518,30 @@ impl Game { } for (&pid, player) in &mut self.players { + player + .movement + .update(&self.walkable, player.direction, player.boost, dt); + + self.players_spatial_index + .update_entry(pid, player.movement.position); + } + + self.players_spatial_index.all(|p1, pos1| { + self.players_spatial_index.query(pos1, 2., |p2, _pos2| { + if let Some([a, b]) = self.players.get_many_mut([&p1, &p2]) { + a.movement.collide(&mut b.movement, dt) + } + }) + }); + + for (&pid, player) in &mut self.players { + self.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) { match effect { TickEffect::Progress(warn) => self.packet_out.push_back(PacketC::SetProgress { diff --git a/server/src/lib.rs b/server/src/lib.rs index afd6f1db..0339b535 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -22,3 +22,4 @@ pub mod entity; pub mod game; pub mod interaction; pub mod state; +pub mod spatial_index; diff --git a/server/src/main.rs b/server/src/main.rs index 6f73851a..1cee94cf 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -207,7 +207,7 @@ async fn run() -> anyhow::Result<()> { if matches!( packet, - PacketS::Position { .. } | PacketS::ReplayTick { .. } + PacketS::Movement { .. } | PacketS::ReplayTick { .. } ) { trace!("<- {id:?} {packet:?}"); } else { diff --git a/server/src/spatial_index.rs b/server/src/spatial_index.rs new file mode 100644 index 00000000..4395f0f5 --- /dev/null +++ b/server/src/spatial_index.rs @@ -0,0 +1,31 @@ +use hurrycurry_protocol::glam::Vec2; +use std::{collections::HashMap, hash::Hash}; + +// TODO stub implementation. please implement +pub struct SpatialIndex<T> { + entries: HashMap<T, Vec2>, +} + +impl<T: Eq + Hash + Copy> SpatialIndex<T> { + pub fn update_entry(&mut self, id: T, position: Vec2) { + self.entries.insert(id, position); + } + pub fn remove_entry(&mut self, id: T) { + self.entries.remove(&id); + } + pub fn all(&self, mut cb: impl FnMut(T, Vec2)) { + for (&e, &pos) in &self.entries { + cb(e, pos) + } + } + pub fn query(&self, _position: Vec2, _radius: f32, cb: impl FnMut(T, Vec2)) { + self.all(cb) + } +} +impl<T> Default for SpatialIndex<T> { + fn default() -> Self { + Self { + entries: Default::default(), + } + } +} |