Finish rooted debuff impl and Lung Pummel

This commit is contained in:
Sam 2024-02-25 13:42:12 -05:00
parent 76dc196996
commit 38c74bf182
31 changed files with 463 additions and 381 deletions

View File

@ -233,7 +233,7 @@
],
),
Simple(Hammer(PileDriver), "common.abilities.hammer.pile_driver"),
// Simple(Hammer(LungPummel), "common.abilities.hammer.lung_pummel"),
Simple(Hammer(LungPummel), "common.abilities.hammer.lung_pummel"),
// Simple(Hammer(HelmCrusher), "common.abilities.hammer.helm_crusher"),
// Simple(Hammer(Rampart), "common.abilities.hammer.rampart"),
// Simple(Hammer(Tenacity), "common.abilities.hammer.tenacity"),

View File

@ -14,7 +14,7 @@ BasicMelee(
range: 3.0,
angle: 15.0,
attack_effect: Some((Poise(40), TargetBlocking)),
precision_flank_multiplier: 1.5,
precision_flank_multipliers: (back: 1.0, side: 1.0, front: 1.5),
precision_flank_invert: true,
),
ori_modifier: 0.6,

View File

@ -0,0 +1,25 @@
FinisherMelee(
energy_cost: 0,
buildup_duration: 0.15,
swing_duration: 0.15,
recover_duration: 0.2,
melee_constructor: (
kind: Bash(
damage: 20,
poise: 20,
knockback: 12,
energy_regen: 0,
),
range: 4.0,
angle: 60.0,
damage_effect: Some(Buff((
kind: Winded,
dur_secs: 8,
strength: Value(1.0),
chance: 1.0,
))),
precision_flank_multipliers: (front: 1.0, side: 2.0, back: 1.0),
),
minimum_combo: 0,
combo_consumption: Cost,
)

View File

@ -13,7 +13,7 @@ FinisherMelee(
range: 4.0,
angle: 15.0,
attack_effect: Some((Poise(50), BehindTarget)),
precision_flank_multiplier: 2.0,
precision_flank_multipliers: (front: 1.0, side: 1.0, back: 2.0),
),
minimum_combo: 10,
combo_consumption: Cost,

BIN
assets/voxygen/element/de_buffs/debuff_winded.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/skills/hammer/lung_pummel.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -127,6 +127,9 @@ buff-scornfultaunt = Scornful Taunt
## Rooted
buff-rooted = Rooted
.desc = You are stuck in place and cannot move.
## Winded
buff-winded = Winded
.desc = You can barely breathe hampering how much energy you can recover and how quickly you can move.
## Util
buff-text-over_seconds = over { $dur_secs } seconds
buff-text-for_seconds = for { $dur_secs } seconds

View File

@ -416,3 +416,6 @@ common-abilities-hammer-breach = Breach
common-abilities-hammer-pile_driver = Pile Driver
.desc =
Strike your enemy into the ground, stopping their movement until they free their legs.
common-abilities-hammer-lung_pummel = Lung Pummel
.desc =
Swipe your hammer into your foe's side, winding them.

View File

@ -191,6 +191,7 @@ lazy_static! {
BuffKind::Heatstroke => "heatstroke",
BuffKind::ScornfulTaunt => "scornful_taunt",
BuffKind::Rooted => "rooted",
BuffKind::Winded => "winded",
};
let mut buff_parser = HashMap::new();
for kind in BuffKind::iter() {

View File

@ -2,7 +2,7 @@ use crate::{
comp::{
ability::Capability,
aura::{AuraKindVariant, EnteredAuras},
buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource},
buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource, DestInfo},
inventory::{
item::{
armor::Protection,
@ -13,7 +13,7 @@ use crate::{
},
skillset::SkillGroupKind,
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, HealthChange,
Inventory, Ori, Player, Poise, PoiseChange, SkillSet, Stats,
Inventory, Mass, Ori, Player, Poise, PoiseChange, SkillSet, Stats,
},
event::{
BuffEvent, ComboChangeEvent, EmitExt, EnergyChangeEvent, EntityAttackedHookEvent,
@ -72,6 +72,7 @@ pub struct AttackerInfo<'a> {
pub combo: Option<&'a Combo>,
pub inventory: Option<&'a Inventory>,
pub stats: Option<&'a Stats>,
pub mass: Option<&'a Mass>,
}
pub struct TargetInfo<'a> {
@ -85,6 +86,7 @@ pub struct TargetInfo<'a> {
pub char_state: Option<&'a CharacterState>,
pub energy: Option<&'a Energy>,
pub buffs: Option<&'a Buffs>,
pub mass: Option<&'a Mass>,
}
#[derive(Clone, Copy)]
@ -464,8 +466,8 @@ impl Attack {
entity: target.entity,
buff_change: BuffChange::Add(b.to_buff(
time,
attacker.map(|a| a.uid),
target.stats,
attacker,
target,
applied_damage,
strength_modifier,
)),
@ -697,8 +699,8 @@ impl Attack {
entity: target.entity,
buff_change: BuffChange::Add(b.to_buff(
time,
attacker.map(|a| a.uid),
target.stats,
attacker,
target,
accumulated_damage,
strength_modifier,
)),
@ -1322,17 +1324,21 @@ impl CombatBuff {
fn to_buff(
self,
time: Time,
uid: Option<Uid>,
tgt_stats: Option<&Stats>,
attacker_info: Option<AttackerInfo>,
target_info: &TargetInfo,
damage: f32,
strength_modifier: f32,
) -> Buff {
// TODO: Generate BufCategoryId vec (probably requires damage overhaul?)
let source = if let Some(uid) = uid {
let source = if let Some(uid) = attacker_info.map(|a| a.uid) {
BuffSource::Character { by: uid }
} else {
BuffSource::Unknown
};
let dest_info = DestInfo {
stats: target_info.stats,
mass: target_info.mass,
};
Buff::new(
self.kind,
BuffData::new(
@ -1342,7 +1348,8 @@ impl CombatBuff {
Vec::new(),
source,
time,
tgt_stats,
dest_info,
attacker_info.and_then(|a| a.mass),
)
}
}
@ -1646,7 +1653,7 @@ pub fn compute_poise_resilience(
pub fn precision_mult_from_flank(
attack_dir: Vec3<f32>,
target_ori: Option<&Ori>,
precision_flank_multiplier: f32,
precision_flank_multipliers: FlankMults,
precision_flank_invert: bool,
) -> Option<f32> {
let angle = target_ori.map(|t_ori| {
@ -1657,16 +1664,38 @@ pub fn precision_mult_from_flank(
})
});
match angle {
Some(angle) if angle < FULL_FLANK_ANGLE => {
Some(MAX_BACK_FLANK_PRECISION * precision_flank_multiplier)
},
Some(angle) if angle < FULL_FLANK_ANGLE => Some(
MAX_BACK_FLANK_PRECISION
* if precision_flank_invert {
precision_flank_multipliers.front
} else {
precision_flank_multipliers.back
},
),
Some(angle) if angle < PARTIAL_FLANK_ANGLE => {
Some(MAX_SIDE_FLANK_PRECISION * precision_flank_multiplier)
Some(MAX_SIDE_FLANK_PRECISION * precision_flank_multipliers.side)
},
Some(_) | None => None,
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FlankMults {
pub back: f32,
pub front: f32,
pub side: f32,
}
impl Default for FlankMults {
fn default() -> Self {
FlankMults {
back: 1.0,
front: 1.0,
side: 1.0,
}
}
}
pub fn block_strength(inventory: &Inventory, char_state: &CharacterState) -> f32 {
match char_state {
CharacterState::BasicBlock(data) => data.static_data.block_strength,

View File

@ -1113,7 +1113,7 @@ impl Default for CharacterAbility {
attack_effect: None,
simultaneous_hits: 1,
custom_combo: None,
precision_flank_multiplier: 1.0,
precision_flank_multipliers: Default::default(),
precision_flank_invert: false,
},
ori_modifier: 1.0,

View File

@ -4,7 +4,7 @@ use crate::{
AttackEffect, CombatBuff, CombatBuffStrength, CombatEffect, CombatRequirement,
DamagedEffect, DeathEffect,
},
comp::{aura::AuraKey, Stats},
comp::{aura::AuraKey, Mass, Stats},
resources::{Secs, Time},
uid::Uid,
};
@ -185,6 +185,11 @@ pub enum BuffKind {
/// is higher than the strength allows for, duration gets reduced using a
/// mutiplier from the ratio of masses.
Rooted,
/// Slows movement speed and reduces energy reward
/// Both scale non-linearly with strength, 0.5 leads to 50% reduction of
/// energy reward and 33% reduction of move speed. 1.0 leads to 67%
/// reduction of energy reward and 50% reduction of move speed.
Winded,
// Complex, non-obvious buffs
/// Changed into another body.
Polymorphed,
@ -248,7 +253,8 @@ impl BuffKind {
| BuffKind::Parried
| BuffKind::PotionSickness
| BuffKind::Heatstroke
| BuffKind::Rooted => BuffDescriptor::SimpleNegative,
| BuffKind::Rooted
| BuffKind::Winded => BuffDescriptor::SimpleNegative,
BuffKind::Polymorphed => BuffDescriptor::Complex,
}
}
@ -286,7 +292,10 @@ impl BuffKind {
pub fn effects(&self, data: &BuffData, stats: Option<&Stats>) -> Vec<BuffEffect> {
// Normalized nonlinear scaling
// TODO: Do we want to make denominator term parameterized. Come back to if we
// add nn_scaling3.
let nn_scaling = |a| a / (a + 0.5);
let nn_scaling2 = |a| a / (a + 1.0);
let instance = rand::random();
match self {
BuffKind::Bleeding => vec![BuffEffect::HealthChangeOverTime {
@ -489,6 +498,10 @@ impl BuffKind {
}),
],
BuffKind::Rooted => vec![BuffEffect::MovementSpeed(0.0)],
BuffKind::Winded => vec![
BuffEffect::MovementSpeed(1.0 - nn_scaling2(data.strength)),
BuffEffect::EnergyReward(1.0 - nn_scaling(data.strength)),
],
}
}
@ -504,13 +517,18 @@ impl BuffKind {
cat_ids
}
fn modify_data(&self, mut data: BuffData) -> BuffData {
fn modify_data(
&self,
mut data: BuffData,
source_mass: Option<&Mass>,
dest_mass: Option<&Mass>,
) -> BuffData {
// TODO: Remove clippy allow after another buff needs this
#[allow(clippy::single_match)]
match self {
BuffKind::Rooted => {
let source_mass = 50.0;
let dest_mass = 50.0_f64;
let source_mass = source_mass.map_or(50.0, |m| m.0 as f64);
let dest_mass = dest_mass.map_or(50.0, |m| m.0 as f64);
let ratio = (source_mass / dest_mass).min(1.0);
data.duration = data.duration.map(|dur| Secs(dur.0 * ratio));
},
@ -710,10 +728,12 @@ impl Buff {
cat_ids: Vec<BuffCategory>,
source: BuffSource,
time: Time,
stats: Option<&Stats>,
dest_info: DestInfo,
// Create source_info if we need more parameters from source
source_mass: Option<&Mass>,
) -> Self {
let data = kind.modify_data(data);
let effects = kind.effects(&data, stats);
let data = kind.modify_data(data, source_mass, dest_info.mass);
let effects = kind.effects(&data, dest_info.stats);
let cat_ids = kind.extend_cat_ids(cat_ids);
let start_time = Time(time.0 + data.delay.map_or(0.0, |delay| delay.0));
let end_time = if cat_ids
@ -958,6 +978,12 @@ impl Component for Buffs {
type Storage = DerefFlaggedStorage<Self, VecStorage<Self>>;
}
#[derive(Default, Copy, Clone)]
pub struct DestInfo<'a> {
pub stats: Option<&'a Stats>,
pub mass: Option<&'a Mass>,
}
#[cfg(test)]
pub mod tests {
use crate::comp::buff::*;
@ -973,6 +999,7 @@ pub mod tests {
Vec::new(),
BuffSource::Unknown,
time,
DestInfo::default(),
None,
)
}

View File

@ -1,7 +1,8 @@
use crate::{
combat::{
Attack, AttackDamage, AttackEffect, CombatBuff, CombatBuffStrength, CombatEffect,
CombatRequirement, Damage, DamageKind, DamageSource, GroupTarget, Knockback, KnockbackDir,
CombatRequirement, Damage, DamageKind, DamageSource, FlankMults, GroupTarget, Knockback,
KnockbackDir,
},
comp::{
buff::BuffKind,
@ -23,7 +24,7 @@ pub struct Melee {
pub multi_target: Option<MultiTarget>,
pub break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
pub simultaneous_hits: u32,
pub precision_flank_multiplier: f32,
pub precision_flank_multipliers: FlankMults,
pub precision_flank_invert: bool,
}
@ -53,7 +54,6 @@ impl Component for Melee {
}
fn default_simultaneous_hits() -> u32 { 1 }
fn default_precision_flank_multiplier() -> f32 { 1.0 }
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Scaled {
@ -80,8 +80,8 @@ pub struct MeleeConstructor {
#[serde(default = "default_simultaneous_hits")]
pub simultaneous_hits: u32,
pub custom_combo: Option<CustomCombo>,
#[serde(default = "default_precision_flank_multiplier")]
pub precision_flank_multiplier: f32,
#[serde(default)]
pub precision_flank_multipliers: FlankMults,
#[serde(default)]
pub precision_flank_invert: bool,
}
@ -393,7 +393,7 @@ impl MeleeConstructor {
multi_target: self.multi_target,
break_block: None,
simultaneous_hits: self.simultaneous_hits,
precision_flank_multiplier: self.precision_flank_multiplier,
precision_flank_multipliers: self.precision_flank_multipliers,
precision_flank_invert: self.precision_flank_invert,
}
}

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
buff::{Buff, BuffCategory, BuffChange, BuffData, BuffKind, BuffSource},
buff::{Buff, BuffCategory, BuffChange, BuffData, BuffKind, BuffSource, DestInfo},
character_state::OutputEvents,
CharacterState, StateUpdate,
},
@ -116,6 +116,11 @@ impl CharacterBehavior for Data {
});
}
let dest_info = DestInfo {
stats: Some(data.stats),
mass: Some(data.mass),
};
// Creates buff
let buff = Buff::new(
self.static_data.buff_kind,
@ -126,7 +131,8 @@ impl CharacterBehavior for Data {
buff_cat_ids,
BuffSource::Character { by: *data.uid },
*data.time,
Some(data.stats),
dest_info,
Some(data.mass),
);
output_events.emit_server(BuffEvent {
entity: data.entity,

View File

@ -3,7 +3,7 @@ use crate::{
comp::{
ability::{AbilityInitEvent, AbilityMeta, Capability, SpecifiedAbility, Stance},
arthropod, biped_large, biped_small, bird_medium,
buff::{Buff, BuffCategory, BuffChange, BuffData, BuffSource},
buff::{Buff, BuffCategory, BuffChange, BuffData, BuffSource, DestInfo},
character_state::OutputEvents,
controller::InventoryManip,
golem,
@ -1332,6 +1332,10 @@ fn handle_ability(
strength,
duration,
} => {
let dest_info = DestInfo {
stats: Some(data.stats),
mass: Some(data.mass),
};
output_events.emit_server(BuffEvent {
entity: data.entity,
buff_change: BuffChange::Add(Buff::new(
@ -1340,7 +1344,8 @@ fn handle_ability(
vec![BuffCategory::SelfBuff],
BuffSource::Character { by: *data.uid },
*data.time,
Some(data.stats),
dest_info,
Some(data.mass),
)),
});
},

View File

@ -4,9 +4,9 @@ use common::{
combat,
comp::{
aura::{AuraChange, AuraKey, AuraKind, AuraTarget, EnteredAuras},
buff::{Buff, BuffCategory, BuffChange, BuffSource},
buff::{Buff, BuffCategory, BuffChange, BuffSource, DestInfo},
group::Group,
Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, Stats,
Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Mass, Player, Pos, Stats,
},
event::{AuraEvent, BuffEvent, EmitExt},
event_emitters,
@ -41,6 +41,7 @@ pub struct ReadData<'a> {
buffs: ReadStorage<'a, Buffs>,
auras: ReadStorage<'a, Auras>,
entered_auras: ReadStorage<'a, EnteredAuras>,
masses: ReadStorage<'a, Mass>,
}
#[derive(Default)]
@ -86,80 +87,74 @@ impl<'a> System<'a> for Sys {
read_data.healths.get(target)?,
read_data.uids.get(target)?,
read_data.entered_auras.get(target)?,
read_data.stats.get(target),
))
})
});
target_iter.for_each(
|(target, target_pos, health, target_uid, entered_auras, stats)| {
let target_buffs = match read_data.buffs.get(target) {
Some(buff) => buff,
None => return,
target_iter.for_each(|(target, target_pos, health, target_uid, entered_auras)| {
let target_buffs = match read_data.buffs.get(target) {
Some(buff) => buff,
None => return,
};
// Ensure entity is within the aura radius
if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) {
// Ensure the entity is in the group we want to target
let same_group = |uid: Uid| {
read_data
.id_maps
.uid_entity(uid)
.and_then(|e| read_data.groups.get(e))
.map_or(false, |owner_group| {
Some(owner_group) == read_data.groups.get(target)
})
|| *target_uid == uid
};
// Ensure entity is within the aura radius
if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) {
// Ensure the entity is in the group we want to target
let same_group = |uid: Uid| {
read_data
.id_maps
.uid_entity(uid)
.and_then(|e| read_data.groups.get(e))
.map_or(false, |owner_group| {
Some(owner_group) == read_data.groups.get(target)
})
|| *target_uid == uid
};
let allow_friendly_fire =
combat::allow_friendly_fire(&read_data.entered_auras, entity, target);
let allow_friendly_fire = combat::allow_friendly_fire(
&read_data.entered_auras,
entity,
target,
);
if !(allow_friendly_fire && entity != target
|| match aura.target {
AuraTarget::GroupOf(uid) => same_group(uid),
AuraTarget::NotGroupOf(uid) => !same_group(uid),
AuraTarget::All => true,
})
{
return;
}
let did_activate = activate_aura(
key,
aura,
*uid,
target,
health,
target_buffs,
stats,
allow_friendly_fire,
&read_data,
&mut emitters,
);
if did_activate {
if entered_auras
.auras
.get(aura.aura_kind.as_ref())
.map_or(true, |auras| !auras.contains(&(*uid, key)))
{
emitters.emit(AuraEvent {
entity: target,
aura_change: AuraChange::EnterAura(
*uid,
key,
*aura.aura_kind.as_ref(),
),
});
}
active_auras.insert((*uid, *target_uid, key));
}
if !(allow_friendly_fire && entity != target
|| match aura.target {
AuraTarget::GroupOf(uid) => same_group(uid),
AuraTarget::NotGroupOf(uid) => !same_group(uid),
AuraTarget::All => true,
})
{
return;
}
},
);
let did_activate = activate_aura(
key,
aura,
entity,
*uid,
target,
health,
target_buffs,
allow_friendly_fire,
&read_data,
&mut emitters,
);
if did_activate {
if entered_auras
.auras
.get(aura.aura_kind.as_ref())
.map_or(true, |auras| !auras.contains(&(*uid, key)))
{
emitters.emit(AuraEvent {
entity: target,
aura_change: AuraChange::EnterAura(
*uid,
key,
*aura.aura_kind.as_ref(),
),
});
}
active_auras.insert((*uid, *target_uid, key));
}
}
});
}
if !expired_auras.is_empty() {
emitters.emit(AuraEvent {
@ -200,11 +195,11 @@ impl<'a> System<'a> for Sys {
fn activate_aura(
key: AuraKey,
aura: &Aura,
applier: Uid,
applier: EcsEntity,
applier_uid: Uid,
target: EcsEntity,
health: &Health,
target_buffs: &Buffs,
stats: Option<&Stats>,
allow_friendly_fire: bool,
read_data: &ReadData,
emitters: &mut impl EmitExt<BuffEvent>,
@ -286,20 +281,25 @@ fn activate_aura(
let emit_buff = !target_buffs.buffs.iter().any(|(_, buff)| {
buff.cat_ids
.iter()
.any(|cat_id| matches!(cat_id, BuffCategory::FromActiveAura(uid, aura_key) if *aura_key == key && *uid == applier))
.any(|cat_id| matches!(cat_id, BuffCategory::FromActiveAura(uid, aura_key) if *aura_key == key && *uid == applier_uid))
&& buff.kind == kind
&& buff.data.strength >= data.strength
});
if emit_buff {
let dest_info = DestInfo {
stats: read_data.stats.get(target),
mass: read_data.masses.get(target),
};
emitters.emit(BuffEvent {
entity: target,
buff_change: BuffChange::Add(Buff::new(
kind,
data,
vec![category, BuffCategory::FromActiveAura(applier, key)],
vec![category, BuffCategory::FromActiveAura(applier_uid, key)],
source,
*read_data.time,
stats,
dest_info,
read_data.masses.get(applier),
)),
});
}

View File

@ -3,8 +3,8 @@ use common::{
comp::{
agent::{Sound, SoundKind},
aura::EnteredAuras,
Alignment, Beam, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
Player, Pos, Scale, Stats,
Alignment, Beam, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory,
Mass, Ori, Player, Pos, Scale, Stats,
},
event::{self, EmitExt, EventBus},
event_emitters,
@ -63,6 +63,7 @@ pub struct ReadData<'a> {
entered_auras: ReadStorage<'a, EnteredAuras>,
outcomes: Read<'a, EventBus<Outcome>>,
events: ReadAttackEvents<'a>,
masses: ReadStorage<'a, Mass>,
}
/// This system is responsible for handling beams that heal or do damage
@ -230,6 +231,7 @@ impl<'a> System<'a> for Sys {
combo: read_data.combos.get(entity),
inventory: read_data.inventories.get(entity),
stats: read_data.stats.get(entity),
mass: read_data.masses.get(entity),
});
let target_info = TargetInfo {
@ -243,6 +245,7 @@ impl<'a> System<'a> for Sys {
char_state: read_data.character_states.get(target),
energy: read_data.energies.get(target),
buffs: read_data.buffs.get(target),
mass: read_data.masses.get(target),
};
let target_dodging = read_data
@ -263,7 +266,7 @@ impl<'a> System<'a> for Sys {
let precision_from_flank = combat::precision_mult_from_flank(
beam.bezier.ctrl - beam.bezier.start,
target_info.ori,
1.0,
Default::default(),
false,
);

View File

@ -6,12 +6,12 @@ use common::{
body::{object, Body},
buff::{
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffKey, BuffKind, BuffSource,
Buffs,
Buffs, DestInfo,
},
fluid_dynamics::{Fluid, LiquidKind},
item::MaterialStatManifest,
Alignment, Energy, Group, Health, HealthChange, Inventory, LightEmitter, ModifierKind,
PhysicsState, Player, Pos, Stats,
Alignment, Energy, Group, Health, HealthChange, Inventory, LightEmitter, Mass,
ModifierKind, PhysicsState, Player, Pos, Stats,
},
event::{
BuffEvent, ChangeBodyEvent, CreateSpriteEvent, EmitExt, EnergyChangeEvent,
@ -68,6 +68,7 @@ pub struct ReadData<'a> {
alignments: ReadStorage<'a, Alignment>,
players: ReadStorage<'a, Player>,
uids: ReadStorage<'a, Uid>,
masses: ReadStorage<'a, Mass>,
}
#[derive(Default)]
@ -153,10 +154,16 @@ impl<'a> System<'a> for Sys {
&read_data.energies,
read_data.uids.maybe(),
read_data.physics_states.maybe(),
read_data.masses.maybe(),
)
.lend_join();
buff_join.for_each(|comps| {
let (entity, buff_comp, mut stat, body, health, energy, uid, physics_state) = comps;
let (entity, buff_comp, mut stat, body, health, energy, uid, physics_state, mass) =
comps;
let dest_info = DestInfo {
stats: Some(&stat),
mass,
};
// Apply buffs to entity based off of their current physics_state
if let Some(physics_state) = physics_state {
// Set nearby entities on fire if burning
@ -185,7 +192,13 @@ impl<'a> System<'a> for Sys {
vec![BuffCategory::Natural],
source,
*read_data.time,
None,
DestInfo {
// Can't mutably access stats, and for burning debuff stats
// has no effect (for now)
stats: None,
mass: read_data.masses.get(t_entity),
},
mass,
)),
});
}
@ -204,7 +217,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}
@ -221,7 +235,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}
@ -251,7 +266,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}
@ -269,7 +285,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}
@ -286,7 +303,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}
@ -303,7 +321,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
// When standing on IceSpike also apply Frozen
@ -315,7 +334,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}
@ -332,7 +352,8 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}
@ -353,7 +374,8 @@ impl<'a> System<'a> for Sys {
vec![BuffCategory::Natural],
BuffSource::World,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
} else if matches!(
@ -429,7 +451,8 @@ impl<'a> System<'a> for Sys {
.collect::<Vec<_>>(),
buff.source,
*read_data.time,
Some(&stat),
dest_info,
None,
)),
});
}

View File

@ -4,8 +4,8 @@ use common::{
agent::{Sound, SoundKind},
aura::EnteredAuras,
melee::MultiTarget,
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Melee,
Ori, Player, Pos, Scale, Stats,
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Mass,
Melee, Ori, Player, Pos, Scale, Stats,
},
event::{self, EmitExt, EventBus},
event_emitters,
@ -62,6 +62,7 @@ pub struct ReadData<'a> {
buffs: ReadStorage<'a, Buffs>,
entered_auras: ReadStorage<'a, EnteredAuras>,
events: ReadAttackEvents<'a>,
masses: ReadStorage<'a, Mass>,
}
/// This system is responsible for handling accepted inputs like moving or
@ -213,6 +214,7 @@ impl<'a> System<'a> for Sys {
combo: read_data.combos.get(attacker),
inventory: read_data.inventories.get(attacker),
stats: read_data.stats.get(attacker),
mass: read_data.masses.get(attacker),
});
let target_ori = read_data.orientations.get(target);
@ -228,6 +230,7 @@ impl<'a> System<'a> for Sys {
char_state: target_char_state,
energy: read_data.energies.get(target),
buffs: read_data.buffs.get(target),
mass: read_data.masses.get(target),
};
// PvP check
@ -248,7 +251,7 @@ impl<'a> System<'a> for Sys {
.try_normalized()
.unwrap_or(ori.look_vec()),
target_ori,
melee_attack.precision_flank_multiplier,
melee_attack.precision_flank_multipliers,
melee_attack.precision_flank_invert,
);

View File

@ -4,7 +4,7 @@ use common::{
agent::{Sound, SoundKind},
aura::EnteredAuras,
projectile, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health,
Inventory, Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel,
Inventory, Mass, Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel,
},
event::{
BonkEvent, BuffEvent, ComboChangeEvent, DeleteEvent, EmitExt, Emitter, EnergyChangeEvent,
@ -73,6 +73,7 @@ pub struct ReadData<'a> {
terrain: ReadExpect<'a, TerrainGrid>,
buffs: ReadStorage<'a, Buffs>,
entered_auras: ReadStorage<'a, EnteredAuras>,
masses: ReadStorage<'a, Mass>,
}
/// This system is responsible for handling projectile effect triggers
@ -340,6 +341,7 @@ fn dispatch_hit(
combo: read_data.combos.get(entity),
inventory: read_data.inventories.get(entity),
stats: read_data.stats.get(entity),
mass: read_data.masses.get(entity),
});
let target_info = TargetInfo {
@ -353,6 +355,7 @@ fn dispatch_hit(
char_state: read_data.character_states.get(target),
energy: read_data.energies.get(target),
buffs: read_data.buffs.get(target),
mass: read_data.masses.get(target),
};
// TODO: Is it possible to have projectile without body??
@ -389,8 +392,12 @@ fn dispatch_hit(
.and_then(|cs| cs.attack_immunities())
.map_or(false, |i| i.projectiles);
let precision_from_flank =
combat::precision_mult_from_flank(*projectile_dir, target_info.ori, 1.0, false);
let precision_from_flank = combat::precision_mult_from_flank(
*projectile_dir,
target_info.ori,
Default::default(),
false,
);
let precision_from_head = {
// This performs a cylinder and line segment intersection check. The cylinder is

View File

@ -4,7 +4,7 @@ use common::{
agent::{Sound, SoundKind},
aura::EnteredAuras,
shockwave::ShockwaveDodgeable,
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Mass, Ori,
PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
},
event::{
@ -64,6 +64,7 @@ pub struct ReadData<'a> {
character_states: ReadStorage<'a, CharacterState>,
buffs: ReadStorage<'a, Buffs>,
entered_auras: ReadStorage<'a, EnteredAuras>,
masses: ReadStorage<'a, Mass>,
}
/// This system is responsible for handling accepted inputs like moving or
@ -233,6 +234,7 @@ impl<'a> System<'a> for Sys {
combo: read_data.combos.get(entity),
inventory: read_data.inventories.get(entity),
stats: read_data.stats.get(entity),
mass: read_data.masses.get(entity),
});
let target_info = TargetInfo {
@ -246,6 +248,7 @@ impl<'a> System<'a> for Sys {
char_state: read_data.character_states.get(target),
energy: read_data.energies.get(target),
buffs: read_data.buffs.get(target),
mass: read_data.masses.get(target),
};
let target_dodging = read_data

View File

@ -30,7 +30,7 @@ use common::{
comp::{
self,
aura::{AuraKindVariant, AuraTarget},
buff::{Buff, BuffData, BuffKind, BuffSource, MiscBuffData},
buff::{Buff, BuffData, BuffKind, BuffSource, DestInfo, MiscBuffData},
inventory::{
item::{all_items_expect, tool::AbilityMap, MaterialStatManifest, Quality},
slot::Slot,
@ -4552,7 +4552,8 @@ fn build_buff(
| BuffKind::PotionSickness
| BuffKind::Heatstroke
| BuffKind::ScornfulTaunt
| BuffKind::Rooted => {
| BuffKind::Rooted
| BuffKind::Winded => {
if buff_kind.is_simple() {
unreachable!("is_simple() above")
} else {
@ -4569,8 +4570,13 @@ fn cast_buff(buffkind: BuffKind, data: BuffData, server: &mut Server, target: Ec
let ecs = &server.state.ecs();
let mut buffs_all = ecs.write_storage::<comp::Buffs>();
let stats = ecs.read_storage::<comp::Stats>();
let masses = ecs.read_storage::<comp::Mass>();
let time = ecs.read_resource::<Time>();
if let Some(mut buffs) = buffs_all.get_mut(target) {
let dest_info = DestInfo {
stats: stats.get(target),
mass: masses.get(target),
};
buffs.insert(
Buff::new(
buffkind,
@ -4578,7 +4584,8 @@ fn cast_buff(buffkind: BuffKind, data: BuffData, server: &mut Server, target: Ec
vec![],
BuffSource::Command,
*time,
stats.get(target),
dest_info,
None,
),
*time,
);

View File

@ -332,6 +332,7 @@ pub struct DestroyEventData<'a> {
#[cfg(feature = "worldgen")]
presences: ReadStorage<'a, Presence>,
buff_events: Read<'a, EventBus<BuffEvent>>,
masses: ReadStorage<'a, comp::Mass>,
}
/// Handle an entity dying. If it is a player, it will send a message to all
@ -389,6 +390,7 @@ impl ServerEvent for DestroyEvent {
let attacker_entity = ev.cause.by.and_then(|x| data.id_maps.uid_entity(x.uid()));
let killed_uid = data.uids.get(ev.entity);
let attacker_stats = attacker_entity.and_then(|e| data.stats.get(e));
let attacker_mass = attacker_entity.and_then(|e| data.masses.get(e));
for effect in killed_stats.effects_on_death.iter() {
match effect {
DeathEffect::AttackerBuff {
@ -397,6 +399,10 @@ impl ServerEvent for DestroyEvent {
duration,
} => {
if let Some(attacker) = attacker_entity {
let dest_info = buff::DestInfo {
stats: attacker_stats,
mass: attacker_mass,
};
buff_emitter.emit(BuffEvent {
entity: attacker,
buff_change: buff::BuffChange::Add(buff::Buff::new(
@ -409,7 +415,8 @@ impl ServerEvent for DestroyEvent {
BuffSource::World
},
*data.time,
attacker_stats,
dest_info,
data.masses.get(ev.entity),
)),
});
}
@ -976,80 +983,54 @@ event_emitters! {
}
}
impl ServerEvent for ExplosionEvent {
type SystemData<'a> = (
Entities<'a>,
Write<'a, BlockChange>,
Read<'a, Settings>,
Read<'a, Time>,
Read<'a, IdMaps>,
Read<'a, CachedSpatialGrid>,
ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, MaterialStatManifest>,
ReadExplosionEvents<'a>,
Read<'a, EventBus<Outcome>>,
ReadStorage<'a, Group>,
ReadStorage<'a, Auras>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Player>,
ReadStorage<'a, Energy>,
ReadStorage<'a, comp::Combo>,
ReadStorage<'a, Inventory>,
ReadStorage<'a, Alignment>,
ReadStorage<'a, EnteredAuras>,
ReadStorage<'a, comp::Buffs>,
ReadStorage<'a, comp::Stats>,
ReadStorage<'a, Health>,
ReadStorage<'a, Body>,
ReadStorage<'a, comp::Ori>,
ReadStorage<'a, CharacterState>,
ReadStorage<'a, Uid>,
);
#[derive(SystemData)]
pub struct ExplosionData<'a> {
entities: Entities<'a>,
block_change: Write<'a, BlockChange>,
settings: Read<'a, Settings>,
time: Read<'a, Time>,
id_maps: Read<'a, IdMaps>,
spatial_grid: Read<'a, CachedSpatialGrid>,
terrain: ReadExpect<'a, TerrainGrid>,
msm: ReadExpect<'a, MaterialStatManifest>,
event_busses: ReadExplosionEvents<'a>,
outcomes: Read<'a, EventBus<Outcome>>,
groups: ReadStorage<'a, Group>,
auras: ReadStorage<'a, Auras>,
positions: ReadStorage<'a, Pos>,
players: ReadStorage<'a, Player>,
energies: ReadStorage<'a, Energy>,
combos: ReadStorage<'a, comp::Combo>,
inventories: ReadStorage<'a, Inventory>,
alignments: ReadStorage<'a, Alignment>,
entered_auras: ReadStorage<'a, EnteredAuras>,
buffs: ReadStorage<'a, comp::Buffs>,
stats: ReadStorage<'a, comp::Stats>,
healths: ReadStorage<'a, Health>,
bodies: ReadStorage<'a, Body>,
orientations: ReadStorage<'a, comp::Ori>,
character_states: ReadStorage<'a, CharacterState>,
uids: ReadStorage<'a, Uid>,
masses: ReadStorage<'a, comp::Mass>,
}
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(
entities,
mut block_change,
settings,
time,
id_maps,
spatial_grid,
terrain,
msm,
event_busses,
outcomes,
groups,
auras,
positions,
players,
energies,
combos,
inventories,
alignments,
entered_auras,
buffs,
stats,
healths,
bodies,
orientations,
character_states,
uids,
): Self::SystemData<'_>,
) {
let mut emitters = event_busses.get_emitters();
let mut outcome_emitter = outcomes.emitter();
impl ServerEvent for ExplosionEvent {
type SystemData<'a> = ExplosionData<'a>;
fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
let mut emitters = data.event_busses.get_emitters();
let mut outcome_emitter = data.outcomes.emitter();
// TODO: Faster RNG?
let mut rng = rand::thread_rng();
for ev in events {
let owner_entity = ev.owner.and_then(|uid| id_maps.uid_entity(uid));
let owner_entity = ev.owner.and_then(|uid| data.id_maps.uid_entity(uid));
let explosion_volume = 6.25 * ev.explosion.radius;
emitters.emit(SoundEvent {
sound: Sound::new(SoundKind::Explosion, ev.pos, explosion_volume, time.0),
sound: Sound::new(SoundKind::Explosion, ev.pos, explosion_volume, data.time.0),
});
let outcome_power = ev.explosion.radius;
@ -1105,14 +1086,15 @@ impl ServerEvent for ExplosionEvent {
const RAYS: usize = 500;
// Prevent block colour changes within the radius of a safe zone aura
if spatial_grid
if data
.spatial_grid
.0
.in_circle_aabr(ev.pos.xy(), SAFE_ZONE_RADIUS)
.filter_map(|entity| {
auras
data.auras
.get(entity)
.and_then(|entity_auras| {
positions.get(entity).map(|pos| (entity_auras, pos))
data.positions.get(entity).map(|pos| (entity_auras, pos))
})
.and_then(|(entity_auras, pos)| {
entity_auras
@ -1146,7 +1128,8 @@ impl ServerEvent for ExplosionEvent {
)
.normalized();
let _ = terrain
let _ = data
.terrain
.ray(ev.pos, ev.pos + dir * color_range)
.until(|_| rng.gen::<f32>() < 0.05)
.for_each(|_: &Block, pos| touched_blocks.push(pos))
@ -1154,14 +1137,14 @@ impl ServerEvent for ExplosionEvent {
}
for block_pos in touched_blocks {
if let Ok(block) = terrain.get(block_pos) {
if let Ok(block) = data.terrain.get(block_pos) {
if !matches!(block.kind(), BlockKind::Lava | BlockKind::GlowingRock)
&& (
// Check that owner is not player or explosion_burn_marks by
// players
// is enabled
owner_entity.map_or(true, |e| players.get(e).is_none())
|| settings.gameplay.explosion_burn_marks
owner_entity.map_or(true, |e| data.players.get(e).is_none())
|| data.settings.gameplay.explosion_burn_marks
)
{
let diff2 =
@ -1184,7 +1167,7 @@ impl ServerEvent for ExplosionEvent {
color[0] = (r as u8).max(30);
color[1] = (g as u8).max(30);
color[2] = (b as u8).max(30);
block_change
data.block_change
.set(block_pos, Block::new(block.kind(), color));
}
}
@ -1212,7 +1195,8 @@ impl ServerEvent for ExplosionEvent {
let from = ev.pos;
let to = ev.pos + dir * power;
let _ = terrain
let _ = data
.terrain
.ray(from, to)
.while_(|block: &Block| {
ray_energy -= block.explode_power().unwrap_or(0.0)
@ -1228,7 +1212,7 @@ impl ServerEvent for ExplosionEvent {
})
.for_each(|block: &Block, pos| {
if block.explode_power().is_some() {
block_change.set(pos, block.into_vacant());
data.block_change.set(pos, block.into_vacant());
}
})
.cast();
@ -1241,14 +1225,14 @@ impl ServerEvent for ExplosionEvent {
health_b,
(body_b_maybe, ori_b_maybe, char_state_b_maybe, uid_b),
) in (
&entities,
&positions,
&healths,
&data.entities,
&data.positions,
&data.healths,
(
bodies.maybe(),
orientations.maybe(),
character_states.maybe(),
&uids,
data.bodies.maybe(),
data.orientations.maybe(),
data.character_states.maybe(),
&data.uids,
),
)
.join()
@ -1271,7 +1255,8 @@ impl ServerEvent for ExplosionEvent {
// Cast a ray from the explosion to the entity to check visibility
if strength > 0.0
&& (terrain
&& (data
.terrain
.ray(ev.pos, pos_b.0)
.until(Block::is_opaque)
.cast()
@ -1282,8 +1267,8 @@ impl ServerEvent for ExplosionEvent {
{
// See if entities are in the same group
let same_group = owner_entity
.and_then(|e| groups.get(e))
.map(|group_a| Some(group_a) == groups.get(entity_b))
.and_then(|e| data.groups.get(e))
.map(|group_a| Some(group_a) == data.groups.get(entity_b))
.unwrap_or(Some(entity_b) == owner_entity);
let target_group = if same_group {
@ -1303,25 +1288,27 @@ impl ServerEvent for ExplosionEvent {
combat::AttackerInfo {
entity,
uid,
group: groups.get(entity),
energy: energies.get(entity),
combo: combos.get(entity),
inventory: inventories.get(entity),
stats: stats.get(entity),
group: data.groups.get(entity),
energy: data.energies.get(entity),
combo: data.combos.get(entity),
inventory: data.inventories.get(entity),
stats: data.stats.get(entity),
mass: data.masses.get(entity),
}
});
let target_info = combat::TargetInfo {
entity: entity_b,
uid: *uid_b,
inventory: inventories.get(entity_b),
stats: stats.get(entity_b),
inventory: data.inventories.get(entity_b),
stats: data.stats.get(entity_b),
health: Some(health_b),
pos: pos_b.0,
ori: ori_b_maybe,
char_state: char_state_b_maybe,
energy: energies.get(entity_b),
buffs: buffs.get(entity_b),
energy: data.energies.get(entity_b),
buffs: data.buffs.get(entity_b),
mass: data.masses.get(entity_b),
};
let target_dodging = char_state_b_maybe
@ -1330,17 +1317,17 @@ impl ServerEvent for ExplosionEvent {
let allow_friendly_fire =
owner_entity.is_some_and(|owner_entity| {
combat::allow_friendly_fire(
&entered_auras,
&data.entered_auras,
owner_entity,
entity_b,
)
});
// PvP check
let permit_pvp = combat::permit_pvp(
&alignments,
&players,
&entered_auras,
&id_maps,
&data.alignments,
&data.players,
&data.entered_auras,
&data.id_maps,
owner_entity,
entity_b,
);
@ -1359,7 +1346,7 @@ impl ServerEvent for ExplosionEvent {
attack_options,
strength,
combat::AttackSource::Explosion,
*time,
*data.time,
&mut emitters,
|o| outcome_emitter.emit(o),
&mut rng,
@ -1370,7 +1357,7 @@ impl ServerEvent for ExplosionEvent {
},
RadiusEffect::Entity(mut effect) => {
for (entity_b, pos_b, body_b_maybe) in
(&entities, &positions, bodies.maybe()).join()
(&data.entities, &data.positions, data.bodies.maybe()).join()
{
let strength = if let Some(body) = body_b_maybe {
cylinder_sphere_strength(
@ -1397,38 +1384,41 @@ impl ServerEvent for ExplosionEvent {
// This can be changed later.
let permit_pvp = || {
combat::permit_pvp(
&alignments,
&players,
&entered_auras,
&id_maps,
&data.alignments,
&data.players,
&data.entered_auras,
&data.id_maps,
owner_entity,
entity_b,
) || owner_entity.map_or(true, |entity_a| entity_a == entity_b)
};
if strength > 0.0 {
let is_alive = healths.get(entity_b).map_or(true, |h| !h.is_dead);
let is_alive =
data.healths.get(entity_b).map_or(true, |h| !h.is_dead);
if is_alive {
effect.modify_strength(strength);
if !effect.is_harm() || permit_pvp() {
emit_effect_events(
&mut emitters,
*time,
*data.time,
entity_b,
effect.clone(),
ev.owner.map(|owner| {
(
owner,
id_maps
data.id_maps
.uid_entity(owner)
.and_then(|e| groups.get(e))
.and_then(|e| data.groups.get(e))
.copied(),
)
}),
inventories.get(entity_b),
&msm,
character_states.get(entity_b),
stats.get(entity_b),
data.inventories.get(entity_b),
&data.msm,
data.character_states.get(entity_b),
data.stats.get(entity_b),
data.masses.get(entity_b),
owner_entity.and_then(|e| data.masses.get(e)),
);
}
}
@ -1451,6 +1441,8 @@ pub fn emit_effect_events(
msm: &MaterialStatManifest,
char_state: Option<&CharacterState>,
stats: Option<&Stats>,
tgt_mass: Option<&comp::Mass>,
source_mass: Option<&comp::Mass>,
) {
let damage_contributor = source.map(|(uid, group)| DamageContributor::new(uid, group));
match effect {
@ -1483,17 +1475,24 @@ pub fn emit_effect_events(
);
emitters.emit(HealthChangeEvent { entity, change })
},
common::effect::Effect::Buff(buff) => emitters.emit(BuffEvent {
entity,
buff_change: comp::BuffChange::Add(comp::Buff::new(
buff.kind,
buff.data,
buff.cat_ids,
comp::BuffSource::Item,
time,
common::effect::Effect::Buff(buff) => {
let dest_info = buff::DestInfo {
stats,
)),
}),
mass: tgt_mass,
};
emitters.emit(BuffEvent {
entity,
buff_change: comp::BuffChange::Add(comp::Buff::new(
buff.kind,
buff.data,
buff.cat_ids,
comp::BuffSource::Item,
time,
dest_info,
source_mass,
)),
});
},
}
}
@ -1740,11 +1739,12 @@ impl ServerEvent for ParryHookEvent {
WriteStorage<'a, CharacterState>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Stats>,
ReadStorage<'a, comp::Mass>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(time, energy_change_events, buff_events, mut character_states, uids, stats): Self::SystemData<'_>,
(time, energy_change_events, buff_events, mut character_states, uids, stats, masses): Self::SystemData<'_>,
) {
let mut energy_change_emitter = energy_change_events.emitter();
let mut buff_emitter = buff_events.emitter();
@ -1784,13 +1784,18 @@ impl ServerEvent for ParryHookEvent {
} else {
BuffSource::World
};
let dest_info = buff::DestInfo {
stats: stats.get(attacker),
mass: masses.get(attacker),
};
let buff = buff::Buff::new(
BuffKind::Parried,
data,
vec![buff::BuffCategory::Physical],
source,
*time,
stats.get(attacker),
dest_info,
masses.get(ev.defender),
);
buff_emitter.emit(BuffEvent {
entity: attacker,

View File

@ -107,6 +107,7 @@ pub struct InventoryManipData<'a> {
agents: ReadStorage<'a, comp::Agent>,
pets: ReadStorage<'a, comp::Pet>,
velocities: ReadStorage<'a, comp::Vel>,
masses: ReadStorage<'a, comp::Mass>,
}
impl ServerEvent for InventoryManipEvent {
@ -663,6 +664,8 @@ impl ServerEvent for InventoryManipEvent {
&data.msm,
data.character_states.get(entity),
data.stats.get(entity),
data.masses.get(entity),
None,
);
}
},
@ -678,6 +681,8 @@ impl ServerEvent for InventoryManipEvent {
&data.msm,
data.character_states.get(entity),
data.stats.get(entity),
data.masses.get(entity),
None,
);
}
},
@ -692,6 +697,8 @@ impl ServerEvent for InventoryManipEvent {
&data.msm,
data.character_states.get(entity),
data.stats.get(entity),
data.masses.get(entity),
None,
);
},
}

View File

@ -16,16 +16,10 @@ use crate::{
use common::{calendar::Calendar, resources::TimeOfDay, slowjob::SlowJobPool};
use common::{
character::CharacterId,
combat,
combat::DamageContributor,
comp::{
self,
item::{ItemKind, MaterialStatManifest},
misc::PortalData,
object, ChatType, Content, Group, Inventory, LootOwner, Object, Player, Poise, Presence,
PresenceKind, BASE_ABILITY_LIMIT,
self, item::ItemKind, misc::PortalData, object, ChatType, Content, Group, Inventory,
LootOwner, Object, Player, Poise, Presence, PresenceKind, BASE_ABILITY_LIMIT,
},
effect::Effect,
link::{Is, Link, LinkHandle},
mounting::{Mounting, Rider, VolumeMounting, VolumeRider},
resources::{Secs, Time},
@ -50,8 +44,6 @@ use tracing::{error, trace, warn};
use vek::*;
pub trait StateExt {
/// Updates a component associated with the entity based on the `Effect`
fn apply_effect(&self, entity: EcsEntity, effect: Effect, source: Option<Uid>);
/// Build a non-player character
fn create_npc(
&mut self,
@ -177,109 +169,6 @@ pub trait StateExt {
}
impl StateExt for State {
fn apply_effect(&self, entity: EcsEntity, effects: Effect, source: Option<Uid>) {
let msm = self.ecs().read_resource::<MaterialStatManifest>();
match effects {
Effect::Health(change) => {
self.ecs()
.write_storage::<comp::Health>()
.get_mut(entity)
.map(|mut health| health.change_by(change));
},
Effect::Damage(damage) => {
let inventories = self.ecs().read_storage::<Inventory>();
let stats = self.ecs().read_storage::<comp::Stats>();
let groups = self.ecs().read_storage::<Group>();
let damage_contributor = source.and_then(|uid| {
self.ecs().entity_from_uid(uid).map(|attacker_entity| {
DamageContributor::new(uid, groups.get(attacker_entity).cloned())
})
});
let time = self.ecs().read_resource::<Time>();
let change = damage.calculate_health_change(
combat::Damage::compute_damage_reduction(
Some(damage),
inventories.get(entity),
stats.get(entity),
&msm,
),
0.0,
damage_contributor,
None,
0.0,
1.0,
*time,
random(),
);
self.ecs()
.write_storage::<comp::Health>()
.get_mut(entity)
.map(|mut health| health.change_by(change));
},
Effect::Poise(poise) => {
let inventories = self.ecs().read_storage::<Inventory>();
let char_states = self.ecs().read_storage::<comp::CharacterState>();
let stats = self.ecs().read_storage::<comp::Stats>();
let change = Poise::apply_poise_reduction(
poise,
inventories.get(entity),
&msm,
char_states.get(entity),
stats.get(entity),
);
// Check to make sure the entity is not already stunned
if let Some(character_state) = self
.ecs()
.read_storage::<comp::CharacterState>()
.get(entity)
{
if !character_state.is_stunned() {
let groups = self.ecs().read_storage::<Group>();
let damage_contributor = source.and_then(|uid| {
self.ecs().entity_from_uid(uid).map(|attacker_entity| {
DamageContributor::new(uid, groups.get(attacker_entity).cloned())
})
});
let time = self.ecs().read_resource::<Time>();
let poise_change = comp::PoiseChange {
amount: change,
impulse: Vec3::zero(),
cause: None,
by: damage_contributor,
time: *time,
};
self.ecs()
.write_storage::<Poise>()
.get_mut(entity)
.map(|mut poise| poise.change(poise_change));
}
}
},
Effect::Buff(buff) => {
let time = self.ecs().read_resource::<Time>();
let stats = self.ecs().read_storage::<comp::Stats>();
self.ecs()
.write_storage::<comp::Buffs>()
.get_mut(entity)
.map(|mut buffs| {
buffs.insert(
comp::Buff::new(
buff.kind,
buff.data,
buff.cat_ids,
comp::BuffSource::Item,
*time,
stats.get(entity),
),
*time,
)
});
},
}
}
fn create_npc(
&mut self,
pos: comp::Pos,

View File

@ -462,6 +462,27 @@ impl Animation for FinisherMeleeAnimation {
next.control.orientation.rotate_z(move2 * 1.6);
next.control.position += Vec3::new(-16.0, 12.0, -8.0) * move2;
},
Some("common.abilities.hammer.lung_pummel") => {
hammer_start(&mut next, s_a);
let (move1, move2, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time, 0.0, 0.0),
Some(StageSection::Action) => (1.0, anim_time, 0.0),
Some(StageSection::Recover) => (1.0, 1.0, anim_time),
_ => (0.0, 0.0, 0.0),
};
let pullback = 1.0 - move3;
let move1 = move1 * pullback;
let move2 = move2 * pullback;
twist_back(&mut next, move1, 1.9, 0.7, 0.3, 1.2);
next.control.orientation.rotate_x(move1 * 1.2);
next.control.orientation.rotate_z(move1 * 1.0);
next.control.position += Vec3::new(-12.0, 0.0, 0.0) * move1;
twist_forward(&mut next, move2, 3.4, 1.4, 0.9, 2.1);
next.control.orientation.rotate_z(move2 * -4.0);
next.control.position += Vec3::new(12.0, 0.0, 14.0) * move2;
},
_ => {},
}

View File

@ -407,7 +407,8 @@ fn get_buff_ident(buff: BuffKind) -> &'static str {
| BuffKind::PotionSickness
| BuffKind::Polymorphed
| BuffKind::Heatstroke
| BuffKind::Rooted => {
| BuffKind::Rooted
| BuffKind::Winded => {
tracing::error!("Player was killed by a debuff that doesn't do damage!");
"mysterious"
},

View File

@ -90,7 +90,7 @@ fn maps_basic_melee() {
multi_target: None,
simultaneous_hits: 1,
custom_combo: None,
precision_flank_multiplier: 1.0,
precision_flank_multipliers: Default::default(),
precision_flank_invert: false,
},
ori_modifier: 1.0,

View File

@ -326,6 +326,7 @@ image_ids! {
hammer_spine_cracker: "voxygen.element.skills.hammer.spine_cracker",
hammer_breach: "voxygen.element.skills.hammer.breach",
hammer_pile_driver: "voxygen.element.skills.hammer.pile_driver",
hammer_lung_pummel: "voxygen.element.skills.hammer.lung_pummel",
// Skilltree Icons
health_plus_skill: "voxygen.element.skills.skilltree.health_plus",
energy_plus_skill: "voxygen.element.skills.skilltree.energy_plus",
@ -816,6 +817,7 @@ image_ids! {
debuff_polymorphed: "voxygen.element.de_buffs.debuff_polymorphed",
debuff_heatstroke_0: "voxygen.element.de_buffs.debuff_heatstroke_0",
debuff_rooted: "voxygen.element.de_buffs.debuff_rooted",
debuff_winded: "voxygen.element.de_buffs.debuff_winded",
// Animation Frames
// Buff Frame

View File

@ -5276,6 +5276,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
BuffKind::Polymorphed => imgs.debuff_polymorphed,
BuffKind::Heatstroke => imgs.debuff_heatstroke_0,
BuffKind::Rooted => imgs.debuff_rooted,
BuffKind::Winded => imgs.debuff_winded,
}
}

View File

@ -211,6 +211,8 @@ fn buff_key(buff: BuffKind) -> &'static str {
BuffKind::PotionSickness => "buff-potionsickness",
BuffKind::Heatstroke => "buff-heatstroke",
BuffKind::Rooted => "buff-rooted",
BuffKind::Winded => "buff-winded",
// Neutral
BuffKind::Polymorphed => "buff-polymorphed",
}
@ -322,7 +324,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Berserk
| BuffKind::Heatstroke
| BuffKind::ScornfulTaunt
| BuffKind::Rooted => Cow::Borrowed(""),
| BuffKind::Rooted
| BuffKind::Winded => Cow::Borrowed(""),
};
write!(&mut description, "{}", buff_desc).unwrap();
@ -374,7 +377,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Berserk
| BuffKind::Heatstroke
| BuffKind::ScornfulTaunt
| BuffKind::Rooted => Cow::Borrowed(""),
| BuffKind::Rooted
| BuffKind::Winded => Cow::Borrowed(""),
}
} else if let BuffKind::Saturation
| BuffKind::Regeneration
@ -638,6 +642,7 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id {
"common.abilities.hammer.spine_cracker" => imgs.hammer_spine_cracker,
"common.abilities.hammer.breach" => imgs.hammer_breach,
"common.abilities.hammer.pile_driver" => imgs.hammer_pile_driver,
"common.abilities.hammer.lung_pummel" => imgs.hammer_lung_pummel,
// Bow
"common.abilities.bow.charged" => imgs.bow_m1,
"common.abilities.bow.repeater" => imgs.bow_m2,