From 5c575484f2c055421c4af3077274187175fee2a0 Mon Sep 17 00:00:00 2001 From: flo Date: Tue, 28 May 2024 19:13:49 +0000 Subject: [PATCH] dagon_anticheese --- .../common/abilities/ability_set_manifest.ron | 4 +- .../custom/cursekeeper/transform.ron | 7 ++ .../abilities/custom/cursekeeper/unlive.ron | 9 -- .../custom/terracotta_demolisher/drop.ron | 2 +- .../custom/terracotta_demolisher/throw.ron | 2 +- .../dungeon/terracotta/cursekeeper_fake.ron | 4 +- .../terracotta/shamanic_spirit_key.ron | 14 +++ common/src/comp/projectile.rs | 68 ++++++++++++++ common/src/states/transform.rs | 12 +++ server/agent/src/action_nodes.rs | 2 +- server/agent/src/attack.rs | 94 +++++++++---------- voxygen/src/scene/particle.rs | 24 +++++ world/src/site2/plot/terracotta_palace.rs | 8 +- 13 files changed, 180 insertions(+), 70 deletions(-) create mode 100644 assets/common/abilities/custom/cursekeeper/transform.ron delete mode 100644 assets/common/abilities/custom/cursekeeper/unlive.ron create mode 100644 assets/common/entity/dungeon/terracotta/shamanic_spirit_key.ron diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index a5a3901210..2ee9acfb0e 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -1125,8 +1125,8 @@ ], ), Custom("CursekeeperFake"): ( - primary: Simple(None, "common.abilities.custom.cursekeeper.summonshamanicspirit"), - secondary: Simple(None, "common.abilities.custom.cursekeeper.unlive"), + primary: Simple(None, "common.abilities.custom.cursekeeper.transform"), + secondary: Simple(None, "common.abilities.custom.cursekeeper.transform"), abilities: [], ), Custom("ShamanicSpirit"): ( diff --git a/assets/common/abilities/custom/cursekeeper/transform.ron b/assets/common/abilities/custom/cursekeeper/transform.ron new file mode 100644 index 0000000000..ffc3a876dd --- /dev/null +++ b/assets/common/abilities/custom/cursekeeper/transform.ron @@ -0,0 +1,7 @@ +Transform( + buildup_duration: 0.3, + recover_duration: 0.5, + target: "common.entity.dungeon.terracotta.shamanic_spirit_key", + specifier: Some(Cursekeeper), + allow_players: false, +) diff --git a/assets/common/abilities/custom/cursekeeper/unlive.ron b/assets/common/abilities/custom/cursekeeper/unlive.ron deleted file mode 100644 index cf68be5286..0000000000 --- a/assets/common/abilities/custom/cursekeeper/unlive.ron +++ /dev/null @@ -1,9 +0,0 @@ -SelfBuff( - buildup_duration: 0.1, - cast_duration: 0.1, - recover_duration: 0.1, - buff_kind: Burning, - buff_strength: 2000.0, - buff_duration: Some(60.0), - energy_cost: 0, -) diff --git a/assets/common/abilities/custom/terracotta_demolisher/drop.ron b/assets/common/abilities/custom/terracotta_demolisher/drop.ron index 529115d7cd..649fa151ac 100644 --- a/assets/common/abilities/custom/terracotta_demolisher/drop.ron +++ b/assets/common/abilities/custom/terracotta_demolisher/drop.ron @@ -2,7 +2,7 @@ BasicRanged( energy_cost: 0, buildup_duration: 1.0, recover_duration: 1.5, - projectile: FireDroplet( + projectile: DemolisherBomb( damage: 30.0, radius: 10.0, min_falloff: 0.5, diff --git a/assets/common/abilities/custom/terracotta_demolisher/throw.ron b/assets/common/abilities/custom/terracotta_demolisher/throw.ron index 0764fd126f..29827b146f 100644 --- a/assets/common/abilities/custom/terracotta_demolisher/throw.ron +++ b/assets/common/abilities/custom/terracotta_demolisher/throw.ron @@ -2,7 +2,7 @@ BasicRanged( energy_cost: 0, buildup_duration: 1.0, recover_duration: 1.5, - projectile: FireDroplet( + projectile: DemolisherBomb( damage: 30.0, radius: 10.0, min_falloff: 0.5, diff --git a/assets/common/entity/dungeon/terracotta/cursekeeper_fake.ron b/assets/common/entity/dungeon/terracotta/cursekeeper_fake.ron index e7bb8d7fd4..900444c60b 100644 --- a/assets/common/entity/dungeon/terracotta/cursekeeper_fake.ron +++ b/assets/common/entity/dungeon/terracotta/cursekeeper_fake.ron @@ -3,10 +3,10 @@ name: Name("Cursekeeper"), body: RandomWith("cursekeeper"), alignment: Alignment(Enemy), - loot: Item("common.items.keys.terracotta_key_chest"), + loot: Nothing, inventory: ( loadout: Inline(( active_hands: InHands((Item("common.items.npc_weapons.unique.cursekeeper_sceptre_fake"), None)), )), ), meta: [], -) \ No newline at end of file +) diff --git a/assets/common/entity/dungeon/terracotta/shamanic_spirit_key.ron b/assets/common/entity/dungeon/terracotta/shamanic_spirit_key.ron new file mode 100644 index 0000000000..28d04711d7 --- /dev/null +++ b/assets/common/entity/dungeon/terracotta/shamanic_spirit_key.ron @@ -0,0 +1,14 @@ +#![enable(implicit_some)] +( + name: Name("Shamanic Spirit"), + body: RandomWith("shamanic_spirit"), + alignment: Alignment(Enemy), + loot: Item("common.items.keys.terracotta_key_chest"), + inventory: ( + loadout: Inline(( + inherit: Asset("common.loadout.dungeon.terracotta.shamanic_spirit"), + active_hands: InHands((Item("common.items.npc_weapons.unique.shamanic_spirit"), None)), + )), + ), + meta: [], +) \ No newline at end of file diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index ba247b2b41..555c201319 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -66,6 +66,13 @@ pub enum ProjectileConstructor { min_falloff: f32, reagent: Option, }, + DemolisherBomb { + damage: f32, + radius: f32, + energy_regen: f32, + min_falloff: f32, + reagent: Option, + }, Fireball { damage: f32, radius: f32, @@ -316,6 +323,56 @@ impl ProjectileConstructor { is_point: true, } }, + DemolisherBomb { + damage, + radius, + energy_regen, + min_falloff, + reagent, + } => { + let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) + .with_requirement(CombatRequirement::AnyDamage); + let buff = CombatEffect::Buff(CombatBuff { + kind: BuffKind::Burning, + dur_secs: 4.0, + strength: CombatBuffStrength::DamageFraction(1.0), + chance: 0.6, + }) + .adjusted_by_stats(tool_stats); + let damage = AttackDamage::new( + Damage { + source: DamageSource::Explosion, + kind: DamageKind::Energy, + value: damage, + }, + Some(GroupTarget::OutOfGroup), + instance, + ) + .with_effect(buff); + let attack = Attack::default() + .with_damage(damage) + .with_precision(precision_mult) + .with_effect(energy) + .with_combo_increment(); + let explosion = Explosion { + effects: vec![ + RadiusEffect::Attack(attack), + RadiusEffect::TerrainDestruction(2.0, Rgb::black()), + ], + radius, + reagent, + min_falloff, + }; + 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, + is_sticky: true, + is_point: true, + } + }, Fireball { damage, radius, @@ -1037,6 +1094,16 @@ impl ProjectileConstructor { *energy_regen *= regen; *radius *= range; }, + DemolisherBomb { + ref mut damage, + ref mut energy_regen, + ref mut radius, + .. + } => { + *damage *= power; + *energy_regen *= regen; + *radius *= range; + }, Fireball { ref mut damage, ref mut energy_regen, @@ -1160,6 +1227,7 @@ impl ProjectileConstructor { Arrow { .. } => false, Knife { .. } => false, FireDroplet { .. } => true, + DemolisherBomb { .. } => true, Fireball { .. } => true, Frostball { .. } => true, Poisonball { .. } => true, diff --git a/common/src/states/transform.rs b/common/src/states/transform.rs index 83c67d07ef..3876c60e13 100644 --- a/common/src/states/transform.rs +++ b/common/src/states/transform.rs @@ -21,6 +21,7 @@ use super::{ #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum FrontendSpecifier { Evolve, + Cursekeeper, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -94,6 +95,17 @@ impl CharacterBehavior for Data { }, )) }, + FrontendSpecifier::Cursekeeper => { + output_events.emit_local(crate::event::LocalEvent::CreateOutcome( + crate::outcome::Outcome::Explosion { + pos: data.pos.0, + power: 5.0, + radius: 2.0, + is_attack: false, + reagent: Some(Reagent::Purple), + }, + )) + }, } } diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 56e5edde46..4fc442c672 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -1647,7 +1647,7 @@ impl<'a> AgentData<'a> { rng, ), Tactic::CursekeeperFake => { - self.handle_cursekeeper_fake_attack(agent, controller, &attack_data) + self.handle_cursekeeper_fake_attack(controller, &attack_data) }, Tactic::ShamanicSpirit => self.handle_shamanic_spirit_attack( agent, diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index 62e2af1958..804472d8d3 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -5370,18 +5370,6 @@ impl<'a> AgentData<'a> { read_data: &ReadData, rng: &mut impl Rng, ) { - let line_of_sight_with_target = || { - entities_have_line_of_sight( - self.pos, - self.body, - self.scale, - tgt_data.pos, - tgt_data.body, - tgt_data.scale, - read_data, - ) - }; - enum ActionStateTimers { TimerBeam, TimerSummon, @@ -5412,23 +5400,22 @@ impl<'a> AgentData<'a> { agent.combat_state.timers[ActionStateTimers::TimerSummon as usize] += read_data.dt.0; } - if line_of_sight_with_target() { - if agent.combat_state.timers[ActionStateTimers::TimerSummon as usize] > 32.0 { - match agent.combat_state.timers[ActionStateTimers::SelectSummon as usize] as i32 { - 0 => controller.push_basic_input(InputKind::Ability(0)), - 1 => controller.push_basic_input(InputKind::Ability(1)), - 2 => controller.push_basic_input(InputKind::Ability(2)), - 3 => controller.push_basic_input(InputKind::Ability(3)), - _ => controller.push_basic_input(InputKind::Ability(4)), - } - } else if agent.combat_state.timers[ActionStateTimers::TimerBeam as usize] < 6.0 { - controller.push_basic_input(InputKind::Ability(6)); - } else if agent.combat_state.timers[ActionStateTimers::TimerBeam as usize] < 9.0 { - controller.push_basic_input(InputKind::Primary); - } else { - controller.push_basic_input(InputKind::Secondary); + if agent.combat_state.timers[ActionStateTimers::TimerSummon as usize] > 32.0 { + match agent.combat_state.timers[ActionStateTimers::SelectSummon as usize] as i32 { + 0 => controller.push_basic_input(InputKind::Ability(0)), + 1 => controller.push_basic_input(InputKind::Ability(1)), + 2 => controller.push_basic_input(InputKind::Ability(2)), + 3 => controller.push_basic_input(InputKind::Ability(3)), + _ => controller.push_basic_input(InputKind::Ability(4)), } + } else if agent.combat_state.timers[ActionStateTimers::TimerBeam as usize] < 6.0 { + controller.push_basic_input(InputKind::Ability(6)); + } else if agent.combat_state.timers[ActionStateTimers::TimerBeam as usize] < 9.0 { + controller.push_basic_input(InputKind::Primary); + } else { + controller.push_basic_input(InputKind::Secondary); } + if attack_data.dist_sqrd > 10_f32.powi(2) { self.path_toward_target( agent, @@ -5476,23 +5463,11 @@ impl<'a> AgentData<'a> { pub fn handle_cursekeeper_fake_attack( &self, - agent: &mut Agent, controller: &mut Controller, attack_data: &AttackData, ) { - enum Conditions { - AttackToggle = 0, - } if attack_data.dist_sqrd < 25_f32.powi(2) { - if !agent.combat_state.conditions[Conditions::AttackToggle as usize] { - controller.push_basic_input(InputKind::Primary); - if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover)) - { - agent.combat_state.conditions[Conditions::AttackToggle as usize] = true; - } - } else { - controller.push_basic_input(InputKind::Secondary); - } + controller.push_basic_input(InputKind::Primary); } } @@ -5507,11 +5482,38 @@ impl<'a> AgentData<'a> { enum ActionStateTimers { TimerDagon = 0, } + let line_of_sight_with_target = || { + entities_have_line_of_sight( + self.pos, + self.body, + self.scale, + tgt_data.pos, + tgt_data.body, + tgt_data.scale, + read_data, + ) + }; + // when cheesed from behind the entry, change position to retarget + let home = agent.patrol_origin.unwrap_or(self.pos.0); + let exit = Vec3::new(home.x - 6.0, home.y - 6.0, home.z); + let (station_0, station_1) = (exit + 12.0, exit - 12.0); if agent.combat_state.timers[ActionStateTimers::TimerDagon as usize] > 2.5 { agent.combat_state.timers[ActionStateTimers::TimerDagon as usize] = 0.0; } + if !line_of_sight_with_target() + && (tgt_data.pos.0 - exit).xy().magnitude_squared() < (10.0_f32).powi(2) + { + let station = if (tgt_data.pos.0 - station_0).xy().magnitude_squared() + < (tgt_data.pos.0 - station_1).xy().magnitude_squared() + { + station_0 + } else { + station_1 + }; + self.path_toward_target(agent, controller, station, read_data, Path::Full, None); + } // if target gets very close, shoot dagon bombs and lay out sea urchins - if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) { + else if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) { if agent.combat_state.timers[ActionStateTimers::TimerDagon as usize] > 1.0 { controller.push_basic_input(InputKind::Primary); agent.combat_state.timers[ActionStateTimers::TimerDagon as usize] += read_data.dt.0; @@ -5536,15 +5538,7 @@ impl<'a> AgentData<'a> { controller.push_basic_input(InputKind::Ability(2)); } agent.combat_state.timers[ActionStateTimers::TimerDagon as usize] += read_data.dt.0; - } else if entities_have_line_of_sight( - self.pos, - self.body, - self.scale, - tgt_data.pos, - tgt_data.body, - tgt_data.scale, - read_data, - ) { + } else if line_of_sight_with_target() { // if enemy in mid range shoot dagon bombs and steamwave if agent.combat_state.timers[ActionStateTimers::TimerDagon as usize] > 1.0 { controller.push_basic_input(InputKind::Primary); diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 1d8faf868c..b50aeee38b 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -1501,6 +1501,30 @@ impl ParticleMgr { }, ) }, + states::transform::FrontendSpecifier::Cursekeeper => { + self.particles.resize_with( + self.particles.len() + + usize::from( + self.scheduler.heartbeats(Duration::from_millis(10)), + ), + || { + let start_pos = interpolated.pos + + (Vec2::unit_y() + * rng.gen::() + * body.max_radius()) + .rotated_z(rng.gen_range(0.0..(PI * 2.0))) + .with_z(body.height() * rng.gen::()); + + Particle::new_directed( + Duration::from_millis(100), + time, + ParticleMode::FireworkPurple, + start_pos, + start_pos + Vec3::unit_z() * 2.0, + ) + }, + ) + }, } } }, diff --git a/world/src/site2/plot/terracotta_palace.rs b/world/src/site2/plot/terracotta_palace.rs index f014e26d97..89516d3726 100644 --- a/world/src/site2/plot/terracotta_palace.rs +++ b/world/src/site2/plot/terracotta_palace.rs @@ -520,15 +520,15 @@ impl Structure for TerracottaPalace { .fill(clay_unbroken.clone()); painter .cylinder(Aabb { - min: (center - (room_size / 4)).with_z(base + (3 * (room_size / 10)) - 1), - max: (center + (room_size / 4)).with_z(base + (3 * (room_size / 10)) + 2), + min: (center - (room_size / 4) + 1).with_z(base + (3 * (room_size / 10)) - 1), + max: (center + (room_size / 4) - 1).with_z(base + (3 * (room_size / 10)) + 2), }) .clear(); // center podium with spikes painter .cylinder(Aabb { - min: (center - (room_size / 4)).with_z(base + (3 * (room_size / 10)) + 1), - max: (center + (room_size / 4)).with_z(base + (3 * (room_size / 10)) + 2), + min: (center - (room_size / 4) + 1).with_z(base + (3 * (room_size / 10)) + 1), + max: (center + (room_size / 4) - 1).with_z(base + (3 * (room_size / 10)) + 2), }) .fill(Fill::Block(Block::air(SpriteKind::IronSpike))); painter