minor range tweaks; body_dist and macro refactorings of harvester attack code

This commit is contained in:
horblegorble 2024-05-31 19:07:45 +10:00
parent 0fd5548346
commit 4300dd11f4
5 changed files with 165 additions and 101 deletions

View File

@ -4,7 +4,7 @@ BasicRanged(
recover_duration: 1.6, recover_duration: 1.6,
projectile: ( projectile: (
kind: Explosive( kind: Explosive(
radius: 7.5, radius: 7.2,
min_falloff: 0.6, min_falloff: 0.6,
reagent: Some(Red), reagent: Some(Red),
terrain: Some((5, Black)), terrain: Some((5, Black)),

View File

@ -11,7 +11,7 @@ BasicMelee(
knockback: 10.0, knockback: 10.0,
energy_regen: 0.0, energy_regen: 0.0,
), ),
range: 4.0, range: 4.5,
angle: 60.0, angle: 60.0,
multi_target: Some(Normal), multi_target: Some(Normal),
), ),

View File

@ -1204,9 +1204,13 @@ impl<'a> AgentData<'a> {
// Wield the weapon as running towards the target // Wield the weapon as running towards the target
controller.push_action(ControlAction::Wield); controller.push_action(ControlAction::Wield);
let min_attack_dist = (self.body.map_or(0.5, |b| b.max_radius()) + DEFAULT_ATTACK_RANGE) let self_radius = self.body.map_or(0.5, |b| b.max_radius()) * self.scale;
* self.scale let self_attack_range =
+ tgt_data.body.map_or(0.5, |b| b.max_radius()) * tgt_data.scale.map_or(1.0, |s| s.0); (self.body.map_or(0.5, |b| b.max_radius()) + DEFAULT_ATTACK_RANGE) * self.scale;
let tgt_radius =
tgt_data.body.map_or(0.5, |b| b.max_radius()) * tgt_data.scale.map_or(1.0, |s| s.0);
let min_attack_dist = self_attack_range + tgt_radius;
let body_dist = self_radius + tgt_radius;
let dist_sqrd = self.pos.0.distance_squared(tgt_data.pos.0); let dist_sqrd = self.pos.0.distance_squared(tgt_data.pos.0);
let angle = self let angle = self
.ori .ori
@ -1358,6 +1362,7 @@ impl<'a> AgentData<'a> {
} }
let attack_data = AttackData { let attack_data = AttackData {
body_dist,
min_attack_dist, min_attack_dist,
dist_sqrd, dist_sqrd,
angle, angle,

View File

@ -4758,40 +4758,34 @@ impl<'a> AgentData<'a> {
read_data: &ReadData, read_data: &ReadData,
rng: &mut impl Rng, rng: &mut impl Rng,
) { ) {
// --- setup ---
// behaviour parameters
const FIRST_VINE_CREATION_THRESHOLD: f32 = 0.60; const FIRST_VINE_CREATION_THRESHOLD: f32 = 0.60;
const SECOND_VINE_CREATION_THRESHOLD: f32 = 0.30; const SECOND_VINE_CREATION_THRESHOLD: f32 = 0.30;
const MELEE_RANGE: f32 = 5.0; // hard coded from scythe attack
const MAX_PUMPKIN_RANGE: f32 = 50.0; const MAX_PUMPKIN_RANGE: f32 = 50.0;
const FIRE_BREATH_RANGE: f32 = 20.0; const FIREBREATH_RANGE: f32 = 20.0; // hard coded from firebreath attack
const FIRE_BREATH_TIME: f32 = 4.0; const FIREBREATH_TIME: f32 = 4.0;
const FIRE_BREATH_SHORT_TIME: f32 = 2.0; const FIREBREATH_SHORT_TIME: f32 = 2.5; // cutoff sooner at close range
const FIRE_BREATH_COOLDOWN: f32 = 3.0; const FIREBREATH_COOLDOWN: f32 = 3.0;
const CLOSE_MIXUP_COOLDOWN: f32 = 6.0; const CLOSE_MIXUP_COOLDOWN: f32 = 5.0; // variation in attacks at close range
const FAR_PUMPKIN_COOLDOWN: f32 = 1.0; // allows for pathing to player between throws
// conditions
enum ActionStateConditions { enum ActionStateConditions {
ConditionHasSummonedFirstVines = 0, HasSummonedFirstVines = 0,
ConditionHasSummonedSecondVines, HasSummonedSecondVines,
} }
// timers
enum ActionStateTimers { enum ActionStateTimers {
TimerFire = 0, SinceFirebreath = 0,
TimerCloseMixup, SinceCloseMixup,
SinceFarPumpkin,
} }
match self.char_state { // setup line of sight check
CharacterState::BasicBeam(_) => {
agent.combat_state.timers[ActionStateTimers::TimerFire as usize] = 0.0;
},
CharacterState::BasicRanged(_) => {
agent.combat_state.timers[ActionStateTimers::TimerCloseMixup as usize] = 0.0;
},
_ => {
agent.combat_state.timers[ActionStateTimers::TimerFire as usize] += read_data.dt.0;
agent.combat_state.timers[ActionStateTimers::TimerCloseMixup as usize] +=
read_data.dt.0;
},
}
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight( entities_have_line_of_sight(
self.pos, self.pos,
@ -4804,95 +4798,159 @@ impl<'a> AgentData<'a> {
) )
}; };
if (health_fraction < FIRST_VINE_CREATION_THRESHOLD // --- readability macros ---
&& !agent.combat_state.conditions
[ActionStateConditions::ConditionHasSummonedFirstVines as usize]) // actions
macro_rules! reset_timer {
($timer:ident) => {
agent.combat_state.timers[ActionStateTimers::$timer as usize] = 0.0
};
}
macro_rules! increment_timer {
($timer:ident) => {
agent.combat_state.timers[ActionStateTimers::$timer as usize] += read_data.dt.0
};
}
macro_rules! use_scythe {
() => {
controller.push_basic_input(InputKind::Primary)
};
}
macro_rules! use_fire_breath {
() => {
controller.push_basic_input(InputKind::Secondary)
};
}
macro_rules! use_pumpkin {
() => {
controller.push_basic_input(InputKind::Ability(0))
};
}
macro_rules! use_first_vines {
() => {
controller.push_basic_input(InputKind::Ability(1))
};
}
macro_rules! use_second_vines {
() => {
controller.push_basic_input(InputKind::Ability(2))
};
}
macro_rules! move_to_target {
() => {
self.path_toward_target(
agent,
controller,
tgt_data.pos.0,
read_data,
Path::Partial,
None,
)
};
}
// shortcuts
macro_rules! conditions {
($condition:ident) => {
agent.combat_state.conditions[ActionStateConditions::$condition as usize]
};
}
macro_rules! timers {
($timer:ident) => {
agent.combat_state.timers[ActionStateTimers::$timer as usize]
};
}
macro_rules! is_in_vines_recovery {
() => (
matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover))
)
}
macro_rules! is_using_firebreath {
// currently using firebreath and under time limit
($time_limit:ident) => (
matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32($time_limit))
)
}
// --- main ---
// handle timers
match self.char_state {
CharacterState::BasicBeam(_) => {
reset_timer!(SinceFirebreath);
},
CharacterState::BasicRanged(_) => {
reset_timer!(SinceCloseMixup);
reset_timer!(SinceFarPumpkin);
},
_ => {
increment_timer!(SinceFirebreath);
increment_timer!(SinceCloseMixup);
increment_timer!(SinceFarPumpkin);
},
}
// vine summoning
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
if (health_fraction < FIRST_VINE_CREATION_THRESHOLD && !conditions!(HasSummonedFirstVines))
|| (health_fraction < SECOND_VINE_CREATION_THRESHOLD || (health_fraction < SECOND_VINE_CREATION_THRESHOLD
&& !agent.combat_state.conditions && !conditions!(HasSummonedSecondVines))
[ActionStateConditions::ConditionHasSummonedSecondVines as usize])
{ {
if health_fraction < SECOND_VINE_CREATION_THRESHOLD { if health_fraction < SECOND_VINE_CREATION_THRESHOLD {
// Summon second vines when reach threshold of health use_second_vines!();
controller.push_basic_input(InputKind::Ability(2)); if is_in_vines_recovery!() {
conditions!(HasSummonedSecondVines) = true;
if matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.combat_state.conditions
[ActionStateConditions::ConditionHasSummonedSecondVines as usize] = true;
} }
} else { } else {
// Summon first vines when reach threshold of health use_first_vines!();
controller.push_basic_input(InputKind::Ability(1)); if is_in_vines_recovery!() {
conditions!(HasSummonedFirstVines) = true;
if matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.combat_state.conditions
[ActionStateConditions::ConditionHasSummonedFirstVines as usize] = true;
} }
} }
} else if attack_data.in_min_range() { }
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32(FIRE_BREATH_SHORT_TIME)) // close range
else if attack_data.dist_sqrd < (attack_data.body_dist + 0.75 * MELEE_RANGE).powi(2) {
if is_using_firebreath!(FIREBREATH_SHORT_TIME) {
use_fire_breath!()
} else if timers!(SinceCloseMixup) > CLOSE_MIXUP_COOLDOWN
// for now, no line of sight check for consitency in attacks
{ {
// Keep breathing fire if close enough, can see target, and have not been if timers!(SinceFirebreath) < FIREBREATH_COOLDOWN {
// breathing for more than short limit use_pumpkin!();
controller.push_basic_input(InputKind::Secondary);
} else if agent.combat_state.timers[ActionStateTimers::TimerCloseMixup as usize]
> CLOSE_MIXUP_COOLDOWN
&& line_of_sight_with_target()
{
if agent.combat_state.timers[ActionStateTimers::TimerFire as usize]
< FIRE_BREATH_COOLDOWN
{
// Throw a close range pumpkin to knock back player
controller.push_basic_input(InputKind::Ability(0));
} else { } else {
let randomise = rng.gen_range(1..=3); let randomise = rng.gen_range(1..=2);
match randomise { match randomise {
1 => controller.push_basic_input(InputKind::Secondary), /* start fire 1 => use_fire_breath!(),
* breath */ _ => use_pumpkin!(),
_ => controller.push_basic_input(InputKind::Ability(0)), /* close range
* pumpkin
* _ => controller.push_basic_input(InputKind::Primary), // scythe */
} }
} }
} else if attack_data.angle < 60.0 { } else if attack_data.angle < 60.0 {
// Scythe them if they're in range and angle use_scythe!();
controller.push_basic_input(InputKind::Primary);
} }
} else if attack_data.dist_sqrd < FIRE_BREATH_RANGE.powi(2) { }
if line_of_sight_with_target() { // mid range (with line of sight)
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32(FIRE_BREATH_TIME)) else if attack_data.dist_sqrd < FIREBREATH_RANGE.powi(2) && line_of_sight_with_target() {
{ if is_using_firebreath!(FIREBREATH_TIME) {
// Keep breathing fire if close enough, can see target, and have not been use_fire_breath!()
// breathing for more than upper limit } else if attack_data.angle < 30.0 && timers!(SinceFirebreath) > FIREBREATH_COOLDOWN {
controller.push_basic_input(InputKind::Secondary); use_fire_breath!()
} else if attack_data.angle < 30.0 } else if timers!(SinceCloseMixup) > CLOSE_MIXUP_COOLDOWN {
&& agent.combat_state.timers[ActionStateTimers::TimerFire as usize] use_pumpkin!();
> FIRE_BREATH_COOLDOWN
{
// Start breathing fire at them if close enough, in angle, and can see target
controller.push_basic_input(InputKind::Secondary);
} else if agent.combat_state.timers[ActionStateTimers::TimerCloseMixup as usize]
> CLOSE_MIXUP_COOLDOWN
{
// Throw a close range pumpkin to knock back player
controller.push_basic_input(InputKind::Ability(0));
}
} }
} else if attack_data.dist_sqrd < MAX_PUMPKIN_RANGE.powi(2) && line_of_sight_with_target() { }
// Throw a pumpkin at them if close enough and can see them // long range (with line of sight)
controller.push_basic_input(InputKind::Ability(0)); else if attack_data.dist_sqrd < MAX_PUMPKIN_RANGE.powi(2)
&& line_of_sight_with_target()
&& timers!(SinceFarPumpkin) > FAR_PUMPKIN_COOLDOWN
{
use_pumpkin!();
} }
// Always attempt to path towards target // closing gap
self.path_toward_target( if !(attack_data.dist_sqrd < (attack_data.body_dist + 0.4 * MELEE_RANGE).powi(2)) {
agent, move_to_target!();
controller, }
tgt_data.pos.0,
read_data,
Path::Partial,
None,
);
} }
pub fn handle_frostgigas_attack( pub fn handle_frostgigas_attack(

View File

@ -145,6 +145,7 @@ impl<'a> TargetData<'a> {
} }
pub struct AttackData { pub struct AttackData {
pub body_dist: f32,
pub min_attack_dist: f32, pub min_attack_dist: f32,
pub dist_sqrd: f32, pub dist_sqrd: f32,
pub angle: f32, pub angle: f32,