dagon_anticheese

This commit is contained in:
flo 2024-05-28 19:13:49 +00:00
parent 601712cf77
commit 5c575484f2
13 changed files with 180 additions and 70 deletions

View File

@ -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"): (

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,

View File

@ -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,

View File

@ -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: [],
)
)

View File

@ -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: [],
)

View File

@ -66,6 +66,13 @@ pub enum ProjectileConstructor {
min_falloff: f32,
reagent: Option<Reagent>,
},
DemolisherBomb {
damage: f32,
radius: f32,
energy_regen: f32,
min_falloff: f32,
reagent: Option<Reagent>,
},
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,

View File

@ -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),
},
))
},
}
}

View File

@ -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,

View File

@ -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);

View File

@ -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::<f32>()
* body.max_radius())
.rotated_z(rng.gen_range(0.0..(PI * 2.0)))
.with_z(body.height() * rng.gen::<f32>());
Particle::new_directed(
Duration::from_millis(100),
time,
ParticleMode::FireworkPurple,
start_pos,
start_pos + Vec3::unit_z() * 2.0,
)
},
)
},
}
}
},

View File

@ -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