mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'knightresspaladin/sceptre-ai' into 'master'
Knightresspaladin/sceptre ai See merge request veloren/veloren!2572
This commit is contained in:
commit
72fa4f2180
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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"),
|
||||
])
|
||||
|
21
assets/common/skillset/dungeon/tier-5/sceptre.ron
Normal file
21
assets/common/skillset/dungeon/tier-5/sceptre.ron
Normal 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))),
|
||||
])
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user