feat: fireball explosions

This commit is contained in:
timokoesters 2020-03-22 20:39:50 +01:00
parent 1f363da248
commit f3ca06aa71
12 changed files with 210 additions and 25 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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),

View File

@ -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,

View File

@ -73,7 +73,8 @@ pub enum LocalEvent {
pub enum ServerEvent {
Explosion {
pos: Vec3<f32>,
radius: f32,
power: f32,
owner: Option<Uid>,
},
Damage {
uid: Uid,

View File

@ -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
}
}

View File

@ -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;

View File

@ -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),

View File

@ -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<ServerEvent>>,
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,

View File

@ -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::<comp::Pos>(entity) {
Some(pos) => server
.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::Explosion { pos: pos.0, radius }),
Some(pos) => {
ecs.read_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::Explosion {
pos: pos.0,
power,
owner: ecs.read_storage::<Uid>().get(entity).copied(),
})
},
None => server.notify_client(
entity,
ServerMsg::private(String::from("You have no position!")),

View File

@ -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<f32>, radius: f32) {
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Option<Uid>) {
// 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::<Uid>(),
&ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::Ori>(),
&ecs.read_storage::<comp::CharacterState>(),
&mut ecs.write_storage::<comp::Stats>(),
)
.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<f32>, radius: f32) {
)
.normalized();
let ecs = server.state.ecs();
let mut block_change = ecs.write_resource::<BlockChange>();
let _ = ecs
.read_resource::<TerrainGrid>()
.ray(pos, pos + dir * radius)
.ray(pos, pos + dir * power)
.until(|_| rand::random::<f32>() < 0.05)
.for_each(|pos| block_change.set(pos, Block::empty()))
.cast();

View File

@ -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,