Savage sense

This commit is contained in:
Sam 2023-05-14 21:38:11 -04:00
parent 8c66bf8f70
commit 4a690840e4
15 changed files with 128 additions and 52 deletions

View File

@ -1,22 +1,9 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 4,
poise: 5,
knockback: 0,
energy_regen: 5,
),
range: 3.0,
angle: 45.0,
),
buildup_duration: 0.15,
swing_duration: 0.05,
hit_timing: 0.5,
SelfBuff(
buildup_duration: 0.1,
cast_duration: 0.1,
recover_duration: 0.1,
ori_modifier: 0.6,
),
],
energy_cost_per_strike: 0,
buff_kind: ImminentCritical,
buff_strength: 1.0,
buff_duration: Some(30.0),
energy_cost: /*15*/0,
)

View File

@ -88,9 +88,12 @@ buff-desc-frigid = Freeze your foes.
## Lifesteal
buff-title-lifesteal = Lifesteal
buff-desc-lifesteal = Siphon your enemies life away.
## Polymorped
## Salamander's Aspect
buff-title-salamanderaspect = Salamander's Aspect
buff-desc-salamanderaspect = You cannot burn and move fast through lava.
## Imminent Critical
buff-title-imminentcritical = Imminent Critical
buff-desc-imminentcritical = Your next attack will critically hit the enemy.
## Util
buff-text-over_seconds = over { $dur_secs } seconds
buff-text-for_seconds = for { $dur_secs } seconds

View File

@ -172,6 +172,7 @@ lazy_static! {
BuffKind::Frigid => "frigid",
BuffKind::Lifesteal => "lifesteal",
// BuffKind::SalamanderAspect => "salamander_aspect",
BuffKind::ImminentCritical => "imminent_critical",
};
let mut buff_parser = HashMap::new();
for kind in BuffKind::iter() {

View File

@ -214,11 +214,14 @@ impl Attack {
matches!(attack_effect.target, Some(GroupTarget::OutOfGroup))
&& (target_dodging || !may_harm)
};
let is_crit = rng.gen::<f32>()
< self.crit_chance
* attacker
let crit_chance = attacker
.and_then(|a| a.stats)
.map_or(1.0, |s| s.crit_chance_modifier);
.map(|s| s.crit_chance_modifier)
.map_or(self.crit_chance, |cc_mod| {
self.crit_chance * cc_mod.mult_mod + cc_mod.add_mod
})
.clamp(0.0, 1.0);
let is_crit = rng.gen::<f32>() < crit_chance;
let mut is_applied = false;
let mut accumulated_damage = 0.0;
let damage_modifier = attacker
@ -687,6 +690,7 @@ impl Attack {
if is_applied {
emit(ServerEvent::EntityAttackedHook {
entity: target.entity,
attacker: attacker.map(|a| a.entity),
});
}
is_applied

View File

@ -74,6 +74,10 @@ pub enum BuffKind {
/// Provides immunity to burning and increases movement speed in lava.
/// Movement speed increases linearly with strength, 1.0 is a 100% increase.
// SalamanderAspect, TODO: Readd in second dwarven mine MR
/// Guarantees that the next attack is a critical hit. Does this kind of
/// hackily by adding 100% to the crit, will need to be adjusted if we ever
/// allow double crits instead of treating 100 as a ceiling.
ImminentCritical,
// Debuffs
/// Does damage to a creature over time.
/// Strength should be the DPS of the debuff.
@ -143,7 +147,7 @@ impl BuffKind {
| BuffKind::Frigid
| BuffKind::Lifesteal
//| BuffKind::SalamanderAspect
=> true,
| BuffKind::ImminentCritical => true,
BuffKind::Bleeding
| BuffKind::Cursed
| BuffKind::Burning
@ -212,7 +216,7 @@ impl BuffKind {
},
BuffKind::CampfireHeal => vec![BuffEffect::HealthChangeOverTime {
rate: data.strength,
kind: ModifierKind::Fractional,
kind: ModifierKind::Multiplicative,
instance,
tick_dur: Secs(2.0),
}],
@ -287,7 +291,10 @@ impl BuffKind {
BuffKind::Hastened => vec![
BuffEffect::MovementSpeed(1.0 + data.strength),
BuffEffect::AttackSpeed(1.0 + data.strength),
BuffEffect::CriticalChance(0.0),
BuffEffect::CriticalChance {
kind: ModifierKind::Multiplicative,
val: 0.0,
},
],
BuffKind::Fortitude => vec![
BuffEffect::PoiseReduction(nn_scaling(data.strength)),
@ -329,8 +336,24 @@ impl BuffKind {
BuffEffect::BuffImmunity(BuffKind::Burning),
BuffEffect::SwimSpeed(1.0 + data.strength),
],*/
BuffKind::ImminentCritical => vec![BuffEffect::CriticalChance {
kind: ModifierKind::Additive,
val: 1.0,
}],
}
}
fn extend_cat_ids(&self, mut cat_ids: Vec<BuffCategory>) -> Vec<BuffCategory> {
// TODO: Remove clippy allow after another buff needs this
#[allow(clippy::single_match)]
match self {
BuffKind::ImminentCritical => {
cat_ids.push(BuffCategory::RemoveOnAttack);
},
_ => {},
}
cat_ids
}
}
// Struct used to store data relevant to a buff
@ -362,12 +385,13 @@ pub enum BuffCategory {
Divine,
PersistOnDeath,
FromActiveAura(Uid, AuraKey),
RemoveOnAttack,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ModifierKind {
Additive,
Fractional,
Multiplicative,
}
/// Data indicating and configuring behaviour of a de/buff.
@ -422,7 +446,10 @@ pub enum BuffEffect {
/// Modifier to the amount of damage dealt with attacks
AttackDamage(f32),
/// Multiplies crit chance of attacks
CriticalChance(f32),
CriticalChance {
kind: ModifierKind,
val: f32,
},
/// Changes body.
BodyChange(Body),
/// Inflict buff to target
@ -489,6 +516,7 @@ impl Buff {
health: Option<&Health>,
) -> Self {
let effects = kind.effects(&data, stats, health);
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
.iter()

View File

@ -63,7 +63,7 @@ pub struct Stats {
pub max_energy_modifiers: StatsModifier,
pub poise_damage_modifier: f32,
pub attack_damage_modifier: f32,
pub crit_chance_modifier: f32,
pub crit_chance_modifier: StatsModifier,
pub buffs_on_hit: Vec<AttackEffect>,
pub swim_speed_modifier: f32,
}
@ -83,7 +83,7 @@ impl Stats {
max_energy_modifiers: StatsModifier::default(),
poise_damage_modifier: 1.0,
attack_damage_modifier: 1.0,
crit_chance_modifier: 1.0,
crit_chance_modifier: StatsModifier::default(),
buffs_on_hit: Vec::new(),
swim_speed_modifier: 1.0,
}

View File

@ -297,6 +297,7 @@ pub enum ServerEvent {
},
EntityAttackedHook {
entity: EcsEntity,
attacker: Option<EcsEntity>,
},
ChangeAbility {
entity: EcsEntity,

View File

@ -535,7 +535,7 @@ fn execute_effect(
};
let amount = match *kind {
ModifierKind::Additive => amount,
ModifierKind::Fractional => health.maximum() * amount,
ModifierKind::Multiplicative => health.maximum() * amount,
};
let damage_contributor = by.and_then(|uid| {
read_data.id_maps.uid_entity(uid).map(|entity| {
@ -565,7 +565,7 @@ fn execute_effect(
let amount = match *kind {
ModifierKind::Additive => amount,
ModifierKind::Fractional => energy.maximum() * amount,
ModifierKind::Multiplicative => energy.maximum() * amount,
};
server_emitter.emit(ServerEvent::EnergyChange {
entity,
@ -577,7 +577,7 @@ fn execute_effect(
ModifierKind::Additive => {
stat.max_health_modifiers.add_mod += *value;
},
ModifierKind::Fractional => {
ModifierKind::Multiplicative => {
stat.max_health_modifiers.mult_mod *= *value;
},
},
@ -585,7 +585,7 @@ fn execute_effect(
ModifierKind::Additive => {
stat.max_energy_modifiers.add_mod += *value;
},
ModifierKind::Fractional => {
ModifierKind::Multiplicative => {
stat.max_energy_modifiers.mult_mod *= *value;
},
},
@ -608,7 +608,7 @@ fn execute_effect(
// creates fraction
potential_amount / health.base_max()
},
ModifierKind::Fractional => {
ModifierKind::Multiplicative => {
// `rate * dt` is the fraction
potential_amount
},
@ -664,8 +664,9 @@ fn execute_effect(
BuffEffect::AttackDamage(dam) => {
stat.attack_damage_modifier *= *dam;
},
BuffEffect::CriticalChance(cc) => {
stat.crit_chance_modifier *= *cc;
BuffEffect::CriticalChance { kind, val } => match kind {
ModifierKind::Additive => stat.crit_chance_modifier.add_mod += val,
ModifierKind::Multiplicative => stat.crit_chance_modifier.mult_mod *= val,
},
BuffEffect::BodyChange(b) => {
// For when an entity is under the effects of multiple de/buffs that change the

View File

@ -659,7 +659,7 @@ impl<'a> AgentData<'a> {
comp::BuffEffect::HealthChangeOverTime { rate, kind, .. } => {
let amount = match kind {
comp::ModifierKind::Additive => rate * duration.0 as f32,
comp::ModifierKind::Fractional => {
comp::ModifierKind::Multiplicative => {
(1.0 + rate).powf(duration.0 as f32)
},
};

View File

@ -1445,11 +1445,25 @@ pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_r
/// Intended to handle things that should happen for any successful attack,
/// regardless of the damages and effects specific to that attack
pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
pub fn handle_entity_attacked_hook(
server: &Server,
entity: EcsEntity,
attacker: Option<EcsEntity>,
) {
let ecs = &server.state.ecs();
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
let time = ecs.read_resource::<Time>();
if let Some(attacker) = attacker {
server_eventbus.emit_now(ServerEvent::Buff {
entity: attacker,
buff_change: buff::BuffChange::RemoveByCategory {
all_required: vec![buff::BuffCategory::RemoveOnAttack],
any_required: vec![],
none_required: vec![],
},
});
}
if let (Some(mut char_state), Some(mut poise), Some(pos)) = (
ecs.write_storage::<CharacterState>().get_mut(entity),

View File

@ -264,8 +264,8 @@ impl Server {
pet_entity,
owner_entity,
} => handle_tame_pet(self, pet_entity, owner_entity),
ServerEvent::EntityAttackedHook { entity } => {
handle_entity_attacked_hook(self, entity)
ServerEvent::EntityAttackedHook { entity, attacker } => {
handle_entity_attacked_hook(self, entity, attacker)
},
ServerEvent::ChangeAbility {
entity,

View File

@ -194,6 +194,39 @@ impl Animation for SelfBuffAnimation {
next.head.orientation.rotate_x(move2 * 0.6);
next.chest.orientation.rotate_x(move2 * 0.4);
},
Some("common.abilities.axe.savage_sense") => {
let (move1, move2, move3) = match stage_section {
Some(StageSection::Movement) => (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;
next.hand_l.position = Vec3::new(s_a.ahl.0, s_a.ahl.1, s_a.ahl.2);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.ahl.3) * Quaternion::rotation_y(s_a.ahl.4);
next.hand_r.position = Vec3::new(s_a.ahr.0, s_a.ahr.1, s_a.ahr.2);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.ahr.3) * Quaternion::rotation_z(s_a.ahr.5);
next.control.position = Vec3::new(s_a.ac.0, s_a.ac.1, s_a.ac.2);
next.control.orientation = Quaternion::rotation_x(s_a.ac.3)
* Quaternion::rotation_y(s_a.ac.4)
* Quaternion::rotation_z(s_a.ac.5);
next.chest.orientation = Quaternion::rotation_z(move1 * 0.6);
next.head.orientation = Quaternion::rotation_z(move1 * -0.2);
next.belt.orientation = Quaternion::rotation_z(move1 * -0.3);
next.shorts.orientation = Quaternion::rotation_z(move1 * -0.1);
next.foot_r.position += Vec3::new(0.0, move1 * 4.0, move1 * 4.0);
next.foot_r.orientation.rotate_x(move1 * 1.2);
next.foot_r.position += Vec3::new(0.0, move2 * 4.0, move2 * -4.0);
next.foot_r.orientation.rotate_x(move2 * -1.2);
},
_ => {},
}

View File

@ -121,7 +121,7 @@ pub fn localize_chat_message(
| BuffKind::Frigid
| BuffKind::Lifesteal
// | BuffKind::SalamanderAspect
=> {
| BuffKind::ImminentCritical => {
tracing::error!("Player was killed by a positive buff!");
"hud-outcome-mysterious"
},

View File

@ -5111,6 +5111,8 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
BuffKind::Lifesteal => imgs.buff_plus_0,
// TODO: Get image
// BuffKind::SalamanderAspect => imgs.debuff_burning_0,
// TODO: Get buff image
BuffKind::ImminentCritical => imgs.buff_reckless,
// Debuffs
BuffKind::Bleeding => imgs.debuff_bleed_0,
BuffKind::Cursed => imgs.debuff_skull_0,
@ -5147,6 +5149,7 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> Cow<s
BuffKind::Fortitude => localized_strings.get_msg("buff-title-fortitude"),
BuffKind::Reckless => localized_strings.get_msg("buff-title-reckless"),
// BuffKind::SalamanderAspect => localized_strings.get_msg("buff-title-salamanderaspect"),
BuffKind::ImminentCritical => localized_strings.get_msg("buff-title-imminentcritical"),
// Debuffs
BuffKind::Bleeding { .. } => localized_strings.get_msg("buff-title-bleed"),
BuffKind::Cursed { .. } => localized_strings.get_msg("buff-title-cursed"),
@ -5190,6 +5193,7 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz
BuffKind::Fortitude => localized_strings.get_msg("buff-desc-fortitude"),
BuffKind::Reckless => localized_strings.get_msg("buff-desc-reckless"),
// BuffKind::SalamanderAspect => localized_strings.get_msg("buff-desc-salamanderaspect"),
BuffKind::ImminentCritical => localized_strings.get_msg("buff-desc-imminentcritical"),
// Debuffs
BuffKind::Bleeding { .. } => localized_strings.get_msg("buff-desc-bleed"),
BuffKind::Cursed { .. } => localized_strings.get_msg("buff-desc-cursed"),

View File

@ -210,7 +210,7 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Frigid
| BuffKind::Lifesteal
// | BuffKind::SalamanderAspect
=> Cow::Borrowed(""),
| BuffKind::ImminentCritical => Cow::Borrowed(""),
};
write!(&mut description, "{}", buff_desc).unwrap();
@ -253,7 +253,7 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Frigid
| BuffKind::Lifesteal
// | BuffKind::SalamanderAspect
=> Cow::Borrowed(""),
| BuffKind::ImminentCritical => Cow::Borrowed(""),
}
} else if let BuffKind::Saturation
| BuffKind::Regeneration