diff --git a/Cargo.lock b/Cargo.lock index d624eeb828..6777d2e7e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1902,6 +1902,7 @@ dependencies = [ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "shred 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/client/src/lib.rs b/client/src/lib.rs index 4a4dc91de1..bd9a7ae61f 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -148,21 +148,9 @@ impl Client { println!("Chunk at {:?}", k); }); - // Step 1 - if let (Some(_), Some(vel), Some(_)) = ( - self.state.read_component_cloned::(self.player), - self.state.read_component_cloned::(self.player), - self.state.read_component_cloned::(self.player), - ) { - self.state.write_component(self.player, comp::phys::Vel(vel.0 + input.move_dir * 2.0 - vel.0.map(|e| e * e.abs() + e) * 0.03)); - - if input.move_dir.magnitude() > 0.01 { - self.state.write_component(self.player, comp::phys::Dir(vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0))); - self.state.write_component(self.player, comp::Animation::Run); - } else { - self.state.write_component(self.player, comp::Animation::Idle); - } - } + self.state.write_component(self.player, comp::Control { + move_dir: input.move_dir, + }); // Tick the client's LocalState (step 3) self.state.tick(dt); diff --git a/common/Cargo.toml b/common/Cargo.toml index 1b99516d23..e12e94bbab 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -18,3 +18,4 @@ serde = "1.0" serde_derive = "1.0" bincode = "1.0" log = "0.4" +rand = "0.5" diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs new file mode 100644 index 0000000000..4617e78f3c --- /dev/null +++ b/common/src/comp/agent.rs @@ -0,0 +1,28 @@ +use specs::{Component, VecStorage}; +use vek::*; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum Agent { + Wanderer(Vec2), +} + +impl Component for Agent { + type Storage = VecStorage; +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct Control { + pub move_dir: Vec2, +} + +impl Default for Control { + fn default() -> Self { + Self { + move_dir: Vec2::zero(), + } + } +} + +impl Component for Control { + type Storage = VecStorage; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 4b12f8becf..c67f27cee2 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -1,8 +1,10 @@ +pub mod agent; pub mod character; pub mod player; pub mod phys; // Reexports +pub use agent::{Agent, Control}; pub use character::Character; pub use player::Player; pub use character::Animation; diff --git a/common/src/state.rs b/common/src/state.rs index 3a26e6126e..84a6736362 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -101,6 +101,8 @@ impl State { ecs.internal_mut().register::(); ecs.internal_mut().register::(); ecs.internal_mut().register::(); + ecs.internal_mut().register::(); + ecs.internal_mut().register::(); // Register resources used by the ECS ecs.internal_mut().add_resource(TimeOfDay(0.0)); diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs new file mode 100644 index 0000000000..2ae4b5ff2b --- /dev/null +++ b/common/src/sys/agent.rs @@ -0,0 +1,34 @@ +// Library +use specs::{Join, Read, ReadStorage, System, WriteStorage}; +use vek::*; + +// Crate +use crate::comp::{Agent, Control, phys::Pos}; + +// Basic ECS AI agent system +pub struct Sys; + +impl<'a> System<'a> for Sys { + type SystemData = ( + WriteStorage<'a, Agent>, + ReadStorage<'a, Pos>, + WriteStorage<'a, Control>, + ); + + fn run(&mut self, (mut agents, pos, mut controls): Self::SystemData) { + for (mut agent, pos, mut control) in (&mut agents, &pos, &mut controls).join() { + match agent { + Agent::Wanderer(bearing) => { + *bearing += Vec2::new( + rand::random::().fract() - 0.5, + rand::random::().fract() - 0.5, + ) - *bearing * 0.05 - pos.0 * 0.001; + + if bearing.magnitude_squared() != 0.0 { + control.move_dir = bearing.normalized(); + } + }, + } + } + } +} diff --git a/common/src/sys/control.rs b/common/src/sys/control.rs new file mode 100644 index 0000000000..750b9a2e04 --- /dev/null +++ b/common/src/sys/control.rs @@ -0,0 +1,34 @@ +// Library +use specs::{Join, Read, ReadStorage, System, WriteStorage, Entities}; +use vek::*; + +// Crate +use crate::comp::{Control, Animation, phys::{Pos, Vel, Dir}}; + +// Basic ECS AI agent system +pub struct Sys; + +impl<'a> System<'a> for Sys { + type SystemData = ( + Entities<'a>, + WriteStorage<'a, Vel>, + WriteStorage<'a, Dir>, + WriteStorage<'a, Animation>, + ReadStorage<'a, Control>, + ); + + fn run(&mut self, (entities, mut vels, mut dirs, mut anims, controls): Self::SystemData) { + for (entity, mut vel, mut dir, control) in (&entities, &mut vels, &mut dirs, &controls).join() { + // TODO: Don't hard-code this + // Apply physics to the player: acceleration and non-linear decceleration + vel.0 += control.move_dir * 2.0 - vel.0.map(|e| e * e.abs() + e) * 0.03; + + if control.move_dir.magnitude() > 0.01 { + dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0); + anims.insert(entity, Animation::Run); + } else { + anims.insert(entity, Animation::Run); + } + } + } +} diff --git a/common/src/sys/mod.rs b/common/src/sys/mod.rs index f4e5ea313a..968fe2aa79 100644 --- a/common/src/sys/mod.rs +++ b/common/src/sys/mod.rs @@ -1,11 +1,17 @@ +pub mod agent; +pub mod control; pub mod phys; // External use specs::DispatcherBuilder; // System names +const AGENT_SYS: &str = "agent_sys"; +const CONTROL_SYS: &str = "control_sys"; const MOVEMENT_SYS: &str = "movement_sys"; pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { - dispatch_builder.add(phys::MovementSys, MOVEMENT_SYS, &[]); + dispatch_builder.add(agent::Sys, AGENT_SYS, &[]); + dispatch_builder.add(control::Sys, CONTROL_SYS, &[]); + dispatch_builder.add(phys::Sys, MOVEMENT_SYS, &[]); } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 2cf6c46aa6..ab25dcaf6e 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -8,9 +8,9 @@ use crate::{ }; // Basic ECS physics system -pub struct MovementSys; +pub struct Sys; -impl<'a> System<'a> for MovementSys { +impl<'a> System<'a> for Sys { type SystemData = ( WriteStorage<'a, Pos>, ReadStorage<'a, Vel>, diff --git a/server/src/lib.rs b/server/src/lib.rs index a1eb2fa86c..9b43de8e82 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -13,7 +13,7 @@ use common::{ comp, msg::{ClientMsg, ServerMsg}, net::PostOffice, - state::State, + state::{State, Uid}, terrain::TerrainChunk, }; use specs::{ @@ -52,8 +52,11 @@ impl Server { pub fn new() -> Result { let (chunk_tx, chunk_rx) = mpsc::channel(); - Ok(Self { - state: State::new(), + let mut state = State::new(); + state.ecs_mut().internal_mut().register::(); + + let mut this = Self { + state, world: World::new(), postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?, @@ -65,7 +68,16 @@ impl Server { chunk_tx, chunk_rx, pending_chunks: HashSet::new(), - }) + }; + + for i in 0..4 { + this.create_character(comp::Character::test()) + .with(comp::Agent::Wanderer(Vec2::zero())) + .with(comp::Control::default()) + .build(); + } + + Ok(this) } /// Get a reference to the server's game state. @@ -90,6 +102,18 @@ impl Server { &mut self.world } + /// Build a non-player character + #[allow(dead_code)] + pub fn create_character(&mut self, character: comp::Character) -> EcsEntityBuilder { + self.state + .ecs_mut() + .create_entity_synced() + .with(comp::phys::Pos(Vec3::zero())) + .with(comp::phys::Vel(Vec3::zero())) + .with(comp::phys::Dir(Vec3::unit_y())) + .with(character) + } + /// Execute a single server tick, handle input and update the game state by the given duration #[allow(dead_code)] pub fn tick(&mut self, input: Input, dt: Duration) -> Result, Error> { @@ -219,8 +243,8 @@ impl Server { state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); if let Some(character) = character { state.write_component(entity, character); - } + state.write_component(entity, comp::phys::ForceUpdate); client.state = ClientState::Connected; @@ -339,8 +363,6 @@ impl Server { }; match force_update { - - Some(_) => self.clients.notify_connected(msg), None => self.clients.notify_connected_except(entity, msg), }