From dcc2163188f5e828032fe063e9ac40783335e08d Mon Sep 17 00:00:00 2001 From: Papinha Date: Sun, 4 Aug 2024 22:14:52 +0000 Subject: [PATCH] Freezing potion and debuff immunity --- CHANGELOG.md | 1 + assets/common/item_i18n_manifest.ron | 3 + .../items/consumable/potion_combustion.ron | 10 ++- .../items/consumable/potion_freezing.ron | 27 +++++++ assets/common/items/recipes/potions.ron | 1 + assets/common/recipe_book_manifest.ron | 9 +++ assets/voxygen/i18n/en/item/items/potion.ftl | 3 + assets/voxygen/item_image_manifest.ron | 4 ++ assets/voxygen/voxel/item_drop_manifest.ron | 1 + assets/voxygen/voxel/object/potion_blue_1.vox | 3 + common/src/comp/buff.rs | 71 +++++++++++++------ common/src/comp/stats.rs | 8 +-- common/systems/src/buff.rs | 9 +-- server/agent/src/action_nodes.rs | 6 +- server/src/events/entity_manipulation.rs | 16 ++++- 15 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 assets/common/items/consumable/potion_freezing.ron create mode 100644 assets/voxygen/voxel/object/potion_blue_1.vox diff --git a/CHANGELOG.md b/CHANGELOG.md index fea1028147..dd4bcea509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Protocol to query game server information (player count, version, etc.) and make ping tests. - Unlockable recipes - Localization support for prompt dialogs, diary sections, trade and group invitations. +- Added Freezing Potion ### Changed diff --git a/assets/common/item_i18n_manifest.ron b/assets/common/item_i18n_manifest.ron index bd5491166d..a1434ee993 100644 --- a/assets/common/item_i18n_manifest.ron +++ b/assets/common/item_i18n_manifest.ron @@ -3183,6 +3183,9 @@ Simple( "common.items.consumable.potion_minor", ): "object-potion_minor", + Simple( + "common.items.consumable.potion_freezing", + ): "object-potion_freezing", Simple( "common.items.lantern.black_0", ): "lantern-black", diff --git a/assets/common/items/consumable/potion_combustion.ron b/assets/common/items/consumable/potion_combustion.ron index 42d3af3d5c..a970c537b6 100644 --- a/assets/common/items/consumable/potion_combustion.ron +++ b/assets/common/items/consumable/potion_combustion.ron @@ -11,7 +11,15 @@ ItemDef( duration: Some(10) ), cat_ids: [Natural], - )) + )), + Buff(( + kind: PotionSickness, + data: ( + strength: 0.15, + duration: Some(30), + ), + cat_ids: [Natural], + )), ]) ), quality: Moderate, diff --git a/assets/common/items/consumable/potion_freezing.ron b/assets/common/items/consumable/potion_freezing.ron new file mode 100644 index 0000000000..86117e4cc2 --- /dev/null +++ b/assets/common/items/consumable/potion_freezing.ron @@ -0,0 +1,27 @@ +ItemDef( + legacy_name: "Freezing Potion", + legacy_description: "Freezes the user's brain", + kind: Consumable( + kind: Drink, + effects: All([ + Buff(( + kind: Frozen, + data: ( + strength: 0.1, + duration: Some(30) + ), + cat_ids: [Natural], + )), + Buff(( + kind: PotionSickness, + data: ( + strength: 0.15, + duration: Some(30), + ), + cat_ids: [Natural], + )), + ]) + ), + quality: Moderate, + tags: [Potion], +) diff --git a/assets/common/items/recipes/potions.ron b/assets/common/items/recipes/potions.ron index ce11966021..e072a45080 100644 --- a/assets/common/items/recipes/potions.ron +++ b/assets/common/items/recipes/potions.ron @@ -4,6 +4,7 @@ ItemDef( kind: RecipeGroup( recipes: [ "potion_combustion", + "potion_freezing", "potion_agility", "potion_minor", "potion_medium", diff --git a/assets/common/recipe_book_manifest.ron b/assets/common/recipe_book_manifest.ron index babab7a4ba..ffc604adc6 100644 --- a/assets/common/recipe_book_manifest.ron +++ b/assets/common/recipe_book_manifest.ron @@ -31,6 +31,15 @@ ], craft_sprite: Some(Anvil), ), + "potion_freezing": ( + output: ("common.items.consumable.potion_freezing", 1), + inputs: [ + (Item("common.items.crafting_ing.empty_vial"), 1, false), + (Item("common.items.crafting_ing.animal_misc.icy_fang"), 3, false), + (Item("common.items.crafting_ing.animal_misc.viscous_ooze"), 1, false), + ], + craft_sprite: Some(Cauldron), + ), "potion_combustion": ( output: ("common.items.consumable.potion_combustion", 1), inputs: [ diff --git a/assets/voxygen/i18n/en/item/items/potion.ftl b/assets/voxygen/i18n/en/item/items/potion.ftl index 0b759ca224..e7e98fc075 100644 --- a/assets/voxygen/i18n/en/item/items/potion.ftl +++ b/assets/voxygen/i18n/en/item/items/potion.ftl @@ -19,6 +19,9 @@ object-potion_med = Medium Potion object-potion_minor = Minor Potion .desc = A small potion concocted from apples and honey. +object-potion_freezing = Freezing Potion + .desc = Freezes the user's brain. + object-burning_charm = Blazing Charm .desc = Flame is your ally, harness its power to burn your foes. diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 83010c3453..c4aba734d9 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -3411,6 +3411,10 @@ "voxel.object.potion_combustion", (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7, ), + Simple("common.items.consumable.potion_freezing"): VoxTrans( + "voxel.object.potion_blue_1", + (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7, + ), Simple("common.items.consumable.potion_agility"): VoxTrans( "voxel.object.potion_agility", (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7, diff --git a/assets/voxygen/voxel/item_drop_manifest.ron b/assets/voxygen/voxel/item_drop_manifest.ron index 8481ac76c5..0f9d253966 100644 --- a/assets/voxygen/voxel/item_drop_manifest.ron +++ b/assets/voxygen/voxel/item_drop_manifest.ron @@ -862,6 +862,7 @@ Simple("common.items.consumable.potion_big"): "voxel.object.potion_red", Simple("common.items.consumable.potion_curious"): "voxel.object.potion_curious", Simple("common.items.consumable.potion_combustion"): "voxel.object.potion_combustion", + Simple("common.items.consumable.potion_freezing"): "voxel.object.potion_blue_1", Simple("common.items.consumable.potion_agility"): "voxel.object.potion_agility", Simple("common.items.charms.burning_charm"): "voxel.object.burning_charm", Simple("common.items.charms.frozen_charm"): "voxel.object.frozen_charm", diff --git a/assets/voxygen/voxel/object/potion_blue_1.vox b/assets/voxygen/voxel/object/potion_blue_1.vox new file mode 100644 index 0000000000..5eab7f0bed --- /dev/null +++ b/assets/voxygen/voxel/object/potion_blue_1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15afce418fa1435ad1c06535656caf77ae75639974af9b9034e6e6074996ca4a +size 26190 diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index a71c3c423c..221ee6006e 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -152,6 +152,7 @@ pub enum BuffKind { // ================= /// Does damage to a creature over time. /// Strength should be the DPS of the debuff. + /// Provides immunity against Frozen. Burning, /// Lowers health over time for some duration. /// Strength should be the DPS of the debuff. @@ -168,10 +169,12 @@ pub enum BuffKind { /// Strength scales the attack speed debuff non-linearly. 0.5 is ~50% /// speed, 1.0 is 33% speed. Movement speed debuff is scaled to be slightly /// smaller than attack speed debuff. + /// Provides immunity against Heatstroke. Frozen, /// Makes you wet and causes you to have reduced friction on the ground. /// Strength scales the friction you ignore non-linearly. 0.5 is 50% ground /// friction, 1.0 is 33% ground friction. + /// Provides immunity against Burning. Wet, /// Makes you move slower. /// Strength scales the movement speed debuff non-linearly. 0.5 is 50% @@ -319,7 +322,7 @@ impl BuffKind { /// only the strongest. pub fn stacks(self) -> bool { matches!(self, BuffKind::PotionSickness | BuffKind::Resilience) } - pub fn effects(&self, data: &BuffData, stats: Option<&Stats>) -> Vec { + pub fn effects(&self, data: &BuffData) -> Vec { // Normalized nonlinear scaling // TODO: Do we want to make denominator term parameterized. Come back to if we // add nn_scaling3. @@ -347,16 +350,14 @@ impl BuffKind { }], BuffKind::Potion => { vec![BuffEffect::HealthChangeOverTime { - rate: data.strength * stats.map_or(1.0, |s| s.heal_multiplier), + rate: data.strength, kind: ModifierKind::Additive, instance, tick_dur: Secs(0.1), }] }, BuffKind::Agility => vec![ - BuffEffect::MovementSpeed( - 1.0 + data.strength * stats.map_or(1.0, |s| s.move_speed_multiplier), - ), + BuffEffect::MovementSpeed(1.0 + data.strength), BuffEffect::DamageReduction(-1.0), BuffEffect::AttackDamage(0.0), ], @@ -399,12 +400,15 @@ impl BuffKind { // strength. 0.5 also still provides 50% damage reduction. nn_scaling(data.strength), )], - BuffKind::Burning => vec![BuffEffect::HealthChangeOverTime { - rate: -data.strength, - kind: ModifierKind::Additive, - instance, - tick_dur: Secs(0.25), - }], + BuffKind::Burning => vec![ + BuffEffect::HealthChangeOverTime { + rate: -data.strength, + kind: ModifierKind::Additive, + instance, + tick_dur: Secs(0.25), + }, + BuffEffect::BuffImmunity(BuffKind::Frozen), + ], BuffKind::Poisoned => vec![BuffEffect::EnergyChangeOverTime { rate: -data.strength, kind: ModifierKind::Additive, @@ -431,8 +435,12 @@ impl BuffKind { BuffKind::Frozen => vec![ BuffEffect::MovementSpeed(f32::powf(1.0 - nn_scaling(data.strength), 1.1)), BuffEffect::AttackSpeed(1.0 - nn_scaling(data.strength)), + BuffEffect::BuffImmunity(BuffKind::Heatstroke), + ], + BuffKind::Wet => vec![ + BuffEffect::GroundFriction(1.0 - nn_scaling(data.strength)), + BuffEffect::BuffImmunity(BuffKind::Burning), ], - BuffKind::Wet => vec![BuffEffect::GroundFriction(1.0 - nn_scaling(data.strength))], BuffKind::Ensnared => vec![BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength))], BuffKind::Hastened => vec![ BuffEffect::MovementSpeed(1.0 + data.strength), @@ -447,11 +455,7 @@ impl BuffKind { BuffEffect::RecoverySpeed(0.25), BuffEffect::PrecisionVulnerabilityOverride(1.0), ], - //TODO: Handle potion sickness in a more general way. - BuffKind::PotionSickness => vec![ - BuffEffect::HealReduction(data.strength), - BuffEffect::MoveSpeedReduction(data.strength), - ], + BuffKind::PotionSickness => vec![BuffEffect::ItemEffectReduction(data.strength)], BuffKind::Reckless => vec![ BuffEffect::DamageReduction(-data.strength), BuffEffect::AttackDamage(1.0 + data.strength), @@ -562,6 +566,7 @@ impl BuffKind { mut data: BuffData, source_mass: Option<&Mass>, dest_info: DestInfo, + source: BuffSource, ) -> BuffData { // TODO: Remove clippy allow after another buff needs this #[allow(clippy::single_match)] @@ -580,6 +585,7 @@ impl BuffKind { .map_or(1.0, |s| (1.0 - s.crowd_control_resistance).max(0.0)); data.duration = data.duration.map(|dur| dur * dur_mult as f64); } + self.apply_item_effect_reduction(&mut data, source, dest_info); data } @@ -592,6 +598,27 @@ impl BuffKind { _ => None, } } + + pub fn apply_item_effect_reduction( + &self, + data: &mut BuffData, + source: BuffSource, + dest_info: DestInfo, + ) { + if !matches!(source, BuffSource::Item) { + return; + } + let item_effect_reduction = dest_info.stats.map_or(1.0, |s| s.item_effect_reduction); + match self { + BuffKind::Potion | BuffKind::Agility => { + data.strength *= item_effect_reduction; + }, + BuffKind::Burning | BuffKind::Frozen | BuffKind::Resilience => { + data.duration = data.duration.map(|dur| dur * item_effect_reduction as f64); + }, + _ => {}, + }; + } } // Struct used to store data relevant to a buff @@ -705,10 +732,6 @@ pub enum BuffEffect { GroundFriction(f32), /// Reduces poise damage taken after armor is accounted for by this fraction PoiseReduction(f32), - /// Reduces amount healed by consumables - HealReduction(f32), - /// Reduces amount of speed increase by consumables - MoveSpeedReduction(f32), /// Increases poise damage dealt when health is lost PoiseDamageFromLostHealth(f32), /// Modifier to the amount of damage dealt with attacks @@ -737,6 +760,8 @@ pub enum BuffEffect { DisableAuxiliaryAbilities, /// Reduces duration of crowd control debuffs CrowdControlResistance(f32), + /// Reduces the strength or duration of item buff + ItemEffectReduction(f32), } /// Actual de/buff. @@ -796,8 +821,8 @@ impl Buff { // Create source_info if we need more parameters from source source_mass: Option<&Mass>, ) -> Self { - let data = kind.modify_data(data, source_mass, dest_info); - let effects = kind.effects(&data, dest_info.stats); + let data = kind.modify_data(data, source_mass, dest_info, source); + let effects = kind.effects(&data); 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 diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index b0aaedbf9f..c7fb0e6efc 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -74,10 +74,6 @@ pub struct Stats { pub original_body: Body, pub damage_reduction: StatsSplit, pub poise_reduction: StatsSplit, - pub heal_multiplier: f32, - // Note: This is used to counteract agility as a "potion sickness" right now, and otherwise - // does not impact movement speed - pub move_speed_multiplier: f32, pub max_health_modifiers: StatsModifier, pub move_speed_modifier: f32, pub jump_modifier: f32, @@ -102,6 +98,7 @@ pub struct Stats { pub effects_on_death: Vec, pub disable_auxiliary_abilities: bool, pub crowd_control_resistance: f32, + pub item_effect_reduction: f32, } impl Stats { @@ -111,8 +108,6 @@ impl Stats { original_body: body, damage_reduction: StatsSplit::default(), poise_reduction: StatsSplit::default(), - heal_multiplier: 1.0, - move_speed_multiplier: 1.0, max_health_modifiers: StatsModifier::default(), move_speed_modifier: 1.0, jump_modifier: 1.0, @@ -132,6 +127,7 @@ impl Stats { effects_on_death: Vec::new(), disable_auxiliary_abilities: false, crowd_control_resistance: 0.0, + item_effect_reduction: 1.0, } } diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index f945ed4299..a9b08f0988 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -790,12 +790,6 @@ fn execute_effect( stat.poise_reduction.neg_mod += pr; } }, - BuffEffect::HealReduction(red) => { - stat.heal_multiplier *= 1.0 - *red; - }, - BuffEffect::MoveSpeedReduction(red) => { - stat.move_speed_multiplier *= 1.0 - *red; - }, BuffEffect::PoiseDamageFromLostHealth(strength) => { stat.poise_damage_modifier *= 1.0 + (1.0 - health.fraction()) * *strength; }, @@ -855,5 +849,8 @@ fn execute_effect( BuffEffect::CrowdControlResistance(ccr) => { stat.crowd_control_resistance += ccr; }, + BuffEffect::ItemEffectReduction(ier) => { + stat.item_effect_reduction *= 1.0 - ier; + }, }; } diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 5ac624fc6c..30ad349609 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -740,7 +740,7 @@ impl<'a> AgentData<'a> { relaxed: bool, ) -> bool { // Wait for potion sickness to wear off if potions are less than 50% effective. - let heal_multiplier = self.stats.map_or(1.0, |s| s.heal_multiplier); + let heal_multiplier = self.stats.map_or(1.0, |s| s.item_effect_reduction); if heal_multiplier < 0.5 { return false; } @@ -754,7 +754,7 @@ impl<'a> AgentData<'a> { }, Effect::Buff(BuffEffect { kind, data, .. }) => { if let Some(duration) = data.duration { - for effect in kind.effects(data, self.stats) { + for effect in kind.effects(data) { match effect { comp::BuffEffect::HealthChangeOverTime { rate, kind, .. } => { let amount = match kind { @@ -766,7 +766,7 @@ impl<'a> AgentData<'a> { value += amount; }, - comp::BuffEffect::HealReduction(amount) => { + comp::BuffEffect::ItemEffectReduction(amount) => { heal_reduction = heal_reduction + amount - heal_reduction * amount; }, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 7953aefd5a..8e5d79fa80 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -27,9 +27,9 @@ use common::{ inventory::item::{AbilityMap, MaterialStatManifest}, item::flatten_counted_items, loot_owner::LootOwnerKind, - Alignment, Auras, Body, CharacterState, Energy, Group, Health, Inventory, Object, - PickupItem, Player, Poise, PoiseChange, Pos, Presence, PresenceKind, SkillSet, Stats, - BASE_ABILITY_LIMIT, + Alignment, Auras, Body, BuffEffect, CharacterState, Energy, Group, Health, Inventory, + Object, PickupItem, Player, Poise, PoiseChange, Pos, Presence, PresenceKind, SkillSet, + Stats, BASE_ABILITY_LIMIT, }, consts::TELEPORTER_RADIUS, event::{ @@ -1630,9 +1630,19 @@ impl ServerEvent for BuffEvent { use buff::BuffChange; match ev.buff_change { BuffChange::Add(new_buff) => { + let immunity_by_buff = buffs + .buffs + .values_mut() + .flat_map(|b| b.kind.effects(&b.data)) + .find(|b| match b { + BuffEffect::BuffImmunity(kind) => new_buff.kind == *kind, + _ => false, + }); + if !bodies .get(ev.entity) .map_or(false, |body| body.immune_to(new_buff.kind)) + && immunity_by_buff.is_none() && healths.get(ev.entity).map_or(true, |h| !h.is_dead) { if let Some(strength) =