mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Tidal warrior AI.
This commit is contained in:
parent
c81e1534f7
commit
ac2f097d80
@ -108,7 +108,7 @@
|
||||
secondary: "common.abilities.custom.wendigomagic.singlestrike",
|
||||
abilities: [],
|
||||
),
|
||||
Custom("Tidal Claws"): (
|
||||
Custom("Tidal Warrior"): (
|
||||
primary: "common.abilities.custom.tidalwarrior.pincer",
|
||||
secondary: "common.abilities.custom.tidalwarrior.scuttle",
|
||||
abilities: [
|
||||
|
@ -10,7 +10,7 @@ BasicBeam(
|
||||
kind: Wet,
|
||||
dur_secs: 15.0,
|
||||
strength: Value(4.5),
|
||||
chance: 0.25,
|
||||
chance: 1.0,
|
||||
))),
|
||||
energy_regen: 0,
|
||||
energy_drain: 0,
|
||||
|
@ -14,7 +14,7 @@ DashMelee(
|
||||
charge_duration: 2.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.5,
|
||||
charge_through: false,
|
||||
charge_through: true,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
)
|
||||
|
@ -1,18 +1,14 @@
|
||||
BasicMelee(
|
||||
energy_cost: 0,
|
||||
buildup_duration: 0.3,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.6,
|
||||
base_damage: 50.0,
|
||||
base_poise_damage: 100.0,
|
||||
knockback: ( strength: 50.0, direction: Towards),
|
||||
range: 5.0,
|
||||
max_angle: 60.0,
|
||||
damage_effect: Some(Buff((
|
||||
kind: Crippled,
|
||||
dur_secs: 15.0,
|
||||
strength: Value(0.5),
|
||||
chance: 1.0,
|
||||
))),
|
||||
damage_kind: Slashing,
|
||||
BasicSummon(
|
||||
buildup_duration: 0.5,
|
||||
cast_duration: 1.0,
|
||||
recover_duration: 0.5,
|
||||
summon_amount: 6,
|
||||
summon_info: (
|
||||
// If this is still HaniwaSentry, code reviewers open a comment. I'll know what to do.
|
||||
body: Object(HaniwaSentry),
|
||||
scale: None,
|
||||
health_scaling: 20,
|
||||
loadout_config: None,
|
||||
skillset_config: None,
|
||||
),
|
||||
)
|
@ -15,5 +15,5 @@ ItemDef(
|
||||
)),
|
||||
quality: Low,
|
||||
tags: [],
|
||||
ability_spec: Some(Custom("Tidal Claws")),
|
||||
ability_spec: Some(Custom("Tidal Warrior")),
|
||||
)
|
@ -235,14 +235,9 @@ impl CharacterBehavior for Data {
|
||||
handle_orientation(data, &mut update, 0.4 * self.static_data.ori_modifier);
|
||||
|
||||
// Forward movement
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Forward {
|
||||
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
|
||||
strength: self.static_data.stage_data[stage_index].forward_movement,
|
||||
},
|
||||
0.3,
|
||||
);
|
||||
});
|
||||
|
||||
// Swings
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
|
@ -106,14 +106,9 @@ impl CharacterBehavior for Data {
|
||||
.min(1.0);
|
||||
|
||||
handle_orientation(data, &mut update, 0.6);
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Forward {
|
||||
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
|
||||
strength: self.static_data.forward_speed * charge_frac.sqrt(),
|
||||
},
|
||||
0.1,
|
||||
);
|
||||
});
|
||||
|
||||
// This logic basically just decides if a charge should end, and prevents the
|
||||
// character state spamming attacks while checking if it has hit something
|
||||
|
@ -86,17 +86,12 @@ impl CharacterBehavior for Data {
|
||||
let progress = 1.0
|
||||
- self.timer.as_secs_f32()
|
||||
/ self.static_data.movement_duration.as_secs_f32();
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Leap {
|
||||
handle_forced_movement(data, &mut update, ForcedMovement::Leap {
|
||||
vertical: self.static_data.vertical_leap_strength,
|
||||
forward: self.static_data.forward_leap_strength,
|
||||
progress,
|
||||
direction: MovementDirection::Look,
|
||||
},
|
||||
0.15,
|
||||
);
|
||||
});
|
||||
|
||||
// Increment duration
|
||||
// If we were to set a timeout for state, this would be
|
||||
|
@ -78,19 +78,14 @@ impl CharacterBehavior for Data {
|
||||
},
|
||||
StageSection::Movement => {
|
||||
// Update velocity
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Forward {
|
||||
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
|
||||
strength: self.static_data.roll_strength
|
||||
* ((1.0
|
||||
- self.timer.as_secs_f32()
|
||||
/ self.static_data.movement_duration.as_secs_f32())
|
||||
/ 2.0
|
||||
+ 0.5),
|
||||
},
|
||||
0.0,
|
||||
);
|
||||
});
|
||||
|
||||
if self.timer < self.static_data.movement_duration {
|
||||
// Movement
|
||||
|
@ -167,14 +167,9 @@ impl CharacterBehavior for Data {
|
||||
self.static_data.movement_behavior,
|
||||
MovementBehavior::ForwardGround
|
||||
) {
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Forward {
|
||||
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
|
||||
strength: self.static_data.forward_speed,
|
||||
},
|
||||
0.1,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Swings
|
||||
|
@ -263,21 +263,15 @@ fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
}
|
||||
|
||||
/// Handles forced movement
|
||||
pub fn handle_forced_movement(
|
||||
data: &JoinData,
|
||||
update: &mut StateUpdate,
|
||||
movement: ForcedMovement,
|
||||
efficiency: f32,
|
||||
) {
|
||||
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
|
||||
|
||||
pub fn handle_forced_movement(data: &JoinData, update: &mut StateUpdate, movement: ForcedMovement) {
|
||||
match movement {
|
||||
ForcedMovement::Forward { strength } => {
|
||||
let strength = strength * data.stats.move_speed_modifier * data.stats.friction_modifier;
|
||||
if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) {
|
||||
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||
* accel
|
||||
* (data.inputs.move_dir + Vec2::from(update.ori) * strength)
|
||||
* efficiency;
|
||||
* (data.inputs.move_dir + Vec2::from(update.ori))
|
||||
* strength;
|
||||
}
|
||||
},
|
||||
ForcedMovement::Leap {
|
||||
|
@ -87,6 +87,10 @@ struct AttackData {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl AttackData {
|
||||
fn in_min_range(&self) -> bool { self.dist_sqrd < self.min_attack_dist.powi(2) }
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum Tactic {
|
||||
Melee,
|
||||
@ -113,6 +117,7 @@ pub enum Tactic {
|
||||
BirdLargeFire,
|
||||
Minotaur,
|
||||
ClayGolem,
|
||||
TidalWarrior,
|
||||
}
|
||||
|
||||
#[derive(SystemData)]
|
||||
@ -1601,6 +1606,7 @@ impl<'a> AgentData<'a> {
|
||||
"Mindflayer" => Tactic::Mindflayer,
|
||||
"Minotaur" => Tactic::Minotaur,
|
||||
"Clay Golem" => Tactic::ClayGolem,
|
||||
"Tidal Warrior" => Tactic::TidalWarrior,
|
||||
_ => Tactic::Melee,
|
||||
},
|
||||
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
|
||||
@ -1843,6 +1849,13 @@ impl<'a> AgentData<'a> {
|
||||
&tgt_data,
|
||||
&read_data,
|
||||
),
|
||||
Tactic::TidalWarrior => self.handle_tidal_warrior_attack(
|
||||
agent,
|
||||
controller,
|
||||
&attack_data,
|
||||
&tgt_data,
|
||||
&read_data,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1854,7 +1867,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
|
||||
if attack_data.in_min_range() && attack_data.angle < 45.0 {
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Primary));
|
||||
@ -1883,7 +1896,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
|
||||
if attack_data.in_min_range() && attack_data.angle < 45.0 {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
if agent.action_state.timer > 6.0 {
|
||||
controller
|
||||
@ -1932,7 +1945,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
|
||||
if attack_data.in_min_range() && attack_data.angle < 45.0 {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
if agent.action_state.timer > 4.0 {
|
||||
controller
|
||||
@ -2004,7 +2017,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
|
||||
if attack_data.in_min_range() && attack_data.angle < 45.0 {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
if self
|
||||
.skill_set
|
||||
@ -2226,9 +2239,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
||||
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2)
|
||||
{
|
||||
if self.body.map(|b| b.is_humanoid()).unwrap_or(false) && attack_data.in_min_range() {
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Roll));
|
||||
@ -2325,7 +2336,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 90.0 {
|
||||
if attack_data.in_min_range() && attack_data.angle < 90.0 {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
controller
|
||||
.actions
|
||||
@ -2371,8 +2382,7 @@ impl<'a> AgentData<'a> {
|
||||
radius: u32,
|
||||
circle_time: u32,
|
||||
) {
|
||||
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && thread_rng().gen_bool(0.5)
|
||||
{
|
||||
if attack_data.in_min_range() && thread_rng().gen_bool(0.5) {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
controller
|
||||
.actions
|
||||
@ -2679,7 +2689,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) {
|
||||
if attack_data.angle < 90.0 && attack_data.in_min_range() {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
if agent.action_state.timer < 2.0 {
|
||||
controller
|
||||
@ -2762,7 +2772,7 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) {
|
||||
if attack_data.angle < 90.0 && attack_data.in_min_range() {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
controller
|
||||
.actions
|
||||
@ -3190,7 +3200,7 @@ impl<'a> AgentData<'a> {
|
||||
agent.action_state.timer += read_data.dt.0;
|
||||
} else if agent.action_state.timer < 6.0
|
||||
&& attack_data.angle < 90.0
|
||||
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2)
|
||||
&& attack_data.in_min_range()
|
||||
{
|
||||
// Triplestrike
|
||||
controller
|
||||
@ -3355,6 +3365,88 @@ impl<'a> AgentData<'a> {
|
||||
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
|
||||
}
|
||||
|
||||
fn handle_tidal_warrior_attack(
|
||||
&self,
|
||||
agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
attack_data: &AttackData,
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
const SCUTTLE_RANGE: f32 = 40.0;
|
||||
const BUBBLE_RANGE: f32 = 20.0;
|
||||
const MINION_SUMMON_THRESHOLD: f32 = 0.20;
|
||||
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
|
||||
// Sets counter at start of combat, using `condition` to keep track of whether
|
||||
// it was already intitialized
|
||||
if !agent.action_state.condition {
|
||||
agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD;
|
||||
agent.action_state.condition = true;
|
||||
}
|
||||
|
||||
if agent.action_state.counter > health_fraction {
|
||||
// Summon minions at particular thresholds of health
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Ability(1)));
|
||||
|
||||
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
|
||||
{
|
||||
agent.action_state.counter -= MINION_SUMMON_THRESHOLD;
|
||||
}
|
||||
} else if attack_data.dist_sqrd < SCUTTLE_RANGE.powi(2) {
|
||||
if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
|
||||
{
|
||||
// Keep scuttling if already in dash melee and not in recover
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Secondary));
|
||||
} else if attack_data.dist_sqrd < BUBBLE_RANGE.powi(2) {
|
||||
if matches!(self.char_state, CharacterState::BasicBeam(c) if !matches!(c.stage_section, StageSection::Recover) && c.timer < Duration::from_secs(10))
|
||||
{
|
||||
// Keep shooting bubbles at them if already in basic beam and not in recover and
|
||||
// have not been bubbling too long
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Ability(0)));
|
||||
} else if attack_data.in_min_range() && attack_data.angle < 60.0 {
|
||||
// Pincer them if they're in range and angle
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Primary));
|
||||
} else if attack_data.angle < 30.0
|
||||
&& can_see_tgt(
|
||||
&*read_data.terrain,
|
||||
self.pos,
|
||||
tgt_data.pos,
|
||||
attack_data.dist_sqrd,
|
||||
)
|
||||
{
|
||||
// Start bubbling them if not close enough to do something else and in angle and
|
||||
// can see target
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Ability(0)));
|
||||
}
|
||||
} else if attack_data.angle < 90.0
|
||||
&& can_see_tgt(
|
||||
&*read_data.terrain,
|
||||
self.pos,
|
||||
tgt_data.pos,
|
||||
attack_data.dist_sqrd,
|
||||
)
|
||||
{
|
||||
// Start scuttling if not close enough to do something else and in angle and can
|
||||
// see target
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Secondary));
|
||||
}
|
||||
}
|
||||
// Always attempt to path towards target
|
||||
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
|
||||
}
|
||||
|
||||
fn follow(
|
||||
&self,
|
||||
agent: &mut Agent,
|
||||
|
Loading…
Reference in New Issue
Block a user