diff --git a/CHANGELOG.md b/CHANGELOG.md index f65a340d3a..8321bc2488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -125,6 +125,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Wolf AI will no longer circle into walls and will instead use the power of raycasts to stop early - Squirrels are no longer immune to arrows at some angles. - /spawn command's auto-complete now works for species names +- Mindflayer AI now correctly summons husks at certain HP thresholds. ## [0.9.0] - 2021-03-20 diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 33150c51e5..f0ef774303 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -204,6 +204,7 @@ secondary: "common.abilities.custom.mindflayer.necroticvortex", abilities: [ (None, "common.abilities.custom.mindflayer.dimensionaldoor"), + (None, "common.abilities.custom.mindflayer.necroticsphere"), (None, "common.abilities.custom.mindflayer.summonminions"), ], ), diff --git a/assets/common/abilities/custom/mindflayer/necroticsphere.ron b/assets/common/abilities/custom/mindflayer/necroticsphere.ron new file mode 100644 index 0000000000..c643a741c3 --- /dev/null +++ b/assets/common/abilities/custom/mindflayer/necroticsphere.ron @@ -0,0 +1,12 @@ +BasicRanged( + energy_cost: 0, + buildup_duration: 0.75, + recover_duration: 0.4, + projectile: NecroticSphere( + damage: 300.0, + radius: 5.0, + ), + projectile_body: Object(FireworkPurple), + projectile_speed: 100.0, +) + diff --git a/assets/common/abilities/staff/firebomb.ron b/assets/common/abilities/staff/firebomb.ron index 6564328543..79494def06 100644 --- a/assets/common/abilities/staff/firebomb.ron +++ b/assets/common/abilities/staff/firebomb.ron @@ -8,9 +8,5 @@ BasicRanged( energy_regen: 50, ), projectile_body: Object(BoltFire), - /*projectile_light: Some(LightEmitter { - col: (1.0, 0.75, 0.11).into(), - ..Default::default() - }),*/ projectile_speed: 60.0, ) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 480d25197a..456471c106 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -281,6 +281,7 @@ pub struct ActionState { pub timer: f32, pub counter: f32, pub condition: bool, + pub int_counter: u8, } impl Agent { diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index 392ed68ed9..7bf4ed5f35 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -54,6 +54,10 @@ pub enum ProjectileConstructor { damage: f32, radius: f32, }, + NecroticSphere { + damage: f32, + radius: f32, + }, Possess, } @@ -168,6 +172,32 @@ impl ProjectileConstructor { ignore_group: true, } }, + NecroticSphere { damage, radius } => { + let damage = AttackDamage::new( + Damage { + source: DamageSource::Explosion, + kind: DamageKind::Energy, + value: damage, + }, + Some(GroupTarget::OutOfGroup), + ); + let attack = Attack::default() + .with_damage(damage) + .with_crit(crit_chance, crit_mult) + .with_combo_increment(); + let explosion = Explosion { + effects: vec![RadiusEffect::Attack(attack)], + radius, + reagent: Some(Reagent::Purple), + }; + Projectile { + hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], + hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], + time_left: Duration::from_secs(10), + owner, + ignore_group: true, + } + }, Possess => Projectile { hit_solid: vec![Effect::Stick], hit_entity: vec![Effect::Stick, Effect::Possess], @@ -207,6 +237,14 @@ impl ProjectileConstructor { *damage *= power; *radius *= range; }, + NecroticSphere { + ref mut damage, + ref mut radius, + .. + } => { + *damage *= power; + *radius *= range; + }, Possess => {}, } self diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 7b155d1787..1547bc9838 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -581,9 +581,9 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) { InputKind::Primary => Some(abilities.primary.clone()), InputKind::Secondary => Some(abilities.secondary.clone()), InputKind::Ability(0) => abilities.abilities.get(0).cloned().and_then(unlocked), - InputKind::Ability(_) => abilities + InputKind::Ability(i) => abilities .abilities - .get(skill_index) + .get(if i < 2 { skill_index } else { i }) .cloned() .and_then(unlocked), InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None, diff --git a/server/Cargo.toml b/server/Cargo.toml index b30c8b1a12..7d81585259 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -22,6 +22,8 @@ common-net = { package = "veloren-common-net", path = "../common/net" } world = { package = "veloren-world", path = "../world" } network = { package = "veloren-network", path = "../network", features = ["metrics", "compression"], default-features = false } +# inline_tweak = "1.0.8" + specs = { git = "https://github.com/amethyst/specs.git", features = ["shred-derive"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index a64eab9050..12b5168d76 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -2997,8 +2997,9 @@ impl<'a> AgentData<'a> { const MINDFLAYER_ATTACK_DIST: f32 = 16.0; const MINION_SUMMON_THRESHOLD: f32 = 0.20; let health_fraction = self.health.map_or(0.5, |h| h.fraction()); - // Sets counter at start of combat - if agent.action_state.condition { + // Sets counter at start of combat, using `condition` to keep track of whether + // it was already intitialized + if !agent.action_state.condition { agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD; agent.action_state.condition = true; } @@ -3007,22 +3008,41 @@ impl<'a> AgentData<'a> { // Summon minions at particular thresholds of health controller .actions - .push(ControlAction::basic_input(InputKind::Ability(1))); + .push(ControlAction::basic_input(InputKind::Ability(2))); + if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover)) { agent.action_state.counter -= MINION_SUMMON_THRESHOLD; } } else if mindflayer_is_far { - // If too far from target, blink to them. - 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, - }); + // If too far from target, throw a random number of necrotic spheres at them and + // then blink to them. + let num_fireballs = &mut agent.action_state.int_counter; + if *num_fireballs == 0 { + 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, + }); + if matches!(self.char_state, CharacterState::Blink(_)) { + *num_fireballs = rand::random::() % 4; + } + } else if matches!(self.char_state, CharacterState::Wielding) { + *num_fireballs -= 1; + controller.actions.push(ControlAction::StartInput { + input: InputKind::Ability(1), + target_entity: agent + .target + .as_ref() + .and_then(|t| read_data.uids.get(t.target)) + .copied(), + select_pos: None, + }); + } } else { // If close to target, use either primary or secondary ability if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(10) && !matches!(c.stage_section, StageSection::Recover)) diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index fdd166abaf..142de8e0c6 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -117,6 +117,23 @@ impl ParticleMgr { }, ); }, + Some(Reagent::Purple) => { + self.particles.resize_with( + self.particles.len() + (75.0 * power.abs()) as usize, + || { + Particle::new_directed( + Duration::from_millis(500), + time, + ParticleMode::CultistFlame, + *pos, + *pos + Vec3::::zero() + .map(|_| rng.gen_range(-1.0..1.0)) + .normalized() + * *radius, + ) + }, + ); + }, _ => {}, } } else {