diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index 84737db..ce4dedd 100644 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -29,11 +29,19 @@ pub struct Entity { /// Used to access compoents on an entity in an efficient /// way. -#[derive(Clone, Copy)] pub struct Key { id: usize, _t: PhantomData, } +impl Clone for Key { + fn clone(&self) -> Self { + Key { + id: self.id, + _t: PhantomData, + } + } +} +impl Copy for Key {} /// Used to search for entities with the requested components. pub struct Filter { @@ -73,8 +81,8 @@ pub struct Manager { component_ids: RefCell>, - systems: Vec>, - render_systems: Vec>, + systems: Vec>, + render_systems: Vec>, } impl Manager { @@ -101,12 +109,12 @@ impl Manager { } /// Adds a system which will be called every tick - pub fn add_system(&mut self, s: S) { + pub fn add_system(&mut self, s: S) { self.systems.push(Box::new(s)); } /// Adds a system which will be called every frame - pub fn add_render_system(&mut self, s: S) { + pub fn add_render_system(&mut self, s: S) { self.render_systems.push(Box::new(s)); } @@ -242,8 +250,44 @@ impl Manager { self.add_component(entity, key, val); } + /// Removes the component to the target entity. Returns whether anything + /// was removed. + /// # Panics + /// Panics when the target entity doesn't exist + pub fn remove_component(&mut self, entity: Entity, key: Key) -> bool { + if self.components.len() <= key.id { + return false; + } + if self.components[key.id].is_none() { + return false; + } + let components = self.components.get_mut(key.id).and_then(|v| v.as_mut()).unwrap(); + let mut e = self.entities.get_mut(entity.id); + let set = match e { + Some(ref mut val) => if val.1 == entity.generation { &mut val.0 } else { panic!("Missing entity") }, + None => panic!("Missing entity"), + }; + let set = match set.as_mut() { + Some(val) => val, + None => panic!("Missing entity"), + }; + if !set.get(key.id) { + return false + } + set.set(key.id, false); + components.remove(entity.id); + true + } + + /// Same as `remove_component` but doesn't require a key. Using a key + /// is better for frequent lookups. + pub fn remove_component_direct(&mut self, entity: Entity) -> bool { + let key = self.get_key(); + self.remove_component::(entity, key) + } + /// Returns the given component that the key points to if it exists. - pub fn get_component(&self, entity: Entity, key: Key) -> Option<&T> { + pub fn get_component<'a, 'b, T>(&'a self, entity: Entity, key: Key) -> Option<&'b T> { let components = match self.components.get(key.id).and_then(|v| v.as_ref()) { Some(val) => val, None => return None, @@ -256,18 +300,18 @@ impl Manager { return None; } - Some(components.get(entity.id)) + Some(unsafe { mem::transmute::<&T, &T>(components.get(entity.id)) }) } /// Same as `get_component` but doesn't require a key. Using a key /// is better for frequent lookups. - pub fn get_component_direct(&self, entity: Entity) -> Option<&T> { + pub fn get_component_direct<'a, 'b, T: Any>(&'a self, entity: Entity) -> Option<&'b T> { let key = self.get_key(); self.get_component(entity, key) } /// Returns the given component that the key points to if it exists. - pub fn get_component_mut(&mut self, entity: Entity, key: Key) -> Option<&mut T> { + pub fn get_component_mut<'a, 'b, T>(&'a mut self, entity: Entity, key: Key) -> Option<&'b mut T> { let components = match self.components.get_mut(key.id).and_then(|v| v.as_mut()) { Some(val) => val, None => return None, @@ -280,12 +324,12 @@ impl Manager { return None; } - Some(components.get_mut(entity.id)) + Some(unsafe { mem::transmute::<&mut T, &mut T>(components.get_mut(entity.id)) }) } /// Same as `get_component_mut` but doesn't require a key. Using a key /// is better for frequent lookups. - pub fn get_component_mut_direct(&mut self, entity: Entity) -> Option<&mut T> { + pub fn get_component_mut_direct<'a, 'b, T: Any>(&'a mut self, entity: Entity) -> Option<&'b mut T> { let key = self.get_key(); self.get_component_mut(entity, key) } @@ -296,7 +340,7 @@ const COMPONENTS_PER_BLOCK: usize = 64; struct ComponentMem { data: Vec, BSet, usize)>>, component_size: usize, - drop_func: Box, + drop_func: Box, } impl ComponentMem { diff --git a/src/entity/mod.rs b/src/entity/mod.rs new file mode 100644 index 0000000..2187023 --- /dev/null +++ b/src/entity/mod.rs @@ -0,0 +1,146 @@ + +pub mod player; + +use ecs; +use cgmath::Vector3; +use collision::Aabb3; + +mod systems; + +pub fn add_systems(m: &mut ecs::Manager) { + let sys = systems::UpdateLastPosition::new(m); + m.add_system(sys); + + player::add_systems(m); + + let sys = systems::ApplyVelocity::new(m); + m.add_system(sys); + let sys = systems::ApplyGravity::new(m); + m.add_system(sys); +} + +/// Location of an entity in the world. +#[derive(Debug)] +pub struct Position { + pub position: Vector3, + pub last_position: Vector3, +} + +impl Position { + pub fn new(x: f64, y: f64, z: f64) -> Position { + Position { + position: Vector3::new(x, y, z), + last_position: Vector3::new(x, y, z), + } + } + + pub fn zero() -> Position { + Position::new(0.0, 0.0, 0.0) + } +} + +/// Velocity of an entity in the world. +#[derive(Debug)] +pub struct Velocity { + pub velocity: Vector3, +} + +impl Velocity { + pub fn new(x: f64, y: f64, z: f64) -> Velocity { + Velocity { + velocity: Vector3::new(x, y, z), + } + } + + pub fn zero() -> Velocity { + Velocity::new(0.0, 0.0, 0.0) + } +} + +/// Rotation of an entity in the world +#[derive(Debug)] +pub struct Rotation { + pub yaw: f64, + pub pitch: f64, +} + +impl Rotation { + pub fn new(yaw: f64, pitch: f64) -> Rotation { + Rotation { + yaw: yaw, + pitch: pitch, + } + } + + pub fn zero() -> Rotation { + Rotation::new(0.0, 0.0) + } +} + +pub struct Gravity { + pub on_ground: bool, +} + +impl Gravity { + pub fn new() -> Gravity { + Gravity { + on_ground: false, + } + } +} + +pub struct Bounds { + pub bounds: Aabb3, +} + +impl Bounds { + pub fn new(bounds: Aabb3) -> Bounds { + Bounds { + bounds: bounds, + } + } +} + +/// Hack to allow temp access to a type +pub struct Proxy { + inner: Option, +} + +impl ::std::ops::Deref for Proxy { + type Target = T; + + fn deref(&self) -> &T { + self.inner.as_ref().unwrap() + } +} + +impl Proxy { + + pub fn new() -> Proxy { + Proxy { + inner: None, + } + } + + pub fn give(&mut self, v: &mut T) { + use std::mem; + self.inner = Some(mem::replace(v, unsafe { mem::uninitialized() })); + } + + pub fn take(&mut self, v: &mut T) { + use std::mem; + mem::forget(mem::replace(v, self.inner.take().unwrap())); + } +} + +pub struct GameInfo { + pub delta: f64, +} + +impl GameInfo { + pub fn new() -> GameInfo { + GameInfo { + delta: 0.0, + } + } +} diff --git a/src/entity/player.rs b/src/entity/player.rs new file mode 100644 index 0000000..ce98176 --- /dev/null +++ b/src/entity/player.rs @@ -0,0 +1,338 @@ + +use ecs; +use super::{ + Position, + Velocity, + Rotation, + Gravity, + Bounds, + Proxy, + GameInfo, +}; +use world; +use types::Gamemode; +use collision::{Aabb, Aabb3}; +use cgmath::{self, Point3}; +use std::collections::HashMap; +use std::hash::BuildHasherDefault; +use types::hash::FNVHash; +use sdl2::keyboard::Keycode; + +pub fn add_systems(m: &mut ecs::Manager) { + // Not actually rendering related but the faster + // we can handle input the better. + let sys = MovementHandler::new(m); + m.add_render_system(sys); +} + +pub fn create_local(m: &mut ecs::Manager) -> ecs::Entity { + let entity = m.create_entity(); + m.add_component_direct(entity, Position::new(0.5, 13.2, 0.5)); + m.add_component_direct(entity, Rotation::new(0.0, 0.0)); + m.add_component_direct(entity, Velocity::new(0.0, 0.0, 0.0)); + m.add_component_direct(entity, Gamemode::Survival); + m.add_component_direct(entity, Gravity::new()); + m.add_component_direct(entity, PlayerMovement::new()); + m.add_component_direct(entity, Bounds::new(Aabb3::new( + Point3::new(-0.3, 0.0, -0.3), + Point3::new(0.3, 1.8, 0.3) + ))); + entity +} + +pub struct PlayerMovement { + pub flying: bool, + pub did_touch_ground: bool, + pub pressed_keys: HashMap>, +} + +impl PlayerMovement { + pub fn new() -> PlayerMovement { + PlayerMovement { + flying: false, + did_touch_ground: false, + pressed_keys: HashMap::with_hasher(BuildHasherDefault::default()), + } + } + + fn calculate_movement(&self, player_yaw: f64) -> (f64, f64) { + use std::f64::consts::PI; + let mut forward = 0.0f64; + let mut yaw = player_yaw - (PI/2.0); + if self.is_key_pressed(Keycode::W) || self.is_key_pressed(Keycode::S) { + forward = 1.0; + if self.is_key_pressed(Keycode::S) { + yaw += PI; + } + } + let mut change = 0.0; + if self.is_key_pressed(Keycode::A) { + change = (PI / 2.0) / (forward.abs() + 1.0); + } + if self.is_key_pressed(Keycode::D) { + change = -(PI / 2.0) / (forward.abs() + 1.0); + } + if self.is_key_pressed(Keycode::A) || self.is_key_pressed(Keycode::D) { + forward = 1.0; + } + if self.is_key_pressed(Keycode::S) { + yaw -= change; + } else { + yaw += change; + } + + (forward, yaw) + } + + fn is_key_pressed(&self, key: Keycode) -> bool { + self.pressed_keys.get(&key).map_or(false, |v| *v) + } +} + +struct MovementHandler { + filter: ecs::Filter, + movement: ecs::Key, + gravity: ecs::Key, + gamemode: ecs::Key, + world: ecs::Key>, + position: ecs::Key, + velocity: ecs::Key, + game_info: ecs::Key, + bounds: ecs::Key, + rotation: ecs::Key, +} + +impl MovementHandler { + pub fn new(m: &mut ecs::Manager) -> MovementHandler { + let movement = m.get_key(); + let position = m.get_key(); + let velocity = m.get_key(); + let bounds = m.get_key(); + let rotation = m.get_key(); + MovementHandler { + filter: ecs::Filter::new() + .with(movement) + .with(position) + .with(velocity) + .with(bounds) + .with(rotation), + movement: movement, + gravity: m.get_key(), + gamemode: m.get_key(), + world: m.get_key(), + position: position, + velocity: velocity, + game_info: m.get_key(), + bounds: bounds, + rotation: rotation, + } + } +} + +impl ecs::System for MovementHandler { + fn update(&mut self, m: &mut ecs::Manager) { + let world_entity = m.get_world(); + let world: &world::World = m.get_component(world_entity, self.world).unwrap(); + let delta = m.get_component(world_entity, self.game_info).unwrap().delta; + for e in m.find(&self.filter) { + let movement = m.get_component_mut(e, self.movement).unwrap(); + if movement.flying && m.get_component(e, self.gravity).is_some() { + m.remove_component(e, self.gravity); + } else if !movement.flying && m.get_component(e, self.gravity).is_none() { + m.add_component(e, self.gravity, Gravity::new()); + } + let gamemode = m.get_component(e, self.gamemode).unwrap(); + movement.flying |= gamemode.always_fly(); + + let position = m.get_component_mut(e, self.position).unwrap(); + let rotation = m.get_component(e, self.rotation).unwrap(); + let velocity = m.get_component_mut(e, self.velocity).unwrap(); + let gravity = m.get_component_mut(e, self.gravity); + + let player_bounds = m.get_component(e, self.bounds).unwrap().bounds; + + if world.is_chunk_loaded((position.position.x as i32) >> 4, (position.position.z as i32) >> 4) { + let (forward, yaw) = movement.calculate_movement(rotation.yaw); + let mut speed = 4.317 / 60.0; + if movement.is_key_pressed(Keycode::LShift) { + speed = 5.612 / 60.0; + } + if movement.flying { + speed *= 2.5; + + if movement.is_key_pressed(Keycode::Space) { + position.position.y += speed * delta; + } + if movement.is_key_pressed(Keycode::LCtrl) { + position.position.y -= speed * delta; + } + } else if gravity.as_ref().map_or(false, |v| v.on_ground) { + if movement.is_key_pressed(Keycode::Space) { + velocity.velocity.y = 0.15; + } else { + velocity.velocity.y = 0.0; + } + } else { + velocity.velocity.y -= 0.01 * delta; + if velocity.velocity.y < -0.3 { + velocity.velocity.y = -0.3; + } + } + position.position.x += forward * yaw.cos() * delta * speed; + position.position.z -= forward * yaw.sin() * delta * speed; + position.position.y += velocity.velocity.y * delta; + } + + if !gamemode.noclip() { + let mut target = position.position; + position.position.y = position.last_position.y; + position.position.z = position.last_position.z; + + // We handle each axis separately to allow for a sliding + // effect when pushing up against walls. + + let (bounds, xhit) = check_collisions(world, position, player_bounds); + position.position.x = bounds.min.x + 0.3; + position.last_position.x = position.position.x; + + position.position.z = target.z; + let (bounds, zhit) = check_collisions(world, position, player_bounds); + position.position.z = bounds.min.z + 0.3; + position.last_position.z = position.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) && gravity.as_ref().map_or(false, |v| v.on_ground) { + let mut ox = position.position.x; + let mut oz = position.position.z; + position.position.x = target.x; + position.position.z = target.z; + for offset in 1 .. 9 { + let mini = player_bounds.add_v(cgmath::Vector3::new(0.0, offset as f64 / 16.0, 0.0)); + let (_, hit) = check_collisions(world, position, mini); + if !hit { + target.y += offset as f64 / 16.0; + ox = target.x; + oz = target.z; + break; + } + } + position.position.x = ox; + position.position.z = oz; + } + + position.position.y = target.y; + let (bounds, yhit) = check_collisions(world, position, player_bounds); + position.position.y = bounds.min.y; + position.last_position.y = position.position.y; + if yhit { + velocity.velocity.y = 0.0; + } + + if let Some(gravity) = gravity { + let ground = Aabb3::new( + Point3::new(-0.3, -0.05, -0.3), + Point3::new(0.3, 0.0, 0.3) + ); + let prev = gravity.on_ground; + let (_, hit) = check_collisions(world, position, ground); + gravity.on_ground = hit; + if !prev && gravity.on_ground { + movement.did_touch_ground = true; + } + } + } + } + } +} + + +fn check_collisions(world: &world::World, position: &mut Position, bounds: Aabb3) -> (Aabb3, bool) { + let mut bounds = bounds.add_v(position.position); + + let dir = position.position - position.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 = 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) +} + +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/entity/systems.rs b/src/entity/systems.rs new file mode 100644 index 0000000..da30e28 --- /dev/null +++ b/src/entity/systems.rs @@ -0,0 +1,105 @@ + +use super::*; +use ecs; + +pub struct ApplyVelocity { + filter: ecs::Filter, + position: ecs::Key, + velocity: ecs::Key, + movement: ecs::Key, +} + +impl ApplyVelocity { + pub fn new(m: &mut ecs::Manager) -> ApplyVelocity { + let position = m.get_key(); + let velocity = m.get_key(); + ApplyVelocity { + filter: ecs::Filter::new() + .with(position) + .with(velocity), + position: position, + velocity: velocity, + movement: m.get_key(), + } + } +} + +impl ecs::System for ApplyVelocity { + + fn update(&mut self, m: &mut ecs::Manager) { + for e in m.find(&self.filter) { + if m.get_component(e, self.movement).is_some() { + // Player's handle their own phyiscs + continue; + } + let pos = m.get_component_mut(e, self.position).unwrap(); + let vel = m.get_component(e, self.velocity).unwrap(); + pos.position = pos.position + vel.velocity; + } + } +} + +pub struct ApplyGravity { + filter: ecs::Filter, + velocity: ecs::Key, + movement: ecs::Key, +} + +impl ApplyGravity { + pub fn new(m: &mut ecs::Manager) -> ApplyGravity { + let gravity = m.get_key::(); + let velocity = m.get_key(); + ApplyGravity { + filter: ecs::Filter::new() + .with(gravity) + .with(velocity), + velocity: velocity, + movement: m.get_key(), + } + } +} + +impl ecs::System for ApplyGravity { + + fn update(&mut self, m: &mut ecs::Manager) { + for e in m.find(&self.filter) { + if m.get_component(e, self.movement).is_some() { + // Player's handle their own phyiscs + continue; + } + let vel = m.get_component_mut(e, self.velocity).unwrap(); + + vel.velocity.y -= 0.03; + if vel.velocity.y < -0.3 { + vel.velocity.y = -0.3; + } + } + } +} + +pub struct UpdateLastPosition { + filter: ecs::Filter, + position: ecs::Key, +} + +impl UpdateLastPosition { + pub fn new(m: &mut ecs::Manager) -> UpdateLastPosition { + let position = m.get_key(); + UpdateLastPosition { + filter: ecs::Filter::new() + .with(position), + position: position, + } + } +} + +impl ecs::System for UpdateLastPosition { + + fn update(&mut self, m: &mut ecs::Manager) { + for e in m.find(&self.filter) { + let pos = m.get_component_mut(e, self.position).unwrap(); + + pos.last_position = pos.position; + } + } +} diff --git a/src/main.rs b/src/main.rs index 70d8c46..abbe609 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,6 +64,7 @@ pub mod world; pub mod chunk_builder; pub mod auth; pub mod model; +pub mod entity; use std::sync::{Arc, RwLock, Mutex}; use std::marker::PhantomData; @@ -108,9 +109,10 @@ impl Game { pub fn tick(&mut self, delta: f64) { if !self.server.is_connected() { - self.server.yaw += 0.005 * delta; - if self.server.yaw > ::std::f64::consts::PI * 2.0 { - self.server.yaw = 0.0; + let rotation = self.server.entities.get_component_mut(self.server.player, self.server.rotation).unwrap(); + rotation.yaw += 0.005 * delta; + if rotation.yaw > ::std::f64::consts::PI * 2.0 { + rotation.yaw = 0.0; } } let mut clear_reply = false; @@ -270,13 +272,14 @@ fn handle_window_event(window: &sdl2::video::Window, } let s = 2000.0 + 0.01; let (rx, ry) = (xrel as f64 / s, yrel as f64 / s); - game.server.yaw -= rx; - game.server.pitch -= ry; - if game.server.pitch < (PI/2.0) + 0.01 { - game.server.pitch = (PI/2.0) + 0.01; + let rotation = game.server.entities.get_component_mut(game.server.player, game.server.rotation).unwrap(); + rotation.yaw -= rx; + rotation.pitch -= ry; + if rotation.pitch < (PI/2.0) + 0.01 { + rotation.pitch = (PI/2.0) + 0.01; } - if game.server.pitch > (PI/2.0)*3.0 - 0.01 { - game.server.pitch = (PI/2.0)*3.0 - 0.01; + if rotation.pitch > (PI/2.0)*3.0 - 0.01 { + rotation.pitch = (PI/2.0)*3.0 - 0.01; } } else { ui_container.hover_at(game, x as f64, y as f64, width as f64, height as f64); diff --git a/src/server.rs b/src/server.rs index 793e570..b73bb90 100644 --- a/src/server.rs +++ b/src/server.rs @@ -25,15 +25,19 @@ use openssl; use console; use render; use auth; -use cgmath::{self, Vector, Point3}; -use collision::{Aabb, Aabb3}; +use ecs; +use entity; +use cgmath; +use collision::Aabb; use sdl2::keyboard::Keycode; +use types::Gamemode; pub struct Server { conn: Option, read_queue: Option>>, pub world: world::World, + pub entities: ecs::Manager, world_age: i64, world_time: f64, world_time_target: f64, @@ -43,62 +47,22 @@ pub struct Server { console: Arc>, 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, + // Entity accessors + world_proxy: ecs::Key>, + game_info: ecs::Key, + player_movement: ecs::Key, + gravity: ecs::Key, + position: ecs::Key, + gamemode: ecs::Key, + pub rotation: ecs::Key, + // + + pub player: ecs::Entity, 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,)* @@ -209,8 +173,7 @@ impl Server { } } } - server.gamemode = Gamemode::Spectator; - server.flying = false; + *server.entities.get_component_mut(server.player, server.gamemode).unwrap() = Gamemode::Spectator; server } @@ -218,6 +181,16 @@ impl Server { resources: Arc>, console: Arc>, conn: Option, read_queue: Option>> ) -> Server { + let mut entities = ecs::Manager::new(); + entity::add_systems(&mut entities); + + let player = entity::player::create_local(&mut entities); + let world_entity = entities.get_world(); + let world_proxy = entities.get_key(); + entities.add_component(world_entity, world_proxy, entity::Proxy::new()); + let game_info = entities.get_key(); + entities.add_component(world_entity, game_info, entity::GameInfo::new()); + let version = resources.read().unwrap().version(); Server { conn: conn, @@ -235,19 +208,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, + // Entity accessors + world_proxy: world_proxy, + game_info: game_info, + player_movement: entities.get_key(), + gravity: entities.get_key(), + position: entities.get_key(), + gamemode: entities.get_key(), + rotation: entities.get_key(), + // + entities: entities, + + player: player, tick_timer: 0.0, } @@ -285,114 +257,37 @@ impl Server { self.read_queue = Some(rx); } - 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 (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) { - self.position.y += speed * delta; - } - if self.is_key_pressed(Keycode::LCtrl) { - self.position.y -= speed * delta; - } - } 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; while self.tick_timer >= 3.0 && self.is_connected() { self.minecraft_tick(); self.tick_timer -= 3.0; } + self.render_tick(delta); + self.update_time(renderer, delta); // Copy to camera - 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; + let position = self.entities.get_component(self.player, self.position).unwrap(); + let rotation = self.entities.get_component(self.player, self.rotation).unwrap(); + renderer.camera.pos = cgmath::Point::from_vec(position.position + cgmath::Vector3::new(0.0, 1.62, 0.0)); + renderer.camera.yaw = rotation.yaw; + renderer.camera.pitch = rotation.pitch; + } + + fn render_tick(&mut self, delta: f64) { + let world_entity = self.entities.get_world(); + self.entities.get_component_mut(world_entity, self.world_proxy) + .unwrap() + .give(&mut self.world); + self.entities.get_component_mut(world_entity, self.game_info) + .unwrap().delta = delta; + + self.entities.render_tick(); + + self.entities.get_component_mut(world_entity, self.world_proxy) + .unwrap() + .take(&mut self.world); } fn update_time(&mut self, renderer: &mut render::Renderer, delta: f64) { @@ -436,85 +331,39 @@ 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; - let mut yaw = self.yaw - (PI/2.0); - if self.is_key_pressed(Keycode::W) || self.is_key_pressed(Keycode::S) { - forward = 1.0; - if self.is_key_pressed(Keycode::S) { - yaw += PI; - } - } - let mut change = 0.0; - if self.is_key_pressed(Keycode::A) { - change = (PI / 2.0) / (forward.abs() + 1.0); - } - if self.is_key_pressed(Keycode::D) { - change = -(PI / 2.0) / (forward.abs() + 1.0); - } - if self.is_key_pressed(Keycode::A) || self.is_key_pressed(Keycode::D) { - forward = 1.0; - } - if self.is_key_pressed(Keycode::S) { - yaw -= change; - } else { - yaw += change; - } - - (forward, yaw) - } - pub fn minecraft_tick(&mut self) { + let world_entity = self.entities.get_world(); + self.entities.get_component_mut(world_entity, self.world_proxy) + .unwrap() + .give(&mut self.world); + self.entities.tick(); + self.entities.get_component_mut(world_entity, self.world_proxy) + .unwrap() + .take(&mut self.world); + + let movement = self.entities.get_component_mut(self.player, self.player_movement).unwrap(); + let on_ground = self.entities.get_component(self.player, self.gravity).map_or(false, |v| v.on_ground); + let position = self.entities.get_component(self.player, self.position).unwrap(); + let rotation = self.entities.get_component(self.player, self.rotation).unwrap(); + // 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; + let on_ground = if movement.did_touch_ground { + movement.did_touch_ground = false; true } else { - self.on_ground + 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, + x: position.position.x, + y: position.position.y, + z: position.position.z, + yaw: rotation.yaw as f32, + pitch: rotation.pitch as f32, on_ground: on_ground, }; self.write_packet(packet); @@ -522,6 +371,9 @@ impl Server { pub fn key_press(&mut self, down: bool, key: Keycode) { self.pressed_keys.insert(key, down); + if let Some(movement) = self.entities.get_component_mut(self.player, self.player_movement) { + movement.pressed_keys.insert(key, down); + } } fn is_key_pressed(&self, key: Keycode) -> bool { @@ -539,16 +391,18 @@ impl Server { } fn on_game_join(&mut self, join: packet::play::clientbound::JoinGame) { - self.gamemode = Gamemode::from_int((join.gamemode & 0x7) as i32); + let gamemode = Gamemode::from_int((join.gamemode & 0x7) as i32); + *self.entities.get_component_mut(self.player, self.gamemode).unwrap() = gamemode; // TODO: Temp - self.flying = self.gamemode.can_fly(); + self.entities.get_component_mut(self.player, self.player_movement).unwrap().flying = 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); + let gamemode = Gamemode::from_int((respawn.gamemode & 0x7) as i32); + *self.entities.get_component_mut(self.player, self.gamemode).unwrap() = gamemode; // TODO: Temp - self.flying = self.gamemode.can_fly(); + self.entities.get_component_mut(self.player, self.player_movement).unwrap().flying = gamemode.can_fly(); } fn on_time_update(&mut self, time_update: packet::play::clientbound::TimeUpdate) { @@ -565,21 +419,24 @@ 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); + let gamemode = Gamemode::from_int(game_state.value as i32); + *self.entities.get_component_mut(self.player, self.gamemode).unwrap() = gamemode; // TODO: Temp - self.flying = self.gamemode.can_fly(); + self.entities.get_component_mut(self.player, self.player_movement).unwrap().flying = gamemode.can_fly(); }, _ => {}, } } fn on_teleport(&mut self, teleport: packet::play::clientbound::TeleportPlayer) { + let position = self.entities.get_component_mut(self.player, self.position).unwrap(); + let rotation = self.entities.get_component_mut(self.player, self.rotation).unwrap(); // TODO: relative teleports - self.position.x = teleport.x; - self.position.y = teleport.y; - self.position.z = teleport.z; - self.yaw = teleport.yaw as f64; - self.pitch = teleport.pitch as f64; + position.position.x = teleport.x; + position.position.y = teleport.y; + position.position.z = teleport.z; + rotation.yaw = teleport.yaw as f64; + rotation.pitch = teleport.pitch as f64; self.write_packet(packet::play::serverbound::TeleportConfirm { teleport_id: teleport.teleport_id, @@ -600,58 +457,3 @@ 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/types/mod.rs b/src/types/mod.rs index 330263c..562cbd0 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -140,3 +140,45 @@ impl Direction { } } } + + + +#[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, + } + } +}