diff --git a/assets/common/abilities/unique/mindflayer/cursedflames.ron b/assets/common/abilities/unique/mindflayer/cursedflames.ron index 0fdf731d35..f7171ce7dd 100644 --- a/assets/common/abilities/unique/mindflayer/cursedflames.ron +++ b/assets/common/abilities/unique/mindflayer/cursedflames.ron @@ -1,10 +1,10 @@ BasicBeam( buildup_duration: 0.50, recover_duration: 0.50, - beam_duration: 0.5, - damage: 200, - tick_rate: 2.0, - range: 10.0, + beam_duration: 1.0, + damage: 350, + tick_rate: 0.9, + range: 20.0, max_angle: 15.0, damage_effect: Some(Buff(( kind: Cursed, diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 4e940ea2e8..e80c6695e8 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -35,6 +35,7 @@ pub enum Tactic { Turret, FixedTurret, RotatingTurret, + Mindflayer, } #[derive(Copy, Clone, Debug, PartialEq)] diff --git a/common/src/comp/health.rs b/common/src/comp/health.rs index 61234f26bf..280fa3732a 100644 --- a/common/src/comp/health.rs +++ b/common/src/comp/health.rs @@ -130,6 +130,9 @@ impl Health { }); } } + + /// Returns the fraction of health an entity has remaining + pub fn fraction(&self) -> f32 { self.current as f32 / self.maximum as f32 } } #[cfg(not(target_arch = "wasm32"))] diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index b46555b6ab..1a2217fab6 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -92,6 +92,12 @@ impl CharacterBehavior for Data { ) .build(); + let alignment = if matches!(data.alignment, Some(comp::Alignment::Enemy)) { + comp::Alignment::Enemy + } else { + comp::Alignment::Owned(*data.uid) + }; + update.server_events.push_front(ServerEvent::CreateNpc { pos: *data.pos, stats, @@ -103,7 +109,7 @@ impl CharacterBehavior for Data { loadout, body, agent: Some(comp::Agent::new(None, false, None, &body, true)), - alignment: comp::Alignment::Owned(*data.uid), + alignment, scale: self .static_data .summon_info diff --git a/common/src/states/behavior.rs b/common/src/states/behavior.rs index a650270331..2c4f4a3ecc 100644 --- a/common/src/states/behavior.rs +++ b/common/src/states/behavior.rs @@ -1,8 +1,8 @@ use crate::{ comp::{ - item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction, Controller, - ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory, InventoryAction, Melee, - Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, + self, item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction, + Controller, ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory, + InventoryAction, Melee, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, }, resources::DeltaTime, uid::Uid, @@ -93,6 +93,7 @@ pub struct JoinData<'a> { pub stats: &'a Stats, pub msm: &'a MaterialStatManifest, pub combo: &'a Combo, + pub alignment: Option<&'a comp::Alignment>, } type RestrictedMut<'a, C> = PairedStorage< @@ -121,6 +122,7 @@ pub struct JoinStruct<'a> { pub beam: Option<&'a Beam>, pub stat: &'a Stats, pub combo: &'a Combo, + pub alignment: Option<&'a comp::Alignment>, } impl<'a> JoinData<'a> { @@ -150,6 +152,7 @@ impl<'a> JoinData<'a> { dt, msm, combo: j.combo, + alignment: j.alignment, } } } diff --git a/common/sys/src/character_behavior.rs b/common/sys/src/character_behavior.rs index acd301a83f..debc10c2b8 100644 --- a/common/sys/src/character_behavior.rs +++ b/common/sys/src/character_behavior.rs @@ -5,6 +5,7 @@ use specs::{ use common::{ comp::{ + self, inventory::{ item::MaterialStatManifest, slot::{EquipSlot, Slot}, @@ -71,6 +72,7 @@ pub struct ReadData<'a> { stats: ReadStorage<'a, Stats>, msm: Read<'a, MaterialStatManifest>, combos: ReadStorage<'a, Combo>, + alignments: ReadStorage<'a, comp::Alignment>, } /// ## Character Behavior System @@ -255,6 +257,7 @@ impl<'a> System<'a> for Sys { beam: read_data.beams.get(entity), stat: &stat, combo: &combo, + alignment: read_data.alignments.get(entity), }; for action in actions { diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 9e2d78ed03..0714416431 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -60,6 +60,8 @@ struct AgentData<'a> { light_emitter: Option<&'a LightEmitter>, glider_equipped: bool, is_gliding: bool, + health: &'a Health, + char_state: &'a CharacterState, } #[derive(SystemData)] @@ -145,14 +147,17 @@ impl<'a> System<'a> for Sys { read_data.light_emitter.maybe(), read_data.groups.maybe(), read_data.mount_states.maybe(), + &read_data.char_states, ) .par_join() - .filter(|(_, _, _, _, _, _, _, _, _, _, _, _, _, _, mount_state)| { - // Skip mounted entities - mount_state - .map(|ms| *ms == MountState::Unmounted) - .unwrap_or(true) - }) + .filter( + |(_, _, _, _, _, _, _, _, _, _, _, _, _, _, mount_state, _)| { + // Skip mounted entities + mount_state + .map(|ms| *ms == MountState::Unmounted) + .unwrap_or(true) + }, + ) .for_each_init( || { prof_span!(guard, "agent rayon job"); @@ -175,6 +180,7 @@ impl<'a> System<'a> for Sys { light_emitter, groups, _, + char_state, )| { //// Hack, replace with better system when groups are more sophisticated //// Override alignment if in a group unless entity is owned already @@ -269,6 +275,8 @@ impl<'a> System<'a> for Sys { light_emitter, glider_equipped, is_gliding, + health, + char_state, }; /////////////////////////////////////////////////////////// @@ -1189,7 +1197,7 @@ impl<'a> AgentData<'a> { tgt_pos: &Pos, tgt_body: Option<&Body>, dt: &DeltaTime, - _read_data: &ReadData, + read_data: &ReadData, ) { let min_attack_dist = self.body.map_or(3.0, |b| b.radius() * self.scale + 2.0); let tactic = match self @@ -1236,6 +1244,7 @@ impl<'a> AgentData<'a> { Some(ToolKind::Unique(UniqueKind::TheropodBasic)) => Tactic::Theropod, Some(ToolKind::Unique(UniqueKind::TheropodBird)) => Tactic::Theropod, Some(ToolKind::Unique(UniqueKind::ObjectTurret)) => Tactic::Turret, + Some(ToolKind::Unique(UniqueKind::MindflayerStaff)) => Tactic::Mindflayer, _ => Tactic::Melee, }; @@ -2193,6 +2202,37 @@ impl<'a> AgentData<'a> { agent.target = None; } }, + Tactic::Mindflayer => { + agent.action_timer += dt.0; + const MINDFLAYER_ATTACK_DIST: f32 = 15.0; + let mindflayer_is_far = dist_sqrd > MINDFLAYER_ATTACK_DIST.powi(2); + if mindflayer_is_far && agent.action_timer / self.health.fraction() > 5.0 { + if !self.char_state.is_attack() { + controller + .actions + .push(ControlAction::basic_input(InputKind::Ability(1))); + agent.action_timer = 0.0; + } + } else if mindflayer_is_far { + controller.actions.push(ControlAction::StartInput { + input: InputKind::Ability(0), + target_entity: agent + .target + .as_ref() + .and_then(|t| read_data.uids.get(t.target)) + .copied(), + select_pos: None, + }); + } else if self.health.fraction() < 0.5 { + controller + .actions + .push(ControlAction::basic_input(InputKind::Secondary)); + } else { + controller + .actions + .push(ControlAction::basic_input(InputKind::Primary)); + } + }, } } diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 2c75426f05..be8b41c554 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -190,13 +190,12 @@ impl ParticleMgr { // add new Particle self.maintain_body_particles(scene_data); - self.maintain_boost_particles(scene_data); + self.maintain_char_state_particles(scene_data); self.maintain_beam_particles(scene_data, lights); self.maintain_block_particles(scene_data, terrain); self.maintain_shockwave_particles(scene_data); self.maintain_aura_particles(scene_data); self.maintain_buff_particles(scene_data); - self.maintain_spin_melee_particles(scene_data); } else { // remove all particle lifespans self.particles.clear(); @@ -412,11 +411,11 @@ impl ParticleMgr { } } - fn maintain_boost_particles(&mut self, scene_data: &SceneData) { + fn maintain_char_state_particles(&mut self, scene_data: &SceneData) { span!( _guard, - "boost_particles", - "ParticleMgr::maintain_boost_particles" + "char_state_particles", + "ParticleMgr::maintain_char_state_particles" ); let state = scene_data.state; let ecs = state.ecs(); @@ -431,19 +430,61 @@ impl ParticleMgr { ) .join() { - if let CharacterState::Boost(_) = character_state { - self.particles.resize_with( - self.particles.len() - + usize::from(self.scheduler.heartbeats(Duration::from_millis(10))), - || { - Particle::new( - Duration::from_secs(15), - time, - ParticleMode::CampfireSmoke, - pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::()), - ) - }, - ); + match character_state { + CharacterState::Boost(_) => { + self.particles.resize_with( + self.particles.len() + + usize::from(self.scheduler.heartbeats(Duration::from_millis(10))), + || { + Particle::new( + Duration::from_secs(15), + time, + ParticleMode::CampfireSmoke, + pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::()), + ) + }, + ); + }, + CharacterState::SpinMelee(spin) => { + if let Some(specifier) = spin.static_data.specifier { + match specifier { + states::spin_melee::FrontendSpecifier::CultistVortex => { + if matches!(spin.stage_section, states::utils::StageSection::Swing) + { + let heartbeats = + self.scheduler.heartbeats(Duration::from_millis(3)); + self.particles.resize_with( + self.particles.len() + + spin.static_data.range.powi(2) as usize + * usize::from(heartbeats) + / 150, + || { + let rand_dist = spin.static_data.range + * (1.0 - rng.gen::().powi(10)); + let init_pos = Vec3::new( + 2.0 * rng.gen::() - 1.0, + 2.0 * rng.gen::() - 1.0, + 0.0, + ) + .normalized() + * rand_dist + + pos.0 + + Vec3::unit_z() * 0.05; + Particle::new_directed( + Duration::from_millis(900), + time, + ParticleMode::CultistFlame, + init_pos, + pos.0, + ) + }, + ); + } + }, + } + } + }, + _ => {}, } } } @@ -863,56 +904,6 @@ impl ParticleMgr { } } - fn maintain_spin_melee_particles(&mut self, scene_data: &SceneData) { - let state = scene_data.state; - let ecs = state.ecs(); - let time = state.get_time(); - let mut rng = thread_rng(); - - for (pos, character_state) in ( - &ecs.read_storage::(), - &ecs.read_storage::(), - ) - .join() - { - if let CharacterState::SpinMelee(c) = character_state { - if let Some(specifier) = c.static_data.specifier { - match specifier { - states::spin_melee::FrontendSpecifier::CultistVortex => { - let heartbeats = self.scheduler.heartbeats(Duration::from_millis(3)); - self.particles.resize_with( - self.particles.len() - + c.static_data.range.powi(2) as usize - * usize::from(heartbeats) - / 150, - || { - let rand_dist = - c.static_data.range * (1.0 - rng.gen::().powi(10)); - let init_pos = Vec3::new( - 2.0 * rng.gen::() - 1.0, - 2.0 * rng.gen::() - 1.0, - 0.0, - ) - .normalized() - * rand_dist - + pos.0 - + Vec3::unit_z() * 0.05; - Particle::new_directed( - Duration::from_millis(900), - time, - ParticleMode::CultistFlame, - init_pos, - pos.0, - ) - }, - ); - }, - } - } - } - } - } - fn upload_particles(&mut self, renderer: &mut Renderer) { span!(_guard, "upload_particles", "ParticleMgr::upload_particles"); let all_cpu_instances = self