From f3ca06aa71816a3eee350814d6009d16fa1c3ad3 Mon Sep 17 00:00:00 2001 From: timokoesters Date: Sun, 22 Mar 2020 20:39:50 +0100 Subject: [PATCH] feat: fireball explosions --- common/src/comp/ability.rs | 23 +++++++ common/src/comp/character_state.rs | 4 ++ common/src/comp/inventory/item.rs | 22 ++++--- common/src/comp/projectile.rs | 1 + common/src/event.rs | 3 +- common/src/states/cast_fireball.rs | 81 ++++++++++++++++++++++++ common/src/states/mod.rs | 1 + common/src/sys/character_behavior.rs | 1 + common/src/sys/projectile.rs | 28 +++++++- server/src/cmd.rs | 16 +++-- server/src/events/entity_manipulation.rs | 51 +++++++++++++-- server/src/events/mod.rs | 4 +- 12 files changed, 210 insertions(+), 25 deletions(-) create mode 100644 common/src/states/cast_fireball.rs diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index cd3c489900..40a9fb41c7 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -21,6 +21,11 @@ pub enum CharacterAbility { projectile: Projectile, projectile_body: Body, }, + CastFireball { + recover_duration: Duration, + projectile: Projectile, + projectile_body: Body, + }, Boost { duration: Duration, only_up: bool, @@ -63,6 +68,13 @@ impl CharacterAbility { .try_change_by(-300, EnergySource::Ability) .is_ok() }, + CharacterAbility::CastFireball { .. } => { + !data.physics.in_fluid + && update + .energy + .try_change_by(-500, EnergySource::Ability) + .is_ok() + }, _ => true, } } @@ -118,6 +130,17 @@ impl From<&CharacterAbility> for CharacterState { projectile: projectile.clone(), projectile_body: *projectile_body, }), + CharacterAbility::CastFireball { + recover_duration, + projectile, + projectile_body, + } => CharacterState::CastFireball(cast_fireball::Data { + exhausted: false, + prepare_timer: Duration::default(), + recover_duration: *recover_duration, + projectile: projectile.clone(), + projectile_body: *projectile_body, + }), CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data { duration: *duration, only_up: *only_up, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 7a9c08c499..0fe75fbf4c 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -52,6 +52,8 @@ pub enum CharacterState { BasicMelee(basic_melee::Data), /// A basic ranged attack (e.g. bow) BasicRanged(basic_ranged::Data), + /// Cast a fireball + CastFireball(cast_fireball::Data), /// A force will boost you into a direction for some duration Boost(boost::Data), /// Dash forward and then attack @@ -70,6 +72,7 @@ impl CharacterState { CharacterState::Wielding | CharacterState::BasicMelee(_) | CharacterState::BasicRanged(_) + | CharacterState::CastFireball(_) | CharacterState::DashMelee(_) | CharacterState::TripleStrike(_) | CharacterState::TimedCombo(_) @@ -89,6 +92,7 @@ impl CharacterState { match self { CharacterState::BasicMelee(_) | CharacterState::BasicRanged(_) + | CharacterState::CastFireball(_) | CharacterState::TimedCombo(_) | CharacterState::DashMelee(_) | CharacterState::TripleStrike(_) => true, diff --git a/common/src/comp/inventory/item.rs b/common/src/comp/inventory/item.rs index 3b95c04e6f..ed82aa1039 100644 --- a/common/src/comp/inventory/item.rs +++ b/common/src/comp/inventory/item.rs @@ -120,19 +120,21 @@ impl ToolData { range: 10.0, max_angle: 45.0, }, - BasicRanged { + CastFireball { projectile: Projectile { - hit_ground: vec![projectile::Effect::Vanish], - hit_wall: vec![projectile::Effect::Vanish], - hit_entity: vec![ - projectile::Effect::Damage(HealthChange { - // TODO: This should not be fixed (?) - amount: -8, - cause: HealthSource::Projectile { owner: None }, - }), + hit_ground: vec![ + projectile::Effect::Explode { power: 5.0 }, projectile::Effect::Vanish, ], - time_left: Duration::from_secs(5), + hit_wall: vec![ + projectile::Effect::Explode { power: 5.0 }, + projectile::Effect::Vanish, + ], + hit_entity: vec![ + projectile::Effect::Explode { power: 5.0 }, + projectile::Effect::Vanish, + ], + time_left: Duration::from_secs(20), owner: None, }, projectile_body: Body::Object(object::Body::BoltFire), diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index 4c32bf0ee9..6f1982be33 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -6,6 +6,7 @@ use std::time::Duration; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Effect { Damage(comp::HealthChange), + Explode { power: f32 }, Vanish, Stick, Possess, diff --git a/common/src/event.rs b/common/src/event.rs index 39e8a63e23..8192923c0c 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -73,7 +73,8 @@ pub enum LocalEvent { pub enum ServerEvent { Explosion { pos: Vec3, - radius: f32, + power: f32, + owner: Option, }, Damage { uid: Uid, diff --git a/common/src/states/cast_fireball.rs b/common/src/states/cast_fireball.rs new file mode 100644 index 0000000000..9887803309 --- /dev/null +++ b/common/src/states/cast_fireball.rs @@ -0,0 +1,81 @@ +use crate::{ + comp::{Body, CharacterState, Gravity, Projectile, StateUpdate}, + event::ServerEvent, + states::utils::*, + sys::character_behavior::*, +}; +use std::time::Duration; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// How long we prepared the weapon already + pub prepare_timer: Duration, + /// How long the state has until exiting + pub recover_duration: Duration, + /// Projectile + pub projectile: Projectile, + /// Projectile + pub projectile_body: Body, + /// Whether the attack fired already + pub exhausted: bool, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + + handle_move(data, &mut update); + handle_jump(data, &mut update); + + if !self.exhausted + && (data.inputs.primary.is_pressed() | data.inputs.secondary.is_pressed()) + { + // Prepare (draw the bow) + update.character = CharacterState::CastFireball(Data { + prepare_timer: self.prepare_timer + Duration::from_secs_f32(data.dt.0), + recover_duration: self.recover_duration, + projectile: self.projectile.clone(), + projectile_body: self.projectile_body, + exhausted: false, + }); + } else if !self.exhausted { + // Fire + let mut projectile = self.projectile.clone(); + projectile.set_owner(*data.uid); + update.server_events.push_front(ServerEvent::Shoot { + entity: data.entity, + dir: data.inputs.look_dir, + body: self.projectile_body, + light: None, + projectile, + gravity: Some(Gravity(0.1)), + }); + + update.character = CharacterState::CastFireball(Data { + prepare_timer: self.prepare_timer, + recover_duration: self.recover_duration, + projectile: self.projectile.clone(), + projectile_body: self.projectile_body, + exhausted: true, + }); + } else if self.recover_duration != Duration::default() { + // Recovery + update.character = CharacterState::CastFireball(Data { + prepare_timer: self.prepare_timer, + recover_duration: self + .recover_duration + .checked_sub(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + projectile: self.projectile.clone(), + projectile_body: self.projectile_body, + exhausted: true, + }); + return update; + } else { + // Done + update.character = CharacterState::Wielding; + } + + update + } +} diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 1dcf09c211..f249cc5638 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -2,6 +2,7 @@ pub mod basic_block; pub mod basic_melee; pub mod basic_ranged; pub mod boost; +pub mod cast_fireball; pub mod climb; pub mod dash_melee; pub mod equipping; diff --git a/common/src/sys/character_behavior.rs b/common/src/sys/character_behavior.rs index ebccef6258..8e9423510e 100644 --- a/common/src/sys/character_behavior.rs +++ b/common/src/sys/character_behavior.rs @@ -183,6 +183,7 @@ impl<'a> System<'a> for Sys { CharacterState::TripleStrike(data) => data.behavior(&j), CharacterState::BasicMelee(data) => data.behavior(&j), CharacterState::BasicRanged(data) => data.behavior(&j), + CharacterState::CastFireball(data) => data.behavior(&j), CharacterState::Boost(data) => data.behavior(&j), CharacterState::DashMelee(data) => data.behavior(&j), CharacterState::TimedCombo(data) => data.behavior(&j), diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 168035a4ca..4a8b15dd7b 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{projectile, HealthSource, Ori, PhysicsState, Projectile, Vel}, + comp::{projectile, HealthSource, Ori, PhysicsState, Pos, Projectile, Vel}, event::{EventBus, ServerEvent}, state::DeltaTime, }; @@ -13,6 +13,7 @@ impl<'a> System<'a> for Sys { Entities<'a>, Read<'a, DeltaTime>, Read<'a, EventBus>, + ReadStorage<'a, Pos>, ReadStorage<'a, PhysicsState>, ReadStorage<'a, Vel>, WriteStorage<'a, Ori>, @@ -25,6 +26,7 @@ impl<'a> System<'a> for Sys { entities, dt, server_bus, + positions, physics_states, velocities, mut orientations, @@ -34,8 +36,9 @@ impl<'a> System<'a> for Sys { let mut server_emitter = server_bus.emitter(); // Attacks - for (entity, physics, ori, projectile) in ( + for (entity, pos, physics, ori, projectile) in ( &entities, + &positions, &physics_states, &mut orientations, &mut projectiles, @@ -46,6 +49,13 @@ impl<'a> System<'a> for Sys { if physics.on_ground { for effect in projectile.hit_ground.drain(..) { match effect { + projectile::Effect::Explode { power } => { + server_emitter.emit(ServerEvent::Explosion { + pos: pos.0, + power, + owner: projectile.owner, + }) + }, projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy { entity, cause: HealthSource::World, @@ -58,6 +68,13 @@ impl<'a> System<'a> for Sys { else if physics.on_wall.is_some() { for effect in projectile.hit_wall.drain(..) { match effect { + projectile::Effect::Explode { power } => { + server_emitter.emit(ServerEvent::Explosion { + pos: pos.0, + power, + owner: projectile.owner, + }) + }, projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy { entity, cause: HealthSource::World, @@ -73,6 +90,13 @@ impl<'a> System<'a> for Sys { projectile::Effect::Damage(change) => { server_emitter.emit(ServerEvent::Damage { uid: other, change }) }, + projectile::Effect::Explode { power } => { + server_emitter.emit(ServerEvent::Explosion { + pos: pos.0, + power, + owner: projectile.owner, + }) + }, projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy { entity, cause: HealthSource::World, diff --git a/server/src/cmd.rs b/server/src/cmd.rs index a53247706d..d8e0ac61fa 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -829,14 +829,18 @@ 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); + let power = scan_fmt!(&args, action.arg_fmt, f32).unwrap_or(8.0); + let ecs = server.state.ecs(); match server.state.read_component_cloned::(entity) { - Some(pos) => server - .state - .ecs() - .read_resource::>() - .emit_now(ServerEvent::Explosion { pos: pos.0, radius }), + Some(pos) => { + ecs.read_resource::>() + .emit_now(ServerEvent::Explosion { + pos: pos.0, + power, + owner: ecs.read_storage::().get(entity).copied(), + }) + }, None => server.notify_client( entity, ServerMsg::private(String::from("You have no position!")), diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 8d9de9215c..1c22232a9f 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -9,8 +9,11 @@ use common::{ vol::{ReadVol, Vox}, }; use log::error; -use specs::{Entity as EcsEntity, WorldExt}; -use vek::Vec3; +use specs::{join::Join, Entity as EcsEntity, WorldExt}; +use vek::{Vec3, *}; + +const BLOCK_EFFICIENCY: f32 = 0.9; +const BLOCK_ANGLE: f32 = 180.0; pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) { let state = &server.state; @@ -189,7 +192,46 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) { } } -pub fn handle_explosion(server: &Server, pos: Vec3, radius: f32) { +pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Option) { + // Go through all other entities + let ecs = &server.state.ecs(); + for (b, uid_b, pos_b, ori_b, character_b, stats_b) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &mut ecs.write_storage::(), + ) + .join() + { + // Check if it is a hit + if !stats_b.is_dead + // Spherical wedge shaped attack field + // RADIUS + && pos.distance_squared(pos_b.0) < 10_f32.powi(2) + { + // Weapon gives base damage + let mut dmg = power as u32 * 2; + + if rand::random() { + dmg += 1; + } + + // Block + if character_b.is_block() + && 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 + } + + stats_b.health.change_by(HealthChange { + amount: -(dmg as i32), + cause: HealthSource::Projectile { owner }, + }); + } + } + const RAYS: usize = 500; for _ in 0..RAYS { @@ -200,12 +242,11 @@ pub fn handle_explosion(server: &Server, pos: Vec3, radius: f32) { ) .normalized(); - let ecs = server.state.ecs(); let mut block_change = ecs.write_resource::(); let _ = ecs .read_resource::() - .ray(pos, pos + dir * radius) + .ray(pos, pos + dir * power) .until(|_| rand::random::() < 0.05) .for_each(|pos| block_change.set(pos, Block::empty())) .cast(); diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index d1a833cd79..22607c185a 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -45,7 +45,9 @@ impl Server { for event in events { match event { - ServerEvent::Explosion { pos, radius } => handle_explosion(&self, pos, radius), + ServerEvent::Explosion { pos, power, owner } => { + handle_explosion(&self, pos, power, owner) + }, ServerEvent::Shoot { entity, dir,