mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Converted beam system from spherical shell wedges to quadratic beziers
This commit is contained in:
parent
32b0b33abe
commit
ef5e37a64d
@ -43,7 +43,7 @@ macro_rules! synced_components {
|
||||
character_state: CharacterState,
|
||||
character_activity: CharacterActivity,
|
||||
shockwave: Shockwave,
|
||||
beam_segment: BeamSegment,
|
||||
beam: Beam,
|
||||
alignment: Alignment,
|
||||
stance: Stance,
|
||||
// TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum
|
||||
@ -214,7 +214,7 @@ impl NetSync for Shockwave {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for BeamSegment {
|
||||
impl NetSync for Beam {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
|
@ -902,7 +902,7 @@ pub enum CharacterAbility {
|
||||
BasicBeam {
|
||||
buildup_duration: f32,
|
||||
recover_duration: f32,
|
||||
beam_duration: f32,
|
||||
beam_duration: f64,
|
||||
damage: f32,
|
||||
tick_rate: f32,
|
||||
range: f32,
|
||||
@ -1536,7 +1536,7 @@ impl CharacterAbility {
|
||||
*tick_rate *= stats.speed;
|
||||
*range *= stats.range;
|
||||
// Duration modified to keep velocity constant
|
||||
*beam_duration *= stats.range;
|
||||
*beam_duration *= stats.range as f64;
|
||||
*energy_drain /= stats.energy_efficiency;
|
||||
*damage_effect = damage_effect.map(|de| de.adjusted_by_stats(stats));
|
||||
},
|
||||
@ -2127,7 +2127,7 @@ impl CharacterAbility {
|
||||
let range_mod = modifiers.range.powi(level.into());
|
||||
*range *= range_mod;
|
||||
// Duration modified to keep velocity constant
|
||||
*beam_duration *= range_mod;
|
||||
*beam_duration *= range_mod as f64;
|
||||
}
|
||||
if let Ok(level) = skillset.skill_level(Staff(FDrain)) {
|
||||
*energy_drain *= modifiers.energy_drain.powi(level.into());
|
||||
@ -2135,7 +2135,7 @@ impl CharacterAbility {
|
||||
if let Ok(level) = skillset.skill_level(Staff(FVelocity)) {
|
||||
let velocity_increase = modifiers.velocity.powi(level.into());
|
||||
let duration_mod = 1.0 / (1.0 + velocity_increase);
|
||||
*beam_duration *= duration_mod;
|
||||
*beam_duration *= duration_mod as f64;
|
||||
}
|
||||
},
|
||||
CharacterAbility::Shockwave {
|
||||
@ -2185,7 +2185,7 @@ impl CharacterAbility {
|
||||
let range_mod = modifiers.range.powi(level.into());
|
||||
*range *= range_mod;
|
||||
// Duration modified to keep velocity constant
|
||||
*beam_duration *= range_mod;
|
||||
*beam_duration *= range_mod as f64;
|
||||
}
|
||||
if let Ok(level) = skillset.skill_level(Sceptre(LRegen)) {
|
||||
*energy_regen *= modifiers.energy_regen.powi(level.into());
|
||||
@ -2736,11 +2736,11 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
||||
static_data: basic_beam::StaticData {
|
||||
buildup_duration: Duration::from_secs_f32(*buildup_duration),
|
||||
recover_duration: Duration::from_secs_f32(*recover_duration),
|
||||
beam_duration: Duration::from_secs_f32(*beam_duration),
|
||||
beam_duration: Secs(*beam_duration),
|
||||
damage: *damage,
|
||||
tick_rate: *tick_rate,
|
||||
range: *range,
|
||||
max_angle: *max_angle,
|
||||
end_radius: max_angle.to_radians().tan() * *range,
|
||||
damage_effect: *damage_effect,
|
||||
energy_regen: *energy_regen,
|
||||
energy_drain: *energy_drain,
|
||||
@ -2750,6 +2750,8 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
aim_dir: data.ori.look_dir(),
|
||||
beam_offset: data.pos.0,
|
||||
}),
|
||||
CharacterAbility::BasicAura {
|
||||
buildup_duration,
|
||||
|
@ -1,48 +1,23 @@
|
||||
use crate::{combat::Attack, uid::Uid};
|
||||
use crate::{combat::Attack, resources::Secs};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use std::time::Duration;
|
||||
use specs::{Component, DerefFlaggedStorage, Entity as EcsEntity};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Properties {
|
||||
pub attack: Attack,
|
||||
pub angle: f32,
|
||||
pub speed: f32,
|
||||
pub duration: Duration,
|
||||
pub owner: Option<Uid>,
|
||||
pub specifier: FrontendSpecifier,
|
||||
}
|
||||
|
||||
// TODO: Separate components out for cheaper network syncing
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct BeamSegment {
|
||||
pub properties: Properties,
|
||||
#[serde(skip)]
|
||||
/// Time that the beam segment was created at
|
||||
/// Used to calculate beam propagation
|
||||
/// Deserialized from the network as `None`
|
||||
pub creation: Option<f64>,
|
||||
}
|
||||
|
||||
impl Component for BeamSegment {
|
||||
type Storage = DerefFlaggedStorage<Self, specs::DenseVecStorage<Self>>;
|
||||
}
|
||||
|
||||
impl std::ops::Deref for BeamSegment {
|
||||
type Target = Properties;
|
||||
|
||||
fn deref(&self) -> &Properties { &self.properties }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Beam {
|
||||
pub hit_entities: Vec<Uid>,
|
||||
pub tick_dur: Duration,
|
||||
pub timer: Duration,
|
||||
pub attack: Attack,
|
||||
pub end_radius: f32,
|
||||
pub range: f32,
|
||||
pub duration: Secs,
|
||||
pub tick_dur: Secs,
|
||||
pub specifier: FrontendSpecifier,
|
||||
pub bezier: QuadraticBezier3<f32>,
|
||||
#[serde(skip)]
|
||||
pub hit_entities: Vec<EcsEntity>,
|
||||
}
|
||||
|
||||
impl Component for Beam {
|
||||
type Storage = specs::DenseVecStorage<Self>;
|
||||
type Storage = DerefFlaggedStorage<Self, specs::DenseVecStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
|
@ -391,6 +391,8 @@ impl CharacterState {
|
||||
matches!(self.attack_kind(), Some(AttackSource::Melee))
|
||||
}
|
||||
|
||||
pub fn is_beam_attack(&self) -> bool { matches!(self.attack_kind(), Some(AttackSource::Beam)) }
|
||||
|
||||
pub fn can_perform_mounted(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
|
@ -50,7 +50,7 @@ pub use self::{
|
||||
},
|
||||
anchor::Anchor,
|
||||
aura::{Aura, AuraChange, AuraKind, Auras},
|
||||
beam::{Beam, BeamSegment},
|
||||
beam::Beam,
|
||||
body::{
|
||||
arthropod, biped_large, biped_small, bird_large, bird_medium, dragon, fish_medium,
|
||||
fish_small, golem, humanoid, item_drop, object, quadruped_low, quadruped_medium,
|
||||
|
@ -182,11 +182,6 @@ pub enum ServerEvent {
|
||||
entity: EcsEntity,
|
||||
impulse: Vec3<f32>,
|
||||
},
|
||||
BeamSegment {
|
||||
properties: comp::beam::Properties,
|
||||
pos: Pos,
|
||||
ori: Ori,
|
||||
},
|
||||
LandOnGround {
|
||||
entity: EcsEntity,
|
||||
vel: Vec3<f32>,
|
||||
|
@ -5,16 +5,16 @@ use crate::{
|
||||
},
|
||||
comp::{
|
||||
beam, body::biped_large, character_state::OutputEvents, object::Body::Flamethrower, Body,
|
||||
CharacterState, Ori, Pos, StateUpdate,
|
||||
CharacterState, Ori, StateUpdate,
|
||||
},
|
||||
event::{LocalEvent, ServerEvent},
|
||||
event::LocalEvent,
|
||||
outcome::Outcome,
|
||||
resources::Secs,
|
||||
states::{
|
||||
behavior::{CharacterBehavior, JoinData},
|
||||
utils::*,
|
||||
},
|
||||
terrain::Block,
|
||||
uid::Uid,
|
||||
util::Dir,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -28,16 +28,17 @@ pub struct StaticData {
|
||||
pub buildup_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// How long each beam segment persists for
|
||||
pub beam_duration: Duration,
|
||||
/// Time required for beam to travel from start pos to end pos
|
||||
pub beam_duration: Secs,
|
||||
/// Base damage per tick
|
||||
pub damage: f32,
|
||||
/// Ticks per second
|
||||
pub tick_rate: f32,
|
||||
/// Max range
|
||||
pub range: f32,
|
||||
/// Max angle (45.0 will give you a 90.0 angle window)
|
||||
pub max_angle: f32,
|
||||
/// The radius at the far distance of the beam. Radius linearly increases
|
||||
/// from 0 moving from start pos to end po.
|
||||
pub end_radius: f32,
|
||||
/// Adds an effect onto the main damage of the attack
|
||||
pub damage_effect: Option<CombatEffect>,
|
||||
/// Energy regenerated per tick
|
||||
@ -61,6 +62,10 @@ pub struct Data {
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
/// Direction that beam should be aimed in
|
||||
pub aim_dir: Dir,
|
||||
/// Offset for beam start pos
|
||||
pub beam_offset: Vec3<f32>,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
@ -93,11 +98,47 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
let attack = {
|
||||
let energy = AttackEffect::new(
|
||||
None,
|
||||
CombatEffect::EnergyReward(self.static_data.energy_regen),
|
||||
)
|
||||
.with_requirement(CombatRequirement::AnyDamage);
|
||||
let mut damage = AttackDamage::new(
|
||||
Damage {
|
||||
source: DamageSource::Energy,
|
||||
kind: DamageKind::Energy,
|
||||
value: self.static_data.damage,
|
||||
},
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
rand::random(),
|
||||
);
|
||||
if let Some(effect) = self.static_data.damage_effect {
|
||||
damage = damage.with_effect(effect);
|
||||
}
|
||||
let (crit_chance, crit_mult) =
|
||||
get_crit_data(data, self.static_data.ability_info);
|
||||
Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(energy)
|
||||
.with_combo_increment()
|
||||
};
|
||||
|
||||
// Creates beam
|
||||
data.updater.insert(data.entity, beam::Beam {
|
||||
hit_entities: Vec::<Uid>::new(),
|
||||
tick_dur: Duration::from_secs_f32(1.0 / self.static_data.tick_rate),
|
||||
timer: Duration::default(),
|
||||
attack,
|
||||
end_radius: self.static_data.end_radius,
|
||||
range: self.static_data.range,
|
||||
duration: self.static_data.beam_duration,
|
||||
tick_dur: Secs(1.0 / self.static_data.tick_rate as f64),
|
||||
hit_entities: Vec::new(),
|
||||
specifier: self.static_data.specifier,
|
||||
bezier: QuadraticBezier3 {
|
||||
start: data.pos.0,
|
||||
ctrl: data.pos.0,
|
||||
end: data.pos.0,
|
||||
},
|
||||
});
|
||||
// Build up
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
@ -112,42 +153,6 @@ impl CharacterBehavior for Data {
|
||||
&& (self.static_data.energy_drain <= f32::EPSILON
|
||||
|| update.energy.current() > 0.0)
|
||||
{
|
||||
let speed =
|
||||
self.static_data.range / self.static_data.beam_duration.as_secs_f32();
|
||||
|
||||
let energy = AttackEffect::new(
|
||||
None,
|
||||
CombatEffect::EnergyReward(self.static_data.energy_regen),
|
||||
)
|
||||
.with_requirement(CombatRequirement::AnyDamage);
|
||||
let mut damage = AttackDamage::new(
|
||||
Damage {
|
||||
source: DamageSource::Energy,
|
||||
kind: DamageKind::Energy,
|
||||
value: self.static_data.damage,
|
||||
},
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
rand::random(),
|
||||
);
|
||||
if let Some(effect) = self.static_data.damage_effect {
|
||||
damage = damage.with_effect(effect);
|
||||
}
|
||||
let (crit_chance, crit_mult) =
|
||||
get_crit_data(data, self.static_data.ability_info);
|
||||
let attack = Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(energy)
|
||||
.with_combo_increment();
|
||||
|
||||
let properties = beam::Properties {
|
||||
attack,
|
||||
angle: self.static_data.max_angle.to_radians(),
|
||||
speed,
|
||||
duration: self.static_data.beam_duration,
|
||||
owner: Some(*data.uid),
|
||||
specifier: self.static_data.specifier,
|
||||
};
|
||||
let beam_ori = {
|
||||
// We want Beam to use Ori of owner.
|
||||
// But we also want beam to use Z part of where owner looks.
|
||||
@ -184,15 +189,10 @@ impl CharacterBehavior for Data {
|
||||
rel_vel,
|
||||
data.physics.on_ground,
|
||||
);
|
||||
let pos = Pos(data.pos.0 + body_offsets);
|
||||
|
||||
// Create beam segment
|
||||
output_events.emit_server(ServerEvent::BeamSegment {
|
||||
properties,
|
||||
pos,
|
||||
ori: beam_ori,
|
||||
});
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
beam_offset: body_offsets,
|
||||
aim_dir: beam_ori.look_dir(),
|
||||
timer: tick_attack_or_default(data, self.timer, None),
|
||||
..*self
|
||||
});
|
||||
|
@ -213,7 +213,7 @@ impl State {
|
||||
ecs.register::<comp::Group>();
|
||||
ecs.register::<comp::Shockwave>();
|
||||
ecs.register::<comp::ShockwaveHitEntities>();
|
||||
ecs.register::<comp::BeamSegment>();
|
||||
ecs.register::<comp::Beam>();
|
||||
ecs.register::<comp::Alignment>();
|
||||
ecs.register::<comp::LootOwner>();
|
||||
ecs.register::<comp::Admin>();
|
||||
@ -261,7 +261,6 @@ impl State {
|
||||
ecs.register::<comp::Faction>();
|
||||
ecs.register::<comp::invite::Invite>();
|
||||
ecs.register::<comp::invite::PendingInvites>();
|
||||
ecs.register::<comp::Beam>();
|
||||
ecs.register::<VolumeRiders>();
|
||||
|
||||
// Register synced resources used by the ECS.
|
||||
|
@ -2,8 +2,8 @@ use common::{
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
Alignment, Beam, BeamSegment, Body, Buffs, CharacterState, Combo, Energy, Group, Health,
|
||||
Inventory, Ori, Player, Pos, Scale, Stats,
|
||||
Alignment, Beam, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
|
||||
Player, Pos, Scale, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
@ -17,10 +17,9 @@ use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||
use rand::Rng;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{
|
||||
shred::ResourceId, Entities, Join, LendJoin, ParJoin, Read, ReadExpect, ReadStorage,
|
||||
SystemData, World, WriteStorage,
|
||||
shred::ResourceId, Entities, LendJoin, ParJoin, Read, ReadExpect, ReadStorage, SystemData,
|
||||
World, WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
#[derive(SystemData)]
|
||||
@ -47,32 +46,52 @@ pub struct ReadData<'a> {
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
character_states: ReadStorage<'a, CharacterState>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
outcomes: Read<'a, EventBus<Outcome>>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling beams that heal or do damage
|
||||
#[derive(Default)]
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
ReadData<'a>,
|
||||
WriteStorage<'a, BeamSegment>,
|
||||
WriteStorage<'a, Beam>,
|
||||
Read<'a, EventBus<Outcome>>,
|
||||
);
|
||||
type SystemData = (ReadData<'a>, WriteStorage<'a, Beam>);
|
||||
|
||||
const NAME: &'static str = "beam";
|
||||
const ORIGIN: Origin = Origin::Common;
|
||||
const PHASE: Phase = Phase::Create;
|
||||
|
||||
fn run(
|
||||
job: &mut Job<Self>,
|
||||
(read_data, mut beam_segments, mut beams, outcomes): Self::SystemData,
|
||||
) {
|
||||
fn run(job: &mut Job<Self>, (read_data, mut beams): Self::SystemData) {
|
||||
let mut server_emitter = read_data.server_bus.emitter();
|
||||
let mut outcomes_emitter = outcomes.emitter();
|
||||
let mut outcomes_emitter = read_data.outcomes.emitter();
|
||||
|
||||
let time = read_data.time.0;
|
||||
let dt = read_data.dt.0;
|
||||
(
|
||||
&read_data.positions,
|
||||
&read_data.orientations,
|
||||
&read_data.character_states,
|
||||
&mut beams,
|
||||
)
|
||||
.lend_join()
|
||||
.for_each(|(pos, ori, char_state, mut beam)| {
|
||||
// Clear hit entities list if list should be cleared
|
||||
if read_data.time.0 % beam.tick_dur.0 < read_data.dt.0 as f64 {
|
||||
beam.hit_entities.clear();
|
||||
}
|
||||
// Update start, end, and control positions of beam bezier
|
||||
let (offset, target_dir) = if let CharacterState::BasicBeam(c) = char_state {
|
||||
(c.beam_offset, c.aim_dir)
|
||||
} else {
|
||||
(Vec3::zero(), ori.look_dir())
|
||||
};
|
||||
beam.bezier.start = pos.0 + offset;
|
||||
const REL_CTRL_DIST: f32 = 0.3;
|
||||
let target_ctrl = beam.bezier.start + *target_dir * beam.range * REL_CTRL_DIST;
|
||||
let ctrl_translate = (target_ctrl - beam.bezier.ctrl) * read_data.dt.0
|
||||
/ (beam.duration.0 as f32 * REL_CTRL_DIST);
|
||||
beam.bezier.ctrl += ctrl_translate;
|
||||
let target_end = beam.bezier.start + *target_dir * beam.range;
|
||||
let end_translate =
|
||||
(target_end - beam.bezier.end) * read_data.dt.0 / beam.duration.0 as f32;
|
||||
beam.bezier.end += end_translate;
|
||||
});
|
||||
|
||||
job.cpu_stats.measure(ParMode::Rayon);
|
||||
|
||||
@ -81,66 +100,32 @@ impl<'a> System<'a> for Sys {
|
||||
&read_data.entities,
|
||||
&read_data.positions,
|
||||
&read_data.orientations,
|
||||
&beam_segments,
|
||||
&read_data.uids,
|
||||
&beams,
|
||||
)
|
||||
.par_join()
|
||||
.fold(
|
||||
|| (Vec::new(), Vec::new(), Vec::new()),
|
||||
|(mut server_events, mut add_hit_entities, mut outcomes),
|
||||
(entity, pos, ori, beam_segment)| {
|
||||
let creation_time = match beam_segment.creation {
|
||||
Some(time) => time,
|
||||
// Skip newly created beam segments
|
||||
None => return (server_events, add_hit_entities, outcomes),
|
||||
};
|
||||
let end_time = creation_time + beam_segment.duration.as_secs_f64();
|
||||
|
||||
let beam_owner = beam_segment
|
||||
.owner
|
||||
.and_then(|uid| read_data.id_maps.uid_entity(uid));
|
||||
|
||||
(entity, pos, ori, uid, beam)| {
|
||||
// Note: rayon makes it difficult to hold onto a thread-local RNG, if grabbing
|
||||
// this becomes a bottleneck we can look into alternatives.
|
||||
let mut rng = rand::thread_rng();
|
||||
if rng.gen_bool(0.005) {
|
||||
server_events.push(ServerEvent::Sound {
|
||||
sound: Sound::new(SoundKind::Beam, pos.0, 13.0, time),
|
||||
sound: Sound::new(SoundKind::Beam, pos.0, 13.0, read_data.time.0),
|
||||
});
|
||||
}
|
||||
|
||||
// If beam segment is out of time emit destroy event but still continue since it
|
||||
// may have traveled and produced effects a bit before reaching its end point
|
||||
if end_time < time {
|
||||
server_events.push(ServerEvent::Delete(entity));
|
||||
}
|
||||
|
||||
// Determine area that was covered by the beam in the last tick
|
||||
let frame_time = dt.min((end_time - time) as f32);
|
||||
if frame_time <= 0.0 {
|
||||
return (server_events, add_hit_entities, outcomes);
|
||||
}
|
||||
// Note: min() probably unneeded
|
||||
let time_since_creation = (time - creation_time) as f32;
|
||||
let frame_start_dist =
|
||||
(beam_segment.speed * (time_since_creation - frame_time)).max(0.0);
|
||||
let frame_end_dist =
|
||||
(beam_segment.speed * time_since_creation).max(frame_start_dist);
|
||||
|
||||
// Group to ignore collisions with
|
||||
// Might make this more nuanced if beams are used for non damage effects
|
||||
let group = beam_owner.and_then(|e| read_data.groups.get(e));
|
||||
|
||||
let hit_entities = if let Some(beam) = beam_owner.and_then(|e| beams.get(e)) {
|
||||
&beam.hit_entities
|
||||
} else {
|
||||
return (server_events, add_hit_entities, outcomes);
|
||||
};
|
||||
let group = read_data.groups.get(entity);
|
||||
|
||||
// Go through all affectable entities by querying the spatial grid
|
||||
let target_iter = read_data
|
||||
.cached_spatial_grid
|
||||
.0
|
||||
.in_circle_aabr(pos.0.xy(), frame_end_dist - frame_start_dist)
|
||||
.in_circle_aabr(beam.bezier.start.xy(), beam.range)
|
||||
.filter_map(|target| {
|
||||
read_data
|
||||
.positions
|
||||
@ -154,7 +139,7 @@ impl<'a> System<'a> for Sys {
|
||||
});
|
||||
target_iter.for_each(|(target, uid_b, pos_b, health_b, body_b)| {
|
||||
// Check to see if entity has already been hit recently
|
||||
if hit_entities.iter().any(|&uid| uid == *uid_b) {
|
||||
if beam.hit_entities.iter().any(|&e| e == target) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -167,12 +152,10 @@ impl<'a> System<'a> for Sys {
|
||||
// TODO: use Capsule Prism instead of cylinder
|
||||
let hit = entity != target
|
||||
&& !health_b.is_dead
|
||||
&& sphere_wedge_cylinder_collision(
|
||||
pos.0,
|
||||
frame_start_dist,
|
||||
frame_end_dist,
|
||||
*ori.look_dir(),
|
||||
beam_segment.angle,
|
||||
&& conical_bezier_cylinder_collision(
|
||||
beam.bezier,
|
||||
beam.end_radius,
|
||||
beam.range,
|
||||
pos_b.0,
|
||||
rad_b,
|
||||
height_b,
|
||||
@ -195,7 +178,7 @@ impl<'a> System<'a> for Sys {
|
||||
// See if entities are in the same group
|
||||
let same_group = group
|
||||
.map(|group_a| Some(group_a) == read_data.groups.get(target))
|
||||
.unwrap_or(Some(*uid_b) == beam_segment.owner);
|
||||
.unwrap_or(false);
|
||||
|
||||
let target_group = if same_group {
|
||||
GroupTarget::InGroup
|
||||
@ -203,23 +186,15 @@ impl<'a> System<'a> for Sys {
|
||||
GroupTarget::OutOfGroup
|
||||
};
|
||||
|
||||
// If owner, shouldn't heal or damage
|
||||
if Some(*uid_b) == beam_segment.owner {
|
||||
return;
|
||||
}
|
||||
|
||||
let attacker_info =
|
||||
beam_owner.zip(beam_segment.owner).map(|(entity, uid)| {
|
||||
AttackerInfo {
|
||||
entity,
|
||||
uid,
|
||||
group: read_data.groups.get(entity),
|
||||
energy: read_data.energies.get(entity),
|
||||
combo: read_data.combos.get(entity),
|
||||
inventory: read_data.inventories.get(entity),
|
||||
stats: read_data.stats.get(entity),
|
||||
}
|
||||
});
|
||||
let attacker_info = Some(AttackerInfo {
|
||||
entity,
|
||||
uid: *uid,
|
||||
group: read_data.groups.get(entity),
|
||||
energy: read_data.energies.get(entity),
|
||||
combo: read_data.combos.get(entity),
|
||||
inventory: read_data.inventories.get(entity),
|
||||
stats: read_data.stats.get(entity),
|
||||
});
|
||||
|
||||
let target_info = TargetInfo {
|
||||
entity: target,
|
||||
@ -244,7 +219,7 @@ impl<'a> System<'a> for Sys {
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.id_maps,
|
||||
beam_owner,
|
||||
Some(entity),
|
||||
target,
|
||||
);
|
||||
let attack_options = AttackOptions {
|
||||
@ -253,7 +228,7 @@ impl<'a> System<'a> for Sys {
|
||||
target_group,
|
||||
};
|
||||
|
||||
beam_segment.properties.attack.apply_attack(
|
||||
beam.attack.apply_attack(
|
||||
attacker_info,
|
||||
&target_info,
|
||||
ori.look_dir(),
|
||||
@ -267,7 +242,7 @@ impl<'a> System<'a> for Sys {
|
||||
0,
|
||||
);
|
||||
|
||||
add_hit_entities.push((beam_owner, *uid_b));
|
||||
add_hit_entities.push((entity, target));
|
||||
}
|
||||
});
|
||||
(server_events, add_hit_entities, outcomes)
|
||||
@ -286,144 +261,42 @@ impl<'a> System<'a> for Sys {
|
||||
job.cpu_stats.measure(ParMode::Single);
|
||||
|
||||
outcomes_emitter.emit_many(new_outcomes);
|
||||
server_emitter.emit_many(server_events);
|
||||
|
||||
for event in server_events {
|
||||
server_emitter.emit(event);
|
||||
}
|
||||
|
||||
for (owner, hit_entity) in add_hit_entities {
|
||||
if let Some(ref mut beam) = owner.and_then(|e| beams.get_mut(e)) {
|
||||
for (entity, hit_entity) in add_hit_entities {
|
||||
if let Some(ref mut beam) = beams.get_mut(entity) {
|
||||
beam.hit_entities.push(hit_entity);
|
||||
}
|
||||
}
|
||||
|
||||
for beam in (&mut beams).join() {
|
||||
beam.timer = beam
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(dt))
|
||||
.unwrap_or(beam.tick_dur);
|
||||
if beam.timer >= beam.tick_dur {
|
||||
beam.hit_entities.clear();
|
||||
beam.timer = beam.timer.checked_sub(beam.tick_dur).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
// Set start time on new beams
|
||||
// This change doesn't need to be recorded as it is not sent to the client
|
||||
beam_segments.set_event_emission(false);
|
||||
(&mut beam_segments)
|
||||
.lend_join()
|
||||
.for_each(|mut beam_segment| {
|
||||
if beam_segment.creation.is_none() {
|
||||
beam_segment.creation = Some(time);
|
||||
}
|
||||
});
|
||||
beam_segments.set_event_emission(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Assumes upright cylinder
|
||||
/// See page 12 of https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.396.7952&rep=rep1&type=pdf
|
||||
fn sphere_wedge_cylinder_collision(
|
||||
fn conical_bezier_cylinder_collision(
|
||||
// Values for spherical wedge
|
||||
real_pos: Vec3<f32>,
|
||||
min_rad: f32, // Distance from beam origin to inner section of beam
|
||||
max_rad: f32, //Distance from beam origin to outer section of beam
|
||||
ori: Vec3<f32>,
|
||||
angle: f32,
|
||||
bezier: QuadraticBezier3<f32>,
|
||||
max_rad: f32, // Radius at end_pos (radius is 0 at start_pos)
|
||||
range: f32, // Used to decide number of steps in bezier function
|
||||
// Values for cylinder
|
||||
bottom_pos_b: Vec3<f32>, // Position of bottom of cylinder
|
||||
rad_b: f32,
|
||||
length_b: f32,
|
||||
) -> bool {
|
||||
// Converts all coordinates so that the new origin is in the center of the
|
||||
// cylinder
|
||||
let center_pos_b = Vec3::new(
|
||||
bottom_pos_b.x,
|
||||
bottom_pos_b.y,
|
||||
bottom_pos_b.z + length_b / 2.0,
|
||||
);
|
||||
let pos = real_pos - center_pos_b;
|
||||
let pos_b = Vec3::zero();
|
||||
if pos.distance_squared(pos_b) > (max_rad + rad_b + length_b).powi(2) {
|
||||
// Does quick check if entity is too far (I'm not sure if necessary, but
|
||||
// probably makes detection more efficient)
|
||||
false
|
||||
} else if pos.z.abs() <= length_b / 2.0 {
|
||||
// Checks case 1: center of sphere is on same z-height as cylinder
|
||||
let pos2 = Vec2::<f32>::from(pos);
|
||||
let ori2 = Vec2::from(ori);
|
||||
let distance = pos2.distance(Vec2::zero());
|
||||
let in_range = distance < max_rad && distance > min_rad;
|
||||
// Done so that if distance = 0, atan() can still be calculated https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6d2221bb9454debdfca8f9c52d1edb29
|
||||
let tangent_value1: f32 = rad_b / distance;
|
||||
let tangent_value2: f32 = length_b / 2.0 / distance;
|
||||
let in_angle = pos2.angle_between(-ori2) < angle + (tangent_value1).atan().abs()
|
||||
&& pos.angle_between(-ori) < angle + (tangent_value2).atan().abs();
|
||||
in_range && in_angle
|
||||
} else {
|
||||
// Checks case 2: if sphere collides with top/bottom of cylinder, doesn't use
|
||||
// paper. Logic used here is it checks if line between centers passes through
|
||||
// either cap, then if the cap is within range, then if withing angle of beam.
|
||||
// If line
|
||||
let sign = if pos.z > 0.0 { 1.0 } else { -1.0 };
|
||||
let height = sign * length_b / 2.0;
|
||||
let (in_range, in_angle): (bool, bool);
|
||||
// Gets relatively how far along the line (between sphere and cylinder centers)
|
||||
// the endcap of the cylinder is, is between 0 and 1 when sphere center is not
|
||||
// in cylinder
|
||||
let intersect_frac = (length_b / 2.0 / pos.z).abs();
|
||||
// Gets the position of the cylinder edge closest to the sphere center
|
||||
let edge_pos = if let Some(vec) = Vec3::new(pos.x, pos.y, 0.0).try_normalized() {
|
||||
vec * rad_b
|
||||
} else {
|
||||
// Returns an arbitrary location that is still guaranteed to be on the cylinder
|
||||
// edge. This case should only happen when the sphere is directly above the
|
||||
// cylinder, in which case all positions on edge are equally close.
|
||||
Vec3::new(rad_b, 0.0, 0.0)
|
||||
};
|
||||
// Gets position on opposite edge of same endcap
|
||||
let opp_end_edge_pos = Vec3::new(-edge_pos.x, -edge_pos.y, height);
|
||||
// Gets position on same edge of opposite endcap
|
||||
let bot_end_edge_pos = Vec3::new(edge_pos.x, edge_pos.y, -height);
|
||||
// Gets point on line between sphere and cylinder centers that the z value is
|
||||
// equal to the endcap z location
|
||||
let intersect_point = Vec2::new(pos.x * intersect_frac, pos.y * intersect_frac);
|
||||
// Checks if line between sphere and cylinder center passes through cap of
|
||||
// cylinder
|
||||
if intersect_point.distance_squared(Vec2::zero()) <= rad_b.powi(2) {
|
||||
let distance_squared =
|
||||
Vec3::new(intersect_point.x, intersect_point.y, height).distance_squared(pos);
|
||||
in_range = distance_squared < max_rad.powi(2) && distance_squared > min_rad.powi(2);
|
||||
// Angle between (line between centers of cylinder and sphere) and either (line
|
||||
// between opposite edge of endcap and sphere center) or (line between close
|
||||
// edge of endcap on bottom of cylinder and sphere center). Whichever angle is
|
||||
// largest is used.
|
||||
let angle2 = (pos_b - pos)
|
||||
.angle_between(opp_end_edge_pos - pos)
|
||||
.max((pos_b - pos).angle_between(bot_end_edge_pos - pos));
|
||||
in_angle = pos.angle_between(-ori) < angle + angle2;
|
||||
} else {
|
||||
// TODO: Handle collision for this case more accurately
|
||||
// For this case, the nearest point will be the edge of the endcap
|
||||
let endcap_edge_pos = Vec3::new(edge_pos.x, edge_pos.y, height);
|
||||
let distance_squared = endcap_edge_pos.distance_squared(pos);
|
||||
in_range = distance_squared > min_rad.powi(2) && distance_squared < max_rad.powi(2);
|
||||
// Gets side positions on same endcap
|
||||
let side_end_edge_pos_1 = Vec3::new(edge_pos.y, -edge_pos.x, height);
|
||||
let side_end_edge_pos_2 = Vec3::new(-edge_pos.y, edge_pos.x, height);
|
||||
// Gets whichever angle is bigger, between sphere center and opposite edge,
|
||||
// sphere center and bottom edge, or half of sphere center and both the side
|
||||
// edges
|
||||
let angle2 = (pos_b - pos).angle_between(opp_end_edge_pos - pos).max(
|
||||
(pos_b - pos).angle_between(bot_end_edge_pos - pos).max(
|
||||
(side_end_edge_pos_1 - pos).angle_between(side_end_edge_pos_2 - pos) / 2.0,
|
||||
),
|
||||
);
|
||||
// Will be somewhat inaccurate, tends towards hitting when it shouldn't
|
||||
// Checks angle between orientation and line between sphere and cylinder centers
|
||||
in_angle = pos.angle_between(-ori) < angle + angle2;
|
||||
}
|
||||
in_range && in_angle
|
||||
}
|
||||
// This algorithm first determines the nearest point on the bezier to the point
|
||||
// in the middle of the cylinder. It then checks that the bezier cone's radius
|
||||
// at this point could allow it to be in the z bounds of the cylinder and within
|
||||
// the cylinder's radius.
|
||||
let center_pos_b = bottom_pos_b.with_z(bottom_pos_b.z + length_b / 2.0);
|
||||
let (t, closest_pos) =
|
||||
bezier.binary_search_point_by_steps(center_pos_b, (range * 5.0) as u16, 0.1);
|
||||
let bezier_rad = t * max_rad;
|
||||
let z_check = {
|
||||
let dist = (closest_pos.z - center_pos_b.z).abs();
|
||||
dist < bezier_rad + length_b / 2.0
|
||||
};
|
||||
let rad_check = {
|
||||
let dist_sqrd = closest_pos.xy().distance_squared(center_pos_b.xy());
|
||||
dist_sqrd < (bezier_rad + rad_b).powi(2)
|
||||
};
|
||||
z_check && rad_check
|
||||
}
|
||||
|
@ -156,6 +156,14 @@ impl<'a> System<'a> for Sys {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove components that entity should not have if not in relevant char state
|
||||
if !char_state.is_melee_attack() {
|
||||
read_data.lazy_update.remove::<Melee>(entity);
|
||||
}
|
||||
if !char_state.is_beam_attack() {
|
||||
read_data.lazy_update.remove::<Beam>(entity);
|
||||
}
|
||||
|
||||
// Enter stunned state if poise damage is enough
|
||||
if let Some(mut poise) = poises.get_mut(entity) {
|
||||
let was_wielded = char_state.is_wield();
|
||||
|
@ -7,7 +7,6 @@ use common::{
|
||||
comp::{
|
||||
self,
|
||||
aura::{Aura, AuraKind, AuraTarget},
|
||||
beam,
|
||||
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
|
||||
misc::PortalData,
|
||||
ship::figuredata::VOXEL_COLLIDER_MANIFEST,
|
||||
@ -367,17 +366,6 @@ pub fn handle_shockwave(
|
||||
state.create_shockwave(properties, pos, ori).build();
|
||||
}
|
||||
|
||||
pub fn handle_beam(server: &mut Server, properties: beam::Properties, pos: Pos, ori: Ori) {
|
||||
let state = server.state_mut();
|
||||
let ecs = state.ecs();
|
||||
ecs.read_resource::<EventBus<Outcome>>()
|
||||
.emit_now(Outcome::Beam {
|
||||
pos: pos.0,
|
||||
specifier: properties.specifier,
|
||||
});
|
||||
state.create_beam(properties, pos, ori).build();
|
||||
}
|
||||
|
||||
pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
||||
let time = server.state.get_time();
|
||||
server
|
||||
|
@ -140,6 +140,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove components that should not persist across death
|
||||
state.ecs().write_storage::<comp::Melee>().remove(entity);
|
||||
state.ecs().write_storage::<comp::Beam>().remove(entity);
|
||||
|
||||
let get_attacker_name = |cause_of_death: KillType, by: Uid| -> KillSource {
|
||||
// Get attacker entity
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by) {
|
||||
|
@ -11,9 +11,8 @@ use crate::{
|
||||
use common::event::{EventBus, ServerEvent, ServerEventDiscriminants};
|
||||
use common_base::span;
|
||||
use entity_creation::{
|
||||
handle_beam, handle_create_npc, handle_create_ship, handle_create_waypoint,
|
||||
handle_initialize_character, handle_initialize_spectator, handle_loaded_character_data,
|
||||
handle_shockwave, handle_shoot,
|
||||
handle_create_npc, handle_create_ship, handle_create_waypoint, handle_initialize_character,
|
||||
handle_initialize_spectator, handle_loaded_character_data, handle_shockwave, handle_shoot,
|
||||
};
|
||||
use entity_manipulation::{
|
||||
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_change_body,
|
||||
@ -107,11 +106,6 @@ impl Server {
|
||||
pos,
|
||||
ori,
|
||||
} => handle_shockwave(self, properties, pos, ori),
|
||||
ServerEvent::BeamSegment {
|
||||
properties,
|
||||
pos,
|
||||
ori,
|
||||
} => handle_beam(self, properties, pos, ori),
|
||||
ServerEvent::Knockback { entity, impulse } => {
|
||||
handle_knockback(self, entity, impulse)
|
||||
},
|
||||
|
@ -91,13 +91,6 @@ pub trait StateExt {
|
||||
pos: comp::Pos,
|
||||
ori: comp::Ori,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Build a beam entity
|
||||
fn create_beam(
|
||||
&mut self,
|
||||
properties: comp::beam::Properties,
|
||||
pos: comp::Pos,
|
||||
ori: comp::Ori,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Creates a safezone
|
||||
fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder;
|
||||
fn create_wiring(
|
||||
@ -505,22 +498,6 @@ impl StateExt for State {
|
||||
})
|
||||
}
|
||||
|
||||
fn create_beam(
|
||||
&mut self,
|
||||
properties: comp::beam::Properties,
|
||||
pos: comp::Pos,
|
||||
ori: comp::Ori,
|
||||
) -> EcsEntityBuilder {
|
||||
self.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
.with(ori)
|
||||
.with(comp::BeamSegment {
|
||||
properties,
|
||||
creation: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder {
|
||||
use comp::{
|
||||
aura::{Aura, AuraKind, AuraTarget, Auras},
|
||||
|
@ -15,7 +15,7 @@ use common::{
|
||||
item::Reagent,
|
||||
object,
|
||||
shockwave::{self, ShockwaveDodgeable},
|
||||
BeamSegment, Body, CharacterState, Ori, Pos, Scale, Shockwave, Vel,
|
||||
Beam, Body, CharacterState, Ori, Pos, Scale, Shockwave, Vel,
|
||||
},
|
||||
figure::Segment,
|
||||
outcome::Outcome,
|
||||
@ -1030,35 +1030,28 @@ impl ParticleMgr {
|
||||
let state = scene_data.state;
|
||||
let ecs = state.ecs();
|
||||
let time = state.get_time();
|
||||
let dt = scene_data.state.ecs().fetch::<DeltaTime>().0;
|
||||
|
||||
for (interp, pos, ori, beam) in (
|
||||
ecs.read_storage::<Interpolated>().maybe(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<Ori>(),
|
||||
&ecs.read_storage::<BeamSegment>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, _, _, b)| b.creation.map_or(true, |c| (c + dt as f64) >= time))
|
||||
{
|
||||
let pos = interp.map_or(pos.0, |i| i.pos);
|
||||
let ori = interp.map_or(*ori, |i| i.ori);
|
||||
|
||||
for (beam, ori) in (&ecs.read_storage::<Beam>(), &ecs.read_storage::<Ori>()).join() {
|
||||
// TODO: Handle this less hackily. Done this way as beam segments are created
|
||||
// every server tick, which is approximately 33 ms. Heartbeat scheduler used to
|
||||
// account for clients with less than 30 fps because they start the creation
|
||||
// time when the segments are received and could receive 2 at once
|
||||
// TODO: Above limitation no longer exists. Evaluate changing below behavior
|
||||
// later to allow for better beam particles.
|
||||
let beam_tick_count = 33.max(self.scheduler.heartbeats(Duration::from_millis(1)));
|
||||
let range = beam.properties.speed * beam.properties.duration.as_secs_f32();
|
||||
match beam.properties.specifier {
|
||||
let angle = (beam.end_radius / beam.range).atan();
|
||||
let beam_dir = (beam.bezier.ctrl - beam.bezier.start)
|
||||
.try_normalized()
|
||||
.unwrap_or(*ori.look_dir());
|
||||
match beam.specifier {
|
||||
beam::FrontendSpecifier::Flamethrower => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), beam_dir);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
// Emit a light when using flames
|
||||
if scene_data.flashing_lights_enabled {
|
||||
lights.push(Light::new(
|
||||
pos,
|
||||
beam.bezier.start,
|
||||
Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)),
|
||||
2.0,
|
||||
));
|
||||
@ -1066,7 +1059,7 @@ impl ParticleMgr {
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + usize::from(beam_tick_count) / 2,
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let phi: f32 = rng.gen_range(0.0..angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
@ -1075,23 +1068,23 @@ impl ParticleMgr {
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::FlameThrower,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + random_ori * beam.range,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
beam::FrontendSpecifier::Cultist => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), beam_dir);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
// Emit a light when using flames
|
||||
if scene_data.flashing_lights_enabled {
|
||||
lights.push(Light::new(
|
||||
pos,
|
||||
beam.bezier.start,
|
||||
Rgb::new(1.0, 0.0, 1.0).map(|e| e * rng.gen_range(0.5..1.0)),
|
||||
2.0,
|
||||
));
|
||||
@ -1099,7 +1092,7 @@ impl ParticleMgr {
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + usize::from(beam_tick_count) / 2,
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let phi: f32 = rng.gen_range(0.0..angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
@ -1108,11 +1101,11 @@ impl ParticleMgr {
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::CultistFlame,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + random_ori * beam.range,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -1120,49 +1113,49 @@ impl ParticleMgr {
|
||||
beam::FrontendSpecifier::LifestealBeam => {
|
||||
// Emit a light when using lifesteal beam
|
||||
if scene_data.flashing_lights_enabled {
|
||||
lights.push(Light::new(pos, Rgb::new(0.8, 1.0, 0.5), 1.0));
|
||||
lights.push(Light::new(beam.bezier.start, Rgb::new(0.8, 1.0, 0.5), 1.0));
|
||||
}
|
||||
self.particles.reserve(beam_tick_count as usize);
|
||||
for i in 0..beam_tick_count {
|
||||
self.particles.push(Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time + i as f64 / 1000.0,
|
||||
ParticleMode::LifestealBeam,
|
||||
pos,
|
||||
pos + *ori.look_dir() * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + beam_dir * beam.range,
|
||||
));
|
||||
}
|
||||
},
|
||||
beam::FrontendSpecifier::ClayGolem => {
|
||||
self.particles.resize_with(self.particles.len() + 2, || {
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::Laser,
|
||||
pos,
|
||||
pos + *ori.look_dir() * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + beam_dir * beam.range,
|
||||
)
|
||||
})
|
||||
},
|
||||
beam::FrontendSpecifier::WebStrand => {
|
||||
self.particles.resize_with(self.particles.len() + 1, || {
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::WebStrand,
|
||||
pos,
|
||||
pos + *ori.look_dir() * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + beam_dir * beam.range,
|
||||
)
|
||||
})
|
||||
},
|
||||
beam::FrontendSpecifier::Bubbles => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), beam_dir);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + usize::from(beam_tick_count) / 15,
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let phi: f32 = rng.gen_range(0.0..angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
@ -1171,23 +1164,23 @@ impl ParticleMgr {
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::Bubbles,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + random_ori * beam.range,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
beam::FrontendSpecifier::Poison => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), beam_dir);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + usize::from(beam_tick_count) / 15,
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let phi: f32 = rng.gen_range(0.0..angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
@ -1196,23 +1189,23 @@ impl ParticleMgr {
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::CultistFlame,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + random_ori * beam.range,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
beam::FrontendSpecifier::Ink => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), beam_dir);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + usize::from(beam_tick_count) / 15,
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let phi: f32 = rng.gen_range(0.0..angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
@ -1221,23 +1214,23 @@ impl ParticleMgr {
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::Ink,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + random_ori * beam.range,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
beam::FrontendSpecifier::Steam => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), beam_dir);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + usize::from(beam_tick_count) / 15,
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let phi: f32 = rng.gen_range(0.0..angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
@ -1246,11 +1239,11 @@ impl ParticleMgr {
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::Steam,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + random_ori * beam.range,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -1258,22 +1251,22 @@ impl ParticleMgr {
|
||||
beam::FrontendSpecifier::Lightning => {
|
||||
self.particles.resize_with(self.particles.len() + 2, || {
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::Lightning,
|
||||
pos,
|
||||
pos + *ori.look_dir() * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + beam_dir * beam.range,
|
||||
)
|
||||
})
|
||||
},
|
||||
beam::FrontendSpecifier::Frost => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), beam_dir);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + usize::from(beam_tick_count) / 4,
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let phi: f32 = rng.gen_range(0.0..angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
@ -1282,11 +1275,11 @@ impl ParticleMgr {
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
Duration::from_secs_f64(beam.duration.0),
|
||||
time,
|
||||
ParticleMode::Ice,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
beam.bezier.start,
|
||||
beam.bezier.start + random_ori * beam.range,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user