mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Addressed feedback from testing
This commit is contained in:
parent
b5682c4682
commit
0fe073fcdc
@ -4,9 +4,9 @@ ChargedRanged(
|
||||
initial_regen: 2,
|
||||
scaled_regen: 14,
|
||||
initial_damage: 2,
|
||||
scaled_damage: 14,
|
||||
scaled_damage: 12,
|
||||
initial_knockback: 0,
|
||||
scaled_knockback: 15,
|
||||
scaled_knockback: 12,
|
||||
buildup_duration: 0.25,
|
||||
charge_duration: 1.0,
|
||||
recover_duration: 0.4,
|
||||
|
@ -1,5 +1,5 @@
|
||||
RepeaterRanged(
|
||||
energy_cost: 5.0,
|
||||
energy_cost: 6.0,
|
||||
buildup_duration: 0.2,
|
||||
shoot_duration: 0.3,
|
||||
recover_duration: 0.5,
|
||||
|
@ -48,7 +48,7 @@ ComboMelee2(
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 6,
|
||||
energy_cost_per_strike: 5,
|
||||
meta: (
|
||||
kind: Some(Sword(Cleaving)),
|
||||
),
|
||||
|
@ -3,10 +3,10 @@ ComboMelee2(
|
||||
(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 20,
|
||||
damage: 16,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 10,
|
||||
energy_regen: 5,
|
||||
),
|
||||
range: 6.0,
|
||||
angle: 360.0,
|
||||
|
@ -14,7 +14,7 @@ ComboMelee2(
|
||||
kind: Bleeding,
|
||||
dur_secs: 10.0,
|
||||
strength: DamageFraction(0.2),
|
||||
chance: 0.25,
|
||||
chance: 0.1,
|
||||
))),
|
||||
),
|
||||
buildup_duration: 0.2,
|
||||
@ -26,7 +26,7 @@ ComboMelee2(
|
||||
(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 22,
|
||||
damage: 14,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 13,
|
||||
@ -37,18 +37,18 @@ ComboMelee2(
|
||||
kind: Bleeding,
|
||||
dur_secs: 10.0,
|
||||
strength: DamageFraction(0.2),
|
||||
chance: 0.5,
|
||||
chance: 0.25,
|
||||
))),
|
||||
),
|
||||
buildup_duration: 0.3,
|
||||
swing_duration: 0.2,
|
||||
buildup_duration: 0.2,
|
||||
swing_duration: 0.1,
|
||||
hit_timing: 0.5,
|
||||
recover_duration: 0.4,
|
||||
recover_duration: 0.3,
|
||||
ori_modifier: 0.6,
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 5,
|
||||
energy_cost_per_strike: 4,
|
||||
meta: (
|
||||
kind: Some(Sword(Crippling)),
|
||||
),
|
||||
|
@ -15,13 +15,13 @@ FinisherMelee(
|
||||
damage_effect: Some(Buff((
|
||||
kind: Bleeding,
|
||||
dur_secs: 10.0,
|
||||
strength: DamageFraction(0.5),
|
||||
strength: DamageFraction(0.25),
|
||||
chance: 1.0,
|
||||
))),
|
||||
),
|
||||
scaling: Some((
|
||||
target: Buff,
|
||||
kind: Linear,
|
||||
kind: Sqrt,
|
||||
)),
|
||||
minimum_combo: 10,
|
||||
meta: (
|
||||
|
@ -3,7 +3,7 @@ ComboMelee2(
|
||||
(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 20,
|
||||
damage: 14,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 5,
|
||||
|
@ -3,15 +3,15 @@ ComboMelee2(
|
||||
(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 10,
|
||||
damage: 8,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 6,
|
||||
energy_regen: 8,
|
||||
),
|
||||
range: 3.0,
|
||||
angle: 45.0,
|
||||
),
|
||||
buildup_duration: 0.4,
|
||||
buildup_duration: 0.3,
|
||||
swing_duration: 0.1,
|
||||
hit_timing: 0.5,
|
||||
recover_duration: 0.5,
|
||||
@ -36,7 +36,7 @@ ComboMelee2(
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 3,
|
||||
energy_cost_per_strike: 2,
|
||||
meta: (
|
||||
kind: Some(Sword(Defensive)),
|
||||
capabilities: (
|
||||
|
@ -18,7 +18,7 @@ ComboMelee2(
|
||||
movement: (
|
||||
buildup: None,
|
||||
swing: None,
|
||||
recover: Some(Reverse(1.0)),
|
||||
recover: Some(Reverse(1.5)),
|
||||
),
|
||||
ori_modifier: 0.6,
|
||||
),
|
||||
|
@ -4,8 +4,8 @@ ComboMelee2(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 18,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
poise: 5,
|
||||
knockback: 5,
|
||||
energy_regen: 10,
|
||||
),
|
||||
range: 3.0,
|
||||
@ -21,8 +21,8 @@ ComboMelee2(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 30,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
poise: 5,
|
||||
knockback: 5,
|
||||
energy_regen: 15,
|
||||
),
|
||||
range: 3.0,
|
||||
@ -36,12 +36,12 @@ ComboMelee2(
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 5,
|
||||
energy_cost_per_strike: 4,
|
||||
meta: (
|
||||
kind: Some(Sword(Heavy)),
|
||||
capabilities: (
|
||||
// Poise resistant during attack
|
||||
bits: 0b00001000,
|
||||
// Poise and knockback resistant during attack
|
||||
bits: 0b00011000,
|
||||
),
|
||||
),
|
||||
)
|
@ -5,8 +5,8 @@ FinisherMelee(
|
||||
recover_duration: 0.4,
|
||||
melee_constructor: (
|
||||
kind: Bash(
|
||||
damage: 30,
|
||||
poise: 30,
|
||||
damage: 40,
|
||||
poise: 40,
|
||||
knockback: 0,
|
||||
energy_regen: 10,
|
||||
),
|
||||
|
@ -4,7 +4,7 @@ ComboMelee2(
|
||||
melee_constructor: (
|
||||
kind: Bash(
|
||||
damage: 20,
|
||||
poise: 20,
|
||||
poise: 25,
|
||||
knockback: 0,
|
||||
energy_regen: 5,
|
||||
),
|
||||
|
@ -70,7 +70,7 @@ ComboMelee2(
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 3,
|
||||
energy_cost_per_strike: 2,
|
||||
meta: (
|
||||
kind: Some(Sword(Mobility)),
|
||||
capabilities: (
|
||||
|
@ -58,7 +58,7 @@ ComboMelee2(
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 4,
|
||||
energy_cost_per_strike: 3,
|
||||
meta: (
|
||||
kind: Some(Sword(Offensive)),
|
||||
),
|
||||
|
@ -21,7 +21,7 @@ FinisherMelee(
|
||||
),
|
||||
scaling: Some((
|
||||
target: Attack,
|
||||
kind: Linear,
|
||||
kind: Sqrt,
|
||||
)),
|
||||
minimum_combo: 10,
|
||||
meta: (
|
||||
|
@ -3,7 +3,7 @@ ComboMelee2(
|
||||
(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 7,
|
||||
damage: 6,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 9,
|
||||
@ -20,7 +20,7 @@ ComboMelee2(
|
||||
(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 13,
|
||||
damage: 11,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 13,
|
||||
@ -36,7 +36,7 @@ ComboMelee2(
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 6,
|
||||
energy_cost_per_strike: 5,
|
||||
meta: (
|
||||
kind: Some(Sword(Parrying)),
|
||||
capabilities: (
|
||||
|
@ -3,7 +3,7 @@ ComboMelee2(
|
||||
(
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 25,
|
||||
damage: 30,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 5,
|
||||
|
@ -7,7 +7,7 @@ BasicBlock(
|
||||
buildup: true,
|
||||
recover: true,
|
||||
),
|
||||
energy_cost: 10,
|
||||
energy_cost: 15,
|
||||
can_hold: false,
|
||||
meta: (
|
||||
kind: Some(Sword(Parrying)),
|
||||
|
@ -1,8 +1,8 @@
|
||||
RiposteMelee(
|
||||
energy_cost: 20,
|
||||
buildup_duration: 0.2,
|
||||
buildup_duration: 0.3,
|
||||
swing_duration: 0.2,
|
||||
recover_duration: 0.4,
|
||||
recover_duration: 0.3,
|
||||
melee_constructor: (
|
||||
kind: Slash(
|
||||
damage: 25,
|
||||
|
@ -36,7 +36,7 @@ ComboMelee2(
|
||||
),
|
||||
],
|
||||
is_stance: true,
|
||||
energy_cost_per_strike: 5,
|
||||
energy_cost_per_strike: 4,
|
||||
meta: (
|
||||
kind: Some(Sword(Reaching)),
|
||||
),
|
||||
|
@ -7,12 +7,12 @@ RapidMelee(
|
||||
damage: 10,
|
||||
poise: 0,
|
||||
knockback: 0,
|
||||
energy_regen: 5,
|
||||
energy_regen: 4,
|
||||
),
|
||||
range: 4.5,
|
||||
angle: 10.0,
|
||||
),
|
||||
energy_cost: 10,
|
||||
energy_cost: 8,
|
||||
max_strikes: 6,
|
||||
meta: (
|
||||
kind: Some(Sword(Reaching)),
|
||||
|
@ -6,12 +6,12 @@ ItemDef(
|
||||
toolkind: Sword,
|
||||
stats: (
|
||||
equip_time_secs: 1.1,
|
||||
power: 1.3,
|
||||
effect_power: 1.2,
|
||||
speed: 0.7,
|
||||
power: 1.1,
|
||||
effect_power: 1.1,
|
||||
speed: 0.9,
|
||||
crit_chance: 0.9,
|
||||
range: 1.1,
|
||||
energy_efficiency: 0.8,
|
||||
energy_efficiency: 0.9,
|
||||
buff_strength: 1.1,
|
||||
),
|
||||
hand_restriction: Some(Two),
|
||||
|
@ -6,12 +6,12 @@ ItemDef(
|
||||
toolkind: Sword,
|
||||
stats: (
|
||||
equip_time_secs: 0.9,
|
||||
power: 0.7,
|
||||
effect_power: 0.8,
|
||||
speed: 1.3,
|
||||
power: 0.9,
|
||||
effect_power: 0.9,
|
||||
speed: 1.1,
|
||||
crit_chance: 1.1,
|
||||
range: 0.9,
|
||||
energy_efficiency: 1.2,
|
||||
energy_efficiency: 1.1,
|
||||
buff_strength: 0.9,
|
||||
),
|
||||
hand_restriction: Some(One),
|
||||
|
BIN
assets/voxygen/element/de_buffs/buff_fortitude_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/de_buffs/buff_fortitude_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/de_buffs/debuff_parried_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/de_buffs/debuff_parried_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/skills/sword/balanced_combo.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skills/sword/balanced_combo.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skills/sword/balanced_finisher.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skills/sword/balanced_finisher.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skills/sword/balanced_thrust.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skills/sword/balanced_thrust.png
(Stored with Git LFS)
Binary file not shown.
@ -15,7 +15,7 @@ common-abilities-sceptre-wardingaura = Warding Aura
|
||||
common-abilities-sword-balanced_combo = Balanced Stance
|
||||
.desc = This stance has few downsides, but is not particularly special.
|
||||
common-abilities-sword-balanced_thrust = Sword Thrust
|
||||
.desc = Extend yourself to hit those a little further away.
|
||||
.desc = Charge a thrust to hit enemies with a longer distance.
|
||||
common-abilities-sword-balanced_finisher = Finisher
|
||||
.desc = A powerful strike you can use after fighting long enough.
|
||||
common-abilities-sword-offensive_combo = Offensive Stance
|
||||
|
@ -2,6 +2,7 @@ use crate::comp::buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::{
|
||||
comp::{
|
||||
ability::Capability,
|
||||
inventory::{
|
||||
item::{armor::Protection, tool::ToolKind, ItemDesc, ItemKind, MaterialStatManifest},
|
||||
slot::EquipSlot,
|
||||
@ -334,7 +335,8 @@ impl Attack {
|
||||
for effect in damage.effects.iter() {
|
||||
match effect {
|
||||
CombatEffect::Knockback(kb) => {
|
||||
let impulse = kb.calculate_impulse(dir) * strength_modifier;
|
||||
let impulse =
|
||||
kb.calculate_impulse(dir, target.char_state) * strength_modifier;
|
||||
if !impulse.is_approx_zero() {
|
||||
emit(ServerEvent::Knockback {
|
||||
entity: target.entity,
|
||||
@ -432,7 +434,10 @@ impl Attack {
|
||||
},
|
||||
CombatEffect::BuildupsVulnerable => {
|
||||
if target.char_state.map_or(false, |cs| {
|
||||
matches!(cs.stage_section(), Some(StageSection::Buildup))
|
||||
matches!(
|
||||
cs.stage_section(),
|
||||
Some(StageSection::Buildup | StageSection::Charge)
|
||||
)
|
||||
}) {
|
||||
emit(ServerEvent::HealthChange {
|
||||
entity: target.entity,
|
||||
@ -496,7 +501,8 @@ impl Attack {
|
||||
is_applied = true;
|
||||
match effect.effect {
|
||||
CombatEffect::Knockback(kb) => {
|
||||
let impulse = kb.calculate_impulse(dir) * strength_modifier;
|
||||
let impulse =
|
||||
kb.calculate_impulse(dir, target.char_state) * strength_modifier;
|
||||
if !impulse.is_approx_zero() {
|
||||
emit(ServerEvent::Knockback {
|
||||
entity: target.entity,
|
||||
@ -951,18 +957,26 @@ pub enum KnockbackDir {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Knockback {
|
||||
pub fn calculate_impulse(self, dir: Dir) -> Vec3<f32> {
|
||||
// TEMP until source knockback values have been updated
|
||||
50.0 * match self.direction {
|
||||
KnockbackDir::Away => self.strength * *Dir::slerp(dir, Dir::new(Vec3::unit_z()), 0.5),
|
||||
KnockbackDir::Towards => {
|
||||
self.strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.5)
|
||||
},
|
||||
KnockbackDir::Up => self.strength * Vec3::unit_z(),
|
||||
KnockbackDir::TowardsUp => {
|
||||
self.strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.85)
|
||||
},
|
||||
}
|
||||
pub fn calculate_impulse(self, dir: Dir, char_state: Option<&CharacterState>) -> Vec3<f32> {
|
||||
let from_char = {
|
||||
let resistant = char_state
|
||||
.and_then(|cs| cs.ability_info())
|
||||
.and_then(|a| a.ability_meta)
|
||||
.map_or(false, |a| {
|
||||
a.capabilities.contains(Capability::KNOCKBACK_RESISTANT)
|
||||
});
|
||||
if resistant { 0.5 } else { 1.0 }
|
||||
};
|
||||
// TEMP: 50.0 multiplication kept until source knockback values have been
|
||||
// updated
|
||||
50.0 * self.strength
|
||||
* from_char
|
||||
* match self.direction {
|
||||
KnockbackDir::Away => *Dir::slerp(dir, Dir::new(Vec3::unit_z()), 0.5),
|
||||
KnockbackDir::Towards => *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.5),
|
||||
KnockbackDir::Up => Vec3::unit_z(),
|
||||
KnockbackDir::TowardsUp => *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.85),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
@ -3,6 +3,7 @@ use crate::{
|
||||
combat::{self, CombatEffect, DamageKind, Knockback},
|
||||
comp::{
|
||||
self, aura, beam, buff,
|
||||
character_state::AttackImmunities,
|
||||
inventory::{
|
||||
item::{
|
||||
tool::{AbilityContext, AbilityItem, AuxiliaryAbilityKind, Stats, ToolKind},
|
||||
@ -534,7 +535,7 @@ pub enum CharacterAbility {
|
||||
movement_duration: f32,
|
||||
recover_duration: f32,
|
||||
roll_strength: f32,
|
||||
immune_melee: bool,
|
||||
attack_immunities: AttackImmunities,
|
||||
#[serde(default)]
|
||||
meta: AbilityMeta,
|
||||
},
|
||||
@ -871,8 +872,15 @@ impl CharacterAbility {
|
||||
buildup_duration: 0.05,
|
||||
movement_duration: 0.33,
|
||||
recover_duration: 0.125,
|
||||
roll_strength: 2.0,
|
||||
immune_melee: true,
|
||||
roll_strength: 2.5,
|
||||
attack_immunities: AttackImmunities {
|
||||
melee: true,
|
||||
projectiles: false,
|
||||
beams: true,
|
||||
ground_shockwaves: false,
|
||||
air_shockwaves: true,
|
||||
explosions: true,
|
||||
},
|
||||
meta: Default::default(),
|
||||
}
|
||||
}
|
||||
@ -1002,7 +1010,7 @@ impl CharacterAbility {
|
||||
ref mut movement_duration,
|
||||
ref mut recover_duration,
|
||||
roll_strength: _,
|
||||
immune_melee: _,
|
||||
attack_immunities: _,
|
||||
meta: _,
|
||||
} => {
|
||||
*buildup_duration /= stats.speed;
|
||||
@ -1346,7 +1354,7 @@ impl CharacterAbility {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_energy_cost(&self) -> f32 {
|
||||
pub fn energy_cost(&self) -> f32 {
|
||||
use CharacterAbility::*;
|
||||
match self {
|
||||
BasicMelee { energy_cost, .. }
|
||||
@ -1386,6 +1394,46 @@ impl CharacterAbility {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::bool_to_int_with_if)]
|
||||
pub fn combo_cost(&self) -> u32 {
|
||||
use CharacterAbility::*;
|
||||
match self {
|
||||
BasicAura {
|
||||
scales_with_combo, ..
|
||||
} => {
|
||||
if *scales_with_combo {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
},
|
||||
FinisherMelee { minimum_combo, .. } => *minimum_combo,
|
||||
BasicMelee { .. }
|
||||
| BasicRanged { .. }
|
||||
| RepeaterRanged { .. }
|
||||
| DashMelee { .. }
|
||||
| Roll { .. }
|
||||
| LeapMelee { .. }
|
||||
| SpinMelee { .. }
|
||||
| ChargedMelee { .. }
|
||||
| ChargedRanged { .. }
|
||||
| Shockwave { .. }
|
||||
| BasicBlock { .. }
|
||||
| SelfBuff { .. }
|
||||
| ComboMelee2 { .. }
|
||||
| DiveMelee { .. }
|
||||
| RiposteMelee { .. }
|
||||
| RapidMelee { .. }
|
||||
| BasicBeam { .. }
|
||||
| Boost { .. }
|
||||
| ComboMelee { .. }
|
||||
| Blink { .. }
|
||||
| Music { .. }
|
||||
| BasicSummon { .. }
|
||||
| SpriteSummon { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe consider making CharacterAbility a struct at some point?
|
||||
pub fn ability_meta(&self) -> AbilityMeta {
|
||||
use CharacterAbility::*;
|
||||
@ -2060,7 +2108,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
||||
movement_duration,
|
||||
recover_duration,
|
||||
roll_strength,
|
||||
immune_melee,
|
||||
attack_immunities,
|
||||
meta: _,
|
||||
} => CharacterState::Roll(roll::Data {
|
||||
static_data: roll::StaticData {
|
||||
@ -2068,7 +2116,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
||||
movement_duration: Duration::from_secs_f32(*movement_duration),
|
||||
recover_duration: Duration::from_secs_f32(*recover_duration),
|
||||
roll_strength: *roll_strength,
|
||||
immune_melee: *immune_melee,
|
||||
attack_immunities: *attack_immunities,
|
||||
ability_info,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
@ -2120,10 +2168,15 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
||||
exhausted: false,
|
||||
start_next_strike: false,
|
||||
timer: Duration::default(),
|
||||
// If ability is a stance, enter the stance without beginning a strike, otherwise
|
||||
// immediately begin the strike
|
||||
// If ability is a stance, if starting from wielding, get ready to enter stance,
|
||||
// otherwise enter stance immediately, otherwise if not a stance immediately begin
|
||||
// the strike
|
||||
stage_section: if *is_stance {
|
||||
Some(StageSection::Ready)
|
||||
if matches!(data.character, CharacterState::Wielding(_)) {
|
||||
Some(StageSection::Ready)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(StageSection::Buildup)
|
||||
},
|
||||
@ -2608,12 +2661,14 @@ bitflags::bitflags! {
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Capability: u8 {
|
||||
// Allows rolls to interrupt the ability at any point, not just during buildup
|
||||
const ROLL_INTERRUPT = 0b00000001;
|
||||
const ROLL_INTERRUPT = 0b00000001;
|
||||
// Allows blocking to interrupt the ability at any point
|
||||
const BLOCK_INTERRUPT = 0b00000010;
|
||||
const BLOCK_INTERRUPT = 0b00000010;
|
||||
// When the ability is in the buildup section, it counts as a parry
|
||||
const BUILDUP_PARRIES = 0b00000100;
|
||||
const BUILDUP_PARRIES = 0b00000100;
|
||||
// When in the ability, an entity only receives half as much poise damage
|
||||
const POISE_RESISTANT = 0b00001000;
|
||||
const POISE_RESISTANT = 0b00001000;
|
||||
// WHen in the ability, an entity only receives half as much knockback
|
||||
const KNOCKBACK_RESISTANT = 0b00010000;
|
||||
}
|
||||
}
|
||||
|
@ -828,7 +828,12 @@ impl Body {
|
||||
|
||||
pub fn immune_to(&self, buff: BuffKind) -> bool {
|
||||
match buff {
|
||||
BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_) | Body::Ship(_)),
|
||||
BuffKind::Bleeding => match self {
|
||||
Body::Object(_) | Body::Golem(_) | Body::Ship(_) => true,
|
||||
Body::BipedSmall(b) => matches!(b.species, biped_small::Species::Husk),
|
||||
Body::BipedLarge(b) => matches!(b.species, biped_large::Species::Huskbrute),
|
||||
_ => false,
|
||||
},
|
||||
BuffKind::Burning => match self {
|
||||
Body::Golem(g) => matches!(g.species, golem::Species::ClayGolem),
|
||||
Body::BipedSmall(b) => matches!(b.species, biped_small::Species::Haniwa),
|
||||
|
@ -323,8 +323,12 @@ impl CharacterState {
|
||||
|
||||
pub fn is_music(&self) -> bool { matches!(self, CharacterState::Music(_)) }
|
||||
|
||||
pub fn is_melee_dodge(&self) -> bool {
|
||||
matches!(self, CharacterState::Roll(d) if d.static_data.immune_melee)
|
||||
pub fn attack_immunities(&self) -> Option<AttackImmunities> {
|
||||
if let CharacterState::Roll(c) = self {
|
||||
Some(c.static_data.attack_immunities)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_stunned(&self) -> bool { matches!(self, CharacterState::Stunned(_)) }
|
||||
@ -854,6 +858,16 @@ pub struct DurationsInfo {
|
||||
pub ready: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq)]
|
||||
pub struct AttackImmunities {
|
||||
pub melee: bool,
|
||||
pub projectiles: bool,
|
||||
pub beams: bool,
|
||||
pub ground_shockwaves: bool,
|
||||
pub air_shockwaves: bool,
|
||||
pub explosions: bool,
|
||||
}
|
||||
|
||||
impl Default for CharacterState {
|
||||
fn default() -> Self {
|
||||
Self::Idle(idle::Data {
|
||||
|
@ -145,7 +145,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -219,7 +219,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ pub struct Data {
|
||||
}
|
||||
|
||||
pub const STANCE_ENTER_TIME: Duration = Duration::from_millis(250);
|
||||
pub const STANCE_LEAVE_TIME: Duration = Duration::from_secs(20);
|
||||
pub const STANCE_LEAVE_TIME: Duration = Duration::from_secs(30);
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
|
||||
@ -127,7 +127,7 @@ impl CharacterBehavior for Data {
|
||||
1.0
|
||||
};
|
||||
handle_move(data, &mut update, move_eff);
|
||||
let interrupted = handle_interrupts(data, &mut update, Some(ability_input));
|
||||
let interrupted = handle_interrupts(data, &mut update);
|
||||
|
||||
let strike_data = self.strike_data();
|
||||
|
||||
|
@ -257,7 +257,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ impl CharacterBehavior for Data {
|
||||
|
||||
match self.stage_section {
|
||||
StageSection::Movement => {
|
||||
handle_move(data, &mut update, 1.0);
|
||||
if data.physics.on_ground.is_some() {
|
||||
// Transitions to swing portion of state upon hitting ground
|
||||
if let CharacterState::DiveMelee(c) = &mut update.character {
|
||||
|
@ -51,7 +51,7 @@ impl CharacterBehavior for Data {
|
||||
handle_orientation(data, &mut update, 1.0, None);
|
||||
handle_move(data, &mut update, 0.7);
|
||||
handle_jump(data, output_events, &mut update, 1.0);
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
@ -88,6 +88,9 @@ impl CharacterBehavior for Data {
|
||||
self.static_data.combo_on_use as f32
|
||||
/ self.static_data.minimum_combo as f32
|
||||
},
|
||||
ScalingKind::Sqrt => {
|
||||
(self.static_data.combo_on_use as f32 / self.static_data.minimum_combo as f32).sqrt()
|
||||
},
|
||||
};
|
||||
match scaling.target {
|
||||
ScalingTarget::Attack => {
|
||||
@ -155,6 +158,8 @@ pub enum ScalingKind {
|
||||
// Reaches a scaling of 1 when at minimum combo, and a scaling of 2 when at double minimum
|
||||
// combo
|
||||
Linear,
|
||||
// Reaches a scaling of 1 when at minimum combo, and a scaling of 2 when at 4x minimum combo
|
||||
Sqrt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
@ -149,7 +149,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ impl CharacterBehavior for Data {
|
||||
|
||||
handle_orientation(data, &mut update, 1.0, None);
|
||||
handle_move(data, &mut update, 0.7);
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
|
@ -158,7 +158,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ impl CharacterBehavior for Data {
|
||||
handle_orientation(data, &mut update, 1.0, None);
|
||||
handle_move(data, &mut update, 0.7);
|
||||
handle_jump(data, output_events, &mut update, 1.0);
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
buff::{BuffChange, BuffKind},
|
||||
character_state::OutputEvents,
|
||||
character_state::{OutputEvents, AttackImmunities},
|
||||
CharacterState, InputKind, StateUpdate,
|
||||
},
|
||||
event::ServerEvent,
|
||||
@ -24,8 +24,8 @@ pub struct StaticData {
|
||||
pub recover_duration: Duration,
|
||||
/// Affects the speed and distance of the roll
|
||||
pub roll_strength: f32,
|
||||
/// Affects whether you are immune to melee attacks while rolling
|
||||
pub immune_melee: bool,
|
||||
/// Affects whether you are immune to various attacks while rolling
|
||||
pub attack_immunities: AttackImmunities,
|
||||
/// Information about the ability
|
||||
pub ability_info: AbilityInfo,
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// At end of state logic so an interrupt isn't overwritten
|
||||
handle_interrupts(data, &mut update, None);
|
||||
handle_interrupts(data, &mut update);
|
||||
|
||||
update
|
||||
}
|
||||
|
@ -1096,40 +1096,28 @@ pub fn handle_dodge_input(data: &JoinData<'_>, update: &mut StateUpdate) -> bool
|
||||
pub fn handle_interrupts(
|
||||
data: &JoinData,
|
||||
update: &mut StateUpdate,
|
||||
// Used when an input other than the one that activated the ability being pressed should block
|
||||
// an interrupt
|
||||
input_override: Option<InputKind>,
|
||||
) -> bool {
|
||||
// Check that the input used to enter current character state (if there was one)
|
||||
// is not pressed
|
||||
if input_override
|
||||
.or_else(|| data.character.ability_info().and_then(|a| a.input))
|
||||
.map_or(true, |input| !input_is_pressed(data, input))
|
||||
{
|
||||
let can_dodge = {
|
||||
let in_buildup = data
|
||||
.character
|
||||
.stage_section()
|
||||
.map_or(true, |stage_section| {
|
||||
matches!(stage_section, StageSection::Buildup)
|
||||
});
|
||||
let interruptible = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
|
||||
meta.capabilities
|
||||
.contains(Capability::ROLL_INTERRUPT)
|
||||
let can_dodge = {
|
||||
let in_buildup = data
|
||||
.character
|
||||
.stage_section()
|
||||
.map_or(true, |stage_section| {
|
||||
matches!(stage_section, StageSection::Buildup)
|
||||
});
|
||||
in_buildup || interruptible
|
||||
};
|
||||
let can_block = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
|
||||
let interruptible = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
|
||||
meta.capabilities
|
||||
.contains(Capability::BLOCK_INTERRUPT)
|
||||
.contains(Capability::ROLL_INTERRUPT)
|
||||
});
|
||||
if can_dodge {
|
||||
handle_dodge_input(data, update)
|
||||
} else if can_block {
|
||||
handle_block_input(data, update)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
in_buildup || interruptible
|
||||
};
|
||||
let can_block = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
|
||||
meta.capabilities
|
||||
.contains(Capability::BLOCK_INTERRUPT)
|
||||
});
|
||||
if can_dodge {
|
||||
handle_dodge_input(data, update)
|
||||
} else if can_block {
|
||||
handle_block_input(data, update)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@ -1358,6 +1346,9 @@ impl AbilityInfo {
|
||||
None
|
||||
};
|
||||
|
||||
// If this ability should not be returned to, check if this ability was going to return to another ability, and return to that one instead
|
||||
let return_ability = return_ability.or_else(|| char_state.ability_info().and_then(|info| info.return_ability));
|
||||
|
||||
Self {
|
||||
tool: None,
|
||||
hand: None,
|
||||
|
@ -229,6 +229,11 @@ impl<'a> System<'a> for Sys {
|
||||
energy: read_data.energies.get(target),
|
||||
};
|
||||
|
||||
let target_dodging = read_data
|
||||
.character_states
|
||||
.get(target)
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| i.beams);
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
&read_data.alignments,
|
||||
@ -238,8 +243,7 @@ impl<'a> System<'a> for Sys {
|
||||
target,
|
||||
);
|
||||
let attack_options = AttackOptions {
|
||||
// No luck with dodging beams
|
||||
target_dodging: false,
|
||||
target_dodging,
|
||||
may_harm,
|
||||
target_group,
|
||||
};
|
||||
|
@ -136,7 +136,8 @@ impl<'a> System<'a> for Sys {
|
||||
let target_dodging = read_data
|
||||
.char_states
|
||||
.get(target)
|
||||
.map_or(false, |c_s| c_s.is_melee_dodge());
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| i.melee);
|
||||
|
||||
// Check if it is a hit
|
||||
if attacker != target
|
||||
|
@ -307,10 +307,13 @@ fn dispatch_hit(
|
||||
target,
|
||||
);
|
||||
|
||||
let target_dodging = read_data
|
||||
.character_states
|
||||
.get(target)
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| i.projectiles);
|
||||
let attack_options = AttackOptions {
|
||||
// They say witchers can dodge arrows,
|
||||
// but we don't have witchers
|
||||
target_dodging: false,
|
||||
target_dodging,
|
||||
may_harm,
|
||||
target_group: projectile_target_info.target_group,
|
||||
};
|
||||
|
@ -211,6 +211,17 @@ impl<'a> System<'a> for Sys {
|
||||
energy: read_data.energies.get(target),
|
||||
};
|
||||
|
||||
let target_dodging = read_data
|
||||
.character_states
|
||||
.get(target)
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| {
|
||||
if shockwave.requires_ground {
|
||||
i.ground_shockwaves
|
||||
} else {
|
||||
i.air_shockwaves
|
||||
}
|
||||
});
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
&read_data.alignments,
|
||||
@ -220,8 +231,7 @@ impl<'a> System<'a> for Sys {
|
||||
target,
|
||||
);
|
||||
let attack_options = AttackOptions {
|
||||
// Trying roll during earthquake isn't the best idea
|
||||
target_dodging: false,
|
||||
target_dodging,
|
||||
may_harm,
|
||||
target_group,
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ use common::{
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
resources::{DeltaTime, EntitiesDiedLastTick, Time},
|
||||
states,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
@ -154,7 +155,11 @@ impl<'a> System<'a> for Sys {
|
||||
| CharacterState::GlideWield(_)
|
||||
| CharacterState::Wielding(_)
|
||||
| CharacterState::Equipping(_)
|
||||
| CharacterState::Boost(_) => {
|
||||
| CharacterState::Boost(_)
|
||||
| CharacterState::ComboMelee2(states::combo_melee2::Data {
|
||||
stage_section: None,
|
||||
..
|
||||
}) => {
|
||||
let res = { energy.current() < energy.maximum() };
|
||||
|
||||
if res {
|
||||
|
@ -1410,7 +1410,7 @@ impl<'a> AgentData<'a> {
|
||||
// Use shotgun if target close and have sufficient energy
|
||||
controller.push_basic_input(InputKind::Ability(0));
|
||||
} else if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
||||
&& self.energy.current() > CharacterAbility::default_roll().get_energy_cost()
|
||||
&& self.energy.current() > CharacterAbility::default_roll().energy_cost()
|
||||
&& !matches!(self.char_state, CharacterState::BasicRanged(c) if !matches!(c.stage_section, StageSection::Recover))
|
||||
{
|
||||
// Else roll away if can roll and have enough energy, and not using shotgun
|
||||
@ -1536,10 +1536,10 @@ impl<'a> AgentData<'a> {
|
||||
CharacterAbility::BasicBeam { range, .. } => range,
|
||||
_ => 20.0_f32,
|
||||
};
|
||||
let shockwave_cost = shockwave.get_energy_cost();
|
||||
let shockwave_cost = shockwave.energy_cost();
|
||||
if self.body.map_or(false, |b| b.is_humanoid())
|
||||
&& attack_data.in_min_range()
|
||||
&& self.energy.current() > CharacterAbility::default_roll().get_energy_cost()
|
||||
&& self.energy.current() > CharacterAbility::default_roll().energy_cost()
|
||||
&& !matches!(self.char_state, CharacterState::Shockwave(_))
|
||||
{
|
||||
// if a humanoid, have enough stamina, not in shockwave, and in melee range,
|
||||
@ -1576,7 +1576,7 @@ impl<'a> AgentData<'a> {
|
||||
[ActionStateConditions::ConditionStaffCanShockwave as usize] = true;
|
||||
}
|
||||
} else if self.energy.current()
|
||||
> shockwave_cost + CharacterAbility::default_roll().get_energy_cost()
|
||||
> shockwave_cost + CharacterAbility::default_roll().energy_cost()
|
||||
&& attack_data.dist_sqrd < flamethrower_range.powi(2)
|
||||
{
|
||||
controller.push_basic_input(InputKind::Secondary);
|
||||
@ -1713,7 +1713,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
} 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()
|
||||
&& self.energy.current() > CharacterAbility::default_roll().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
|
||||
@ -3610,7 +3610,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
} 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()
|
||||
&& self.energy.current() > CharacterAbility::default_roll().energy_cost()
|
||||
&& !matches!(self.char_state, CharacterState::BasicAura(c) if !matches!(c.stage_section, StageSection::Recover))
|
||||
{
|
||||
// Else use steam beam
|
||||
|
@ -543,16 +543,23 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
|
||||
let ecs = server.state.ecs();
|
||||
|
||||
if vel.z <= -30.0 {
|
||||
let char_states = ecs.read_storage::<CharacterState>();
|
||||
|
||||
let reduced_vel_z = if let Some(CharacterState::DiveMelee(c)) = char_states.get(entity) {
|
||||
(vel.z + c.static_data.vertical_speed).min(0.0)
|
||||
} else {
|
||||
vel.z
|
||||
};
|
||||
|
||||
let mass = ecs
|
||||
.read_storage::<comp::Mass>()
|
||||
.get(entity)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
let impact_energy = mass.0 * vel.z.powi(2) / 2.0;
|
||||
let impact_energy = mass.0 * reduced_vel_z.powi(2) / 2.0;
|
||||
let falldmg = impact_energy / 1000.0;
|
||||
|
||||
let inventories = ecs.read_storage::<Inventory>();
|
||||
let char_states = ecs.read_storage::<CharacterState>();
|
||||
let stats = ecs.read_storage::<Stats>();
|
||||
let time = ecs.read_resource::<Time>();
|
||||
let msm = ecs.read_resource::<MaterialStatManifest>();
|
||||
@ -583,7 +590,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
|
||||
server_eventbus.emit_now(ServerEvent::HealthChange { entity, change });
|
||||
|
||||
// Emit poise change
|
||||
let poise_damage = -(mass.0 * vel.magnitude_squared() / 1500.0);
|
||||
let poise_damage = -(mass.0 * reduced_vel_z.powi(2) / 1500.0);
|
||||
let poise_change = Poise::apply_poise_reduction(
|
||||
poise_damage,
|
||||
inventories.get(entity),
|
||||
@ -929,6 +936,9 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
energy: energies.get(entity_b),
|
||||
};
|
||||
|
||||
let target_dodging = char_state_b_maybe
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| i.explosions);
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
alignments,
|
||||
@ -938,9 +948,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
entity_b,
|
||||
);
|
||||
let attack_options = combat::AttackOptions {
|
||||
// cool guyz maybe don't look at explosions
|
||||
// but they still got hurt, it's not Hollywood
|
||||
target_dodging: false,
|
||||
target_dodging,
|
||||
may_harm,
|
||||
target_group,
|
||||
};
|
||||
@ -1237,20 +1245,53 @@ pub fn handle_parry_hook(server: &Server, defender: EcsEntity, attacker: Option<
|
||||
.and_then(|info| info.return_ability)
|
||||
.is_some();
|
||||
|
||||
match &mut *char_state {
|
||||
let return_to_wield = match &mut *char_state {
|
||||
CharacterState::RiposteMelee(c) => {
|
||||
c.stage_section = StageSection::Action;
|
||||
c.timer = Duration::default();
|
||||
false
|
||||
},
|
||||
CharacterState::BasicBlock(c) if should_return => {
|
||||
c.timer = c.static_data.recover_duration;
|
||||
c.stage_section = StageSection::Recover;
|
||||
// Refund half the energy of entering the block for a successful parry
|
||||
server_eventbus.emit_now(ServerEvent::EnergyChange {
|
||||
entity: defender,
|
||||
change: c.static_data.energy_cost / 2.0,
|
||||
});
|
||||
false
|
||||
},
|
||||
char_state @ CharacterState::BasicBlock(_) => {
|
||||
*char_state =
|
||||
CharacterState::Wielding(common::states::wielding::Data { is_sneaking: false });
|
||||
CharacterState::BasicBlock(c) => {
|
||||
// Refund half the energy of entering the block for a successful parry
|
||||
server_eventbus.emit_now(ServerEvent::EnergyChange {
|
||||
entity: defender,
|
||||
change: c.static_data.energy_cost / 2.0,
|
||||
});
|
||||
true
|
||||
},
|
||||
_char_state => {},
|
||||
char_state => {
|
||||
// If the character state is not one of the ones above, if it parried because it
|
||||
// had the capability to parry in its buildup, subtract 5 energy from the
|
||||
// defender
|
||||
if char_state
|
||||
.ability_info()
|
||||
.and_then(|info| info.ability_meta)
|
||||
.map_or(false, |meta| {
|
||||
meta.capabilities
|
||||
.contains(comp::ability::Capability::BUILDUP_PARRIES)
|
||||
})
|
||||
{
|
||||
server_eventbus.emit_now(ServerEvent::EnergyChange {
|
||||
entity: defender,
|
||||
change: -5.0,
|
||||
});
|
||||
}
|
||||
false
|
||||
},
|
||||
};
|
||||
if return_to_wield {
|
||||
*char_state =
|
||||
CharacterState::Wielding(common::states::wielding::Data { is_sneaking: false });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,8 +2,8 @@ use super::*;
|
||||
use crate::audio::sfx::SfxEvent;
|
||||
use common::{
|
||||
comp::{
|
||||
bird_large, humanoid, quadruped_medium, quadruped_small, Body, CharacterState, Ori,
|
||||
PhysicsState,
|
||||
bird_large, character_state::AttackImmunities, humanoid, quadruped_medium, quadruped_small,
|
||||
Body, CharacterState, Ori, PhysicsState,
|
||||
},
|
||||
states,
|
||||
terrain::{Block, BlockKind},
|
||||
@ -184,7 +184,14 @@ fn maps_roll() {
|
||||
movement_duration: Duration::default(),
|
||||
recover_duration: Duration::default(),
|
||||
roll_strength: 0.0,
|
||||
immune_melee: false,
|
||||
attack_immunities: AttackImmunities {
|
||||
melee: false,
|
||||
projectiles: false,
|
||||
beams: false,
|
||||
ground_shockwaves: false,
|
||||
air_shockwaves: false,
|
||||
explosions: false,
|
||||
},
|
||||
ability_info: empty_ability_info(),
|
||||
},
|
||||
timer: Duration::default(),
|
||||
|
@ -682,6 +682,7 @@ image_ids! {
|
||||
buff_dmg_red_0: "voxygen.element.de_buffs.buff_damage_reduce_0",
|
||||
buff_frenzy_0: "voxygen.element.de_buffs.buff_frenzy_0",
|
||||
buff_haste_0: "voxygen.element.de_buffs.buff_haste_0",
|
||||
buff_fortitude_0: "voxygen.element.de_buffs.buff_fortitude_0",
|
||||
|
||||
// Debuffs
|
||||
debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0",
|
||||
@ -692,6 +693,7 @@ image_ids! {
|
||||
debuff_wet_0: "voxygen.element.de_buffs.debuff_wet_0",
|
||||
debuff_ensnared_0: "voxygen.element.de_buffs.debuff_ensnared_0",
|
||||
debuff_poisoned_0: "voxygen.element.de_buffs.debuff_poisoned_0",
|
||||
debuff_parried_0: "voxygen.element.de_buffs.debuff_parried_0",
|
||||
|
||||
// Animation Frames
|
||||
// Buff Frame
|
||||
|
@ -84,7 +84,7 @@ use common::{
|
||||
combat,
|
||||
comp::{
|
||||
self,
|
||||
ability::{self, AuxiliaryAbility},
|
||||
ability::AuxiliaryAbility,
|
||||
fluid_dynamics,
|
||||
inventory::{slot::InvSlotId, trade_pricing::TradePricing, CollectFailedReason},
|
||||
item::{
|
||||
@ -565,39 +565,12 @@ impl<'a> BuffIcon<'a> {
|
||||
}
|
||||
|
||||
fn from_char_state(char_state: &comp::CharacterState) -> Option<Self> {
|
||||
use ability::{AbilityKind, SwordStance};
|
||||
if let Some(ability_kind) = char_state
|
||||
.ability_info()
|
||||
.and_then(|info| info.ability_meta)
|
||||
.and_then(|meta| meta.kind)
|
||||
{
|
||||
let id = match ability_kind {
|
||||
AbilityKind::Sword(SwordStance::Balanced) => {
|
||||
"common.abilities.sword.balanced_combo"
|
||||
},
|
||||
AbilityKind::Sword(SwordStance::Offensive) => {
|
||||
"common.abilities.sword.offensive_combo"
|
||||
},
|
||||
AbilityKind::Sword(SwordStance::Crippling) => {
|
||||
"common.abilities.sword.crippling_combo"
|
||||
},
|
||||
AbilityKind::Sword(SwordStance::Cleaving) => {
|
||||
"common.abilities.sword.cleaving_combo"
|
||||
},
|
||||
AbilityKind::Sword(SwordStance::Defensive) => {
|
||||
"common.abilities.sword.defensive_combo"
|
||||
},
|
||||
AbilityKind::Sword(SwordStance::Parrying) => {
|
||||
"common.abilities.sword.parrying_combo"
|
||||
},
|
||||
AbilityKind::Sword(SwordStance::Heavy) => "common.abilities.sword.heavy_combo",
|
||||
AbilityKind::Sword(SwordStance::Mobility) => {
|
||||
"common.abilities.sword.mobility_combo"
|
||||
},
|
||||
AbilityKind::Sword(SwordStance::Reaching) => {
|
||||
"common.abilities.sword.reaching_combo"
|
||||
},
|
||||
};
|
||||
let id = util::representative_ability_id(ability_kind);
|
||||
Some(BuffIcon {
|
||||
kind: BuffIconKind::Ability { ability_id: id },
|
||||
is_buff: true,
|
||||
@ -2890,6 +2863,7 @@ impl Hud {
|
||||
let active_abilities = ecs.read_storage::<comp::ActiveAbilities>();
|
||||
let bodies = ecs.read_storage::<comp::Body>();
|
||||
let poises = ecs.read_storage::<comp::Poise>();
|
||||
let combos = ecs.read_storage::<comp::Combo>();
|
||||
// Combo floater stuffs
|
||||
self.floaters.combo_floater = self.floaters.combo_floater.map(|mut f| {
|
||||
f.timer -= dt.as_secs_f64();
|
||||
@ -2939,6 +2913,8 @@ impl Hud {
|
||||
&msm,
|
||||
self.floaters.combo_floater,
|
||||
context,
|
||||
combos.get(entity),
|
||||
char_states.get(entity),
|
||||
)
|
||||
.set(self.ids.skillbar, ui_widgets);
|
||||
}
|
||||
@ -3642,6 +3618,12 @@ impl Hud {
|
||||
} else if let (Crafting(c), Inventory(_)) = (a, b) {
|
||||
// Remove item from crafting input
|
||||
self.show.crafting_fields.recipe_inputs.remove(&c.index);
|
||||
} else if let (Ability(AbilitySlot::Ability(ability)), Hotbar(slot)) = (a, b) {
|
||||
if let Some(Some(HotbarSlotContents::Ability(index))) =
|
||||
self.hotbar.slots.get(slot as usize)
|
||||
{
|
||||
events.push(Event::ChangeAbility(*index, ability));
|
||||
}
|
||||
}
|
||||
},
|
||||
slot::Event::Dropped(from) => {
|
||||
@ -4774,8 +4756,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
|
||||
BuffKind::ProtectingWard => imgs.buff_dmg_red_0,
|
||||
BuffKind::Frenzied => imgs.buff_frenzy_0,
|
||||
BuffKind::Hastened => imgs.buff_haste_0,
|
||||
// TODO: Get unique icon
|
||||
BuffKind::Fortitude => imgs.buff_dmg_red_0,
|
||||
BuffKind::Fortitude => imgs.buff_fortitude_0,
|
||||
// Debuffs
|
||||
BuffKind::Bleeding => imgs.debuff_bleed_0,
|
||||
BuffKind::Cursed => imgs.debuff_skull_0,
|
||||
@ -4785,8 +4766,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
|
||||
BuffKind::Wet => imgs.debuff_wet_0,
|
||||
BuffKind::Ensnared => imgs.debuff_ensnared_0,
|
||||
BuffKind::Poisoned => imgs.debuff_poisoned_0,
|
||||
// TODO: Get unique icon
|
||||
BuffKind::Parried => imgs.debuff_crippled_0,
|
||||
BuffKind::Parried => imgs.debuff_parried_0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,8 @@ use common::comp::{
|
||||
self,
|
||||
ability::AbilityInput,
|
||||
item::{tool::AbilityContext, ItemDesc, MaterialStatManifest},
|
||||
Ability, ActiveAbilities, Body, Energy, Health, Inventory, Poise, PoiseState, SkillSet,
|
||||
Ability, ActiveAbilities, Body, CharacterState, Combo, Energy, Health, Inventory, Poise,
|
||||
PoiseState, SkillSet,
|
||||
};
|
||||
use conrod_core::{
|
||||
color,
|
||||
@ -276,8 +277,10 @@ pub struct Skillbar<'a> {
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
msm: &'a MaterialStatManifest,
|
||||
combo: Option<ComboFloater>,
|
||||
combo_floater: Option<ComboFloater>,
|
||||
context: Option<AbilityContext>,
|
||||
combo: Option<&'a Combo>,
|
||||
char_state: Option<&'a CharacterState>,
|
||||
}
|
||||
|
||||
impl<'a> Skillbar<'a> {
|
||||
@ -306,8 +309,10 @@ impl<'a> Skillbar<'a> {
|
||||
slot_manager: &'a mut slots::SlotManager,
|
||||
localized_strings: &'a Localization,
|
||||
msm: &'a MaterialStatManifest,
|
||||
combo: Option<ComboFloater>,
|
||||
combo_floater: Option<ComboFloater>,
|
||||
context: Option<AbilityContext>,
|
||||
combo: Option<&'a Combo>,
|
||||
char_state: Option<&'a CharacterState>,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
@ -334,8 +339,10 @@ impl<'a> Skillbar<'a> {
|
||||
slot_manager,
|
||||
localized_strings,
|
||||
msm,
|
||||
combo,
|
||||
combo_floater,
|
||||
context,
|
||||
combo,
|
||||
char_state,
|
||||
}
|
||||
}
|
||||
|
||||
@ -605,6 +612,7 @@ impl<'a> Skillbar<'a> {
|
||||
self.active_abilities,
|
||||
self.body,
|
||||
self.context,
|
||||
self.combo,
|
||||
);
|
||||
|
||||
let image_source = (self.item_imgs, self.imgs);
|
||||
@ -686,7 +694,7 @@ impl<'a> Skillbar<'a> {
|
||||
|
||||
// Helper
|
||||
let tooltip_text = |slot| {
|
||||
let (hotbar, inventory, _, skill_set, active_abilities, _, context) = content_source;
|
||||
let (hotbar, inventory, _, skill_set, active_abilities, _, context, _) = content_source;
|
||||
hotbar.get(slot).and_then(|content| match content {
|
||||
hotbar::SlotContents::Inventory(i, _) => inventory
|
||||
.get_by_hash(i)
|
||||
@ -768,11 +776,33 @@ impl<'a> Skillbar<'a> {
|
||||
.active_abilities
|
||||
.and_then(|a| Ability::from(a.primary).ability_id(Some(self.inventory), self.context));
|
||||
|
||||
let primary_ability_id = if let Some(override_id) = self
|
||||
.char_state
|
||||
.and_then(|cs| cs.ability_info())
|
||||
.and_then(|info| info.ability_meta)
|
||||
.and_then(|meta| meta.kind)
|
||||
.map(util::representative_ability_id)
|
||||
{
|
||||
Some(override_id)
|
||||
} else {
|
||||
primary_ability_id
|
||||
};
|
||||
|
||||
let (primary_ability_title, primary_ability_desc) =
|
||||
util::ability_description(primary_ability_id.unwrap_or(""), self.localized_strings);
|
||||
|
||||
Button::image(
|
||||
primary_ability_id.map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)),
|
||||
)
|
||||
.w_h(36.0, 36.0)
|
||||
.middle_of(state.ids.m1_slot_bg)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&primary_ability_title,
|
||||
&primary_ability_desc,
|
||||
&tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.ids.m1_content, ui);
|
||||
// Slot M2
|
||||
Image::new(self.imgs.skillbar_slot)
|
||||
@ -784,31 +814,43 @@ impl<'a> Skillbar<'a> {
|
||||
Ability::from(a.secondary).ability_id(Some(self.inventory), self.context)
|
||||
});
|
||||
|
||||
let (secondary_ability_title, secondary_ability_desc) =
|
||||
util::ability_description(secondary_ability_id.unwrap_or(""), self.localized_strings);
|
||||
|
||||
Button::image(
|
||||
secondary_ability_id.map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)),
|
||||
)
|
||||
.w_h(36.0, 36.0)
|
||||
.middle_of(state.ids.m2_slot_bg)
|
||||
.image_color(
|
||||
if self.energy.current()
|
||||
>= self
|
||||
.active_abilities
|
||||
.and_then(|a| {
|
||||
a.activate_ability(
|
||||
AbilityInput::Secondary,
|
||||
Some(self.inventory),
|
||||
self.skillset,
|
||||
Some(self.body),
|
||||
self.context,
|
||||
)
|
||||
})
|
||||
.map_or(0.0, |(a, _)| a.get_energy_cost())
|
||||
if self
|
||||
.active_abilities
|
||||
.and_then(|a| {
|
||||
a.activate_ability(
|
||||
AbilityInput::Secondary,
|
||||
Some(self.inventory),
|
||||
self.skillset,
|
||||
Some(self.body),
|
||||
self.context,
|
||||
)
|
||||
})
|
||||
.map_or(false, |(a, _)| {
|
||||
self.energy.current() >= a.energy_cost()
|
||||
&& self.combo.map_or(false, |c| c.counter() >= a.combo_cost())
|
||||
})
|
||||
{
|
||||
Color::Rgba(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::Rgba(0.3, 0.3, 0.3, 0.8)
|
||||
},
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&secondary_ability_title,
|
||||
&secondary_ability_desc,
|
||||
&tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.ids.m2_content, ui);
|
||||
|
||||
// M1 and M2 icons
|
||||
@ -822,11 +864,11 @@ impl<'a> Skillbar<'a> {
|
||||
.set(state.ids.m2_ico, ui);
|
||||
}
|
||||
|
||||
fn show_combo_counter(&self, combo: ComboFloater, state: &State, ui: &mut UiCell) {
|
||||
if combo.combo > 0 {
|
||||
let combo_txt = format!("{} Combo", combo.combo);
|
||||
let combo_cnt = combo.combo as f32;
|
||||
let time_since_last_update = comp::combo::COMBO_DECAY_START - combo.timer;
|
||||
fn show_combo_counter(&self, combo_floater: ComboFloater, state: &State, ui: &mut UiCell) {
|
||||
if combo_floater.combo > 0 {
|
||||
let combo_txt = format!("{} Combo", combo_floater.combo);
|
||||
let combo_cnt = combo_floater.combo as f32;
|
||||
let time_since_last_update = comp::combo::COMBO_DECAY_START - combo_floater.timer;
|
||||
let alpha = (1.0 - time_since_last_update * 0.2).min(1.0) as f32;
|
||||
let fnt_col = Color::Rgba(
|
||||
// White -> Yellow -> Red text color gradient depending on count
|
||||
@ -837,7 +879,7 @@ impl<'a> Skillbar<'a> {
|
||||
);
|
||||
// Increase size for higher counts,
|
||||
// "flash" on update by increasing the font size by 2.
|
||||
let fnt_size = ((14.0 + combo.timer as f32 * 0.8).min(30.0)) as u32
|
||||
let fnt_size = ((14.0 + combo_floater.timer as f32 * 0.8).min(30.0)) as u32
|
||||
+ if (time_since_last_update) < 0.1 { 2 } else { 0 };
|
||||
|
||||
Rectangle::fill_with([10.0, 10.0], color::TRANSPARENT)
|
||||
@ -914,8 +956,8 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
self.show_slotbar(state, ui, slot_offset);
|
||||
|
||||
// Combo Counter
|
||||
if let Some(combo) = self.combo {
|
||||
self.show_combo_counter(combo, state, ui);
|
||||
if let Some(combo_floater) = self.combo_floater {
|
||||
self.show_combo_counter(combo_floater, state, ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use common::{
|
||||
ability::{Ability, AbilityInput, AuxiliaryAbility},
|
||||
item::tool::{AbilityContext, ToolKind},
|
||||
slot::InvSlotId,
|
||||
ActiveAbilities, Body, Energy, Inventory, Item, ItemKey, SkillSet,
|
||||
ActiveAbilities, Body, Combo, Energy, Inventory, Item, ItemKey, SkillSet,
|
||||
},
|
||||
recipe::ComponentRecipeBook,
|
||||
};
|
||||
@ -129,6 +129,7 @@ type HotbarSource<'a> = (
|
||||
Option<&'a ActiveAbilities>,
|
||||
&'a Body,
|
||||
Option<AbilityContext>,
|
||||
Option<&'a Combo>,
|
||||
);
|
||||
type HotbarImageSource<'a> = (&'a ItemImgs, &'a img_ids::Imgs);
|
||||
|
||||
@ -137,7 +138,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
|
||||
fn image_key(
|
||||
&self,
|
||||
(hotbar, inventory, energy, skillset, active_abilities, body, context): &HotbarSource<'a>,
|
||||
(hotbar, inventory, energy, skillset, active_abilities, body, context, combo): &HotbarSource<'a>,
|
||||
) -> Option<(Self::ImageKey, Option<Color>)> {
|
||||
const GREYED_OUT: Color = Color::Rgba(0.3, 0.3, 0.3, 0.8);
|
||||
hotbar.get(*self).and_then(|contents| match contents {
|
||||
@ -171,7 +172,10 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
.map(|(ability, _)| {
|
||||
(
|
||||
image,
|
||||
if energy.current() > ability.get_energy_cost() {
|
||||
if energy.current() >= ability.energy_cost()
|
||||
&& combo
|
||||
.map_or(false, |c| c.counter() >= ability.combo_cost())
|
||||
{
|
||||
Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
||||
} else {
|
||||
Some(GREYED_OUT)
|
||||
@ -183,7 +187,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
})
|
||||
}
|
||||
|
||||
fn amount(&self, (hotbar, inventory, _, _, _, _, _): &HotbarSource<'a>) -> Option<u32> {
|
||||
fn amount(&self, (hotbar, inventory, ..): &HotbarSource<'a>) -> Option<u32> {
|
||||
hotbar
|
||||
.get(*self)
|
||||
.and_then(|content| match content {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::img_ids;
|
||||
use common::{
|
||||
comp::{
|
||||
ability::{AbilityKind, SwordStance},
|
||||
inventory::trade_pricing::TradePricing,
|
||||
item::{
|
||||
armor::{Armor, ArmorKind, Protection},
|
||||
@ -418,3 +419,17 @@ pub fn ability_description<'a>(
|
||||
|
||||
(loc.get_msg(&ability), loc.get_attr(&ability, "desc"))
|
||||
}
|
||||
|
||||
pub fn representative_ability_id(ability_kind: AbilityKind) -> &'static str {
|
||||
match ability_kind {
|
||||
AbilityKind::Sword(SwordStance::Balanced) => "common.abilities.sword.balanced_combo",
|
||||
AbilityKind::Sword(SwordStance::Offensive) => "common.abilities.sword.offensive_combo",
|
||||
AbilityKind::Sword(SwordStance::Crippling) => "common.abilities.sword.crippling_combo",
|
||||
AbilityKind::Sword(SwordStance::Cleaving) => "common.abilities.sword.cleaving_combo",
|
||||
AbilityKind::Sword(SwordStance::Defensive) => "common.abilities.sword.defensive_combo",
|
||||
AbilityKind::Sword(SwordStance::Parrying) => "common.abilities.sword.parrying_combo",
|
||||
AbilityKind::Sword(SwordStance::Heavy) => "common.abilities.sword.heavy_combo",
|
||||
AbilityKind::Sword(SwordStance::Mobility) => "common.abilities.sword.mobility_combo",
|
||||
AbilityKind::Sword(SwordStance::Reaching) => "common.abilities.sword.reaching_combo",
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user