diff --git a/assets/common/abilities/gnarling/chieftain/firebarrage.ron b/assets/common/abilities/gnarling/chieftain/firebarrage.ron index 6ec3edce1b..9e7c027262 100644 --- a/assets/common/abilities/gnarling/chieftain/firebarrage.ron +++ b/assets/common/abilities/gnarling/chieftain/firebarrage.ron @@ -1,6 +1,6 @@ BasicRanged( energy_cost: 0, - buildup_duration: 0.825, + buildup_duration: 0.9, recover_duration: 0.6, projectile: ( kind: Explosive( diff --git a/assets/common/abilities/gnarling/chieftain/fireshockwave.ron b/assets/common/abilities/gnarling/chieftain/fireshockwave.ron index 52fb6fe94f..388630ef6d 100644 --- a/assets/common/abilities/gnarling/chieftain/fireshockwave.ron +++ b/assets/common/abilities/gnarling/chieftain/fireshockwave.ron @@ -9,8 +9,8 @@ Shockwave( shockwave_angle: 360, shockwave_vertical_angle: 90, shockwave_speed: 12, - shockwave_duration: 1, - dodgeable: Roll, + shockwave_duration: 1.0, + dodgeable: Jump, move_efficiency: 0, damage_kind: Energy, specifier: Fire, diff --git a/assets/common/abilities/gnarling/chieftain/flamestrike.ron b/assets/common/abilities/gnarling/chieftain/flamestrike.ron index a736b3ac08..1d227f4481 100644 --- a/assets/common/abilities/gnarling/chieftain/flamestrike.ron +++ b/assets/common/abilities/gnarling/chieftain/flamestrike.ron @@ -1,7 +1,7 @@ BasicMelee( energy_cost: 0, - buildup_duration: 0.7, - swing_duration: 0.35, + buildup_duration: 0.8, + swing_duration: 0.4, hit_timing: 0.5, recover_duration: 0.6, melee_constructor: ( @@ -11,8 +11,8 @@ BasicMelee( knockback: 0, energy_regen: 0, ), - range: 4.0, - angle: 60.0, + range: 3.0, + angle: 50.0, damage_effect: Some(Buff(( kind: Burning, dur_secs: 4.0, diff --git a/assets/common/abilities/gnarling/totem/green.ron b/assets/common/abilities/gnarling/totem/green.ron index ddb7ad1c9d..db583e0062 100644 --- a/assets/common/abilities/gnarling/totem/green.ron +++ b/assets/common/abilities/gnarling/totem/green.ron @@ -12,7 +12,7 @@ BasicAura( ), ( kind: ProtectingWard, - strength: 1, + strength: 0.5, duration: Some(1), category: Magical, ), diff --git a/assets/common/abilities/gnarling/totem/red.ron b/assets/common/abilities/gnarling/totem/red.ron index cd4740bae2..62e5c9935b 100644 --- a/assets/common/abilities/gnarling/totem/red.ron +++ b/assets/common/abilities/gnarling/totem/red.ron @@ -6,7 +6,7 @@ BasicAura( auras: [ ( kind: Burning, - strength: 1, + strength: 1.2, duration: Some(1), category: Magical, ), diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 79a9ea6953..cf747f521b 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -1273,6 +1273,7 @@ impl Body { _ => 300, }, Body::BipedSmall(b) => match b.species { + biped_small::Species::GnarlingChieftain => 130, biped_small::Species::IronDwarf | biped_small::Species::Flamekeeper => 300, _ => 100, }, diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index 603236ce81..625a9a0924 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -4796,9 +4796,9 @@ impl<'a> AgentData<'a> { // timers enum ActionStateTimers { - FirebreathCooldown = 0, - MixupCooldown, - FarPumpkinCooldown, + Firebreath = 0, + Mixup, + FarPumpkin, } // //counters @@ -4822,38 +4822,39 @@ impl<'a> AgentData<'a> { }; // === main === - + // --- timers --- // initialise randomised cooldowns - if !agent.combat_state.initialized { + if !agent.combat_state.initialized { agent.combat_state.initialized = true; agent.combat_state.counters[ActionStateCounters::CloseMixupCooldown as usize] = rng.gen_range(CLOSE_MIXUP_COOLDOWN_SPAN[0]..=CLOSE_MIXUP_COOLDOWN_SPAN[1]); - agent.combat_state.counters[ActionStateCounters::MidMixupCooldown as usize] = + agent.combat_state.counters[ActionStateCounters::MidMixupCooldown as usize] = rng.gen_range(MID_MIXUP_COOLDOWN_SPAN[0]..=MID_MIXUP_COOLDOWN_SPAN[1]); - agent.combat_state.counters[ActionStateCounters::FarPumpkinCooldown as usize] = + agent.combat_state.counters[ActionStateCounters::FarPumpkinCooldown as usize] = rng.gen_range(FAR_PUMPKIN_COOLDOWN_SPAN[0]..=FAR_PUMPKIN_COOLDOWN_SPAN[1]); - // note: if f32 rng is too expensive, could use randomised u8 multipliers of a const f32 + // note: if f32 rng is too expensive, could use randomised u8 + // multipliers of a const f32 } // timer changes if matches!(self.char_state, CharacterState::SpriteSummon(_)) { // reset all timers when summoning - agent.combat_state.timers[ActionStateTimers::MixupCooldown as usize] = 0.0; - agent.combat_state.timers[ActionStateTimers::FarPumpkinCooldown as usize] = 0.0; - agent.combat_state.timers[ActionStateTimers::FirebreathCooldown as usize] = 0.0; + agent.combat_state.timers[ActionStateTimers::Mixup as usize] = 0.0; + agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] = 0.0; + agent.combat_state.timers[ActionStateTimers::Firebreath as usize] = 0.0; } else { // increment timers when not in corresponding state let is_using_firebreath = matches!(self.char_state, CharacterState::BasicBeam(_)); let is_using_pumpkin = matches!(self.char_state, CharacterState::BasicRanged(_)); let is_using_mixup = is_using_firebreath || is_using_pumpkin; if !is_using_mixup { - agent.combat_state.timers[ActionStateTimers::MixupCooldown as usize] += read_data.dt.0; + agent.combat_state.timers[ActionStateTimers::Mixup as usize] += read_data.dt.0; } if !is_using_firebreath { - agent.combat_state.timers[ActionStateTimers::FirebreathCooldown as usize] += read_data.dt.0; + agent.combat_state.timers[ActionStateTimers::Firebreath as usize] += read_data.dt.0; } if !is_using_pumpkin { - agent.combat_state.timers[ActionStateTimers::FarPumpkinCooldown as usize] += read_data.dt.0; + agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] += read_data.dt.0; } } // note: some timer resets are performerd in attack logic @@ -4862,15 +4863,13 @@ impl<'a> AgentData<'a> { let health_fraction = self.health.map_or(0.5, |h| h.fraction()); // second vine summon if health_fraction < SECOND_VINE_CREATION_THRESHOLD - && !agent.combat_state.conditions - [ActionStateConditions::SecondVines as usize] + && !agent.combat_state.conditions[ActionStateConditions::SecondVines as usize] { controller.push_basic_input(InputKind::Ability(2)); // wait till recovery before finishing if matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover)) { - agent.combat_state.conditions - [ActionStateConditions::SecondVines as usize] = true; + agent.combat_state.conditions[ActionStateConditions::SecondVines as usize] = true; } } // first vine summon @@ -4881,8 +4880,7 @@ impl<'a> AgentData<'a> { // wait till recovery before finishing if matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover)) { - agent.combat_state.conditions - [ActionStateConditions::FirstVines as usize] = true; + agent.combat_state.conditions[ActionStateConditions::FirstVines as usize] = true; } } // close range @@ -4893,16 +4891,16 @@ impl<'a> AgentData<'a> { if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32(FIREBREATH_SHORT_TIME)) { controller.push_basic_input(InputKind::Secondary); - agent.combat_state.timers[ActionStateTimers::FirebreathCooldown as usize] = 0.0; + agent.combat_state.timers[ActionStateTimers::Firebreath as usize] = 0.0; } // on timer, randomly mixup attacks - else if agent.combat_state.timers[ActionStateTimers::MixupCooldown as usize] + else if agent.combat_state.timers[ActionStateTimers::Mixup as usize] > agent.combat_state.counters[ActionStateCounters::CloseMixupCooldown as usize] && attack_data.angle < SCYTHE_AIM_ANGLE - // for now, no line of sight check for consitency in attacks + // for now, no line of sight check for consitency in attacks { // if on firebreath cooldown, throw pumpkin - if agent.combat_state.timers[ActionStateTimers::FirebreathCooldown as usize] + if agent.combat_state.timers[ActionStateTimers::Firebreath as usize] < FIREBREATH_COOLDOWN { controller.push_basic_input(InputKind::Ability(0)); @@ -4916,12 +4914,14 @@ impl<'a> AgentData<'a> { } } // reset mixup timing if actually being used - if matches!(self.char_state, CharacterState::BasicBeam(_) | CharacterState::BasicRanged(_)) { - agent.combat_state.timers[ActionStateTimers::MixupCooldown as usize] = 0.0; - agent.combat_state.counters[ActionStateCounters::CloseMixupCooldown as usize] = + if matches!( + self.char_state, + CharacterState::BasicBeam(_) | CharacterState::BasicRanged(_) + ) { + agent.combat_state.timers[ActionStateTimers::Mixup as usize] = 0.0; + agent.combat_state.counters[ActionStateCounters::CloseMixupCooldown as usize] = rng.gen_range(CLOSE_MIXUP_COOLDOWN_SPAN[0]..=CLOSE_MIXUP_COOLDOWN_SPAN[1]); } - } // default to using scythe melee else if attack_data.angle < SCYTHE_AIM_ANGLE { @@ -4934,41 +4934,41 @@ impl<'a> AgentData<'a> { if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32(FIREBREATH_TIME)) { controller.push_basic_input(InputKind::Secondary); - agent.combat_state.timers[ActionStateTimers::FirebreathCooldown as usize] = 0.0; + agent.combat_state.timers[ActionStateTimers::Firebreath as usize] = 0.0; } // start using firebreath if close enough, in angle, and off cooldown else if attack_data.dist_sqrd < (FIREBREATH_RANGE * FIREBREATH_RANGE_FACTOR).powi(2) && attack_data.angle < FIREBREATH_AIM_ANGLE - && agent.combat_state.timers[ActionStateTimers::FirebreathCooldown as usize] + && agent.combat_state.timers[ActionStateTimers::Firebreath as usize] > FIREBREATH_COOLDOWN { controller.push_basic_input(InputKind::Secondary); } // on mixup timer, throw a pumpkin - else if agent.combat_state.timers[ActionStateTimers::MixupCooldown as usize] + else if agent.combat_state.timers[ActionStateTimers::Mixup as usize] > agent.combat_state.counters[ActionStateCounters::MidMixupCooldown as usize] { controller.push_basic_input(InputKind::Ability(0)); // reset mixup timing if pumpkin is actually being used if matches!(self.char_state, CharacterState::BasicRanged(_)) { - agent.combat_state.timers[ActionStateTimers::MixupCooldown as usize] = 0.0; - agent.combat_state.counters[ActionStateCounters::MidMixupCooldown as usize] = + agent.combat_state.timers[ActionStateTimers::Mixup as usize] = 0.0; + agent.combat_state.counters[ActionStateCounters::MidMixupCooldown as usize] = rng.gen_range(MID_MIXUP_COOLDOWN_SPAN[0]..=MID_MIXUP_COOLDOWN_SPAN[1]); } } } // long range (with line of sight) else if attack_data.dist_sqrd < MAX_PUMPKIN_RANGE.powi(2) - && line_of_sight_with_target() - && agent.combat_state.timers[ActionStateTimers::FarPumpkinCooldown as usize] + && agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] > agent.combat_state.counters[ActionStateCounters::FarPumpkinCooldown as usize] + && line_of_sight_with_target() { // throw pumpkin controller.push_basic_input(InputKind::Ability(0)); // reset pumpkin timing if actually being used if matches!(self.char_state, CharacterState::BasicRanged(_)) { - agent.combat_state.timers[ActionStateTimers::FarPumpkinCooldown as usize] = 0.0; - agent.combat_state.counters[ActionStateCounters::FarPumpkinCooldown as usize] = + agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] = 0.0; + agent.combat_state.counters[ActionStateCounters::FarPumpkinCooldown as usize] = rng.gen_range(FAR_PUMPKIN_COOLDOWN_SPAN[0]..=FAR_PUMPKIN_COOLDOWN_SPAN[1]); } } @@ -4989,6 +4989,7 @@ impl<'a> AgentData<'a> { } // closing angle else if attack_data.angle > 0.0 { + // some movement is required to trigger re-orientation controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0) .xy() .try_normalized() @@ -5927,15 +5928,16 @@ impl<'a> AgentData<'a> { // === setup === - // hard-coded attack vlues (some scaled for usage) + // hard-coded attack values (some scaled for usage) const STRIKE_RANGE: f32 = 4.0; const STRIKE_AIM_ANGLE: f32 = 55.0 * 0.7; const SPIN_RANGE: f32 = 5.0; const SHOCKWAVE_AIM_ANGLE: f32 = 45.0 * 0.7; const SHOCKWAVE_MAX_RANGE: f32 = 30.0 * 0.6; // behaviour parameters - const ATTACK_RANGE_FACTOR: f32 = 0.6; // start attack while suitably in range const PATH_RANGE_FACTOR: f32 = 0.3; // get comfortably in range, but give player room to breathe + const STRIKE_RANGE_FACTOR: f32 = 0.6; // start attack while suitably in range + const SPIN_RANGE_FACTOR: f32 = 0.6; // ^ const SPIN_COOLDOWN: f32 = 1.5; const SPIN_RELAX_FACTOR: f32 = 0.2; const SHOCKWAVE_COOLDOWN: f32 = 5.0; @@ -5950,11 +5952,11 @@ impl<'a> AgentData<'a> { Mixup, } - // re-used comparisons (makes separating timers and attacks easier) + // re-used checks (makes separating timers and attacks easier) let is_in_spin_range = attack_data.dist_sqrd - < (attack_data.body_dist + SPIN_RANGE * ATTACK_RANGE_FACTOR).powi(2); + < (attack_data.body_dist + SPIN_RANGE * SPIN_RANGE_FACTOR).powi(2); let is_in_strike_range = attack_data.dist_sqrd - < (attack_data.body_dist + STRIKE_RANGE * ATTACK_RANGE_FACTOR).powi(2); + < (attack_data.body_dist + STRIKE_RANGE * STRIKE_RANGE_FACTOR).powi(2); let is_in_strike_angle = attack_data.angle < STRIKE_AIM_ANGLE; // === main === @@ -5973,7 +5975,7 @@ impl<'a> AgentData<'a> { // relax towards zero otherwise agent.combat_state.timers[ActionStateTimers::Spin as usize] = (agent.combat_state.timers[ActionStateTimers::Spin as usize] - - SPIN_RELAX_FACTOR * read_data.dt.0) + - read_data.dt.0 * SPIN_RELAX_FACTOR) .max(0.0); } // shockwave @@ -5993,40 +5995,40 @@ impl<'a> AgentData<'a> { // relax towards zero otherwise agent.combat_state.timers[ActionStateTimers::Mixup as usize] = (agent.combat_state.timers[ActionStateTimers::Mixup as usize] - - MIXUP_RELAX_FACTOR * read_data.dt.0) + - read_data.dt.0 * MIXUP_RELAX_FACTOR) .max(0.0); } // --- attacks --- - // strike range and in angle + // strike range and angle if is_in_strike_range && is_in_strike_angle { + // on timer, randomly mixup between all attacks if agent.combat_state.timers[ActionStateTimers::Mixup as usize] > MIXUP_COOLDOWN { - // randomly mix up let randomise: u8 = rng.gen_range(1..=3); match randomise { 1 => controller.push_basic_input(InputKind::Ability(0)), // shockwave 2 => controller.push_basic_input(InputKind::Primary), // strike _ => controller.push_basic_input(InputKind::Secondary), // spin } - } else { - // use strike + } + // default to strike + else { controller.push_basic_input(InputKind::Primary); } } // spin range (or out of angle in strike range) else if is_in_spin_range || (is_in_strike_range && !is_in_strike_angle) { + // on timer, use spin attack to try and hit evasive target if agent.combat_state.timers[ActionStateTimers::Spin as usize] > SPIN_COOLDOWN { - // If it's been too long since able to hit target, spin controller.push_basic_input(InputKind::Secondary); } } - // shockwave range + // shockwave range and angle with cooldown else if attack_data.dist_sqrd > SHOCKWAVE_MIN_RANGE.powi(2) && attack_data.dist_sqrd < SHOCKWAVE_MAX_RANGE.powi(2) && attack_data.angle < SHOCKWAVE_AIM_ANGLE && agent.combat_state.timers[ActionStateTimers::Shockwave as usize] > SHOCKWAVE_COOLDOWN { - // Shockwave if close enough and haven't shockwaved too recently controller.push_basic_input(InputKind::Ability(0)); } @@ -6046,7 +6048,7 @@ impl<'a> AgentData<'a> { } // closing angle else if attack_data.angle > 0.0 { - // If not in angle, apply slight movement so golem orients itself correctly + // some movement is required to trigger re-orientation controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0) .xy() .try_normalized() @@ -6076,11 +6078,24 @@ impl<'a> AgentData<'a> { // === setup === - // hard-coded attack values - + // hard-coded attack values (some scaled for usage) + const STRIKE_RANGE: f32 = 3.0; + const STRIKE_AIM_ANGLE: f32 = 50.0 * 0.7; + const SHOCKWAVE_RANGE: f32 = 12.0 * 0.6; // attack logic assumes this is less than BARRAGE_RANGE + const BARRAGE_RANGE: f32 = 25.0 * 0.8; // behaviour parameters - const TOTEM_TIMER: f32 = 20.0; - const HEAVY_ATTACK_WAIT_TIME: f32 = 10.0; + const PATH_RANGE_FACTOR: f32 = 0.4; + const STRIKE_RANGE_FACTOR: f32 = 0.7; + const BARRAGE_AIM_ANGLE: f32 = 20.0; + const TOTEM_COOLDOWN: f32 = 20.0; + const HEAVY_ATTACK_COOLDOWN_SPAN: [f32; 2] = [8.0, 13.0]; + const HEAVY_ATTACK_CHARGE_FACTOR: f32 = 3.3; + const HEAVY_ATTACK_FAST_CHARGE_FACTOR: f32 = 5.0; + + // conditions + enum ActionStateConditions { + FirstTotem = 0, + } // timers enum ActionStateTimers { @@ -6088,74 +6103,96 @@ impl<'a> AgentData<'a> { HeavyAttack, } + // counters + enum ActionStateCounters { + HeavyAttackCooldown = 0, + } + + // re-used checks + let is_in_strike_range = attack_data.dist_sqrd + < (attack_data.body_dist + STRIKE_RANGE * STRIKE_RANGE_FACTOR).powi(2); + let is_in_strike_angle = attack_data.angle < STRIKE_AIM_ANGLE; + // === main === // --- timers --- - agent.combat_state.timers[ActionStateTimers::SummonTotem as usize] += read_data.dt.0; + // initialise randomised cooldowns + if !agent.combat_state.initialized { + agent.combat_state.initialized = true; + agent.combat_state.counters[ActionStateCounters::HeavyAttackCooldown as usize] = + rng.gen_range(HEAVY_ATTACK_COOLDOWN_SPAN[0]..=HEAVY_ATTACK_COOLDOWN_SPAN[1]); + // note: if f32 rng is too expensive, could use randomised u8 + // multipliers of a const f32 + } + // resets match self.char_state { - CharacterState::BasicSummon(_) => { - // reset when summoning - agent.combat_state.timers[ActionStateTimers::SummonTotem as usize] = 0.0 + CharacterState::BasicSummon(s) if s.stage_section == StageSection::Recover => { + // reset when finished summoning + agent.combat_state.timers[ActionStateTimers::SummonTotem as usize] = 0.0; + agent.combat_state.conditions[ActionStateConditions::FirstTotem as usize] = true; }, CharacterState::Shockwave(_) | CharacterState::BasicRanged(_) => { - agent.combat_state.counters[ActionStateTimers::HeavyAttack as usize] = 0.0 + // reset heavy attack on either ability + agent.combat_state.counters[ActionStateTimers::HeavyAttack as usize] = 0.0; + agent.combat_state.counters[ActionStateCounters::HeavyAttackCooldown as usize] = + rng.gen_range(HEAVY_ATTACK_COOLDOWN_SPAN[0]..=HEAVY_ATTACK_COOLDOWN_SPAN[1]); }, _ => {}, } - if attack_data.in_min_range() { - // Else if not time to use anything fancy - if attack_data.angle < 20.0 { + // heavy attack + agent.combat_state.timers[ActionStateTimers::SummonTotem as usize] += read_data.dt.0; + if is_in_strike_range { + // recharge at standard rate in strike range and angle + if is_in_strike_angle { agent.combat_state.counters[ActionStateTimers::HeavyAttack as usize] += read_data.dt.0; } else { // If not in angle, charge heavy attack faster agent.combat_state.counters[ActionStateTimers::HeavyAttack as usize] += - read_data.dt.0 * 5.0; + read_data.dt.0 * HEAVY_ATTACK_FAST_CHARGE_FACTOR; } } else { // If not in range, charge heavy attack faster agent.combat_state.counters[ActionStateTimers::HeavyAttack as usize] += - read_data.dt.0 * 3.3; + read_data.dt.0 * HEAVY_ATTACK_CHARGE_FACTOR; } - // --- attacks --- - // start out by summoning green totem - if !agent.combat_state.initialized { - + + // start by summoning green totem + if !agent.combat_state.conditions[ActionStateConditions::FirstTotem as usize] { controller.push_basic_input(InputKind::Ability(2)); - if matches!(self.char_state, CharacterState::BasicSummon(s) if s.stage_section == StageSection::Recover) - { - agent.combat_state.initialized = true; - } - } else if agent.combat_state.timers[ActionStateTimers::SummonTotem as usize] - > TOTEM_TIMER + } + // on timer, summon a new totem + else if agent.combat_state.timers[ActionStateTimers::SummonTotem as usize] + > TOTEM_COOLDOWN { - // If time to summon a totem, do it + // randomly pick a totem to summon let input = rng.gen_range(1..=3); let buff_kind = match input { 2 => Some(BuffKind::Regeneration), 3 => Some(BuffKind::Hastened), _ => None, }; + // if already buffed by chosen totem, skip it if buff_kind.map_or(false, |b| self.has_buff(read_data, b)) && matches!(self.char_state, CharacterState::Wielding { .. }) { - // If already under effects of buff from totem that would be summoned, don't - // summon totem (doesn't work for red totems since that applies debuff to - // enemies instead) + // (doesn't work for red totems since debuff applies to enemies instead) agent.combat_state.timers[ActionStateTimers::SummonTotem as usize] = 0.0; - } else { + } + // summon totem + else { controller.push_basic_input(InputKind::Ability(input)); } - } else if agent.combat_state.counters[ActionStateTimers::HeavyAttack as usize] - > HEAVY_ATTACK_WAIT_TIME + } + // on timer, use a heavy attack in range + else if agent.combat_state.counters[ActionStateTimers::HeavyAttack as usize] + > agent.combat_state.counters[ActionStateCounters::HeavyAttackCooldown as usize] + && attack_data.dist_sqrd < BARRAGE_RANGE.powi(2) { - // Else if time for a heavy attack - if attack_data.in_min_range() { - // If in range, shockwave - controller.push_basic_input(InputKind::Ability(0)); - } else if entities_have_line_of_sight( + // has line of sight + if entities_have_line_of_sight( self.pos, self.body, self.scale, @@ -6164,17 +6201,43 @@ impl<'a> AgentData<'a> { tgt_data.scale, read_data, ) { - // Else if in sight, barrage - controller.push_basic_input(InputKind::Secondary); + // use shockwave out of barrage angle + if attack_data.angle > BARRAGE_AIM_ANGLE { + controller.push_basic_input(InputKind::Ability(0)); + } + // randomise in shockwave range + else if attack_data.dist_sqrd < SHOCKWAVE_RANGE.powi(2) { + let randomise: u8 = rng.gen_range(1..=2); + match randomise { + 1 => controller.push_basic_input(InputKind::Ability(0)), // shockwave + _ => controller.push_basic_input(InputKind::Secondary), // barrage + } + } + // use barrage if in range and angle + else { + controller.push_basic_input(InputKind::Secondary); + } + // keep pathing to target (no action required) } - } else if attack_data.in_min_range() && attack_data.angle < 20.0 { - // Else if not time to use anything fancy, if in range and angle, strike them + // no line of sight + else { + // use shockwave in range + if attack_data.dist_sqrd < SHOCKWAVE_RANGE.powi(2) { + controller.push_basic_input(InputKind::Ability(0)); + } + } + } + // default to flamestrike when in range and angle + else if is_in_strike_range && is_in_strike_angle { controller.push_basic_input(InputKind::Primary); } // --- movement --- - // close gap - if !attack_data.in_min_range() { + // closing gap + if attack_data.dist_sqrd + > (attack_data.body_dist + STRIKE_RANGE * PATH_RANGE_FACTOR).powi(2) + { + // TODO: pathing gap self.path_toward_target( agent, controller, @@ -6184,6 +6247,15 @@ impl<'a> AgentData<'a> { None, ); } + // closing angle + else if attack_data.angle > 0.0 { + // some movement is required to trigger re-orientation + controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0) + .xy() + .try_normalized() + .unwrap_or_else(Vec2::zero) + * 0.001; // scaled way down to minimise position change and keep close rotation consistent + } } pub fn handle_sword_simple_attack(