diff options
author | metamuffin <metamuffin@disroot.org> | 2024-08-11 01:30:59 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-08-11 01:35:05 +0200 |
commit | 218da36b4f963a5dfcd619d543cb95c5c196d214 (patch) | |
tree | 244ecd10f4ca3d9798cedf7149394a1d4827b5d9 | |
parent | f753629acd906ff7acd42a18a266cc93a883330c (diff) | |
download | hurrycurry-218da36b4f963a5dfcd619d543cb95c5c196d214.tar hurrycurry-218da36b4f963a5dfcd619d543cb95c5c196d214.tar.bz2 hurrycurry-218da36b4f963a5dfcd619d543cb95c5c196d214.tar.zst |
update movement in protocol and all clients
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | client/game.gd | 4 | ||||
-rw-r--r-- | client/multiplayer.gd | 21 | ||||
-rw-r--r-- | pixel-client/src/game.rs | 17 | ||||
-rw-r--r-- | server/bot/Cargo.toml | 2 | ||||
-rw-r--r-- | server/bot/src/main.rs | 59 | ||||
-rw-r--r-- | server/client-lib/src/lib.rs | 44 | ||||
-rw-r--r-- | server/client-lib/src/network/sync.rs | 14 | ||||
-rw-r--r-- | server/protocol/src/lib.rs | 12 | ||||
-rw-r--r-- | server/protocol/src/movement.rs | 19 | ||||
-rw-r--r-- | server/src/entity/customers/mod.rs | 16 | ||||
-rw-r--r-- | server/src/game.rs | 28 | ||||
-rw-r--r-- | server/src/state.rs | 2 | ||||
-rw-r--r-- | test-client/main.ts | 6 | ||||
-rw-r--r-- | test-client/protocol.ts | 6 |
15 files changed, 179 insertions, 73 deletions
@@ -280,7 +280,9 @@ dependencies = [ name = "bot" version = "0.1.0" dependencies = [ + "anyhow", "hurrycurry-client-lib", + "hurrycurry-protocol", "log", ] diff --git a/client/game.gd b/client/game.gd index 07e8aa3b..d3d8ab13 100644 --- a/client/game.gd +++ b/client/game.gd @@ -108,9 +108,9 @@ func _ready(): mp.set_tile.connect(set_tile) mp.remove_tile.connect(func (pos): map.clear_tile(pos)) - mp.position.connect(func(player: int, pos: Vector2, rot: float, boosting: bool): + mp.movement.connect(func(player: int, pos: Vector2, rot: float, boost: bool, dir: Vector2): var player_instance: Player = players[player] - player_instance.update_position(pos, rot, boosting) + player_instance.update_position(pos, rot, boost) if player == player_id: last_position = pos ) diff --git a/client/multiplayer.gd b/client/multiplayer.gd index 7a63059e..095d3d7b 100644 --- a/client/multiplayer.gd +++ b/client/multiplayer.gd @@ -34,7 +34,7 @@ signal item_message(player: int, item: int, persist: bool) signal effect_message(player: int, effect: String, persist: bool) signal add_player(player: int, name: String, pos: Vector2, character: int) signal remove_player(player: int) -signal position(player: int, position: Vector2, rotation: float, boosting: bool) +signal movement(player: int, position: Vector2, rotation: float, boost: bool, direction: Vector2) signal movement_sync() signal take_item(tile: Vector2i, player: int) signal put_item(player: int, tile: Vector2i, ) @@ -57,7 +57,7 @@ signal connection_closed(reason: String) signal show_rating(stars: int, points: int) signal show_book() # TODO: Connect this -const VERSION_MAJOR: int = 3 +const VERSION_MAJOR: int = 4 const VERSION_MINOR: int = 0 var connected := false @@ -86,7 +86,7 @@ func _process(_delta): var code = socket.get_close_code() var reason = socket.get_close_reason() connection_closed.emit( - tr("WebSocket closed with code: %d, reason %s. Clean: %s") % [code, reason, code != - 1] + tr("WebSocket closed with code: %d, reason %s. Clean: %s") % [code, reason, code != -1] ) self.queue_free() @@ -124,12 +124,13 @@ func handle_packet(bytes: PackedByteArray): "remove_player": var id = decoded["id"] remove_player.emit(id) - "position": + "movement": var player = decoded["player"] var pos = decoded["pos"] var rot = decoded["rot"] - var boosting = decoded["boosting"] - position.emit(player, pos_to_vec2(pos), rot, boosting) + var boost = decoded["boost"] + var dir = decoded["dir"] + movement.emit(player, pos_to_vec2(pos), rot, boost, pos_to_vec2(dir)) "movement_sync": movement_sync.emit() "take_item": @@ -279,16 +280,16 @@ func send_join(player_name: String, character: int): "character": character }) -func send_movement(player: int, pos: Vector2, direction: Vector2, boosting: bool): +func send_movement(player: int, pos: Vector2, direction: Vector2, boost: bool): send_packet({ "type": "movement", "player": player, "pos": [pos.x, pos.y], - "direction": [direction.x, direction.y], - "boosting": boosting + "dir": [direction.x, direction.y], + "boost": boost }) -func send_tile_interact(player:int, pos: Vector2i, edge: bool): +func send_tile_interact(player: int, pos: Vector2i, edge: bool): send_packet({ "type": "interact", "player": player, diff --git a/pixel-client/src/game.rs b/pixel-client/src/game.rs index d4f1e4e4..5a06ad30 100644 --- a/pixel-client/src/game.rs +++ b/pixel-client/src/game.rs @@ -113,7 +113,7 @@ impl Game { keyboard: &KeyboardState, layout: &AtlasLayout, ) -> Option<Box<State>> { - self.network.poll(); + self.network.poll().unwrap(); // TODO perf for packet in self.network.queue_in.drain(..).collect::<Vec<_>>() { @@ -160,9 +160,8 @@ impl Game { } if let Some(player) = self.players.get_mut(&self.my_id) { - player - .movement - .update(&self.collision_map, direction, boost, dt); + player.movement.input(direction, boost); + player.movement.update(&self.collision_map, dt); if send_movement { self.network @@ -271,7 +270,8 @@ impl Game { item: None, movement: MovementBase { position, - direction: Vec2::ZERO, + input_direction: Vec2::ZERO, + input_boost: false, facing: Vec2::X, rotation: 0., velocity: Vec2::ZERO, @@ -287,17 +287,18 @@ impl Game { self.player_ids.remove(&id); self.players.remove(&id); } - PacketC::Position { + PacketC::Movement { player, pos, rot, - boosting, + boost, + dir, } => { if player != self.my_id { if let Some(p) = self.players.get_mut(&player) { p.movement.position = pos; p.movement.rotation = rot; - p.movement.boosting = boosting; + p.movement.input(dir, boost); } } } diff --git a/server/bot/Cargo.toml b/server/bot/Cargo.toml index 157472ae..cd0ac383 100644 --- a/server/bot/Cargo.toml +++ b/server/bot/Cargo.toml @@ -5,4 +5,6 @@ edition = "2021" [dependencies] hurrycurry-client-lib = { path = "../client-lib", features = ["tokio-network"] } +hurrycurry-protocol = { path = "../protocol" } log = "0.4.22" +anyhow = "1.0.86" diff --git a/server/bot/src/main.rs b/server/bot/src/main.rs index 0b625703..61cef1f9 100644 --- a/server/bot/src/main.rs +++ b/server/bot/src/main.rs @@ -16,6 +16,59 @@ */ -fn main() { - -}
\ No newline at end of file +use std::{thread::sleep, time::Duration}; + +use anyhow::Result; +use hurrycurry_client_lib::{network::sync::Network, Game}; +use hurrycurry_protocol::{glam::Vec2, PacketC, PacketS, PlayerID}; + +fn main() -> Result<()> { + let mut network = Network::connect("ws://127.0.0.1")?; + + let mut game = Game::default(); + + network.queue_out.push_back(PacketS::Join { + name: "bot".to_string(), + character: 1, + }); + + let mut bots = Vec::new(); + + loop { + let dt = 1. / 50.; + + network.poll()?; + + while let Some(packet) = network.queue_in.pop_front() { + match &packet { + PacketC::Joined { id } => bots.push(Bot::new(*id)), + _ => (), + } + game.apply_packet(packet); + } + + for b in &bots { + network.queue_out.push_back(PacketS::Movement { + player: b.id, + dir: Vec2::ONE, + boost: true, + pos: None, + }); + } + + sleep(Duration::from_secs_f32(dt)); + } +} + +pub struct Bot { + id: PlayerID, +} + +impl Bot { + pub fn new(id: PlayerID) -> Self { + Self { id } + } + pub fn tick(&self, game: &Game) { + if let Some(player) = game.players.get(&self.id) {} + } +} diff --git a/server/client-lib/src/lib.rs b/server/client-lib/src/lib.rs index 649e71b2..d949ac6e 100644 --- a/server/client-lib/src/lib.rs +++ b/server/client-lib/src/lib.rs @@ -15,6 +15,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +#![feature(map_many_mut)] pub mod network; pub mod spatial_index; @@ -63,6 +64,22 @@ pub struct Game { pub score: Score, } +impl Default for Game { + fn default() -> Self { + Self { + data: Default::default(), + tiles: HashMap::new(), + walkable: HashSet::new(), + players: HashMap::new(), + players_spatial_index: SpatialIndex::default(), + end: None, + lobby: false, + environment_effects: HashSet::new(), + score: Score::default(), + } + } +} + impl Game { pub fn apply_packet(&mut self, packet: PacketC) { match packet { @@ -90,16 +107,17 @@ impl Game { PacketC::RemovePlayer { id } => { self.players.remove(&id); } - PacketC::Position { + PacketC::Movement { player, pos, rot, - boosting, + boost, + dir, } => { if let Some(p) = self.players.get_mut(&player) { + p.movement.input(dir, boost); p.movement.position = pos; p.movement.rotation = rot; - p.movement.boosting = boosting; } } @@ -159,6 +177,26 @@ impl Game { _ => (), } } + + pub fn tick(&mut self, dt: f32) { + self.score.time_remaining -= dt as f64; + self.score.time_remaining -= self.score.time_remaining.max(0.); + + for (&pid, player) in &mut self.players { + player.movement.update(&self.walkable, 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) + } + }) + }); + } + pub fn get_item(&mut self, location: ItemLocation) -> &mut Option<Item> { match location { ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item, diff --git a/server/client-lib/src/network/sync.rs b/server/client-lib/src/network/sync.rs index e8aa08de..3a475e01 100644 --- a/server/client-lib/src/network/sync.rs +++ b/server/client-lib/src/network/sync.rs @@ -80,7 +80,7 @@ impl Network { }) } - pub fn poll(&mut self) { + pub fn poll(&mut self) -> anyhow::Result<()> { loop { self.queue_in.extend(match self.sock.read() { Ok(Message::Text(packet)) => match serde_json::from_str(&packet) { @@ -131,18 +131,16 @@ impl Network { for packet in self.queue_out.drain(..) { debug!("-> {packet:?}"); if self.use_bincode { - self.sock - .write(Message::Binary( - bincode::encode_to_vec(&packet, BINCODE_CONFIG).unwrap(), - )) - .unwrap(); + self.sock.write(Message::Binary( + bincode::encode_to_vec(&packet, BINCODE_CONFIG).unwrap(), + ))?; } else { self.sock - .write(Message::Text(serde_json::to_string(&packet).unwrap())) - .unwrap(); + .write(Message::Text(serde_json::to_string(&packet).unwrap()))?; } } self.sock.flush().unwrap(); + Ok(()) } } diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index 1a4e41a9..8f8e9784 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -30,7 +30,7 @@ pub use glam; pub mod movement; -pub const VERSION: (u32, u32) = (3, 0); +pub const VERSION: (u32, u32) = (4, 0); pub const BINCODE_CONFIG: Configuration<LittleEndian, Varint, Limit<4096>> = standard().with_limit(); @@ -94,8 +94,8 @@ pub enum PacketS { Movement { player: PlayerID, #[bincode(with_serde)] - direction: Vec2, - boosting: bool, + dir: Vec2, + boost: bool, #[bincode(with_serde)] pos: Option<Vec2>, }, @@ -155,12 +155,14 @@ pub enum PacketC { RemovePlayer { id: PlayerID, }, - Position { + Movement { player: PlayerID, #[bincode(with_serde)] pos: Vec2, rot: f32, - boosting: bool, + #[bincode(with_serde)] + dir: Vec2, + boost: bool, }, MoveItem { from: ItemLocation, diff --git a/server/protocol/src/movement.rs b/server/protocol/src/movement.rs index dad9d300..ebcc627d 100644 --- a/server/protocol/src/movement.rs +++ b/server/protocol/src/movement.rs @@ -29,7 +29,8 @@ const BOOST_DURATION: f32 = 0.3; const BOOST_RESTORE: f32 = 0.5; pub struct MovementBase { - pub direction: Vec2, + pub input_direction: Vec2, + pub input_boost: bool, pub position: Vec2, pub facing: Vec2, pub rotation: f32, @@ -41,8 +42,9 @@ pub struct MovementBase { impl MovementBase { pub fn new(position: Vec2) -> Self { Self { + input_direction: Vec2::ZERO, + input_boost: false, position, - direction: Vec2::ZERO, facing: Vec2::X, velocity: Vec2::ZERO, boosting: false, @@ -50,8 +52,13 @@ impl MovementBase { rotation: 0., } } - pub fn update(&mut self, map: &HashSet<IVec2>, direction: Vec2, mut boost: bool, dt: f32) { - self.direction = direction.clamp_length_max(1.); + pub fn input(&mut self, direction: Vec2, boost: bool) { + self.input_boost = boost; + self.input_direction = direction; + } + pub fn update(&mut self, map: &HashSet<IVec2>, dt: f32) { + let mut boost = self.input_boost; + let direction = self.input_direction.clamp_length_max(1.); if direction.length() > 0.1 { self.facing = direction + (self.facing - direction) * (-dt * 10.).exp(); } @@ -74,8 +81,8 @@ impl MovementBase { pub fn movement_packet(&self, player: PlayerID) -> PacketS { PacketS::Movement { pos: Some(self.position), - boosting: self.boosting, - direction: self.direction, + boost: self.input_boost, + dir: self.input_direction, player, } } diff --git a/server/src/entity/customers/mod.rs b/server/src/entity/customers/mod.rs index 0bb3f918..97b94942 100644 --- a/server/src/entity/customers/mod.rs +++ b/server/src/entity/customers/mod.rs @@ -102,7 +102,9 @@ impl EntityT for Customers { match state { CustomerState::Entering { path, chair } => { - playerdata.direction = path.next_direction(playerdata.position()); + playerdata + .movement + .input(path.next_direction(playerdata.position()), false); if path.is_done() { let demand = DemandIndex(random::<usize>() % self.demands.len()); self.cpackets.push_back(PacketS::Communicate { @@ -123,7 +125,9 @@ impl EntityT for Customers { demand, timeout, } => { - playerdata.direction = (chair.as_vec2() + 0.5) - playerdata.position(); + playerdata + .movement + .input((chair.as_vec2() + 0.5) - playerdata.position(), false); *timeout -= dt; if *timeout <= 0. { self.cpackets.push_back(PacketS::Communicate { @@ -203,7 +207,9 @@ impl EntityT for Customers { progress, chair, } => { - playerdata.direction = (chair.as_vec2() + 0.5) - playerdata.position(); + playerdata + .movement + .input((chair.as_vec2() + 0.5) - playerdata.position(), false); let demand = &self.demands[demand.0]; *progress += dt / demand.duration; if *progress >= 1. { @@ -234,7 +240,9 @@ impl EntityT for Customers { } } CustomerState::Exiting { path } => { - playerdata.direction = path.next_direction(playerdata.position()); + playerdata + .movement + .input(path.next_direction(playerdata.position()), false); if path.is_done() { info!("{player:?} -> leave"); self.cpackets.push_back(PacketS::Leave { player }); diff --git a/server/src/game.rs b/server/src/game.rs index c010c8b8..5af9658e 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -61,8 +61,6 @@ pub struct Player { pub communicate_persist: Option<Message>, pub movement: MovementBase, - pub direction: Vec2, - pub boost: bool, pub last_position_update: Instant, } @@ -178,7 +176,8 @@ impl Game { } else { self.data.chef_spawn }, - direction: Vec2::ZERO, + input_direction: Vec2::ZERO, + input_boost: false, facing: Vec2::X, rotation: 0., velocity: Vec2::ZERO, @@ -186,8 +185,6 @@ impl Game { stamina: 0., }, last_position_update: Instant::now(), - boost: false, - direction: Vec2::ZERO, communicate_persist: None, interacting: None, name: name.clone(), @@ -298,7 +295,8 @@ impl Game { } else { self.data.chef_spawn }, - direction: Vec2::ZERO, + input_direction: Vec2::ZERO, + input_boost: false, facing: Vec2::X, rotation: 0., velocity: Vec2::ZERO, @@ -306,8 +304,6 @@ impl Game { stamina: 0., }, last_position_update: Instant::now(), - boost: false, - direction: Vec2::ZERO, communicate_persist: None, interacting: None, name: name.clone(), @@ -358,8 +354,8 @@ impl Game { } PacketS::Movement { pos, - boosting, - direction, + boost, + dir: direction, player, } => { let pd = self @@ -367,8 +363,7 @@ impl Game { .get_mut(&player) .ok_or(anyhow!("player does not exist"))?; - pd.direction = direction; - pd.boost = boosting; + pd.movement.input(direction, boost); if let Some(pos) = pos { let dt = pd.last_position_update.elapsed(); @@ -541,9 +536,7 @@ impl Game { } for (&pid, player) in &mut self.players { - player - .movement - .update(&self.walkable, player.direction, player.boost, dt); + player.movement.update(&self.walkable, dt); self.players_spatial_index .update_entry(pid, player.movement.position); @@ -558,10 +551,11 @@ impl Game { }); for (&pid, player) in &mut self.players { - packet_out.push_back(PacketC::Position { + packet_out.push_back(PacketC::Movement { player: pid, pos: player.movement.position, - boosting: player.movement.boosting, + dir: player.movement.input_direction, + boost: player.movement.boosting, rot: player.movement.rotation, }); diff --git a/server/src/state.rs b/server/src/state.rs index 43ca29bd..526f70aa 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -106,7 +106,7 @@ impl State { ); } while let Some(p) = self.packet_out.pop_front() { - if matches!(p, PacketC::UpdateMap { .. } | PacketC::Position { .. }) { + if matches!(p, PacketC::UpdateMap { .. } | PacketC::Movement { .. }) { trace!("-> {p:?}"); } else { debug!("-> {p:?}"); diff --git a/test-client/main.ts b/test-client/main.ts index f0f481b4..00699b60 100644 --- a/test-client/main.ts +++ b/test-client/main.ts @@ -156,7 +156,7 @@ function packet(p: PacketC) { case "remove_player": players.delete(p.id) break; - case "position": { + case "movement": { const pl = players.get(p.player)! const pos = { x: p.pos[0], y: p.pos[1] } // const dist = length(sub_v2(pl.position, pos));a @@ -165,7 +165,7 @@ function packet(p: PacketC) { if (p.player == my_id) return last_server_sent_position = pos pl.position.x = pos.x pl.position.y = pos.y - pl.boosting = p.boosting + pl.boosting = p.boost pl.rot = p.rot break; } @@ -307,7 +307,7 @@ function set_interact(edge: boolean) { function tick_update() { const p = players.get(my_id) if (!p) return - send({ player: my_id, type: "movement", pos: [p.position.x, p.position.y], direction: [p.direction.x, p.direction.y], boosting: p.boosting }) + send({ player: my_id, type: "movement", pos: [p.position.x, p.position.y], dir: [p.direction.x, p.direction.y], boost: p.boosting }) } function frame_update(dt: number) { diff --git a/test-client/protocol.ts b/test-client/protocol.ts index 353c9100..e16f80d3 100644 --- a/test-client/protocol.ts +++ b/test-client/protocol.ts @@ -38,7 +38,7 @@ export interface Gamedata { export type PacketS = { type: "join", name: string, character: number } // Spawns a new character. The server replies with "joined". Dont send it to spectate. | { type: "leave", player: PlayerID } // Despawns a character - | { type: "movement", player: PlayerID, pos: Vec2, direction: Vec2, boosting: boolean } + | { type: "movement", player: PlayerID, pos: Vec2, dir: Vec2, boost: boolean } | { type: "interact", player: PlayerID, pos?: Vec2 } // Interact with some tile. pos is a position when pressing and null when releasing interact button | { type: "communicate", player: PlayerID, message?: Message, persist: boolean } // Send a message | { type: "replay_tick", dt: number } // Steps forward in replay. @@ -49,7 +49,8 @@ export type PacketC = | { type: "data", data: Gamedata } // Game data was changed | { type: "add_player", id: PlayerID, name: string, position: Vec2, character: number } // Somebody else joined (or was already in the game) | { type: "remove_player", id: PlayerID } // Somebody left - | { type: "position", player: PlayerID, pos: Vec2, rot: number, boosting: boolean } // Update the position of a players (your own position is included here) + | { type: "movement", player: PlayerID, pos: Vec2, rot: number, boost: boolean, dir: Vec2 } // Update the movement of a players (your own position is included here) + | { type: "movement_sync" } // Your movement is difference on the server, you should update your position from a `position` packet | { type: "move_item", from: ItemLocation, to: ItemLocation } // Item moved | { type: "set_item", location: ItemLocation, item?: ItemIndex } // the item contained in a tile or player changed | { type: "set_progress", item: ItemLocation, progress?: number, warn: boolean } // A tile is doing something. progress goes from 0 to 1, then null when finished @@ -58,7 +59,6 @@ export type PacketC = | { type: "server_message", text: string } // Text message from the server | { type: "score" } & Score // Supplies information for score OSD | { type: "menu" } & Menu // Open a menu on the client-side - | { type: "movement_sync" } // Your movement is difference on the server, you should update your position from a `position` packet | { type: "environment", effects: string[] } | { type: "set_ingame", state: boolean, lobby: boolean } // Set to false when entering the game or switching maps | { type: "error", message: string } // Your client did something wrong. |