From fd39ee97bcf8f802ab60ffbd5f64454418abddb3 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 5 Jul 2020 13:39:28 +0100 Subject: [PATCH] Added training dummies --- CHANGELOG.md | 1 + assets/common/items/training_dummy.ron | 7 +++++ assets/voxygen/item_image_manifest.ron | 6 +++- common/src/comp/body/object.rs | 4 +++ common/src/comp/inventory/item/mod.rs | 1 + common/src/event.rs | 2 +- common/src/generation.rs | 11 +++++-- common/src/ray.rs | 16 ++++++---- common/src/sys/combat.rs | 4 +-- common/src/sys/stats.rs | 15 ++++++--- common/src/vol.rs | 2 +- server/src/events/entity_creation.rs | 9 ++++-- server/src/events/entity_manipulation.rs | 14 +++------ server/src/events/inventory_manip.rs | 39 +++++++++++++----------- server/src/state_ext.rs | 2 +- server/src/sys/mod.rs | 6 ++-- server/src/sys/object.rs | 2 +- server/src/sys/terrain.rs | 6 +++- voxygen/src/hud/mod.rs | 2 +- voxygen/src/hud/overhead.rs | 36 ++++++++++++---------- world/src/site/settlement/mod.rs | 15 +++++++-- 21 files changed, 128 insertions(+), 72 deletions(-) create mode 100644 assets/common/items/training_dummy.ron diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d78c775b6..59e3321783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new animals - Better pathfinding - Bombs +- Training dummies ### Changed diff --git a/assets/common/items/training_dummy.ron b/assets/common/items/training_dummy.ron new file mode 100644 index 0000000000..9e9a4bd22a --- /dev/null +++ b/assets/common/items/training_dummy.ron @@ -0,0 +1,7 @@ +Item( + name: "Training Dummy", + description: "His name is William. Fire at will.\n\n", + kind: Throwable( + kind: TrainingDummy, + ), +) diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 6363740403..68c68ffc4f 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -778,11 +778,15 @@ "voxel.sprite.velorite.velorite_1", (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, ), - // Throwables + // Throwables Throwable(Bomb): VoxTrans( "voxel.object.bomb", (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, ), + Throwable(TrainingDummy): VoxTrans( + "voxel.object.training_dummy", + (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, + ), // Ingredients Ingredient(Flower): VoxTrans( "voxel.sprite.flowers.flower_red_2", diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index f98f1bdb92..865c4992e5 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -123,6 +123,10 @@ pub const ALL_OBJECTS: [Body; 54] = [ Body::TrainingDummy, ]; +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::Object(body) } +} + impl Body { pub fn to_string(&self) -> &str { match self { diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index fd90138a0c..72218b795b 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -31,6 +31,7 @@ pub enum Consumable { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Throwable { Bomb, + TrainingDummy, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/common/src/event.rs b/common/src/event.rs index 47a6e34df8..7bcb6683db 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -70,7 +70,7 @@ pub enum ServerEvent { stats: comp::Stats, loadout: comp::Loadout, body: comp::Body, - agent: comp::Agent, + agent: Option, alignment: comp::Alignment, scale: comp::Scale, drop_item: Option, diff --git a/common/src/generation.rs b/common/src/generation.rs index 3edd331c24..cb285faf1d 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -12,6 +12,7 @@ pub struct EntityInfo { pub pos: Vec3, pub is_waypoint: bool, // Edge case, overrides everything else pub is_giant: bool, + pub has_agency: bool, pub alignment: Alignment, pub body: Body, pub name: Option, @@ -28,6 +29,7 @@ impl EntityInfo { pos, is_waypoint: false, is_giant: false, + has_agency: true, alignment: Alignment::Wild, body: Body::Humanoid(humanoid::Body::random()), name: None, @@ -66,8 +68,13 @@ impl EntityInfo { self } - pub fn with_name(mut self, name: String) -> Self { - self.name = Some(name); + pub fn with_name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + + pub fn with_agency(mut self, agency: bool) -> Self { + self.has_agency = agency; self } diff --git a/common/src/ray.rs b/common/src/ray.rs index 86b0e51a18..f0e412975e 100644 --- a/common/src/ray.rs +++ b/common/src/ray.rs @@ -2,9 +2,9 @@ use crate::vol::{ReadVol, Vox}; use vek::*; pub trait RayUntil = FnMut(&V) -> bool; -pub trait RayForEach = FnMut(Vec3); +pub trait RayForEach = FnMut(&V, Vec3); -pub struct Ray<'a, V: ReadVol, F: RayUntil, G: RayForEach> { +pub struct Ray<'a, V: ReadVol, F: RayUntil, G: RayForEach> { vol: &'a V, from: Vec3, to: Vec3, @@ -14,7 +14,7 @@ pub struct Ray<'a, V: ReadVol, F: RayUntil, G: RayForEach> { ignore_error: bool, } -impl<'a, V: ReadVol, F: RayUntil, G: RayForEach> Ray<'a, V, F, G> { +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, @@ -29,7 +29,7 @@ impl<'a, V: ReadVol, F: RayUntil, G: RayForEach> Ray<'a, V, F, G> { 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> { + pub fn for_each>(self, f: H) -> Ray<'a, V, F, H> { Ray { for_each: Some(f), vol: self.vol, @@ -69,12 +69,16 @@ impl<'a, V: ReadVol, F: RayUntil, G: RayForEach> Ray<'a, V, F, G> { break; } + let vox = self.vol.get(ipos); + // for_each if let Some(g) = &mut self.for_each { - g(ipos); + if let Ok(vox) = vox { + g(vox, ipos); + } } - match self.vol.get(ipos).map(|vox| (vox, (self.until)(vox))) { + match vox.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/sys/combat.rs b/common/src/sys/combat.rs index ee7ac80143..910c87e5f5 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -85,7 +85,7 @@ impl<'a> System<'a> for Sys { &orientations, scales.maybe(), agents.maybe(), - &character_states, + character_states.maybe(), &stats, &bodies, ) @@ -129,7 +129,7 @@ impl<'a> System<'a> for Sys { } // Block - if character_b.is_block() + if character_b.map(|c_b| c_b.is_block()).unwrap_or(false) && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0 { healthchange *= 1.0 - BLOCK_EFFICIENCY diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index af95d1cc98..28045e8313 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -33,13 +33,10 @@ impl<'a> System<'a> for Sys { } stats.set_event_emission(true); - // Mutates all stats every tick causing the server to resend this component for - // every entity every tick - for (entity, character_state, mut stats, mut energy) in ( + // Update stats + for (entity, mut stats) in ( &entities, - &character_states, &mut stats.restrict_mut(), - &mut energies.restrict_mut(), ) .join() { @@ -74,7 +71,15 @@ impl<'a> System<'a> for Sys { stat.health .set_to(stat.health.maximum(), HealthSource::LevelUp); } + } + // Update energies + for (character_state, mut energy) in ( + &character_states, + &mut energies.restrict_mut(), + ) + .join() + { match character_state { // Accelerate recharging energy. CharacterState::Idle { .. } diff --git a/common/src/vol.rs b/common/src/vol.rs index 2fc3fb54cc..210779c8d6 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -102,7 +102,7 @@ pub trait ReadVol: BaseVol { &'a self, from: Vec3, to: Vec3, - ) -> Ray<'a, Self, fn(&Self::Vox) -> bool, fn(Vec3)> + ) -> Ray<'a, Self, fn(&Self::Vox) -> bool, fn(&Self::Vox, Vec3)> where Self: Sized, { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index b695c20779..fb8606e37a 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -31,7 +31,7 @@ pub fn handle_create_npc( stats: Stats, loadout: Loadout, body: Body, - agent: Agent, + agent: impl Into>, alignment: Alignment, scale: Scale, drop_item: Option, @@ -39,10 +39,15 @@ pub fn handle_create_npc( let entity = server .state .create_npc(pos, stats, loadout, body) - .with(agent) .with(scale) .with(alignment); + let entity = if let Some(agent) = agent.into() { + entity.with(agent) + } else { + entity + }; + let entity = if let Some(drop_item) = drop_item { entity.with(ItemDrop(drop_item)) } else { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index f67770cc87..423b707753 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -211,7 +211,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Opti for (pos_b, ori_b, character_b, stats_b) in ( &ecs.read_storage::(), &ecs.read_storage::(), - &ecs.read_storage::(), + ecs.read_storage::().maybe(), &mut ecs.write_storage::(), ) .join() @@ -231,7 +231,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Opti } // Block - if character_b.is_block() + if character_b.map(|c_b| c_b.is_block()).unwrap_or(false) && ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0 { dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as u32 @@ -261,7 +261,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Opti .read_resource::() .ray(pos, pos + dir * color_range) .until(|_| rand::random::() < 0.05) - .for_each(|pos| touched_blocks.push(pos)) + .for_each(|_: &Block, pos| touched_blocks.push(pos)) .cast(); } @@ -296,12 +296,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Opti let _ = terrain .ray(pos, pos + dir * power) .until(|block| block.is_fluid() || rand::random::() < 0.05) - .for_each(|pos| { - if terrain - .get(pos) - .map(|block| block.is_explodable()) - .unwrap_or(false) - { + .for_each(|block: &Block, pos| { + if block.is_explodable() { block_change.set(pos, Block::empty()); } }) diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index d0d857c89e..4254e196f4 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -159,7 +159,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv maybe_effect = Some(*effect); Some(comp::InventoryUpdateEvent::Consumed(*kind)) }, - ItemKind::Throwable { .. } => { + ItemKind::Throwable { kind, .. } => { if let Some(pos) = state.ecs().read_storage::().get(entity) { @@ -177,7 +177,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .get(entity) .copied() .unwrap_or_default(), - item, + kind.clone(), )); } Some(comp::InventoryUpdateEvent::Used) @@ -344,8 +344,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .build(); } - // Drop items - for (pos, vel, ori, item) in thrown_items { + // Throw items + for (pos, vel, ori, kind) in thrown_items { let vel = vel.0 + *ori.0 * 20.0 + Vec3::unit_z() * 15.0 @@ -353,26 +353,31 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv let uid = state.read_component_cloned::(entity); - let mut entity = state - .create_object(Default::default(), comp::object::Body::Bomb) + let mut new_entity = state + .create_object(Default::default(), match kind { + item::Throwable::Bomb => comp::object::Body::Bomb, + item::Throwable::TrainingDummy => comp::object::Body::TrainingDummy, + }) .with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25)) .with(comp::Vel(vel)); - #[allow(clippy::single_match)] - match item.kind { - item::ItemKind::Throwable { - kind: item::Throwable::Bomb, - .. - } => { - entity = entity.with(comp::Object::Bomb { + match kind { + item::Throwable::Bomb => { + new_entity = new_entity.with(comp::Object::Bomb { timeout: Duration::from_secs_f32(1.0), owner: uid, - }) + }); }, - _ => {}, - } + item::Throwable::TrainingDummy => { + new_entity = new_entity + .with(comp::Stats::new( + "Training Dummy".to_string(), + comp::object::Body::TrainingDummy.into(), + )); + }, + }; - entity.build(); + new_entity.build(); } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index ad9ca3f5e1..20a7baac5d 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -128,7 +128,7 @@ impl StateExt for State { .with(comp::Vel(Vec3::zero())) .with(comp::Ori::default()) .with(comp::Body::Object(object)) - .with(comp::Mass(100.0)) + .with(comp::Mass(5.0)) .with(comp::Collider::Box { radius: 0.4, z_min: 0.0, diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs index 252c05591a..5afef7795c 100644 --- a/server/src/sys/mod.rs +++ b/server/src/sys/mod.rs @@ -31,9 +31,9 @@ pub type PersistenceScheduler = SysScheduler; //const SUBSCRIPTION_SYS: &str = "server_subscription_sys"; //const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys"; const TERRAIN_SYS: &str = "server_terrain_sys"; -const WAYPOINT_SYS: &str = "waypoint_sys"; -const PERSISTENCE_SYS: &str = "persistence_sys"; -const OBJECT_SYS: &str = "object_sys"; +const WAYPOINT_SYS: &str = "server_waypoint_sys"; +const PERSISTENCE_SYS: &str = "server_persistence_sys"; +const OBJECT_SYS: &str = "server_object_sys"; pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]); diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index 36ed8371eb..3becae6218 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -6,7 +6,7 @@ use common::{ use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; -/// This system is responsible for handling projectile effect triggers +/// This system is responsible for handling misc object behaviours pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index cdb3fd387d..54d01b0596 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -346,8 +346,12 @@ impl<'a> System<'a> for Sys { stats, loadout, body, + agent: if entity.has_agency { + Some(comp::Agent::new(entity.pos, can_speak)) + } else { + None + }, alignment, - agent: comp::Agent::new(entity.pos, can_speak), scale: comp::Scale(scale), drop_item: entity.loot_drop, }) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 602ba98fa5..da04cedf2f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -970,7 +970,7 @@ impl Hud { &pos, interpolated.maybe(), &stats, - &energy, + energy.maybe(), players.maybe(), scales.maybe(), &bodies, diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index 32204fb527..da74395dd2 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -54,7 +54,7 @@ pub struct Overhead<'a> { name: &'a str, bubble: Option<&'a SpeechBubble>, stats: &'a Stats, - energy: &'a Energy, + energy: Option<&'a Energy>, own_level: u32, settings: &'a GameplaySettings, pulse: f32, @@ -71,7 +71,7 @@ impl<'a> Overhead<'a> { name: &'a str, bubble: Option<&'a SpeechBubble>, stats: &'a Stats, - energy: &'a Energy, + energy: Option<&'a Energy>, own_level: u32, settings: &'a GameplaySettings, pulse: f32, @@ -297,7 +297,6 @@ impl<'a> Widget for Overhead<'a> { let hp_percentage = self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0; - let energy_percentage = self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0; let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); @@ -325,20 +324,25 @@ impl<'a> Widget for Overhead<'a> { })) .parent(id) .set(state.ids.health_bar, ui); + // % Mana Filling - Rectangle::fill_with( - [ - 72.0 * (self.energy.current() as f64 / self.energy.maximum() as f64) * BARSIZE, - MANA_BAR_HEIGHT, - ], - MANA_COLOR, - ) - .x_y( - ((3.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE, - MANA_BAR_Y, //-32.0, - ) - .parent(id) - .set(state.ids.mana_bar, ui); + if let Some(energy) = self.energy { + let energy_factor = energy.current() as f64 / energy.maximum() as f64; + + Rectangle::fill_with( + [ + 72.0 * energy_factor * BARSIZE, + MANA_BAR_HEIGHT, + ], + MANA_COLOR, + ) + .x_y( + ((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE, + MANA_BAR_Y, //-32.0, + ) + .parent(id) + .set(state.ids.mana_bar, ui); + } // Foreground Image::new(self.imgs.enemy_health) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index be659d840f..546f81215b 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -10,7 +10,7 @@ use crate::{ use common::{ assets, astar::Astar, - comp::{self, bird_medium, humanoid, quadruped_small}, + comp::{self, bird_medium, humanoid, quadruped_small, object}, generation::{ChunkSupplement, EntityInfo}, path::Path, spiral::Spiral2d, @@ -789,8 +789,13 @@ impl Settlement { && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0)) { let is_human: bool; + let is_dummy = RandomField::new(self.seed + 1).chance(Vec3::from(wpos2d), 1.0 / 15.0); let entity = EntityInfo::at(entity_wpos) .with_body(match rng.gen_range(0, 4) { + _ if is_dummy => { + is_human = false; + object::Body::TrainingDummy.into() + }, 0 => { let species = match rng.gen_range(0, 3) { 0 => quadruped_small::Species::Pig, @@ -819,7 +824,10 @@ impl Settlement { comp::Body::Humanoid(humanoid::Body::random()) }, }) - .with_alignment(if is_human { + .with_agency(!is_dummy) + .with_alignment(if is_dummy { + comp::Alignment::Wild + } else if is_human { comp::Alignment::Npc } else { comp::Alignment::Tame @@ -838,7 +846,8 @@ impl Settlement { }, )) }) - .with_automatic_name(); + .do_if(is_dummy, |e| e.with_name("Training Dummy")) + .do_if(!is_dummy, |e| e.with_automatic_name()); supplement.add_entity(entity); }