Freezing potion and debuff immunity

This commit is contained in:
Papinha 2024-08-04 22:14:52 +00:00 committed by Samuel Keiffer
parent e6c2808fbd
commit dcc2163188
15 changed files with 130 additions and 42 deletions

View File

@ -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. - Protocol to query game server information (player count, version, etc.) and make ping tests.
- Unlockable recipes - Unlockable recipes
- Localization support for prompt dialogs, diary sections, trade and group invitations. - Localization support for prompt dialogs, diary sections, trade and group invitations.
- Added Freezing Potion
### Changed ### Changed

View File

@ -3183,6 +3183,9 @@
Simple( Simple(
"common.items.consumable.potion_minor", "common.items.consumable.potion_minor",
): "object-potion_minor", ): "object-potion_minor",
Simple(
"common.items.consumable.potion_freezing",
): "object-potion_freezing",
Simple( Simple(
"common.items.lantern.black_0", "common.items.lantern.black_0",
): "lantern-black", ): "lantern-black",

View File

@ -11,7 +11,15 @@ ItemDef(
duration: Some(10) duration: Some(10)
), ),
cat_ids: [Natural], cat_ids: [Natural],
)) )),
Buff((
kind: PotionSickness,
data: (
strength: 0.15,
duration: Some(30),
),
cat_ids: [Natural],
)),
]) ])
), ),
quality: Moderate, quality: Moderate,

View File

@ -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],
)

View File

@ -4,6 +4,7 @@ ItemDef(
kind: RecipeGroup( kind: RecipeGroup(
recipes: [ recipes: [
"potion_combustion", "potion_combustion",
"potion_freezing",
"potion_agility", "potion_agility",
"potion_minor", "potion_minor",
"potion_medium", "potion_medium",

View File

@ -31,6 +31,15 @@
], ],
craft_sprite: Some(Anvil), 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": ( "potion_combustion": (
output: ("common.items.consumable.potion_combustion", 1), output: ("common.items.consumable.potion_combustion", 1),
inputs: [ inputs: [

View File

@ -19,6 +19,9 @@ object-potion_med = Medium Potion
object-potion_minor = Minor Potion object-potion_minor = Minor Potion
.desc = A small potion concocted from apples and honey. .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 object-burning_charm = Blazing Charm
.desc = Flame is your ally, harness its power to burn your foes. .desc = Flame is your ally, harness its power to burn your foes.

View File

@ -3411,6 +3411,10 @@
"voxel.object.potion_combustion", "voxel.object.potion_combustion",
(0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7, (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( Simple("common.items.consumable.potion_agility"): VoxTrans(
"voxel.object.potion_agility", "voxel.object.potion_agility",
(0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7, (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7,

View File

@ -862,6 +862,7 @@
Simple("common.items.consumable.potion_big"): "voxel.object.potion_red", 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_curious"): "voxel.object.potion_curious",
Simple("common.items.consumable.potion_combustion"): "voxel.object.potion_combustion", 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.consumable.potion_agility"): "voxel.object.potion_agility",
Simple("common.items.charms.burning_charm"): "voxel.object.burning_charm", Simple("common.items.charms.burning_charm"): "voxel.object.burning_charm",
Simple("common.items.charms.frozen_charm"): "voxel.object.frozen_charm", Simple("common.items.charms.frozen_charm"): "voxel.object.frozen_charm",

BIN
assets/voxygen/voxel/object/potion_blue_1.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -152,6 +152,7 @@ pub enum BuffKind {
// ================= // =================
/// Does damage to a creature over time. /// Does damage to a creature over time.
/// Strength should be the DPS of the debuff. /// Strength should be the DPS of the debuff.
/// Provides immunity against Frozen.
Burning, Burning,
/// Lowers health over time for some duration. /// Lowers health over time for some duration.
/// Strength should be the DPS of the debuff. /// 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% /// 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 /// speed, 1.0 is 33% speed. Movement speed debuff is scaled to be slightly
/// smaller than attack speed debuff. /// smaller than attack speed debuff.
/// Provides immunity against Heatstroke.
Frozen, Frozen,
/// Makes you wet and causes you to have reduced friction on the ground. /// 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 /// Strength scales the friction you ignore non-linearly. 0.5 is 50% ground
/// friction, 1.0 is 33% ground friction. /// friction, 1.0 is 33% ground friction.
/// Provides immunity against Burning.
Wet, Wet,
/// Makes you move slower. /// Makes you move slower.
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50% /// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
@ -319,7 +322,7 @@ impl BuffKind {
/// only the strongest. /// only the strongest.
pub fn stacks(self) -> bool { matches!(self, BuffKind::PotionSickness | BuffKind::Resilience) } pub fn stacks(self) -> bool { matches!(self, BuffKind::PotionSickness | BuffKind::Resilience) }
pub fn effects(&self, data: &BuffData, stats: Option<&Stats>) -> Vec<BuffEffect> { pub fn effects(&self, data: &BuffData) -> Vec<BuffEffect> {
// Normalized nonlinear scaling // Normalized nonlinear scaling
// TODO: Do we want to make denominator term parameterized. Come back to if we // TODO: Do we want to make denominator term parameterized. Come back to if we
// add nn_scaling3. // add nn_scaling3.
@ -347,16 +350,14 @@ impl BuffKind {
}], }],
BuffKind::Potion => { BuffKind::Potion => {
vec![BuffEffect::HealthChangeOverTime { vec![BuffEffect::HealthChangeOverTime {
rate: data.strength * stats.map_or(1.0, |s| s.heal_multiplier), rate: data.strength,
kind: ModifierKind::Additive, kind: ModifierKind::Additive,
instance, instance,
tick_dur: Secs(0.1), tick_dur: Secs(0.1),
}] }]
}, },
BuffKind::Agility => vec![ BuffKind::Agility => vec![
BuffEffect::MovementSpeed( BuffEffect::MovementSpeed(1.0 + data.strength),
1.0 + data.strength * stats.map_or(1.0, |s| s.move_speed_multiplier),
),
BuffEffect::DamageReduction(-1.0), BuffEffect::DamageReduction(-1.0),
BuffEffect::AttackDamage(0.0), BuffEffect::AttackDamage(0.0),
], ],
@ -399,12 +400,15 @@ impl BuffKind {
// strength. 0.5 also still provides 50% damage reduction. // strength. 0.5 also still provides 50% damage reduction.
nn_scaling(data.strength), nn_scaling(data.strength),
)], )],
BuffKind::Burning => vec![BuffEffect::HealthChangeOverTime { BuffKind::Burning => vec![
BuffEffect::HealthChangeOverTime {
rate: -data.strength, rate: -data.strength,
kind: ModifierKind::Additive, kind: ModifierKind::Additive,
instance, instance,
tick_dur: Secs(0.25), tick_dur: Secs(0.25),
}], },
BuffEffect::BuffImmunity(BuffKind::Frozen),
],
BuffKind::Poisoned => vec![BuffEffect::EnergyChangeOverTime { BuffKind::Poisoned => vec![BuffEffect::EnergyChangeOverTime {
rate: -data.strength, rate: -data.strength,
kind: ModifierKind::Additive, kind: ModifierKind::Additive,
@ -431,8 +435,12 @@ impl BuffKind {
BuffKind::Frozen => vec![ BuffKind::Frozen => vec![
BuffEffect::MovementSpeed(f32::powf(1.0 - nn_scaling(data.strength), 1.1)), BuffEffect::MovementSpeed(f32::powf(1.0 - nn_scaling(data.strength), 1.1)),
BuffEffect::AttackSpeed(1.0 - nn_scaling(data.strength)), 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::Ensnared => vec![BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength))],
BuffKind::Hastened => vec![ BuffKind::Hastened => vec![
BuffEffect::MovementSpeed(1.0 + data.strength), BuffEffect::MovementSpeed(1.0 + data.strength),
@ -447,11 +455,7 @@ impl BuffKind {
BuffEffect::RecoverySpeed(0.25), BuffEffect::RecoverySpeed(0.25),
BuffEffect::PrecisionVulnerabilityOverride(1.0), BuffEffect::PrecisionVulnerabilityOverride(1.0),
], ],
//TODO: Handle potion sickness in a more general way. BuffKind::PotionSickness => vec![BuffEffect::ItemEffectReduction(data.strength)],
BuffKind::PotionSickness => vec![
BuffEffect::HealReduction(data.strength),
BuffEffect::MoveSpeedReduction(data.strength),
],
BuffKind::Reckless => vec![ BuffKind::Reckless => vec![
BuffEffect::DamageReduction(-data.strength), BuffEffect::DamageReduction(-data.strength),
BuffEffect::AttackDamage(1.0 + data.strength), BuffEffect::AttackDamage(1.0 + data.strength),
@ -562,6 +566,7 @@ impl BuffKind {
mut data: BuffData, mut data: BuffData,
source_mass: Option<&Mass>, source_mass: Option<&Mass>,
dest_info: DestInfo, dest_info: DestInfo,
source: BuffSource,
) -> BuffData { ) -> BuffData {
// TODO: Remove clippy allow after another buff needs this // TODO: Remove clippy allow after another buff needs this
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
@ -580,6 +585,7 @@ impl BuffKind {
.map_or(1.0, |s| (1.0 - s.crowd_control_resistance).max(0.0)); .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); data.duration = data.duration.map(|dur| dur * dur_mult as f64);
} }
self.apply_item_effect_reduction(&mut data, source, dest_info);
data data
} }
@ -592,6 +598,27 @@ impl BuffKind {
_ => None, _ => 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 // Struct used to store data relevant to a buff
@ -705,10 +732,6 @@ pub enum BuffEffect {
GroundFriction(f32), GroundFriction(f32),
/// Reduces poise damage taken after armor is accounted for by this fraction /// Reduces poise damage taken after armor is accounted for by this fraction
PoiseReduction(f32), 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 /// Increases poise damage dealt when health is lost
PoiseDamageFromLostHealth(f32), PoiseDamageFromLostHealth(f32),
/// Modifier to the amount of damage dealt with attacks /// Modifier to the amount of damage dealt with attacks
@ -737,6 +760,8 @@ pub enum BuffEffect {
DisableAuxiliaryAbilities, DisableAuxiliaryAbilities,
/// Reduces duration of crowd control debuffs /// Reduces duration of crowd control debuffs
CrowdControlResistance(f32), CrowdControlResistance(f32),
/// Reduces the strength or duration of item buff
ItemEffectReduction(f32),
} }
/// Actual de/buff. /// Actual de/buff.
@ -796,8 +821,8 @@ impl Buff {
// Create source_info if we need more parameters from source // Create source_info if we need more parameters from source
source_mass: Option<&Mass>, source_mass: Option<&Mass>,
) -> Self { ) -> Self {
let data = kind.modify_data(data, source_mass, dest_info); let data = kind.modify_data(data, source_mass, dest_info, source);
let effects = kind.effects(&data, dest_info.stats); let effects = kind.effects(&data);
let cat_ids = kind.extend_cat_ids(cat_ids); 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 start_time = Time(time.0 + data.delay.map_or(0.0, |delay| delay.0));
let end_time = if cat_ids let end_time = if cat_ids

View File

@ -74,10 +74,6 @@ pub struct Stats {
pub original_body: Body, pub original_body: Body,
pub damage_reduction: StatsSplit, pub damage_reduction: StatsSplit,
pub poise_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 max_health_modifiers: StatsModifier,
pub move_speed_modifier: f32, pub move_speed_modifier: f32,
pub jump_modifier: f32, pub jump_modifier: f32,
@ -102,6 +98,7 @@ pub struct Stats {
pub effects_on_death: Vec<DeathEffect>, pub effects_on_death: Vec<DeathEffect>,
pub disable_auxiliary_abilities: bool, pub disable_auxiliary_abilities: bool,
pub crowd_control_resistance: f32, pub crowd_control_resistance: f32,
pub item_effect_reduction: f32,
} }
impl Stats { impl Stats {
@ -111,8 +108,6 @@ impl Stats {
original_body: body, original_body: body,
damage_reduction: StatsSplit::default(), damage_reduction: StatsSplit::default(),
poise_reduction: StatsSplit::default(), poise_reduction: StatsSplit::default(),
heal_multiplier: 1.0,
move_speed_multiplier: 1.0,
max_health_modifiers: StatsModifier::default(), max_health_modifiers: StatsModifier::default(),
move_speed_modifier: 1.0, move_speed_modifier: 1.0,
jump_modifier: 1.0, jump_modifier: 1.0,
@ -132,6 +127,7 @@ impl Stats {
effects_on_death: Vec::new(), effects_on_death: Vec::new(),
disable_auxiliary_abilities: false, disable_auxiliary_abilities: false,
crowd_control_resistance: 0.0, crowd_control_resistance: 0.0,
item_effect_reduction: 1.0,
} }
} }

View File

@ -790,12 +790,6 @@ fn execute_effect(
stat.poise_reduction.neg_mod += pr; 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) => { BuffEffect::PoiseDamageFromLostHealth(strength) => {
stat.poise_damage_modifier *= 1.0 + (1.0 - health.fraction()) * *strength; stat.poise_damage_modifier *= 1.0 + (1.0 - health.fraction()) * *strength;
}, },
@ -855,5 +849,8 @@ fn execute_effect(
BuffEffect::CrowdControlResistance(ccr) => { BuffEffect::CrowdControlResistance(ccr) => {
stat.crowd_control_resistance += ccr; stat.crowd_control_resistance += ccr;
}, },
BuffEffect::ItemEffectReduction(ier) => {
stat.item_effect_reduction *= 1.0 - ier;
},
}; };
} }

View File

@ -740,7 +740,7 @@ impl<'a> AgentData<'a> {
relaxed: bool, relaxed: bool,
) -> bool { ) -> bool {
// Wait for potion sickness to wear off if potions are less than 50% effective. // 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 { if heal_multiplier < 0.5 {
return false; return false;
} }
@ -754,7 +754,7 @@ impl<'a> AgentData<'a> {
}, },
Effect::Buff(BuffEffect { kind, data, .. }) => { Effect::Buff(BuffEffect { kind, data, .. }) => {
if let Some(duration) = data.duration { if let Some(duration) = data.duration {
for effect in kind.effects(data, self.stats) { for effect in kind.effects(data) {
match effect { match effect {
comp::BuffEffect::HealthChangeOverTime { rate, kind, .. } => { comp::BuffEffect::HealthChangeOverTime { rate, kind, .. } => {
let amount = match kind { let amount = match kind {
@ -766,7 +766,7 @@ impl<'a> AgentData<'a> {
value += amount; value += amount;
}, },
comp::BuffEffect::HealReduction(amount) => { comp::BuffEffect::ItemEffectReduction(amount) => {
heal_reduction = heal_reduction =
heal_reduction + amount - heal_reduction * amount; heal_reduction + amount - heal_reduction * amount;
}, },

View File

@ -27,9 +27,9 @@ use common::{
inventory::item::{AbilityMap, MaterialStatManifest}, inventory::item::{AbilityMap, MaterialStatManifest},
item::flatten_counted_items, item::flatten_counted_items,
loot_owner::LootOwnerKind, loot_owner::LootOwnerKind,
Alignment, Auras, Body, CharacterState, Energy, Group, Health, Inventory, Object, Alignment, Auras, Body, BuffEffect, CharacterState, Energy, Group, Health, Inventory,
PickupItem, Player, Poise, PoiseChange, Pos, Presence, PresenceKind, SkillSet, Stats, Object, PickupItem, Player, Poise, PoiseChange, Pos, Presence, PresenceKind, SkillSet,
BASE_ABILITY_LIMIT, Stats, BASE_ABILITY_LIMIT,
}, },
consts::TELEPORTER_RADIUS, consts::TELEPORTER_RADIUS,
event::{ event::{
@ -1630,9 +1630,19 @@ impl ServerEvent for BuffEvent {
use buff::BuffChange; use buff::BuffChange;
match ev.buff_change { match ev.buff_change {
BuffChange::Add(new_buff) => { 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 if !bodies
.get(ev.entity) .get(ev.entity)
.map_or(false, |body| body.immune_to(new_buff.kind)) .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) && healths.get(ev.entity).map_or(true, |h| !h.is_dead)
{ {
if let Some(strength) = if let Some(strength) =