Merge branch 'knightresspaladin/sceptre-ai' into 'master'

Knightresspaladin/sceptre ai

See merge request veloren/veloren!2572
This commit is contained in:
Samuel Keiffer 2021-07-08 03:43:42 +00:00
commit 72fa4f2180
5 changed files with 155 additions and 1 deletions

View File

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Animations for using consumables.
- New danari character customizations
- Bald hairstyles for humans and danari
- AI for sceptre weilders and sceptre cultists in Tier 5 dungeons
### Changed

View File

@ -10,6 +10,7 @@ EntityConfig (
(1.0, Some(Item("common.items.weapons.hammer.cultist_purp_2h-0"))),
(1.0, Some(Item("common.items.weapons.hammer_1h.orichalcum-0"))),
(1.0, Some(Item("common.items.weapons.bow.velorite"))),
(1.0, Some(Item("common.items.weapons.sceptre.sceptre_velorite_0"))),
])),
second_tool: None,

View File

@ -4,4 +4,5 @@
Tree("common.skillset.dungeon.tier-5.axe"),
Tree("common.skillset.dungeon.tier-5.hammer"),
Tree("common.skillset.dungeon.tier-5.bow"),
Tree("common.skillset.dungeon.tier-5.sceptre"),
])

View File

@ -0,0 +1,21 @@
([
Group(Weapon(Sceptre)),
// Beam
Skill((Sceptre(LDamage), Some(1))),
Skill((Sceptre(LRange), Some(1))),
Skill((Sceptre(LLifesteal), Some(1))),
Skill((Sceptre(LRegen), Some(1))),
// Heal
Skill((Sceptre(HHeal), Some(1))),
Skill((Sceptre(HCost), Some(1))),
Skill((Sceptre(HRange), Some(1))),
// Ward
Skill((Sceptre(UnlockAura), None)),
Skill((Sceptre(AStrength), Some(1))),
Skill((Sceptre(ADuration), Some(1))),
Skill((Sceptre(ARange), Some(1))),
Skill((Sceptre(ACost), Some(1))),
])

View File

@ -17,7 +17,7 @@ use common::{
tool::{AbilitySpec, ToolKind},
Item, ItemDesc, ItemKind,
},
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility,
CharacterState, ControlAction, ControlEvent, Controller, Energy, Health, HealthChange,
InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos,
@ -99,6 +99,7 @@ pub enum Tactic {
Sword,
Bow,
Staff,
Sceptre,
StoneGolem,
CircleCharge { radius: u32, circle_time: u32 },
QuadLowRanged,
@ -1613,6 +1614,7 @@ impl<'a> AgentData<'a> {
let tool_tactic = |tool_kind| match tool_kind {
ToolKind::Bow => Tactic::Bow,
ToolKind::Staff => Tactic::Staff,
ToolKind::Sceptre => Tactic::Sceptre,
ToolKind::Hammer => Tactic::Hammer,
ToolKind::Sword | ToolKind::Spear => Tactic::Sword,
ToolKind::Axe => Tactic::Axe,
@ -1815,6 +1817,9 @@ impl<'a> AgentData<'a> {
Tactic::Staff => {
self.handle_staff_attack(agent, controller, &attack_data, &tgt_data, &read_data)
},
Tactic::Sceptre => {
self.handle_sceptre_attack(agent, controller, &attack_data, &tgt_data, &read_data)
},
Tactic::StoneGolem => self.handle_stone_golem_attack(
agent,
controller,
@ -2431,6 +2436,131 @@ impl<'a> AgentData<'a> {
}
}
fn handle_sceptre_attack(
&self,
agent: &mut Agent,
controller: &mut Controller,
attack_data: &AttackData,
tgt_data: &TargetData,
read_data: &ReadData,
) {
const DESIRED_ENERGY_LEVEL: u32 = 500;
// Logic to use abilities
if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2)
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
)
{
// If far enough away, and can see target, check which skill is appropriate to
// use
if self
.skill_set
.has_skill(Skill::Sceptre(SceptreSkill::UnlockAura))
&& self.energy.current() > DESIRED_ENERGY_LEVEL
&& !read_data.buffs.get(*self.entity).iter().any(|buff| {
buff.iter_kind(BuffKind::ProtectingWard)
.peekable()
.peek()
.is_some()
})
{
// Use ward if target is far enough away, self is not buffed, and have
// sufficient energy
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
} else {
// If low on energy, use primary to attempt to regen energy
// Or if at desired energy level but not able/willing to ward, just attack
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
} else if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
if self.body.map_or(false, |b| b.is_humanoid())
&& self.energy.current() > CharacterAbility::default_roll().get_energy_cost()
&& !matches!(self.char_state, CharacterState::BasicAura(c) if !matches!(c.stage_section, StageSection::Recover))
{
// Else roll away if can roll and have enough energy, and not using aura or in
// recover
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
} else if attack_data.angle < 15.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
}
// Logic to move. Intentionally kept separate from ability logic where possible
// so duplicated work is less necessary.
if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
// Attempt to move away from target if too close
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
controller.inputs.move_dir =
-bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
// Else attempt to circle target if neither too close nor too far
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) && attack_data.angle < 45.0
{
controller.inputs.move_dir = bearing
.xy()
.rotated_z(thread_rng().gen_range(0.5..1.57))
.try_normalized()
.unwrap_or_else(Vec2::zero)
* speed;
} else {
// Unless cannot see target, then move towards them
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
}
// Sometimes try to roll
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& !matches!(self.char_state, CharacterState::BasicAura(_))
&& attack_data.dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.01
{
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
// If too far, move towards target
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
fn handle_stone_golem_attack(
&self,
agent: &mut Agent,