mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
feat: fireball explosions
This commit is contained in:
parent
1f363da248
commit
f3ca06aa71
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -73,7 +73,8 @@ pub enum LocalEvent {
|
||||
pub enum ServerEvent {
|
||||
Explosion {
|
||||
pos: Vec3<f32>,
|
||||
radius: f32,
|
||||
power: f32,
|
||||
owner: Option<Uid>,
|
||||
},
|
||||
Damage {
|
||||
uid: Uid,
|
||||
|
81
common/src/states/cast_fireball.rs
Normal file
81
common/src/states/cast_fireball.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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!")),
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user