From 75eb62c97532e51d89c25f7a9fb08ffae04fa497 Mon Sep 17 00:00:00 2001 From: Thinkofname Date: Sat, 26 Mar 2016 10:19:16 +0000 Subject: [PATCH] Collisions and normal style movement --- src/protocol/mod.rs | 2 +- src/server.rs | 326 ++++++++++++++++++++++++++++++++++++----- src/world/block/mod.rs | 21 +++ src/world/mod.rs | 4 +- 4 files changed, 310 insertions(+), 43 deletions(-) diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index bed109a..6b218af 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -753,7 +753,7 @@ impl Conn { if self.compression_threshold >= 0 && extra == 1 { try!(VarInt(0).write_to(self)); } - try!(self.write_all(&buf.into_boxed_slice())); + try!(self.write_all(&buf)); Result::Ok(()) } diff --git a/src/server.rs b/src/server.rs index 80a8170..ff92fec 100644 --- a/src/server.rs +++ b/src/server.rs @@ -25,7 +25,8 @@ use openssl; use console; use render; use auth; -use cgmath::{self, Vector}; +use cgmath::{self, Vector, Point, Point3}; +use collision::{Aabb, Aabb3}; use sdl2::keyboard::Keycode; pub struct Server { @@ -43,14 +44,61 @@ pub struct Server { version: usize, pub position: cgmath::Vector3, + last_position: cgmath::Vector3, pub yaw: f64, pub pitch: f64, + bounds: Aabb3, + gamemode: Gamemode, + flying: bool, + on_ground: bool, + did_touch_ground: bool, + v_speed: f64, pressed_keys: HashMap, tick_timer: f64, } +#[derive(Clone, Copy, Debug)] +pub enum Gamemode { + Survival = 0, + Creative = 1, + Adventure = 2, + Spectator = 3, +} + +impl Gamemode { + pub fn from_int(val: i32) -> Gamemode { + match val { + 3 => Gamemode::Spectator, + 2 => Gamemode::Adventure, + 1 => Gamemode::Creative, + 0 | _ => Gamemode::Survival, + } + } + + pub fn can_fly(&self) -> bool { + match *self { + Gamemode::Creative | Gamemode::Spectator => true, + _ => false, + } + } + + pub fn always_fly(&self) -> bool { + match *self { + Gamemode::Spectator => true, + _ => false, + } + } + + pub fn noclip(&self) -> bool { + match *self { + Gamemode::Spectator => true, + _ => false, + } + } +} + macro_rules! handle_packet { ($s:ident $pck:ident { $($packet:ident => $func:ident,)* @@ -147,48 +195,35 @@ impl Server { } }); - let version = resources.read().unwrap().version(); - Ok(Server { - conn: Some(write), - read_queue: Some(rx), - - world: world::World::new(), - world_age: 0, - world_time: 0.0, - world_time_target: 0.0, - tick_time: true, - - resources: resources, - console: console, - version: version, - - pressed_keys: HashMap::new(), - - position: cgmath::Vector3::zero(), - yaw: 0.0, - pitch: 0.0, - - tick_timer: 0.0, - }) + Ok(Server::new(resources, console, Some(write), Some(rx))) } pub fn dummy_server(resources: Arc>, console: Arc>) -> Server { - let mut world = world::World::new(); + let mut server = Server::new(resources, console, None, None); let mut rng = rand::thread_rng(); for x in -7*16 .. 7*16 { for z in -7*16 .. 7*16 { let h = rng.gen_range(3, 10); for y in 0 .. h { - world.set_block(x, y, z, block::Dirt{}); + server.world.set_block(x, y, z, block::Dirt{}); } } } + server.gamemode = Gamemode::Spectator; + server.flying = false; + server + } + + fn new( + resources: Arc>, console: Arc>, + conn: Option, read_queue: Option>> + ) -> Server { let version = resources.read().unwrap().version(); Server { - conn: None, - read_queue: None, + conn: conn, + read_queue: read_queue, - world: world, + world: world::World::new(), world_age: 0, world_time: 0.0, world_time_target: 0.0, @@ -201,8 +236,18 @@ impl Server { pressed_keys: HashMap::new(), position: cgmath::Vector3::new(0.5, 13.2, 0.5), + last_position: cgmath::Vector3::zero(), yaw: 0.0, pitch: 0.0, + bounds: Aabb3::new( + Point3::new(-0.3, 0.0, -0.3), + Point3::new(0.3, 1.8, 0.3) + ), + gamemode: Gamemode::Survival, + flying: false, + on_ground: false, + did_touch_ground: false, + v_speed: 0.0, tick_timer: 0.0, } @@ -224,11 +269,14 @@ impl Server { match pck { Ok(pck) => handle_packet!{ self pck { + JoinGame => on_game_join, + Respawn => on_respawn, KeepAliveClientbound => on_keep_alive, ChunkData => on_chunk_data, ChunkUnload => on_chunk_unload, TeleportPlayer => on_teleport, TimeUpdate => on_time_update, + ChangeGameState => on_game_state_change, } }, Err(err) => panic!("Err: {:?}", err), @@ -237,14 +285,15 @@ impl Server { self.read_queue = Some(rx); } - let (forward, yaw) = self.calculate_movement(); - + self.flying |= self.gamemode.always_fly(); + self.last_position = self.position; if self.world.is_chunk_loaded((self.position.x as i32) >> 4, (self.position.z as i32) >> 4) { - let mut speed = 4.317 / 60.0; - if self.is_key_pressed(Keycode::LShift) { - speed = 5.612 / 60.0; - } - // TODO: only do this for flying + let (forward, yaw) = self.calculate_movement(); + let mut speed = 4.317 / 60.0; + if self.is_key_pressed(Keycode::LShift) { + speed = 5.612 / 60.0; + } + if self.flying { speed *= 2.5; if self.is_key_pressed(Keycode::Space) { @@ -253,8 +302,85 @@ impl Server { if self.is_key_pressed(Keycode::LCtrl) { self.position.y -= speed * delta; } - self.position.x += forward * yaw.cos() * delta * speed; - self.position.z -= forward * yaw.sin() * delta * speed; + } else { + if self.on_ground { + if self.is_key_pressed(Keycode::Space) { + self.v_speed = 0.15; + } else { + self.v_speed = 0.0; + } + } else { + self.v_speed -= 0.01 * delta; + if self.v_speed < -0.3 { + self.v_speed = -0.3; + } + } + } + self.position.x += forward * yaw.cos() * delta * speed; + self.position.z -= forward * yaw.sin() * delta * speed; + self.position.y += self.v_speed * delta; + } + + if !self.gamemode.noclip() { + let mut target = self.position; + self.position.y = self.last_position.y; + self.position.z = self.last_position.z; + + // We handle each axis separately to allow for a sliding + // effect when pushing up against walls. + + let (bounds, xhit) = self.check_collisions(self.bounds); + self.position.x = bounds.min.x + 0.3; + self.last_position.x = self.position.x; + + self.position.z = target.z; + let (bounds, zhit) = self.check_collisions(self.bounds); + self.position.z = bounds.min.z + 0.3; + self.last_position.z = self.position.z; + + // Half block jumps + // Minecraft lets you 'jump' up 0.5 blocks + // for slabs and stairs (or smaller blocks). + // Currently we implement this as a teleport to the + // top of the block if we could move there + // but this isn't smooth. + if (xhit || zhit) && self.on_ground { + let mut ox = self.position.x; + let mut oz = self.position.z; + self.position.x = target.x; + self.position.z = target.z; + for offset in 1 .. 9 { + let mini = self.bounds.add_v(cgmath::Vector3::new(0.0, offset as f64 / 16.0, 0.0)); + let (_, hit) = self.check_collisions(mini); + if !hit { + target.y += offset as f64 / 16.0; + ox = target.x; + oz = target.z; + break; + } + } + self.position.x = ox; + self.position.z = oz; + } + + self.position.y = target.y; + let (bounds, yhit) = self.check_collisions(self.bounds); + self.position.y = bounds.min.y; + self.last_position.y = self.position.y; + if yhit { + self.v_speed = 0.0; + } + + let ground = Aabb3::new( + Point3::new(-0.3, -0.05, -0.3), + Point3::new(0.3, 0.0, 0.3) + ); + let prev = self.on_ground; + let (_, hit) = self.check_collisions(ground); + self.on_ground = hit; + if !prev && self.on_ground { + self.did_touch_ground = true; + } } self.tick_timer += delta; @@ -266,7 +392,7 @@ impl Server { self.update_time(renderer, delta); // Copy to camera - renderer.camera.pos = cgmath::Point::from_vec(self.position + cgmath::Vector3::new(0.0, 1.8, 0.0)); + renderer.camera.pos = cgmath::Point::from_vec(self.position + cgmath::Vector3::new(0.0, 1.62, 0.0)); renderer.camera.yaw = self.yaw; renderer.camera.pitch = self.pitch; } @@ -312,6 +438,37 @@ impl Server { offset * 0.8 + 0.2 } + fn check_collisions(&self, bounds: Aabb3) -> (Aabb3, bool) { + let mut bounds = bounds.add_v(self.position); + + let dir = self.position - self.last_position; + + let min_x = (bounds.min.x - 1.0) as i32; + let min_y = (bounds.min.y - 1.0) as i32; + let min_z = (bounds.min.z - 1.0) as i32; + let max_x = (bounds.max.x + 1.0) as i32; + let max_y = (bounds.max.y + 1.0) as i32; + let max_z = (bounds.max.z + 1.0) as i32; + + let mut hit = false; + for y in min_y .. max_y { + for z in min_z .. max_z { + for x in min_x .. max_x { + let block = self.world.get_block(x, y, z); + for bb in block.get_collision_boxes() { + let bb = bb.add_v(cgmath::Vector3::new(x as f64, y as f64, z as f64)); + if bb.collides(&bounds) { + bounds = bounds.move_out_of(bb, dir); + hit = true; + } + } + } + } + } + + (bounds, hit) + } + fn calculate_movement(&self) -> (f64, f64) { use std::f64::consts::PI; let mut forward = 0.0f64; @@ -342,15 +499,25 @@ impl Server { } pub fn minecraft_tick(&mut self) { + // Force the server to know when touched the ground + // otherwise if it happens between ticks the server + // will think we are flying. + let on_ground = if self.did_touch_ground { + self.did_touch_ground = false; + true + } else { + self.on_ground + }; // Sync our position to the server + // Use the smaller packets when possible let packet = packet::play::serverbound::PlayerPositionLook { x: self.position.x, y: self.position.y, z: self.position.z, yaw: self.yaw as f32, pitch: self.pitch as f32, - on_ground: false, + on_ground: on_ground, }; self.write_packet(packet); } @@ -373,6 +540,19 @@ impl Server { }); } + fn on_game_join(&mut self, join: packet::play::clientbound::JoinGame) { + self.gamemode = Gamemode::from_int((join.gamemode & 0x7) as i32); + // TODO: Temp + self.flying = self.gamemode.can_fly(); + } + + fn on_respawn(&mut self, respawn: packet::play::clientbound::Respawn) { + self.world = world::World::new(); + self.gamemode = Gamemode::from_int((respawn.gamemode & 0x7) as i32); + // TODO: Temp + self.flying = self.gamemode.can_fly(); + } + fn on_time_update(&mut self, time_update: packet::play::clientbound::TimeUpdate) { self.world_age = time_update.time_of_day; self.world_time_target = (time_update.time_of_day % 24000) as f64; @@ -384,6 +564,17 @@ impl Server { } } + fn on_game_state_change(&mut self, game_state: packet::play::clientbound::ChangeGameState) { + match game_state.reason { + 3 => { + self.gamemode = Gamemode::from_int(game_state.value as i32); + // TODO: Temp + self.flying = self.gamemode.can_fly(); + }, + _ => {}, + } + } + fn on_teleport(&mut self, teleport: packet::play::clientbound::TeleportPlayer) { // TODO: relative teleports self.position.x = teleport.x; @@ -411,3 +602,58 @@ impl Server { self.world.unload_chunk(chunk_unload.x, chunk_unload.z); } } + +trait Collidable { + fn collides(&self, t: &T) -> bool; + fn move_out_of(self, other: Self, dir: cgmath::Vector3) -> Self; +} + +impl Collidable> for Aabb3 { + fn collides(&self, t: &Aabb3) -> bool { + !( + t.min.x >= self.max.x || + t.max.x <= self.min.x || + t.min.y >= self.max.y || + t.max.y <= self.min.y || + t.min.z >= self.max.z || + t.max.z <= self.min.z + ) + } + + fn move_out_of(mut self, other: Self, dir: cgmath::Vector3) -> Self { + if dir.x != 0.0 { + if dir.x > 0.0 { + let ox = self.max.x; + self.max.x = other.min.x - 0.0001; + self.min.x += self.max.x - ox; + } else { + let ox = self.min.x; + self.min.x = other.max.x + 0.0001; + self.max.x += self.min.x - ox; + } + } + if dir.y != 0.0 { + if dir.y > 0.0 { + let oy = self.max.y; + self.max.y = other.min.y - 0.0001; + self.min.y += self.max.y - oy; + } else { + let oy = self.min.y; + self.min.y = other.max.y + 0.0001; + self.max.y += self.min.y - oy; + } + } + if dir.z != 0.0 { + if dir.z > 0.0 { + let oz = self.max.z; + self.max.z = other.min.z - 0.0001; + self.min.z += self.max.z - oz; + } else { + let oz = self.min.z; + self.min.z = other.max.z + 0.0001; + self.max.z += self.min.z - oz; + } + } + self + } +} diff --git a/src/world/block/mod.rs b/src/world/block/mod.rs index 43e8c10..49a645a 100644 --- a/src/world/block/mod.rs +++ b/src/world/block/mod.rs @@ -1,5 +1,7 @@ use std::fmt::{Display, Formatter, Error}; +use collision::{Aabb, Aabb3}; +use cgmath::{Point3, Point}; pub use self::Block::*; @@ -36,6 +38,7 @@ macro_rules! define_blocks { model $model:expr, $(variant $variant:expr,)* $(tint $tint:expr,)* + $(collision $collision:expr,)* } )+ ) => ( @@ -188,6 +191,23 @@ macro_rules! define_blocks { )+ } } + + #[allow(unused_variables, unreachable_code)] + pub fn get_collision_boxes(&self) -> Vec> { + match *self { + $( + Block::$name { + $($fname,)* + } => { + $(return $collision;)* + return vec![Aabb3::new( + Point3::new(0.0, 0.0, 0.0), + Point3::new(1.0, 1.0, 1.0) + )]; + } + )+ + } + } } lazy_static! { @@ -235,6 +255,7 @@ define_blocks! { transparent: false, }, model { ("minecraft", "air" ) }, + collision vec![], } Stone { props { diff --git a/src/world/mod.rs b/src/world/mod.rs index 2ffe88d..d1569c3 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -209,7 +209,7 @@ impl World { biomes: vec![0; (w * d) as usize], x: x, y: y, z: z, - w: w, h: h, d: d, + w: w, _h: h, d: d, }; for i in 0 .. (w * h * d) as usize { snapshot.sky_light.set(i, 0xF); @@ -383,7 +383,7 @@ pub struct Snapshot { y: i32, z: i32, w: i32, - h: i32, + _h: i32, d: i32, }