From 2a31d26fca33789ccf8ea28cdb214d20dd29f85d Mon Sep 17 00:00:00 2001 From: metamuffin Date: Thu, 18 Jul 2024 12:39:33 +0200 Subject: serve-authorative movement --- server/src/game.rs | 119 ++++++++++++++++++++++++++++++-------------- server/src/lib.rs | 1 + server/src/main.rs | 2 +- server/src/spatial_index.rs | 31 ++++++++++++ 4 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 server/src/spatial_index.rs (limited to 'server/src') 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, pub item: Option, pub communicate_persist: Option, + + movement: MovementBase, + direction: Vec2, + boost: bool, + last_position_update: Instant, } pub struct Game { pub data: Arc, tiles: HashMap, + walkable: HashSet, pub players: HashMap, + players_spatial_index: SpatialIndex, packet_out: VecDeque, demand: Option, 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 { + entries: HashMap, +} + +impl SpatialIndex { + 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 Default for SpatialIndex { + fn default() -> Self { + Self { + entries: Default::default(), + } + } +} -- cgit v1.2.3-70-g09d2