summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/examples/client.rs70
-rw-r--r--server/protocol/src/lib.rs7
-rw-r--r--server/protocol/src/movement.rs21
-rw-r--r--server/src/game.rs119
-rw-r--r--server/src/lib.rs1
-rw-r--r--server/src/main.rs2
-rw-r--r--server/src/spatial_index.rs31
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(),
+ }
+ }
+}