diff --git a/common/src/event.rs b/common/src/event.rs new file mode 100644 index 0000000000..8332317593 --- /dev/null +++ b/common/src/event.rs @@ -0,0 +1,47 @@ +use specs::Entity as EcsEntity; +use std::{collections::VecDeque, ops::DerefMut, sync::Mutex}; +use vek::*; + +pub enum Event { + LandOnGround { entity: EcsEntity, vel: Vec3 }, + Explosion { pos: Vec3, radius: f32 }, +} + +#[derive(Default)] +pub struct EventBus { + queue: Mutex>, +} + +impl EventBus { + pub fn emitter(&self) -> Emitter { + Emitter { + bus: self, + events: VecDeque::new(), + } + } + + pub fn emit(&self, event: Event) { + self.queue.lock().unwrap().push_front(event); + } + + pub fn recv_all(&self) -> impl ExactSizeIterator { + std::mem::replace(self.queue.lock().unwrap().deref_mut(), VecDeque::new()).into_iter() + } +} + +pub struct Emitter<'a> { + bus: &'a EventBus, + events: VecDeque, +} + +impl<'a> Emitter<'a> { + pub fn emit(&mut self, event: Event) { + self.events.push_front(event); + } +} + +impl<'a> Drop for Emitter<'a> { + fn drop(&mut self) { + self.bus.queue.lock().unwrap().append(&mut self.events); + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 42f30e0e23..e128100c78 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -15,6 +15,7 @@ extern crate log; pub mod assets; pub mod clock; pub mod comp; +pub mod event; pub mod figure; pub mod msg; pub mod npc; diff --git a/common/src/ray.rs b/common/src/ray.rs index 98fb20475b..741581cbac 100644 --- a/common/src/ray.rs +++ b/common/src/ray.rs @@ -2,32 +2,47 @@ use crate::vol::{ReadVol, Vox}; use vek::*; pub trait RayUntil = FnMut(&V) -> bool; +pub trait RayForEach = FnMut(Vec3); -pub struct Ray<'a, V: ReadVol, F: RayUntil> { +pub struct Ray<'a, V: ReadVol, F: RayUntil, G: RayForEach> { vol: &'a V, from: Vec3, to: Vec3, until: F, + for_each: Option, max_iter: usize, ignore_error: bool, } -impl<'a, V: ReadVol, F: RayUntil> Ray<'a, V, F> { +impl<'a, V: ReadVol, F: RayUntil, G: RayForEach> Ray<'a, V, F, G> { pub fn new(vol: &'a V, from: Vec3, to: Vec3, until: F) -> Self { Self { vol, from, to, until, + for_each: None, max_iter: 100, ignore_error: false, } } - pub fn until(self, f: F) -> Ray<'a, V, F> { + pub fn until(self, f: F) -> Ray<'a, V, F, G> { Ray { until: f, ..self } } + pub fn for_each(self, f: H) -> Ray<'a, V, F, H> { + Ray { + for_each: Some(f), + vol: self.vol, + from: self.from, + to: self.to, + until: self.until, + max_iter: self.max_iter, + ignore_error: self.ignore_error, + } + } + pub fn max_iter(mut self, max_iter: usize) -> Self { self.max_iter = max_iter; self @@ -56,6 +71,11 @@ impl<'a, V: ReadVol, F: RayUntil> Ray<'a, V, F> { break; } + // for_each + if let Some(g) = &mut self.for_each { + g(ipos); + } + match self.vol.get(ipos).map(|vox| (vox, (self.until)(vox))) { Ok((vox, true)) => return (dist, Ok(Some(vox))), Err(err) if !self.ignore_error => return (dist, Err(err)), diff --git a/common/src/state.rs b/common/src/state.rs index 0a5a4ef7af..2a19a74da9 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -3,6 +3,7 @@ pub use sphynx::Uid; use crate::{ comp, + event::EventBus, msg::{EcsCompPacket, EcsResPacket}, sys, terrain::{Block, TerrainChunk, TerrainMap}, @@ -42,18 +43,11 @@ pub struct DeltaTime(pub f32); /// lag. Ideally, we'd avoid such a situation. const MAX_DELTA_TIME: f32 = 1.0; +#[derive(Default)] pub struct BlockChange { blocks: FxHashMap, Block>, } -impl Default for BlockChange { - fn default() -> Self { - Self { - blocks: FxHashMap::default(), - } - } -} - impl BlockChange { pub fn set(&mut self, pos: Vec3, block: Block) { self.blocks.insert(pos, block); @@ -64,6 +58,7 @@ impl BlockChange { } } +#[derive(Default)] pub struct TerrainChanges { pub new_chunks: FxHashSet>, pub modified_chunks: FxHashSet>, @@ -71,17 +66,6 @@ pub struct TerrainChanges { pub modified_blocks: FxHashMap, Block>, } -impl Default for TerrainChanges { - fn default() -> Self { - Self { - new_chunks: FxHashSet::default(), - modified_chunks: FxHashSet::default(), - removed_chunks: FxHashSet::default(), - modified_blocks: FxHashMap::default(), - } - } -} - impl TerrainChanges { pub fn clear(&mut self) { self.new_chunks.clear(); @@ -178,6 +162,7 @@ impl State { ecs.add_resource(TerrainMap::new().unwrap()); ecs.add_resource(BlockChange::default()); ecs.add_resource(TerrainChanges::default()); + ecs.add_resource(EventBus::default()); } /// Register a component with the state's ECS. @@ -330,16 +315,15 @@ impl State { .for_each(|(pos, block)| { let _ = terrain.set(*pos, *block); }); - std::mem::swap( + self.ecs.write_resource::().modified_blocks = std::mem::replace( &mut self.ecs.write_resource::().blocks, - &mut self.ecs.write_resource::().modified_blocks, - ) + Default::default(), + ); } /// Clean up the state after a tick. pub fn cleanup(&mut self) { // Clean up data structures from the last tick. self.ecs.write_resource::().clear(); - self.ecs.write_resource::().clear(); } } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index de3045b0a6..dadc527fd7 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -4,6 +4,7 @@ use crate::{ ActionState, Body, Jumping, MoveDir, OnGround, Ori, Pos, Rolling, Scale, Stats, Vel, Wielding, }, + event::{Event, EventBus}, state::DeltaTime, terrain::TerrainMap, vol::{ReadVol, Vox}, @@ -35,6 +36,7 @@ impl<'a> System<'a> for Sys { Entities<'a>, ReadExpect<'a, TerrainMap>, Read<'a, DeltaTime>, + Read<'a, EventBus>, ReadStorage<'a, ActionState>, ReadStorage<'a, Scale>, ReadStorage<'a, Body>, @@ -42,7 +44,6 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, - WriteStorage<'a, Stats>, ); fn run( @@ -51,6 +52,7 @@ impl<'a> System<'a> for Sys { entities, terrain, dt, + event_bus, action_states, scales, bodies, @@ -58,11 +60,12 @@ impl<'a> System<'a> for Sys { mut positions, mut velocities, mut orientations, - mut stats, ): Self::SystemData, ) { + let mut event_emitter = event_bus.emitter(); + // Apply movement inputs - for (entity, a, scale, b, mut pos, mut vel, mut ori, mut stat) in ( + for (entity, a, scale, b, mut pos, mut vel, mut ori) in ( &entities, &action_states, scales.maybe(), @@ -70,7 +73,6 @@ impl<'a> System<'a> for Sys { &mut positions, &mut velocities, &mut orientations, - &mut stats, ) .join() { @@ -210,12 +212,11 @@ impl<'a> System<'a> for Sys { // When the resolution direction is pointing upwards, we must be on the ground if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { - // Check for fall damage - let falldmg = (vel.0.z / 1.5 + 6.0) as i32; - if falldmg < 0 { - stat.health.change_by(falldmg, HealthSource::World); - } on_ground = true; + + if !was_on_ground { + event_emitter.emit(Event::LandOnGround { entity, vel: vel.0 }); + } } // When the resolution direction is non-vertical, we must be colliding with a wall diff --git a/common/src/vol.rs b/common/src/vol.rs index 47307344fc..266a3193a9 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -76,7 +76,11 @@ pub trait ReadVol: BaseVol { self.get(pos).unwrap() } - fn ray(&self, from: Vec3, to: Vec3) -> Ray bool> + fn ray( + &self, + from: Vec3, + to: Vec3, + ) -> Ray bool, fn(Vec3)> where Self: Sized, { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 022db3b9a6..9078851e6e 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -6,6 +6,7 @@ use crate::Server; use chrono::{NaiveTime, Timelike}; use common::{ comp, + event::{Event as GameEvent, EventBus}, msg::ServerMsg, npc::{get_npc_name, NpcKind}, state::TimeOfDay, @@ -147,10 +148,16 @@ lazy_static! { ), ChatCommand::new( "lantern", - "{} ", + "{}", "/lantern : adds/remove light near player", handle_lantern, ), + ChatCommand::new( + "explosion", + "{}", + "/explosion : Explodes the ground around you", + handle_explosion, + ), ]; } @@ -676,6 +683,22 @@ fn handle_lantern(server: &mut Server, entity: EcsEntity, args: String, action: } } +fn handle_explosion(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let radius = scan_fmt!(&args, action.arg_fmt, f32).unwrap_or(8.0); + + match server.state.read_component_cloned::(entity) { + Some(pos) => server + .state + .ecs() + .read_resource::() + .emit(GameEvent::Explosion { pos: pos.0, radius }), + None => server.clients.notify( + entity, + ServerMsg::private(String::from("You have no position!")), + ), + } +} + fn handle_tell(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) { let ecs = server.state.ecs(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 804f502620..5b16e6d0ec 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -17,9 +17,10 @@ use crate::{ }; use common::{ comp, + event::{Event as GameEvent, EventBus}, msg::{ClientMsg, ClientState, RequestStateError, ServerError, ServerInfo, ServerMsg}, net::PostOffice, - state::{State, TimeOfDay, Uid}, + state::{BlockChange, State, TimeOfDay, Uid}, terrain::{block::Block, TerrainChunk, TerrainChunkSize, TerrainMap}, vol::Vox, vol::{ReadVol, VolSize}, @@ -89,6 +90,7 @@ impl Server { state .ecs_mut() .add_resource(SpawnPoint(Vec3::new(16_384.0, 16_384.0, 380.0))); + state.ecs_mut().add_resource(EventBus::default()); // Set starting time for the server. state.ecs_mut().write_resource::().0 = settings.start_time; @@ -205,6 +207,44 @@ impl Server { client.allow_state(ClientState::Character); } + /// Handle events coming through via the event bus + fn handle_events(&mut self) { + let terrain = self.state.ecs().read_resource::(); + let mut block_change = self.state.ecs().write_resource::(); + let mut stats = self.state.ecs().write_storage::(); + + for event in self.state.ecs().read_resource::().recv_all() { + match event { + GameEvent::LandOnGround { entity, vel } => { + if let Some(stats) = stats.get_mut(entity) { + let falldmg = (vel.z / 1.5 + 10.0) as i32; + if falldmg < 0 { + stats.health.change_by(falldmg, comp::HealthSource::World); + } + } + } + GameEvent::Explosion { pos, radius } => { + const RAYS: usize = 500; + + for _ in 0..RAYS { + let dir = Vec3::new( + rand::random::() - 0.5, + rand::random::() - 0.5, + rand::random::() - 0.5, + ) + .normalized(); + + let _ = terrain + .ray(pos, pos + dir * radius) + .until(|_| rand::random::() < 0.05) + .for_each(|pos| block_change.set(pos, Block::empty())) + .cast(); + } + } + } + } + } + /// Execute a single server tick, handle input and update the game state by the given duration. pub fn tick(&mut self, _input: Input, dt: Duration) -> Result, Error> { // This tick function is the centre of the Veloren universe. Most server-side things are @@ -235,6 +275,9 @@ impl Server { frontend_events.append(&mut self.handle_new_connections()?); frontend_events.append(&mut self.handle_new_messages()?); + // Handle game events + self.handle_events(); + // 4) Tick the client's LocalState. self.state.tick(dt);