mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
woodgolem broad update; minor fix for harvester mixup attack
This commit is contained in:
parent
557f2ae53f
commit
9187c37ab1
@ -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,
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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),
|
||||||
|
])),
|
||||||
|
]))
|
||||||
]
|
]
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user