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,
projectile: (
kind: Explosive(
radius: 7.5,
radius: 7.2,
min_falloff: 0.6,
reagent: Some(Red),
terrain: Some((5, Black)),

View File

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

View File

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

View File

@ -4758,40 +4758,34 @@ impl<'a> AgentData<'a> {
read_data: &ReadData,
rng: &mut impl Rng,
) {
// --- setup ---
// behaviour parameters
const FIRST_VINE_CREATION_THRESHOLD: f32 = 0.60;
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 FIRE_BREATH_RANGE: f32 = 20.0;
const FIRE_BREATH_TIME: f32 = 4.0;
const FIRE_BREATH_SHORT_TIME: f32 = 2.0;
const FIRE_BREATH_COOLDOWN: f32 = 3.0;
const CLOSE_MIXUP_COOLDOWN: f32 = 6.0;
const FIREBREATH_RANGE: f32 = 20.0; // hard coded from firebreath attack
const FIREBREATH_TIME: f32 = 4.0;
const FIREBREATH_SHORT_TIME: f32 = 2.5; // cutoff sooner at close range
const FIREBREATH_COOLDOWN: f32 = 3.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 {
ConditionHasSummonedFirstVines = 0,
ConditionHasSummonedSecondVines,
HasSummonedFirstVines = 0,
HasSummonedSecondVines,
}
// timers
enum ActionStateTimers {
TimerFire = 0,
TimerCloseMixup,
SinceFirebreath = 0,
SinceCloseMixup,
SinceFarPumpkin,
}
match self.char_state {
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());
// setup line of sight check
let line_of_sight_with_target = || {
entities_have_line_of_sight(
self.pos,
@ -4804,95 +4798,159 @@ impl<'a> AgentData<'a> {
)
};
if (health_fraction < FIRST_VINE_CREATION_THRESHOLD
&& !agent.combat_state.conditions
[ActionStateConditions::ConditionHasSummonedFirstVines as usize])
// --- readability macros ---
// 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
&& !agent.combat_state.conditions
[ActionStateConditions::ConditionHasSummonedSecondVines as usize])
&& !conditions!(HasSummonedSecondVines))
{
if health_fraction < SECOND_VINE_CREATION_THRESHOLD {
// Summon second vines when reach threshold of health
controller.push_basic_input(InputKind::Ability(2));
if matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.combat_state.conditions
[ActionStateConditions::ConditionHasSummonedSecondVines as usize] = true;
use_second_vines!();
if is_in_vines_recovery!() {
conditions!(HasSummonedSecondVines) = true;
}
} else {
// Summon first vines when reach threshold of health
controller.push_basic_input(InputKind::Ability(1));
if matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.combat_state.conditions
[ActionStateConditions::ConditionHasSummonedFirstVines as usize] = true;
use_first_vines!();
if is_in_vines_recovery!() {
conditions!(HasSummonedFirstVines) = 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
// breathing for more than short limit
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));
if timers!(SinceFirebreath) < FIREBREATH_COOLDOWN {
use_pumpkin!();
} else {
let randomise = rng.gen_range(1..=3);
let randomise = rng.gen_range(1..=2);
match randomise {
1 => controller.push_basic_input(InputKind::Secondary), /* start fire
* breath */
_ => controller.push_basic_input(InputKind::Ability(0)), /* close range
* pumpkin
* _ => controller.push_basic_input(InputKind::Primary), // scythe */
1 => use_fire_breath!(),
_ => use_pumpkin!(),
}
}
} else if attack_data.angle < 60.0 {
// Scythe them if they're in range and angle
controller.push_basic_input(InputKind::Primary);
use_scythe!();
}
} else if attack_data.dist_sqrd < FIRE_BREATH_RANGE.powi(2) {
if line_of_sight_with_target() {
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32(FIRE_BREATH_TIME))
{
// Keep breathing fire if close enough, can see target, and have not been
// breathing for more than upper limit
controller.push_basic_input(InputKind::Secondary);
} else if attack_data.angle < 30.0
&& agent.combat_state.timers[ActionStateTimers::TimerFire as usize]
> 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));
}
}
// mid range (with line of sight)
else if attack_data.dist_sqrd < FIREBREATH_RANGE.powi(2) && line_of_sight_with_target() {
if is_using_firebreath!(FIREBREATH_TIME) {
use_fire_breath!()
} else if attack_data.angle < 30.0 && timers!(SinceFirebreath) > FIREBREATH_COOLDOWN {
use_fire_breath!()
} else if timers!(SinceCloseMixup) > CLOSE_MIXUP_COOLDOWN {
use_pumpkin!();
}
} 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
controller.push_basic_input(InputKind::Ability(0));
}
// long range (with line of sight)
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
self.path_toward_target(
agent,
controller,
tgt_data.pos.0,
read_data,
Path::Partial,
None,
);
// closing gap
if !(attack_data.dist_sqrd < (attack_data.body_dist + 0.4 * MELEE_RANGE).powi(2)) {
move_to_target!();
}
}
pub fn handle_frostgigas_attack(

View File

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