mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Ai for staff-wielding enemies. Keyframes for shockwave state.
This commit is contained in:
parent
1c21362bc3
commit
b5091a5891
@ -75,6 +75,7 @@ pub enum CharacterAbility {
|
||||
projectile_light: Option<LightEmitter>,
|
||||
projectile_gravity: Option<Gravity>,
|
||||
projectile_speed: f32,
|
||||
ability_key: AbilityKey,
|
||||
},
|
||||
RepeaterRanged {
|
||||
energy_cost: u32,
|
||||
@ -181,6 +182,7 @@ pub enum CharacterAbility {
|
||||
Shockwave {
|
||||
energy_cost: u32,
|
||||
buildup_duration: Duration,
|
||||
swing_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
damage: u32,
|
||||
knockback: f32,
|
||||
@ -383,6 +385,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
projectile_gravity,
|
||||
projectile_speed,
|
||||
energy_cost: _,
|
||||
ability_key,
|
||||
} => CharacterState::BasicRanged(basic_ranged::Data {
|
||||
exhausted: false,
|
||||
prepare_timer: Duration::default(),
|
||||
@ -394,6 +397,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
projectile_light: *projectile_light,
|
||||
projectile_gravity: *projectile_gravity,
|
||||
projectile_speed: *projectile_speed,
|
||||
ability_key: *ability_key,
|
||||
}),
|
||||
CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data {
|
||||
duration: *duration,
|
||||
@ -625,6 +629,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
CharacterAbility::Shockwave {
|
||||
energy_cost: _,
|
||||
buildup_duration,
|
||||
swing_duration,
|
||||
recover_duration,
|
||||
damage,
|
||||
knockback,
|
||||
@ -633,15 +638,19 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
shockwave_duration,
|
||||
requires_ground,
|
||||
} => CharacterState::Shockwave(shockwave::Data {
|
||||
exhausted: false,
|
||||
buildup_duration: *buildup_duration,
|
||||
recover_duration: *recover_duration,
|
||||
damage: *damage,
|
||||
knockback: *knockback,
|
||||
shockwave_angle: *shockwave_angle,
|
||||
shockwave_speed: *shockwave_speed,
|
||||
shockwave_duration: *shockwave_duration,
|
||||
requires_ground: *requires_ground,
|
||||
static_data: shockwave::StaticData {
|
||||
buildup_duration: *buildup_duration,
|
||||
swing_duration: *swing_duration,
|
||||
recover_duration: *recover_duration,
|
||||
damage: *damage,
|
||||
knockback: *knockback,
|
||||
shockwave_angle: *shockwave_angle,
|
||||
shockwave_speed: *shockwave_speed,
|
||||
shockwave_duration: *shockwave_duration,
|
||||
requires_ground: *requires_ground,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
}),
|
||||
CharacterAbility::BasicBeam {
|
||||
buildup_duration,
|
||||
|
@ -311,6 +311,7 @@ impl Tool {
|
||||
projectile_light: None,
|
||||
projectile_gravity: Some(Gravity(0.2)),
|
||||
projectile_speed: 100.0,
|
||||
ability_key: AbilityKey::Mouse1,
|
||||
},
|
||||
ChargedRanged {
|
||||
energy_cost: 0,
|
||||
@ -420,6 +421,7 @@ impl Tool {
|
||||
}),
|
||||
projectile_gravity: Some(Gravity(0.5)),
|
||||
projectile_speed: 40.0,
|
||||
ability_key: AbilityKey::Mouse2,
|
||||
},
|
||||
],
|
||||
Staff(_) => vec![
|
||||
@ -464,6 +466,7 @@ impl Tool {
|
||||
}),
|
||||
projectile_gravity: Some(Gravity(0.3)),
|
||||
projectile_speed: 60.0,
|
||||
ability_key: AbilityKey::Mouse1,
|
||||
},
|
||||
BasicBeam {
|
||||
buildup_duration: Duration::from_millis(250),
|
||||
@ -482,7 +485,8 @@ impl Tool {
|
||||
},
|
||||
Shockwave {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
buildup_duration: Duration::from_millis(400),
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
damage: (200.0 * self.base_power()) as u32,
|
||||
knockback: 20.0,
|
||||
@ -519,7 +523,8 @@ impl Tool {
|
||||
Shockwave {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
recover_duration: Duration::from_millis(1000),
|
||||
swing_duration: Duration::from_millis(200),
|
||||
recover_duration: Duration::from_millis(800),
|
||||
damage: 500,
|
||||
knockback: -40.0,
|
||||
shockwave_angle: 90.0,
|
||||
@ -573,6 +578,7 @@ impl Tool {
|
||||
}),
|
||||
projectile_gravity: None,
|
||||
projectile_speed: 100.0,
|
||||
ability_key: AbilityKey::Skill1,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
|
@ -24,6 +24,8 @@ pub struct Data {
|
||||
pub projectile_speed: f32,
|
||||
/// Whether the attack fired already
|
||||
pub exhausted: bool,
|
||||
/// What key is used to press ability
|
||||
pub ability_key: AbilityKey,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
@ -35,7 +37,8 @@ impl CharacterBehavior for Data {
|
||||
|
||||
if !self.exhausted
|
||||
&& if self.holdable {
|
||||
data.inputs.holding_ability_key() || self.prepare_timer < self.prepare_duration
|
||||
ability_key_is_pressed(data, self.ability_key)
|
||||
|| self.prepare_timer < self.prepare_duration
|
||||
} else {
|
||||
self.prepare_timer < self.prepare_duration
|
||||
}
|
||||
@ -52,6 +55,7 @@ impl CharacterBehavior for Data {
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: false,
|
||||
ability_key: self.ability_key,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
// Fire
|
||||
@ -78,6 +82,7 @@ impl CharacterBehavior for Data {
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: true,
|
||||
ability_key: self.ability_key,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
@ -95,6 +100,7 @@ impl CharacterBehavior for Data {
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: true,
|
||||
ability_key: self.ability_key,
|
||||
});
|
||||
return update;
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{shockwave, Attacking, CharacterState, StateUpdate},
|
||||
comp::{shockwave, CharacterState, StateUpdate},
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
@ -7,12 +7,13 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Whether the attack can deal more damage
|
||||
pub exhausted: bool,
|
||||
pub struct StaticData {
|
||||
/// How long until state should deal damage
|
||||
pub buildup_duration: Duration,
|
||||
/// How long the state is swinging for
|
||||
pub swing_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// Base damage
|
||||
@ -29,77 +30,100 @@ pub struct Data {
|
||||
pub requires_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||
let mut update = StateUpdate::from(data);
|
||||
|
||||
handle_move(data, &mut update, 0.05);
|
||||
|
||||
if self.buildup_duration != Duration::default() {
|
||||
// Build up
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
exhausted: self.exhausted,
|
||||
buildup_duration: self
|
||||
.buildup_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
recover_duration: self.recover_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
// Attack
|
||||
let properties = shockwave::Properties {
|
||||
angle: self.shockwave_angle,
|
||||
speed: self.shockwave_speed,
|
||||
duration: self.shockwave_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
requires_ground: self.requires_ground,
|
||||
owner: Some(*data.uid),
|
||||
};
|
||||
update.server_events.push_front(ServerEvent::Shockwave {
|
||||
properties,
|
||||
pos: *data.pos,
|
||||
ori: *data.ori,
|
||||
});
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
});
|
||||
} else {
|
||||
// Attack
|
||||
let properties = shockwave::Properties {
|
||||
angle: self.static_data.shockwave_angle,
|
||||
speed: self.static_data.shockwave_speed,
|
||||
duration: self.static_data.shockwave_duration,
|
||||
damage: self.static_data.damage,
|
||||
knockback: self.static_data.knockback,
|
||||
requires_ground: self.static_data.requires_ground,
|
||||
owner: Some(*data.uid),
|
||||
};
|
||||
update.server_events.push_front(ServerEvent::Shockwave {
|
||||
properties,
|
||||
pos: *data.pos,
|
||||
ori: *data.ori,
|
||||
});
|
||||
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
exhausted: true,
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
exhausted: self.exhausted,
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self
|
||||
.recover_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
// Make sure attack component is removed
|
||||
data.updater.remove::<Attacking>(data.entity);
|
||||
// Transitions to swing
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Swing,
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Swing => {
|
||||
if self.timer < self.static_data.swing_duration {
|
||||
// Swings
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.swing_duration {
|
||||
// Recovers
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If it somehow ends up in an incorrect stage section
|
||||
update.character = CharacterState::Wielding;
|
||||
},
|
||||
}
|
||||
|
||||
update
|
||||
|
@ -5,9 +5,9 @@ use crate::{
|
||||
group,
|
||||
group::Invite,
|
||||
item::{tool::ToolKind, ItemKind},
|
||||
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller,
|
||||
GroupManip, LightEmitter, Loadout, MountState, Ori, PhysicsState, Pos, Scale, Stats,
|
||||
UnresolvedChatMsg, Vel,
|
||||
Agent, Alignment, Body, ControlAction, ControlEvent, Controller, Energy, GroupManip,
|
||||
LightEmitter, Loadout, MountState, Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg,
|
||||
Vel,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
@ -45,7 +45,6 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
@ -58,6 +57,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Invite>,
|
||||
Read<'a, TimeOfDay>,
|
||||
ReadStorage<'a, LightEmitter>,
|
||||
ReadStorage<'a, Energy>,
|
||||
);
|
||||
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
@ -77,7 +77,6 @@ impl<'a> System<'a> for Sys {
|
||||
scales,
|
||||
stats,
|
||||
loadouts,
|
||||
character_states,
|
||||
physics_states,
|
||||
uids,
|
||||
groups,
|
||||
@ -90,6 +89,7 @@ impl<'a> System<'a> for Sys {
|
||||
invites,
|
||||
time_of_day,
|
||||
light_emitter,
|
||||
energies,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let start_time = std::time::Instant::now();
|
||||
@ -101,7 +101,6 @@ impl<'a> System<'a> for Sys {
|
||||
ori,
|
||||
alignment,
|
||||
loadout,
|
||||
character_state,
|
||||
physics_state,
|
||||
body,
|
||||
uid,
|
||||
@ -110,6 +109,7 @@ impl<'a> System<'a> for Sys {
|
||||
mount_state,
|
||||
group,
|
||||
light_emitter,
|
||||
energy,
|
||||
) in (
|
||||
&entities,
|
||||
&positions,
|
||||
@ -117,7 +117,6 @@ impl<'a> System<'a> for Sys {
|
||||
&orientations,
|
||||
alignments.maybe(),
|
||||
&loadouts,
|
||||
&character_states,
|
||||
&physics_states,
|
||||
bodies.maybe(),
|
||||
&uids,
|
||||
@ -126,6 +125,7 @@ impl<'a> System<'a> for Sys {
|
||||
mount_states.maybe(),
|
||||
groups.maybe(),
|
||||
light_emitter.maybe(),
|
||||
&energies,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -298,6 +298,7 @@ impl<'a> System<'a> for Sys {
|
||||
powerup,
|
||||
..
|
||||
} => {
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum Tactic {
|
||||
Melee,
|
||||
RangedPowerup,
|
||||
@ -389,7 +390,10 @@ impl<'a> System<'a> for Sys {
|
||||
} else {
|
||||
do_idle = true;
|
||||
}
|
||||
} else if dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0) {
|
||||
} else if (tactic == Tactic::Staff
|
||||
&& dist_sqrd < (5.0 * MIN_ATTACK_DIST * scale).powf(2.0))
|
||||
|| dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0)
|
||||
{
|
||||
// Close-range attack
|
||||
inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0)
|
||||
.try_normalized()
|
||||
@ -397,9 +401,16 @@ impl<'a> System<'a> for Sys {
|
||||
* 0.1;
|
||||
|
||||
match tactic {
|
||||
Tactic::Melee | Tactic::Staff | Tactic::StoneGolemBoss => {
|
||||
Tactic::Melee | Tactic::StoneGolemBoss => {
|
||||
inputs.primary.set_state(true)
|
||||
},
|
||||
Tactic::Staff => {
|
||||
if energy.current() > 10 {
|
||||
inputs.secondary.set_state(true)
|
||||
} else {
|
||||
inputs.primary.set_state(true)
|
||||
}
|
||||
},
|
||||
Tactic::RangedPowerup => inputs.roll.set_state(true),
|
||||
}
|
||||
} else if dist_sqrd < MAX_CHASE_DIST.powf(2.0)
|
||||
@ -424,11 +435,13 @@ impl<'a> System<'a> for Sys {
|
||||
*powerup += dt.0;
|
||||
}
|
||||
} else if let Tactic::Staff = tactic {
|
||||
if !character_state.is_wield() {
|
||||
if *powerup > 2.5 {
|
||||
inputs.primary.set_state(false);
|
||||
*powerup = 0.0;
|
||||
} else {
|
||||
inputs.primary.set_state(true);
|
||||
*powerup += dt.0;
|
||||
}
|
||||
|
||||
inputs.secondary.set_state(true);
|
||||
} else if let Tactic::StoneGolemBoss = tactic {
|
||||
if *powerup > 5.0 {
|
||||
inputs.secondary.set_state(true);
|
||||
|
Loading…
Reference in New Issue
Block a user