This commit is contained in:
Sam 2024-03-28 19:41:31 -04:00
parent 7daa9a29eb
commit 85e9af4e08
18 changed files with 169 additions and 91 deletions

View File

@ -236,7 +236,7 @@
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"),
Simple(Hammer(Tenacity), "common.abilities.hammer.tenacity"),
// Simple(Hammer(Earthshaker), "common.abilities.hammer.earthshaker"),
// Simple(Hammer(Judgement), "common.abilities.hammer.judgement"),
],

View File

@ -1,10 +1,10 @@
SelfBuff(
buildup_duration: 0.1,
cast_duration: 3.0,
cast_duration: 2.0,
recover_duration: 0.1,
buff_kind: Defiance,
buff_strength: 1.0,
buff_duration: Some(3.0),
buff_duration: Some(5.0),
energy_cost: 20,
enforced_limit: false,
)

View File

@ -0,0 +1,13 @@
SelfBuff(
buildup_duration: 0.1,
cast_duration: 2.0,
recover_duration: 0.1,
buff_kind: Tenacity,
buff_strength: 1.0,
buff_duration: Some(5.0),
energy_cost: 20,
enforced_limit: false,
meta: (
contextual_stats: Some((context: PoiseResilience(60.0), field: BuffStrength)),
),
)

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

Binary file not shown.

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

Binary file not shown.

View File

@ -134,8 +134,11 @@ buff-winded = Winded
buff-concussion = Concussion
.desc = You have been hit hard on the head and have trouble focusing, preventing you from using some of your more complex attacks.
## Staggered
buff-title-staggered = Staggered
buff-desc-staggered = You are off balance and more susceptible to heavy attacks.
buff-staggered = Staggered
.desc = You are off balance and more susceptible to heavy attacks.
## Tenacity
buff-tenacity = Tenacity
.desc = You are not only able to shrug off heavier attacks, they energize you as well. However you are also slower.
## Util
buff-text-over_seconds = over { $dur_secs } seconds
buff-text-for_seconds = for { $dur_secs } seconds

View File

@ -437,3 +437,6 @@ common-abilities-hammer-dual_upheaval = Upheaval
common-abilities-hammer-rampart = Rampart
.desc =
Strike the ground, causing very mild tectonic uplift which protects your allies from attacks.
common-abilities-hammer-tenacity = Tenacity
.desc =
Hold yourself strong as you withstand attack after attack, managing to keep attacking all the while.

View File

@ -194,6 +194,7 @@ lazy_static! {
BuffKind::Winded => "winded",
BuffKind::Concussion => "concussion",
BuffKind::Staggered => "staggered",
BuffKind::Tenacity => "tenacity",
};
let mut buff_parser = HashMap::new();
for kind in BuffKind::iter() {

View File

@ -1034,6 +1034,7 @@ pub enum CombatRequirement {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum DamagedEffect {
Combo(i32),
Energy(f32),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]

View File

@ -3038,6 +3038,9 @@ impl StatAdj {
StatField::EffectPower => {
stats.effect_power += add;
},
StatField::BuffStrength => {
stats.buff_strength += add;
},
}
stats
}
@ -3057,6 +3060,7 @@ pub enum StatContext {
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum StatField {
EffectPower,
BuffStrength,
}
// TODO: Later move over things like energy and combo into here

View File

@ -27,7 +27,9 @@ new_key_type! { pub struct BuffKey; }
Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, PartialOrd, Ord, EnumIter, Enum,
)]
pub enum BuffKind {
// Buffs
// =================
// BUFFS
// =================
/// Restores health/time for some period.
/// Strength should be the healing per second.
Regeneration,
@ -115,9 +117,9 @@ pub enum BuffKind {
/// generated when damaged, and decreases movement speed.
/// Damage resistance increases non-linearly with strength, 0.5 is 50% and
/// 1.0 is 67%. Poise resistance increases non-linearly with strength, 0.5
/// is 50% and 1.0 is 67%. Movement speed decreases linearly with strength,
/// 0.5 is 50% and 1.0 is 33%. Combo generation is linear with strength, 1.0
/// is 5 combo generated on being hit.
/// is 50% and 1.0 is 67%. Movement speed is decreased to 50%. Combo
/// generation is linear with strength, 1.0 is 5 combo generated on being
/// hit.
Defiance,
/// Increases both attack damage, vulnerability to damage, attack speed, and
/// movement speed Damage increases linearly with strength, 1.0 is a
@ -133,7 +135,15 @@ pub enum BuffKind {
/// strength, 0.5 is +50% and 1.0 is +100% strength. Reckless buff reward
/// strength is equal to scornful taunt buff strength.
ScornfulTaunt,
// Debuffs
/// Increases damage resistance, causes energy to be generated when damaged,
/// and decreases movement speed. Damage resistance increases non-linearly
/// with strength, 0.5 is 50% and 1.0 is 67%. Energy generation is linear
/// with strength, 1.0 is 10 energy per hit. Movement speed is decreased to
/// 70%.
Tenacity,
// =================
// DEBUFFS
// =================
/// Does damage to a creature over time.
/// Strength should be the DPS of the debuff.
Burning,
@ -197,7 +207,9 @@ pub enum BuffKind {
/// Scales linearly with strength, 1.0 leads to 100% more poise damage
/// received
Staggered,
// Complex, non-obvious buffs
// =================
// COMPLEX
// =================
/// Changed into another body.
Polymorphed,
}
@ -248,7 +260,8 @@ impl BuffKind {
| BuffKind::Defiance
| BuffKind::Bloodfeast
| BuffKind::Berserk
| BuffKind::ScornfulTaunt => BuffDescriptor::SimplePositive,
| BuffKind::ScornfulTaunt
| BuffKind::Tenacity => BuffDescriptor::SimplePositive,
BuffKind::Bleeding
| BuffKind::Cursed
| BuffKind::Burning
@ -482,7 +495,7 @@ impl BuffKind {
BuffKind::Defiance => vec![
BuffEffect::DamageReduction(nn_scaling(data.strength)),
BuffEffect::PoiseReduction(nn_scaling(data.strength)),
BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength)),
BuffEffect::MovementSpeed(0.5),
BuffEffect::DamagedEffect(DamagedEffect::Combo(
(data.strength * 5.0).round() as i32
)),
@ -513,6 +526,11 @@ impl BuffKind {
],
BuffKind::Concussion => vec![BuffEffect::DisableAuxiliaryAbilities],
BuffKind::Staggered => vec![BuffEffect::PoiseReduction(-data.strength)],
BuffKind::Tenacity => vec![
BuffEffect::DamageReduction(nn_scaling(data.strength)),
BuffEffect::MovementSpeed(0.7),
BuffEffect::DamagedEffect(DamagedEffect::Energy(data.strength * 10.0)),
],
}
}

View File

@ -4555,7 +4555,8 @@ fn build_buff(
| BuffKind::Rooted
| BuffKind::Winded
| BuffKind::Concussion
| BuffKind::Staggered => {
| BuffKind::Staggered
| BuffKind::Tenacity => {
if buff_kind.is_simple() {
unreachable!("is_simple() above")
} else {

View File

@ -102,6 +102,28 @@ pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
event_dispatch::<StartTeleportingEvent>(builder);
}
event_emitters! {
struct ReadExplosionEvents[ExplosionEmitters] {
health_change: HealthChangeEvent,
energy_change: EnergyChangeEvent,
poise_change: PoiseChangeEvent,
sound: SoundEvent,
parry_hook: ParryHookEvent,
kockback: KnockbackEvent,
entity_attack_hoow: EntityAttackedHookEvent,
combo_change: ComboChangeEvent,
buff: BuffEvent,
bonk: BonkEvent,
}
struct ReadEntityAttackedHookEvents[EntityAttackedHookEmitters] {
buff: BuffEvent,
combo_change: ComboChangeEvent,
knockback: KnockbackEvent,
energy_change: EnergyChangeEvent,
}
}
pub fn handle_delete(server: &mut Server, DeleteEvent(entity): DeleteEvent) {
let _ = server
.state_mut()
@ -968,21 +990,6 @@ impl ServerEvent for RespawnEvent {
}
}
event_emitters! {
struct ReadExplosionEvents[ExplosionEmitters] {
health_change: HealthChangeEvent,
energy_change: EnergyChangeEvent,
poise_change: PoiseChangeEvent,
sound: SoundEvent,
parry_hook: ParryHookEvent,
kockback: KnockbackEvent,
entity_attack_hoow: EntityAttackedHookEvent,
combo_change: ComboChangeEvent,
buff: BuffEvent,
bonk: BonkEvent,
}
}
#[derive(SystemData)]
pub struct ExplosionData<'a> {
entities: Entities<'a>,
@ -1838,55 +1845,35 @@ impl ServerEvent for TeleportToEvent {
}
}
#[derive(SystemData)]
pub struct EntityAttackedHookData<'a> {
entities: Entities<'a>,
trades: Write<'a, Trades>,
id_maps: Read<'a, IdMaps>,
time: Read<'a, Time>,
event_busses: ReadEntityAttackedHookEvents<'a>,
outcomes: Read<'a, EventBus<Outcome>>,
character_states: WriteStorage<'a, CharacterState>,
poises: WriteStorage<'a, Poise>,
agents: WriteStorage<'a, Agent>,
positions: ReadStorage<'a, Pos>,
uids: ReadStorage<'a, Uid>,
clients: ReadStorage<'a, Client>,
stats: ReadStorage<'a, Stats>,
}
impl ServerEvent for EntityAttackedHookEvent {
type SystemData<'a> = (
Entities<'a>,
Write<'a, Trades>,
Read<'a, IdMaps>,
Read<'a, Time>,
Read<'a, EventBus<BuffEvent>>,
Read<'a, EventBus<ComboChangeEvent>>,
Read<'a, EventBus<KnockbackEvent>>,
Read<'a, EventBus<Outcome>>,
WriteStorage<'a, CharacterState>,
WriteStorage<'a, Poise>,
WriteStorage<'a, Agent>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Client>,
ReadStorage<'a, Stats>,
);
type SystemData<'a> = EntityAttackedHookData<'a>;
/// Intended to handle things that should happen for any successful attack,
/// regardless of the damages and effects specific to that attack
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(
entities,
mut trades,
id_maps,
time,
buff_events,
combo_change_events,
knockback_events,
outcomes,
mut character_states,
mut poises,
mut agents,
positions,
uids,
clients,
stats,
): Self::SystemData<'_>,
) {
let mut buff_emitter = buff_events.emitter();
let mut combo_change_emitter = combo_change_events.emitter();
let mut knockback_emitter = knockback_events.emitter();
fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
let mut emitters = data.event_busses.get_emitters();
let mut outcomes = data.outcomes.emitter();
let mut outcomes = outcomes.emitter();
for ev in events {
if let Some(attacker) = ev.attacker {
buff_emitter.emit(BuffEvent {
emitters.emit(BuffEvent {
entity: attacker,
buff_change: buff::BuffChange::RemoveByCategory {
all_required: vec![buff::BuffCategory::RemoveOnAttack],
@ -1896,10 +1883,13 @@ impl ServerEvent for EntityAttackedHookEvent {
});
}
if let Some((mut char_state, mut poise, pos)) =
(&mut character_states, &mut poises, &positions)
.lend_join()
.get(ev.entity, &entities)
if let Some((mut char_state, mut poise, pos)) = (
&mut data.character_states,
&mut data.poises,
&data.positions,
)
.lend_join()
.get(ev.entity, &data.entities)
{
// Interrupt sprite interaction and item use if any attack is applied to entity
if matches!(
@ -1912,14 +1902,14 @@ impl ServerEvent for EntityAttackedHookEvent {
poise_state.poise_effect(was_wielded)
{
// Reset poise if there is some stunned state to apply
poise.reset(*time, stunned_duration);
poise.reset(*data.time, stunned_duration);
*char_state = stunned_state;
outcomes.emit(Outcome::PoiseChange {
pos: pos.0,
state: poise_state,
});
if let Some(impulse_strength) = impulse_strength {
knockback_emitter.emit(KnockbackEvent {
emitters.emit(KnockbackEvent {
entity: ev.entity,
impulse: impulse_strength * *poise.knockback(),
});
@ -1929,21 +1919,21 @@ impl ServerEvent for EntityAttackedHookEvent {
}
// Remove potion/saturation buff if attacked
buff_emitter.emit(BuffEvent {
emitters.emit(BuffEvent {
entity: ev.entity,
buff_change: buff::BuffChange::RemoveByKind(BuffKind::Potion),
});
buff_emitter.emit(BuffEvent {
emitters.emit(BuffEvent {
entity: ev.entity,
buff_change: buff::BuffChange::RemoveByKind(BuffKind::Saturation),
});
// If entity was in an active trade, cancel it
if let Some(uid) = uids.get(ev.entity) {
if let Some(trade) = trades.entity_trades.get(uid).copied() {
trades
if let Some(uid) = data.uids.get(ev.entity) {
if let Some(trade) = data.trades.entity_trades.get(uid).copied() {
data.trades
.decline_trade(trade, *uid)
.and_then(|uid| id_maps.uid_entity(uid))
.and_then(|uid| data.id_maps.uid_entity(uid))
.map(|entity_b| {
// Notify both parties that the trade ended
let mut notify_trade_party = |entity| {
@ -1951,12 +1941,12 @@ impl ServerEvent for EntityAttackedHookEvent {
// trade invite, since right now it
// may seems like their request was
// purposefully declined, rather than e.g. being interrupted.
if let Some(client) = clients.get(entity) {
if let Some(client) = data.clients.get(entity) {
client.send_fallible(ServerGeneral::FinishedTrade(
TradeResult::Declined,
));
}
if let Some(agent) = agents.get_mut(entity) {
if let Some(agent) = data.agents.get_mut(entity) {
agent.inbox.push_back(AgentEvent::FinishedTrade(
TradeResult::Declined,
));
@ -1968,16 +1958,22 @@ impl ServerEvent for EntityAttackedHookEvent {
}
}
if let Some(stats) = stats.get(ev.entity) {
if let Some(stats) = data.stats.get(ev.entity) {
for effect in &stats.effects_on_damaged {
use combat::DamagedEffect;
match effect {
DamagedEffect::Combo(c) => {
combo_change_emitter.emit(ComboChangeEvent {
emitters.emit(ComboChangeEvent {
entity: ev.entity,
change: *c,
});
},
DamagedEffect::Energy(e) => {
emitters.emit(EnergyChangeEvent {
entity: ev.entity,
change: *e,
});
},
}
}
}

View File

@ -1,6 +1,6 @@
use super::{
super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr,
hammer_start, CharacterSkeleton, SkeletonAttr,
};
use common::states::utils::{AbilityInfo, StageSection};
use core::f32::consts::{PI, TAU};
@ -392,6 +392,30 @@ impl Animation for SelfBuffAnimation {
next.head.orientation.rotate_x(tension * 0.3);
next.control.position += Vec3::new(0.0, 0.0, tension * 2.0);
},
Some("common.abilities.hammer.tenacity") => {
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;
next.control.orientation.rotate_x(move1 * 0.6);
next.control.orientation.rotate_y(move1 * 0.9);
next.control.orientation.rotate_x(move1 * -0.6);
next.chest.orientation.rotate_x(move1 * 0.4);
next.control.position += Vec3::new(0.0, 4.0, 3.0) * move1;
next.control.position += Vec3::new(
(move2 * 50.0).sin(),
(move2 * 67.0).sin(),
(move2 * 83.0).sin(),
);
},
_ => {},
}

View File

@ -396,7 +396,8 @@ fn get_buff_ident(buff: BuffKind) -> &'static str {
| BuffKind::Defiance
| BuffKind::Bloodfeast
| BuffKind::Berserk
| BuffKind::ScornfulTaunt => {
| BuffKind::ScornfulTaunt
| BuffKind::Tenacity => {
tracing::error!("Player was killed by a positive buff!");
"mysterious"
},

View File

@ -331,6 +331,7 @@ image_ids! {
hammer_iron_tempest: "voxygen.element.skills.hammer.iron_tempest",
hammer_upheaval: "voxygen.element.skills.hammer.upheaval",
hammer_rampart: "voxygen.element.skills.hammer.rampart",
hammer_tenacity: "voxygen.element.skills.hammer.tenacity",
// Skilltree Icons
health_plus_skill: "voxygen.element.skills.skilltree.health_plus",
energy_plus_skill: "voxygen.element.skills.skilltree.energy_plus",
@ -806,6 +807,7 @@ image_ids! {
buff_flame: "voxygen.element.de_buffs.buff_flame",
buff_frigid: "voxygen.element.de_buffs.buff_frigid",
buff_scornfultaunt: "voxygen.element.de_buffs.buff_scornfultaunt",
buff_tenacity: "voxygen.element.de_buffs.buff_tenacity",
// Debuffs
debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0",

View File

@ -5264,6 +5264,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
BuffKind::Bloodfeast => imgs.buff_plus_0,
BuffKind::Berserk => imgs.buff_reckless,
BuffKind::ScornfulTaunt => imgs.buff_scornfultaunt,
BuffKind::Tenacity => imgs.buff_tenacity,
// Debuffs
BuffKind::Bleeding => imgs.debuff_bleed_0,
BuffKind::Cursed => imgs.debuff_skull_0,

View File

@ -198,6 +198,7 @@ fn buff_key(buff: BuffKind) -> &'static str {
BuffKind::Bloodfeast => "buff-bloodfeast",
BuffKind::Berserk => "buff-berserk",
BuffKind::ScornfulTaunt => "buff-scornfultaunt",
BuffKind::Tenacity => "buff-tenacity",
// Debuffs
BuffKind::Bleeding => "buff-bleed",
BuffKind::Cursed => "buff-cursed",
@ -328,7 +329,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Rooted
| BuffKind::Winded
| BuffKind::Concussion
| BuffKind::Staggered => Cow::Borrowed(""),
| BuffKind::Staggered
| BuffKind::Tenacity => Cow::Borrowed(""),
};
write!(&mut description, "{}", buff_desc).unwrap();
@ -383,7 +385,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Rooted
| BuffKind::Winded
| BuffKind::Concussion
| BuffKind::Staggered => Cow::Borrowed(""),
| BuffKind::Staggered
| BuffKind::Tenacity => Cow::Borrowed(""),
}
} else if let BuffKind::Saturation
| BuffKind::Regeneration
@ -654,6 +657,7 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id {
"common.abilities.hammer.upheaval" => imgs.hammer_upheaval,
"common.abilities.hammer.dual_upheaval" => imgs.hammer_upheaval,
"common.abilities.hammer.rampart" => imgs.hammer_rampart,
"common.abilities.hammer.tenacity" => imgs.hammer_tenacity,
// Bow
"common.abilities.bow.charged" => imgs.bow_m1,
"common.abilities.bow.repeater" => imgs.bow_m2,