Cleaving stance AI

This commit is contained in:
Sam 2022-10-09 03:17:29 -04:00
parent 5358016ba0
commit df9ef691fd
37 changed files with 203 additions and 90 deletions

View File

@ -13,7 +13,7 @@ LeapMelee(
),
range: 4.5,
angle: 30.0,
multi_target: true,
multi_target: Some(Normal),
),
forward_leap_strength: 20.0,
vertical_leap_strength: 8.0,

View File

@ -11,7 +11,7 @@ SpinMelee(
),
range: 3.5,
angle: 360.0,
multi_target: true,
multi_target: Some(Normal),
),
energy_cost: 10.0,
is_infinite: true,

View File

@ -13,7 +13,7 @@ LeapMelee(
),
range: 4.5,
angle: 180.0,
multi_target: true,
multi_target: Some(Normal),
),
forward_leap_strength: 40.0,
vertical_leap_strength: 7.5,

View File

@ -12,7 +12,7 @@ BasicMelee(
),
range: 4.0,
angle: 45.0,
multi_target: true,
multi_target: Some(Normal),
),
ori_modifier: 1.0,
)

View File

@ -12,7 +12,7 @@ BasicMelee(
),
range: 4.0,
angle: 60.0,
multi_target: true,
multi_target: Some(Normal),
),
ori_modifier: 1.0,
)

View File

@ -10,7 +10,7 @@ SpinMelee(
),
range: 16.0,
angle: 360.0,
multi_target: true,
multi_target: Some(Normal),
),
energy_cost: 0.0,
is_infinite: true,

View File

@ -21,7 +21,7 @@ DashMelee(
strength: DamageFraction(0.3),
chance: 0.25,
))),
multi_target: true,
multi_target: Some(Normal),
),
energy_drain: 0,
forward_speed: 5.0,

View File

@ -16,7 +16,7 @@ ChargedMelee(
)),
range: 5.0,
angle: 45.0,
multi_target: true,
multi_target: Some(Normal),
),
charge_duration: 4.5,
swing_duration: 0.1,

View File

@ -18,7 +18,7 @@ BasicMelee(
strength: Value(0.5),
chance: 1.0,
))),
multi_target: true,
multi_target: Some(Normal),
),
ori_modifier: 1.0,
)

View File

@ -16,7 +16,7 @@ ChargedMelee(
)),
range: 6.0,
angle: 90.0,
multi_target: true,
multi_target: Some(Normal),
),
charge_duration: 3.2,
swing_duration: 0.7,

View File

@ -13,7 +13,7 @@ LeapMelee(
),
range: 6.75,
angle: 180.0,
multi_target: true,
multi_target: Some(Normal),
),
forward_leap_strength: 45.0,
vertical_leap_strength: 10.0,

View File

@ -13,7 +13,7 @@ LeapMelee(
),
range: 4.5,
angle: 180.0,
multi_target: true,
multi_target: Some(Normal),
),
forward_leap_strength: 20.0,
vertical_leap_strength: 5.0,

View File

@ -11,7 +11,7 @@ SpinMelee(
),
range: 7.5,
angle: 360.0,
multi_target: true,
multi_target: Some(Normal),
),
energy_cost: 0,
is_infinite: false,

View File

@ -12,7 +12,7 @@ BasicMelee(
),
range: 5.0,
angle: 60.0,
multi_target: true,
multi_target: Some(Normal),
),
ori_modifier: 1.0,
)

View File

@ -15,7 +15,7 @@ DashMelee(
)),
range: 5.0,
angle: 90.0,
multi_target: true,
multi_target: Some(Normal),
),
energy_drain: 0,
forward_speed: 10.0,

View File

@ -11,7 +11,7 @@ SpinMelee(
),
range: 3.5,
angle: 360.0,
multi_target: true,
multi_target: Some(Normal),
),
energy_cost: 0,
is_infinite: true,

View File

@ -12,7 +12,7 @@ BasicMelee(
),
range: 4.0,
angle: 20.0,
multi_target: true,
multi_target: Some(Normal),
),
ori_modifier: 1.0,
)

View File

@ -16,7 +16,7 @@ ChargedMelee(
)),
range: 3.5,
angle: 30.0,
multi_target: true,
multi_target: Some(Normal),
),
charge_duration: 1.0,
swing_duration: 0.12,

View File

@ -13,7 +13,7 @@ LeapMelee(
),
range: 4.5,
angle: 360.0,
multi_target: true,
multi_target: Some(Normal),
),
forward_leap_strength: 20.0,
vertical_leap_strength: 8.0,

View File

@ -6,11 +6,11 @@ ComboMelee2(
damage: 6,
poise: 0,
knockback: 0,
energy_regen: 3,
energy_regen: 7,
),
range: 3.0,
angle: 45.0,
multi_target: true,
multi_target: Some(Normal),
),
buildup_duration: 0.3,
swing_duration: 0.1,
@ -29,11 +29,11 @@ ComboMelee2(
damage: 8,
poise: 0,
knockback: 0,
energy_regen: 5,
energy_regen: 8,
),
range: 3.0,
angle: 45.0,
multi_target: true,
multi_target: Some(Normal),
),
buildup_duration: 0.3,
swing_duration: 0.15,

View File

@ -1,6 +1,6 @@
DiveMelee(
energy_cost: 20,
vertical_speed: 15,
vertical_speed: 10,
movement_duration: 5,
swing_duration: 0.1,
recover_duration: 0.3,
@ -19,7 +19,7 @@ DiveMelee(
)),
range: 6.0,
angle: 15.0,
multi_target: true,
multi_target: Some(Normal),
),
meta: (
kind: Some(Sword(Cleaving)),

View File

@ -12,7 +12,7 @@ FinisherMelee(
),
range: 3.0,
angle: 15.0,
damage_effect: Some(ResetMelee),
multi_target: Some(Scaling(0.5)),
),
scaling: Some((
target: Buff,

View File

@ -10,7 +10,7 @@ ComboMelee2(
),
range: 6.0,
angle: 360.0,
multi_target: true,
multi_target: Some(Normal),
),
buildup_duration: 0.1,
swing_duration: 0.3,

View File

@ -10,7 +10,7 @@ ComboMelee2(
),
range: 8.0,
angle: 10.0,
multi_target: true,
multi_target: Some(Normal),
),
buildup_duration: 0.3,
swing_duration: 0.1,

View File

@ -430,18 +430,6 @@ impl Attack {
});
}
},
CombatEffect::ResetMelee => {
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
if target
.health
.map_or(false, |h| accumulated_damage > h.current())
{
emit(ServerEvent::ResetMelee {
entity: attacker_entity,
});
}
}
},
CombatEffect::BuildupsVulnerable => {
if target.char_state.map_or(false, |cs| {
matches!(cs.stage_section(), Some(StageSection::Buildup))
@ -604,18 +592,6 @@ impl Attack {
});
}
},
CombatEffect::ResetMelee => {
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
if target
.health
.map_or(false, |h| accumulated_damage > h.current())
{
emit(ServerEvent::ResetMelee {
entity: attacker_entity,
});
}
}
},
// Only has an effect when attached to a damage
CombatEffect::BuildupsVulnerable => {},
}
@ -749,8 +725,6 @@ pub enum CombatEffect {
Lifesteal(f32),
Poise(f32),
Combo(i32),
// If the attack kills the target, reset the melee attack
ResetMelee,
// If the attack hits the target while they are in the buildup portion of a character state,
// deal double damage Only has an effect when attached to a damage, otherwise does nothing
// if only attached to the attack TODO: Maybe try to make it do something if tied to

View File

@ -732,7 +732,7 @@ impl Default for CharacterAbility {
scaled: None,
range: 3.5,
angle: 15.0,
multi_target: false,
multi_target: None,
damage_effect: None,
},
ori_modifier: 1.0,

View File

@ -1,6 +1,6 @@
use crate::DamageSource;
#[cfg(not(target_arch = "wasm32"))]
use crate::{comp, consts::HP_PER_LEVEL};
use crate::{uid::Uid, DamageSource};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
@ -193,6 +193,12 @@ impl Health {
.map(|(damage_contrib, (damage, _))| (damage_contrib, damage))
}
pub fn recent_damagers(&self) -> impl Iterator<Item = (Uid, Time)> + '_ {
self.damage_contributors
.iter()
.map(|(contrib, (_, time))| (contrib.uid(), *time))
}
pub fn should_die(&self) -> bool { self.current == 0 }
pub fn kill(&mut self) { self.current = 0; }

View File

@ -20,10 +20,16 @@ pub struct Melee {
pub max_angle: f32,
pub applied: bool,
pub hit_count: u32,
pub multi_target: bool,
pub multi_target: Option<MultiTarget>,
pub break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MultiTarget {
Normal,
Scaling(f32),
}
impl Melee {
#[must_use]
pub fn with_block_breaking(
@ -47,8 +53,7 @@ pub struct MeleeConstructor {
pub scaled: Option<MeleeConstructorKind>,
pub range: f32,
pub angle: f32,
#[serde(default)]
pub multi_target: bool,
pub multi_target: Option<MultiTarget>,
pub damage_effect: Option<CombatEffect>,
}

View File

@ -226,9 +226,6 @@ pub enum ServerEvent {
entity: EcsEntity,
update: comp::MapMarkerChange,
},
ResetMelee {
entity: EcsEntity,
},
}
pub struct EventBus<E> {

View File

@ -2,7 +2,7 @@ use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{
character_state::OutputEvents,
tool::{Stats, ToolKind},
tool::{Stats, ToolKind}, melee::MultiTarget,
CharacterState, Melee, StateUpdate,
},
states::{
@ -279,7 +279,7 @@ impl CharacterBehavior for Data {
hit_count: 0,
// TODO: Evaluate if we want to leave this true. State will be removed at
// some point anyways and this does preserve behavior
multi_target: true,
multi_target: Some(MultiTarget::Normal),
break_block: data
.inputs
.break_block_pos

View File

@ -278,7 +278,7 @@ fn create_test_melee(static_data: StaticData) -> Melee {
scaled: None,
range: static_data.melee_constructor.range,
angle: static_data.melee_constructor.angle,
multi_target: false,
multi_target: None,
damage_effect: None,
};
melee.create_melee((0.0, 0.0), 0.0)

View File

@ -74,6 +74,8 @@ impl CharacterBehavior for Data {
let crit_data = get_crit_data(data, self.static_data.ability_info);
let buff_strength = get_buff_strength(data, self.static_data.ability_info);
let scaling = self.max_vertical_speed / self.static_data.vertical_speed;
// TODO: Remove when server authoritative physics
let scaling = scaling.max(2.0);
data.updater.insert(
data.entity,

View File

@ -2,6 +2,7 @@ use common::{
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
comp::{
agent::{Sound, SoundKind},
melee::MultiTarget,
Alignment, Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori,
Player, Pos, Scale, Stats,
},
@ -116,7 +117,7 @@ impl<'a> System<'a> for Sys {
{
// Unless the melee attack can hit multiple targets, stop the attack if it has
// already hit 1 target
if !melee_attack.multi_target && melee_attack.hit_count > 0 {
if melee_attack.multi_target.is_none() && melee_attack.hit_count > 0 {
break;
}
@ -197,12 +198,19 @@ impl<'a> System<'a> for Sys {
target_group,
};
let strength =
if let Some(MultiTarget::Scaling(scaling)) = melee_attack.multi_target {
1.0 + melee_attack.hit_count as f32 * scaling
} else {
1.0
};
let is_applied = melee_attack.attack.apply_attack(
attacker_info,
target_info,
dir,
attack_options,
1.0,
strength,
AttackSource::Melee,
*read_data.time,
|e| server_emitter.emit(e),

View File

@ -18,6 +18,7 @@ use common::{
vol::ReadVol,
};
use rand::Rng;
use specs::saveload::MarkerAllocator;
use std::{f32::consts::PI, time::Duration};
use vek::*;
@ -474,16 +475,31 @@ impl<'a> AgentData<'a> {
.map_or(false, |buffs| !buffs.contains(self.buff))
}
}
struct DiveMeleeData {
range: f32,
angle: f32,
energy: f32,
}
impl DiveMeleeData {
fn npc_should_use_hack(&self, agent_data: &AgentData, tgt_data: &TargetData) -> bool {
let dist_sqrd_2d = agent_data.pos.0.xy().distance_squared(tgt_data.pos.0.xy());
agent_data.energy.current() > self.energy
&& agent_data.physics_state.on_ground.is_some()
&& agent_data.pos.0.z >= tgt_data.pos.0.z
&& dist_sqrd_2d > (self.range / 2.0).powi(2)
&& dist_sqrd_2d < (self.range + 5.0).powi(2)
}
}
use ability::SwordStance;
let stance = |stance| match stance {
1 => SwordStance::Offensive,
2 => SwordStance::Defensive,
3 => SwordStance::Mobility,
4 => SwordStance::Crippling,
5 => SwordStance::Cleaving,
6 => SwordStance::Parrying,
7 => SwordStance::Heavy,
8 => SwordStance::Reaching,
5 => SwordStance::Parrying,
6 => SwordStance::Heavy,
7 => SwordStance::Reaching,
8 => SwordStance::Cleaving,
_ => SwordStance::Balanced,
};
if !agent.action_state.initialized {
@ -495,8 +511,12 @@ impl<'a> AgentData<'a> {
.skill_set
.has_skill(Skill::Sword(SwordSkill::CripplingFinisher))
{
// Hack to make cleaving stance come up less often because agents cannot use it
// effectively due to only considering a single target
// Remove when agents properly consider multiple targets
// 4 + rng.gen_range(0..13) / 3
// rng.gen_range(4..9)
4
5
} else if self
.skill_set
.has_skill(Skill::Sword(SwordSkill::OffensiveFinisher))
@ -569,8 +589,8 @@ impl<'a> AgentData<'a> {
set_sword_ability(2, 10);
// Cleaving dive
set_sword_ability(3, 11);
// Offensive finisher
set_sword_ability(4, 2);
// Offensive advance
set_sword_ability(4, 3);
},
SwordStance::Parrying => {
// Parrying combo
@ -1021,8 +1041,6 @@ impl<'a> AgentData<'a> {
if self.energy.current() < DESIRED_ENERGY {
fallback_tactics(agent, controller);
} else if !in_stance(SwordStance::Crippling) {
// controller.push_basic_input(InputKind::Jump);
// agent.action_state.initialized = false;
controller.push_basic_input(InputKind::Ability(0));
} else if CRIPPLING_FINISHER.could_use(attack_data, self)
&& CRIPPLING_FINISHER.use_desirable(tgt_data, self)
@ -1080,7 +1098,121 @@ impl<'a> AgentData<'a> {
}
},
SwordStance::Cleaving => {
fallback_tactics(agent, controller);
// TODO: Rewrite cleaving stance tactics when agents can consider multiple
// targets at once. Remove hack to make cleaving AI appear less frequently above
// when doing so.
const CLEAVING_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0,
max_range: 2.5,
angle: 35.0,
energy: 10.0,
};
const CLEAVING_FINISHER: FinisherMeleeData = FinisherMeleeData {
range: 2.0,
angle: 10.0,
energy: 40.0,
combo: 10,
};
const CLEAVING_SPIN: ComboMeleeData = ComboMeleeData {
min_range: 0.0,
max_range: 5.0,
angle: 360.0,
energy: 15.0,
};
const CLEAVING_DIVE: DiveMeleeData = DiveMeleeData {
range: 5.0,
angle: 10.0,
energy: 20.0,
};
const DESIRED_ENERGY: f32 = 50.0;
// TODO: Remove when agents actually have multiple targets
// Hacky check for multiple nearby melee range targets
let are_nearby_targets = || {
if let Some(health) = self.health {
health
.recent_damagers()
.filter(|(_, time)| read_data.time.0 - time.0 < 5.0)
.filter_map(|(uid, _)| {
read_data.uid_allocator.retrieve_entity_internal(uid.0)
})
.filter(|e| {
read_data.positions.get(*e).map_or(false, |pos| {
pos.0.distance_squared(self.pos.0) < 10_f32.powi(2)
})
})
.count()
> 1
} else {
false
}
};
if matches!(self.char_state, CharacterState::Roll(_)) {
agent.action_state.condition = true;
}
if agent.action_state.condition {
if self.physics_state.on_ground.is_some() {
controller.push_basic_input(InputKind::Jump);
} else {
controller.push_basic_input(InputKind::Ability(3));
}
agent.action_state.timer += read_data.dt.0;
if agent.action_state.timer > 2.0 {
agent.action_state.timer = 0.0;
agent.action_state.condition = false;
}
advance(agent, controller, CLEAVING_DIVE.range, CLEAVING_DIVE.angle);
} else if self.energy.current() < DESIRED_ENERGY {
fallback_tactics(agent, controller);
} else if !in_stance(SwordStance::Cleaving) {
controller.push_basic_input(InputKind::Ability(0));
} else if CLEAVING_FINISHER.could_use(attack_data, self)
&& CLEAVING_FINISHER.use_desirable(tgt_data, self)
{
controller.push_basic_input(InputKind::Ability(1));
advance(
agent,
controller,
CLEAVING_FINISHER.range,
CLEAVING_FINISHER.angle,
);
} else if CLEAVING_SPIN.could_use(attack_data, self) && are_nearby_targets() {
controller.push_basic_input(InputKind::Ability(2));
advance(
agent,
controller,
CLEAVING_SPIN.max_range,
CLEAVING_SPIN.angle,
);
} else if CLEAVING_DIVE.npc_should_use_hack(self, tgt_data) {
controller.push_basic_input(InputKind::Roll);
advance(agent, controller, CLEAVING_DIVE.range, CLEAVING_DIVE.angle);
} else if CLEAVING_COMBO.could_use(attack_data, self) {
controller.push_basic_input(InputKind::Primary);
advance(
agent,
controller,
CLEAVING_COMBO.max_range,
CLEAVING_COMBO.angle,
);
} else if OFFENSIVE_ADVANCE.could_use(attack_data, self) {
controller.push_basic_input(InputKind::Ability(4));
advance(
agent,
controller,
OFFENSIVE_ADVANCE.max_range,
OFFENSIVE_ADVANCE.angle,
);
} else {
advance(
agent,
controller,
CLEAVING_COMBO.max_range,
CLEAVING_COMBO.angle,
);
}
},
SwordStance::Parrying => {
fallback_tactics(agent, controller);

View File

@ -1417,12 +1417,3 @@ pub fn handle_update_map_marker(
}
}
}
pub fn handle_reset_melee(server: &Server, entity: EcsEntity) {
let ecs = &server.state.ecs();
if let Some(mut melee) = ecs.write_storage::<comp::Melee>().get_mut(entity) {
melee.applied = false;
melee.hit_count = 0;
}
}

View File

@ -13,8 +13,7 @@ use entity_manipulation::{
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook,
handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground,
handle_parry_hook, handle_poise, handle_reset_melee, handle_respawn, handle_teleport_to,
handle_update_map_marker,
handle_parry_hook, handle_poise, handle_respawn, handle_teleport_to, handle_update_map_marker,
};
use group_manip::handle_group;
use information::handle_site_info;
@ -287,7 +286,6 @@ impl Server {
ServerEvent::UpdateMapMarker { entity, update } => {
handle_update_map_marker(self, entity, update)
},
ServerEvent::ResetMelee { entity } => handle_reset_melee(self, entity),
}
}

View File

@ -86,7 +86,7 @@ fn maps_basic_melee() {
range: 3.5,
angle: 15.0,
damage_effect: None,
multi_target: false,
multi_target: None,
},
ori_modifier: 1.0,
ability_info: empty_ability_info(),