woodgolem broad update; minor fix for harvester mixup attack

This commit is contained in:
horblegorble 2024-06-21 02:36:04 +10:00
parent 557f2ae53f
commit 9187c37ab1
9 changed files with 163 additions and 70 deletions

View File

@ -6,7 +6,7 @@ BasicMelee(
recover_duration: 1.0, recover_duration: 1.0,
melee_constructor: ( melee_constructor: (
kind: Slash( kind: Slash(
damage: 13.0, damage: 15.0,
poise: 10.0, poise: 10.0,
knockback: 10.0, knockback: 10.0,
energy_regen: 0.0, energy_regen: 0.0,

View File

@ -1,9 +1,9 @@
Shockwave( Shockwave(
energy_cost: 0, energy_cost: 0,
buildup_duration: 3.0, buildup_duration: 1.2,
swing_duration: 0.12, swing_duration: 0.12,
recover_duration: 2.4, recover_duration: 1.5,
damage: 60.0, damage: 22.0,
poise_damage: 30, poise_damage: 30,
knockback: (strength: 30.0, direction: TowardsUp), knockback: (strength: 30.0, direction: TowardsUp),
shockwave_angle: 90.0, shockwave_angle: 90.0,
@ -14,7 +14,7 @@ Shockwave(
move_efficiency: 0.0, move_efficiency: 0.0,
damage_kind: Crushing, damage_kind: Crushing,
specifier: Ground, specifier: Ground,
ori_rate: 1.0, ori_rate: 0.9,
timing: PostBuildup, timing: PostBuildup,
emit_outcome: true, emit_outcome: true,
) )

View File

@ -1,18 +1,18 @@
BasicMelee( BasicMelee(
energy_cost: 0, energy_cost: 0,
buildup_duration: 0.9, buildup_duration: 1.2,
swing_duration: 0.3, swing_duration: 0.3,
hit_timing: 0.6, hit_timing: 0.6,
recover_duration: 0.6, recover_duration: 1.2,
melee_constructor: ( melee_constructor: (
kind: Bash( kind: Bash(
damage: 45, damage: 18,
poise: 30, poise: 30,
knockback: 20, knockback: 20,
energy_regen: 0, energy_regen: 0,
), ),
range: 7.5, range: 6.0,
angle: 360, angle: 360,
), ),
ori_modifier: 1.0, ori_modifier: 0.75,
) )

View File

@ -1,18 +1,18 @@
BasicMelee( BasicMelee(
energy_cost: 0, energy_cost: 0,
buildup_duration: 1.6, buildup_duration: 1.0,
swing_duration: 0.1, swing_duration: 0.2,
hit_timing: 0.6, hit_timing: 0.6,
recover_duration: 1.0, recover_duration: 1.0,
melee_constructor: ( melee_constructor: (
kind: Bash( kind: Bash(
damage: 30.0, damage: 12.0,
poise: 25.0, poise: 25.0,
knockback: 15.0, knockback: 15.0,
energy_regen: 0.0, energy_regen: 0.0,
), ),
range: 4.0, range: 4.5,
angle: 45.0, angle: 55.0,
), ),
ori_modifier: 0.4, ori_modifier: 0.7,
) )

View File

@ -1,6 +1,16 @@
[ [
// Crafting ingredients (1, Lottery([
(2.4, MultiDrop(Item("common.items.log.wood"), 5, 10)), (0.7, All([
(0.1, MultiDrop(Item("common.items.log.hardwood"), 1, 2)), MultiDrop(Item("common.items.log.wood"), 3, 4),
(0.5, LootTable("common.loot_tables.weapons.components.secondary.sceptre")), MultiDrop(Item("common.items.crafting_ing.twigs"), 1, 2),
Lottery([
(0.6, MultiDrop(Item("common.items.log.bamboo"), 1, 2)),
(0.4, LootTable("common.loot_tables.weapons.components.secondary.sceptre")),
]),
])),
(0.3, All([
MultiDrop(Item("common.items.log.wood"), 5, 7),
MultiDrop(Item("common.items.crafting_ing.twigs"), 3, 5),
])),
]))
] ]

View File

@ -1000,7 +1000,7 @@ impl Body {
}, },
Body::ItemDrop(_) => 1000, Body::ItemDrop(_) => 1000,
Body::Golem(golem) => match golem.species { Body::Golem(golem) => match golem.species {
golem::Species::WoodGolem => 200, golem::Species::WoodGolem => 120,
golem::Species::ClayGolem => 350, golem::Species::ClayGolem => 350,
golem::Species::Gravewarden => 1000, golem::Species::Gravewarden => 1000,
golem::Species::CoralGolem => 550, golem::Species::CoralGolem => 550,

View File

@ -230,7 +230,10 @@ impl Body {
Body::BipedSmall(_) => 3.5, Body::BipedSmall(_) => 3.5,
Body::Object(_) => 2.0, Body::Object(_) => 2.0,
Body::ItemDrop(_) => 2.0, Body::ItemDrop(_) => 2.0,
Body::Golem(_) => 2.0, Body::Golem(golem) => match golem.species {
golem::Species::WoodGolem => 1.2,
_ => 2.0,
},
Body::Theropod(theropod) => match theropod.species { Body::Theropod(theropod) => match theropod.species {
theropod::Species::Archaeos => 2.3, theropod::Species::Archaeos => 2.3,
theropod::Species::Odonto => 2.3, theropod::Species::Odonto => 2.3,

View File

@ -1685,7 +1685,7 @@ impl<'a> AgentData<'a> {
self.handle_mandragora(agent, controller, &attack_data, tgt_data, read_data) self.handle_mandragora(agent, controller, &attack_data, tgt_data, read_data)
}, },
Tactic::WoodGolem => { Tactic::WoodGolem => {
self.handle_wood_golem(agent, controller, &attack_data, tgt_data, read_data) self.handle_wood_golem(agent, controller, &attack_data, tgt_data, read_data, rng)
}, },
Tactic::GnarlingChieftain => self.handle_gnarling_chieftain( Tactic::GnarlingChieftain => self.handle_gnarling_chieftain(
agent, agent,

View File

@ -4769,12 +4769,13 @@ impl<'a> AgentData<'a> {
// --- setup --- // --- setup ---
// hard-coded attack values
const SCYTHE_RANGE: f32 = 5.0;
// behaviour parameters // 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 FIREBREATH_RANGE: f32 = 20.0; // hard coded from firebreath attack const FIREBREATH_RANGE: f32 = 20.0;
const FIREBREATH_TIME: f32 = 4.0; const FIREBREATH_TIME: f32 = 4.0;
const FIREBREATH_SHORT_TIME: f32 = 2.5; // cutoff sooner at close range const FIREBREATH_SHORT_TIME: f32 = 2.5; // cutoff sooner at close range
const FIREBREATH_COOLDOWN: f32 = 3.0; const FIREBREATH_COOLDOWN: f32 = 3.0;
@ -4813,15 +4814,16 @@ impl<'a> AgentData<'a> {
match self.char_state { match self.char_state {
CharacterState::BasicBeam(_) => { CharacterState::BasicBeam(_) => {
agent.combat_state.timers[ActionStateTimers::Firebreath as usize] = 0.0; agent.combat_state.timers[ActionStateTimers::Firebreath as usize] = 0.0;
agent.combat_state.timers[ActionStateTimers::CloseMixup as usize] = 0.0;
}, },
CharacterState::BasicRanged(_) => { CharacterState::BasicRanged(_) => {
agent.combat_state.timers[ActionStateTimers::CloseMixup as usize] = 0.0;
agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] = 0.0; agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] = 0.0;
agent.combat_state.timers[ActionStateTimers::CloseMixup as usize] = 0.0;
}, },
_ => { _ => {
agent.combat_state.timers[ActionStateTimers::Firebreath as usize] += read_data.dt.0; agent.combat_state.timers[ActionStateTimers::Firebreath as usize] += read_data.dt.0;
agent.combat_state.timers[ActionStateTimers::CloseMixup as usize] += read_data.dt.0;
agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] += read_data.dt.0; agent.combat_state.timers[ActionStateTimers::FarPumpkin as usize] += read_data.dt.0;
agent.combat_state.timers[ActionStateTimers::CloseMixup as usize] += read_data.dt.0;
}, },
} }
@ -4852,7 +4854,7 @@ impl<'a> AgentData<'a> {
} }
// close range // close range
// 0.75 multiplier tuned to start melee attack while suitably in range // 0.75 multiplier tuned to start melee attack while suitably in range
else if attack_data.dist_sqrd < (attack_data.body_dist + 0.75 * MELEE_RANGE).powi(2) { else if attack_data.dist_sqrd < (attack_data.body_dist + 0.75 * SCYTHE_RANGE).powi(2) {
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32(FIREBREATH_SHORT_TIME)) if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs_f32(FIREBREATH_SHORT_TIME))
{ {
// keep using firebreath under short time limit // keep using firebreath under short time limit
@ -4911,7 +4913,7 @@ impl<'a> AgentData<'a> {
// closing gap // closing gap
// 0.4 multiplier tuned to get comfortably in melee range // 0.4 multiplier tuned to get comfortably in melee range
if attack_data.dist_sqrd > (attack_data.body_dist + 0.4 * MELEE_RANGE).powi(2) { if attack_data.dist_sqrd > (attack_data.body_dist + 0.4 * SCYTHE_RANGE).powi(2) {
self.path_toward_target( self.path_toward_target(
agent, agent,
controller, controller,
@ -5841,59 +5843,123 @@ impl<'a> AgentData<'a> {
attack_data: &AttackData, attack_data: &AttackData,
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
rng: &mut impl Rng,
) { ) {
const SHOCKWAVE_RANGE: f32 = 25.0; // === reference ===
const SHOCKWAVE_WAIT_TIME: f32 = 7.5;
const SPIN_WAIT_TIME: f32 = 3.0;
// Inputs:
// Primary: strike
// Secondary: spin
// Abilities:
// 0: shockwave
// === setup ===
// hard-coded attack vlues
const STRIKE_RANGE: f32 = 4.5;
const STRIKE_AIM_ANGLE: f32 = 55.0 * 0.7;
const SPIN_RANGE: f32 = 6.0;
const SHOCKWAVE_AIM_ANGLE: f32 = 45.0 * 0.7;
// behaviour parameters
const SPIN_COOLDOWN: f32 = 1.5;
const SHOCKWAVE_COOLDOWN: f32 = 4.5;
const SHOCKWAVE_MIN_RANGE: f32 = 8.0;
const SHOCKWAVE_MAX_RANGE: f32 = 25.0;
const MIXUP_COOLDOWN: f32 = 3.0;
// timers
enum ActionStateTimers { enum ActionStateTimers {
TimerSpinWait = 0, Spin = 0,
TimerShockwaveWait, Shockwave,
Mixup,
} }
// After spinning, reset timer // re-used comparisons
// hard-coded multiplier tuned to start attack while suitably in range
let is_in_spin_range =
attack_data.dist_sqrd < (attack_data.body_dist + (0.85 * SPIN_RANGE).powi(2));
let is_in_strike_range =
attack_data.dist_sqrd < (attack_data.body_dist + (0.85 * STRIKE_RANGE).powi(2));
let is_in_strike_angle = attack_data.angle < STRIKE_AIM_ANGLE;
// === main ===
// --- timers ---
// spin
let current_input = self.char_state.ability_info().map(|ai| ai.input); let current_input = self.char_state.ability_info().map(|ai| ai.input);
if matches!(current_input, Some(InputKind::Secondary)) { if matches!(current_input, Some(InputKind::Secondary)) {
agent.combat_state.timers[ActionStateTimers::TimerSpinWait as usize] = 0.0; // reset when spinning
agent.combat_state.timers[ActionStateTimers::Spin as usize] = 0.0;
agent.combat_state.timers[ActionStateTimers::Mixup as usize] = 0.0;
} else if is_in_spin_range && !(is_in_strike_range && is_in_strike_angle) {
// increment within spin range and not in strike range + angle
agent.combat_state.timers[ActionStateTimers::Spin as usize] += read_data.dt.0;
} else {
// relax towards zero otherwise
agent.combat_state.timers[ActionStateTimers::Spin as usize] =
(agent.combat_state.timers[ActionStateTimers::Spin as usize]
- 0.2 * read_data.dt.0)
.max(0.0);
}
// shockwave
if matches!(self.char_state, CharacterState::Shockwave(_)) {
// reset when using shockwave
agent.combat_state.timers[ActionStateTimers::Shockwave as usize] = 0.0;
agent.combat_state.timers[ActionStateTimers::Mixup as usize] = 0.0;
} else {
// increment otherwise
agent.combat_state.timers[ActionStateTimers::Shockwave as usize] += read_data.dt.0;
}
// mixup
if is_in_strike_range && is_in_strike_angle {
// increment within strike range and angle
agent.combat_state.timers[ActionStateTimers::Mixup as usize] += read_data.dt.0;
} else {
// relax towards zero otherwise
agent.combat_state.timers[ActionStateTimers::Mixup as usize] =
(agent.combat_state.timers[ActionStateTimers::Mixup as usize]
- 0.5 * read_data.dt.0)
.max(0.0);
} }
if attack_data.in_min_range() { // --- attacks ---
// If in minimum range // strike range
if agent.combat_state.timers[ActionStateTimers::TimerSpinWait as usize] > SPIN_WAIT_TIME if is_in_strike_range {
{ if agent.combat_state.timers[ActionStateTimers::Mixup as usize] > MIXUP_COOLDOWN {
// randomly mix up
let randomise = 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 if is_in_strike_angle {
// use strike
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) {
if agent.combat_state.timers[ActionStateTimers::Spin as usize] > SPIN_COOLDOWN {
// If it's been too long since able to hit target, spin // If it's been too long since able to hit target, spin
controller.push_basic_input(InputKind::Secondary); controller.push_basic_input(InputKind::Secondary);
} else if attack_data.angle < 30.0 {
// Else if in angle to strike, strike
controller.push_basic_input(InputKind::Primary);
} else {
// Else increment spin timer
agent.combat_state.timers[ActionStateTimers::TimerSpinWait as usize] +=
read_data.dt.0;
// If not in angle, apply slight movement so golem orients itself correctly
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
.xy()
.try_normalized()
.unwrap_or_else(Vec2::zero)
* 0.01;
} }
} else { }
// Else if too far for melee // shockwave range
if attack_data.dist_sqrd < SHOCKWAVE_RANGE.powi(2) && attack_data.angle < 45.0 { else if attack_data.dist_sqrd > SHOCKWAVE_MIN_RANGE.powi(2)
// Shockwave if close enough and haven't shockwaved too recently && attack_data.dist_sqrd < SHOCKWAVE_MAX_RANGE.powi(2)
if agent.combat_state.timers[ActionStateTimers::TimerSpinWait as usize] && attack_data.angle < SHOCKWAVE_AIM_ANGLE
> SHOCKWAVE_WAIT_TIME && agent.combat_state.timers[ActionStateTimers::Shockwave as usize] > SHOCKWAVE_COOLDOWN
{ {
controller.push_basic_input(InputKind::Ability(0)); // Shockwave if close enough and haven't shockwaved too recently
} controller.push_basic_input(InputKind::Ability(0));
if matches!(self.char_state, CharacterState::Shockwave(_)) { }
agent.combat_state.timers[ActionStateTimers::TimerShockwaveWait as usize] = 0.0;
} else { // --- movement ---
agent.combat_state.timers[ActionStateTimers::TimerShockwaveWait as usize] += // closing gap
read_data.dt.0; // hard-coded multiplier tuned to get comfortably in range, while giving player
} // some room to breathe
} if attack_data.dist_sqrd > (attack_data.body_dist + (0.75 * STRIKE_RANGE).powi(2)) {
// And always try to path towards target
self.path_toward_target( self.path_toward_target(
agent, agent,
controller, controller,
@ -5903,6 +5969,20 @@ impl<'a> AgentData<'a> {
None, None,
); );
} }
// closing angle
else if attack_data.angle > 0.0 {
// If not in angle, apply slight movement so golem orients itself correctly
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
.xy()
.try_normalized()
.unwrap_or_else(Vec2::zero)
* 0.001;
// // an attempt modifying look direciton, rather than using
// movement: let delta = (tgt_data.pos.0 -
// self.pos.0).try_normalized().unwrap_or_else(Vec3::zero);
// controller.inputs.look_dir =
// controller.inputs.look_dir.slerped_to(Dir::new(delta), 0.8);
}
} }
pub fn handle_gnarling_chieftain( pub fn handle_gnarling_chieftain(