diff --git a/assets/voxygen/element/icons/de_buffs/buff_invincibility_0.png b/assets/voxygen/element/icons/de_buffs/buff_invincibility_0.png new file mode 100644 index 0000000000..1a411d1ab8 --- /dev/null +++ b/assets/voxygen/element/icons/de_buffs/buff_invincibility_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d535544809197d5d3611f106c4316aaab1e72176361f94eeab7e7d94e101c394 +size 2068 diff --git a/assets/voxygen/i18n/de_DE/_manifest.ron b/assets/voxygen/i18n/de_DE/_manifest.ron index 08ef6db554..4326243bf3 100644 --- a/assets/voxygen/i18n/de_DE/_manifest.ron +++ b/assets/voxygen/i18n/de_DE/_manifest.ron @@ -537,8 +537,8 @@ Willenskraft "buff.title.heal": "HoT Buff Test", "buff.desc.heal": "HoT Buff Test", // Debuffs - "debuff.title.bleed": "Blutung", - "debuff.desc.bleed": "Fügt regelmäßig Schaden zu.", + "buff.title.bleed": "Blutung", + "buff.desc.bleed": "Fügt regelmäßig Schaden zu.", }, vector_map: { diff --git a/assets/voxygen/i18n/en/buff.ron b/assets/voxygen/i18n/en/buff.ron index b1a8b8a401..1c0de6e367 100644 --- a/assets/voxygen/i18n/en/buff.ron +++ b/assets/voxygen/i18n/en/buff.ron @@ -3,6 +3,7 @@ /// Localization for "global" English ( string_map: { + // Buffs "buff.remove": "Click to remove", "buff.title.missing": "Missing Title", "buff.desc.missing": "Missing Description", @@ -14,6 +15,13 @@ "buff.desc.saturation": "Gain health over time from consumables.", "buff.title.campfire_heal": "Campfire Heal", "buff.desc.campfire_heal": "Resting at a campfire heals 1% per second.", + "buff.title.invulnerability": "Invulnerability", + "buff.desc.invulnerability": "You cannot be damaged by any attack.", + // Debuffs + "buff.title.bleed": "Bleeding", + "buff.desc.bleed": "Inflicts regular damage.", + "buff.title.cursed": "Cursed", + "buff.desc.cursed": "You are cursed.", }, diff --git a/assets/voxygen/i18n/en/debuff.ron b/assets/voxygen/i18n/en/debuff.ron deleted file mode 100644 index df35b4df6b..0000000000 --- a/assets/voxygen/i18n/en/debuff.ron +++ /dev/null @@ -1,13 +0,0 @@ -/// WARNING: Localization files shall be saved in UTF-8 format without BOM - -/// Localization for "global" English -( - string_map: { - "debuff.title.bleed": "Bleeding", - "debuff.desc.bleed": "Inflicts regular damage.", - }, - - - vector_map: { - } -) diff --git a/assets/voxygen/i18n/es_la/_manifest.ron b/assets/voxygen/i18n/es_la/_manifest.ron index 31104e3192..e54485fc71 100644 --- a/assets/voxygen/i18n/es_la/_manifest.ron +++ b/assets/voxygen/i18n/es_la/_manifest.ron @@ -554,8 +554,8 @@ Protección "buff.title.campfire_heal": "Curación de fogata", "buff.desc.campfire_heal": "Descansar en una fogata recupera 1% por segundo.", // Debuffs - "debuff.title.bleed": "Sangrando", - "debuff.desc.bleed": "Inflinge daño regularmente.", + "buff.title.bleed": "Sangrando", + "buff.desc.bleed": "Inflinge daño regularmente.", }, diff --git a/assets/voxygen/i18n/fr_FR/_manifest.ron b/assets/voxygen/i18n/fr_FR/_manifest.ron index ab8b149da9..1a5dc72d59 100644 --- a/assets/voxygen/i18n/fr_FR/_manifest.ron +++ b/assets/voxygen/i18n/fr_FR/_manifest.ron @@ -533,8 +533,8 @@ Protection "buff.title.campfire_heal": "Soin autour d'un feu de camp", "buff.desc.campfire_heal": "Se reposer à côté d'un feu de camp restaure 1% de santé par seconde.", // Debuffs - "debuff.title.bleed": "Saignement", - "debuff.desc.bleed": "Inflige régulièrement des dommages.", + "buff.title.bleed": "Saignement", + "buff.desc.bleed": "Inflige régulièrement des dommages.", }, diff --git a/assets/voxygen/i18n/no/_manifest.ron b/assets/voxygen/i18n/no/_manifest.ron index fa9fc1de15..9e6a7677c3 100644 --- a/assets/voxygen/i18n/no/_manifest.ron +++ b/assets/voxygen/i18n/no/_manifest.ron @@ -534,8 +534,8 @@ Beskyttelse "buff.title.saturation": "Metning", "buff.desc.saturation": "Helbred over tid fra forbruksvarer.", // Debuffs - "debuff.title.bleed": "Blør", - "debuff.desc.bleed": "Påfører regelmessig skade.", + "buff.title.bleed": "Blør", + "buff.desc.bleed": "Påfører regelmessig skade.", }, diff --git a/assets/voxygen/i18n/pt_BR/buff.ron b/assets/voxygen/i18n/pt_BR/buff.ron index bd6ff52ca2..f473aa96dc 100644 --- a/assets/voxygen/i18n/pt_BR/buff.ron +++ b/assets/voxygen/i18n/pt_BR/buff.ron @@ -14,6 +14,8 @@ "buff.desc.saturation": "Ganha vida no decorrer do tempo através de consumíveis.", "buff.title.campfire_heal": "Cura de Acampamento", "buff.desc.campfire_heal": "Descansar próximo a uma fogueira de acampamento cura 1% de vida por segundo.", + "buff.title.bleed": "Sangramento", + "buff.desc.bleed": "Inflige Dano contínuo.", }, diff --git a/assets/voxygen/i18n/pt_BR/debuff.ron b/assets/voxygen/i18n/pt_BR/debuff.ron deleted file mode 100644 index 947d2fb6d5..0000000000 --- a/assets/voxygen/i18n/pt_BR/debuff.ron +++ /dev/null @@ -1,13 +0,0 @@ -/// CUIDADO: Arquivos de tradução devem ser criados no formato UTF-8 sem Marca de Ordem de byte - BOM(https://pt.wikipedia.org/wiki/Marca_de_ordem_de_byte) - -/// Localization for Portuguese (Brazil) -( - string_map: { - "debuff.title.bleed": "Sangramento", - "debuff.desc.bleed": "Inflige Dano contínuo.", - }, - - - vector_map: { - } -) diff --git a/assets/voxygen/i18n/ru_RU/_manifest.ron b/assets/voxygen/i18n/ru_RU/_manifest.ron index d5effe23ef..59cccf95fe 100644 --- a/assets/voxygen/i18n/ru_RU/_manifest.ron +++ b/assets/voxygen/i18n/ru_RU/_manifest.ron @@ -550,8 +550,8 @@ https://veloren.net/account/."#, "buff.title.saturation": "Сыт", "buff.desc.saturation": "Получайте здоровье от расходников в течении времени.", // Debuffs - "debuff.title.bleed": "Кровотечение", - "debuff.desc.bleed": "Наносит переодический урон.", + "buff.title.bleed": "Кровотечение", + "buff.desc.bleed": "Наносит переодический урон.", }, diff --git a/assets/voxygen/i18n/tr_TR/buff.ron b/assets/voxygen/i18n/tr_TR/buff.ron index 9ebea8bd2e..398ed2acd9 100644 --- a/assets/voxygen/i18n/tr_TR/buff.ron +++ b/assets/voxygen/i18n/tr_TR/buff.ron @@ -14,6 +14,8 @@ "buff.desc.saturation": "Tüketilebilen maddelerden zamanla can kazan.", "buff.title.campfire_heal": "Kamp Ateşi", "buff.desc.campfire_heal": "Kamp ateşinin yakınında oturmak canını saniyede %1 iyileştirir.", + "buff.title.bleed": "Kanama", + "buff.desc.bleed": "Devamlı hasar verir.", }, vector_map: { diff --git a/assets/voxygen/i18n/tr_TR/debuff.ron b/assets/voxygen/i18n/tr_TR/debuff.ron deleted file mode 100644 index b61b4557f5..0000000000 --- a/assets/voxygen/i18n/tr_TR/debuff.ron +++ /dev/null @@ -1,13 +0,0 @@ -/// WARNING: Localization files shall be saved in UTF-8 format without BOM - -/// Localization for Turkish (Turkey) -( - string_map: { - "debuff.title.bleed": "Kanama", - "debuff.desc.bleed": "Devamlı hasar verir.", - }, - - - vector_map: { - } -) diff --git a/assets/voxygen/i18n/uk_UA/buff.ron b/assets/voxygen/i18n/uk_UA/buff.ron index 81ce8a6b4c..9859b7b660 100644 --- a/assets/voxygen/i18n/uk_UA/buff.ron +++ b/assets/voxygen/i18n/uk_UA/buff.ron @@ -14,6 +14,8 @@ "buff.desc.saturation": "Поступово відновлює здоров'я з їжі.", "buff.title.campfire_heal": "Відновлення біля ватри", "buff.desc.campfire_heal": "Відпочинок біля ватри лікує на 1% за секунду.", + "buff.title.bleed": "Кровотеча", + "buff.desc.bleed": "Наносить регулярні ушкодження.", }, diff --git a/assets/voxygen/i18n/uk_UA/debuff.ron b/assets/voxygen/i18n/uk_UA/debuff.ron deleted file mode 100644 index 477443db4c..0000000000 --- a/assets/voxygen/i18n/uk_UA/debuff.ron +++ /dev/null @@ -1,13 +0,0 @@ -/// WARNING: Localization files shall be saved in UTF-8 format without BOM - -/// Localization for Ukrainian -( - string_map: { - "debuff.title.bleed": "Кровотеча", - "debuff.desc.bleed": "Наносить регулярні ушкодження.", - }, - - - vector_map: { - } -) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 6b5cfb26f5..f6771bdcd3 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -70,6 +70,7 @@ pub enum ChatCommand { Players, Region, RemoveLights, + Safezone, Say, SetMotd, SkillPoint, @@ -122,6 +123,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[ ChatCommand::Players, ChatCommand::Region, ChatCommand::RemoveLights, + ChatCommand::Safezone, ChatCommand::Say, ChatCommand::SetMotd, ChatCommand::SkillPoint, @@ -370,6 +372,11 @@ impl ChatCommand { "Send messages to everyone in your region of the world", NoAdmin, ), + ChatCommand::Safezone => cmd( + vec![Float("range", 100.0, Optional)], + "Creates a safezone", + Admin, + ), ChatCommand::Say => cmd( vec![Message(Optional)], "Send messages to everyone within shouting distance", @@ -476,6 +483,7 @@ impl ChatCommand { ChatCommand::Players => "players", ChatCommand::Region => "region", ChatCommand::RemoveLights => "remove_lights", + ChatCommand::Safezone => "safezone", ChatCommand::Say => "say", ChatCommand::SetMotd => "set_motd", ChatCommand::SkillPoint => "skill_point", diff --git a/common/src/combat.rs b/common/src/combat.rs index 803b009693..5687b237b2 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -47,6 +47,13 @@ pub struct AttackerInfo<'a> { pub energy: Option<&'a Energy>, } +#[cfg(not(target_arch = "wasm32"))] +pub struct TargetInfo<'a> { + pub entity: EcsEntity, + pub inventory: Option<&'a Inventory>, + pub stats: Option<&'a Stats>, +} + #[cfg(not(target_arch = "wasm32"))] #[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive pub struct Attack { @@ -99,9 +106,8 @@ impl Attack { pub fn apply_attack( &self, target_group: GroupTarget, - attacker_info: Option, - target_entity: EcsEntity, - target_inventory: Option<&Inventory>, + attacker: Option, + target: TargetInfo, dir: Dir, target_dodging: bool, // Currently just modifies damage, maybe look into modifying strength of other effects? @@ -116,9 +122,10 @@ impl Attack { .filter(|d| d.target.map_or(true, |t| t == target_group)) .filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging)) { + let damage_reduction = Damage::compute_damage_reduction(target.inventory, target.stats); let change = damage.damage.calculate_health_change( - target_inventory, - attacker_info.map(|a| a.uid), + damage_reduction, + attacker.map(|a| a.uid), is_crit, self.crit_multiplier, strength_modifier, @@ -127,7 +134,7 @@ impl Attack { accumulated_damage += applied_damage; if change.amount != 0 { emit(ServerEvent::Damage { - entity: target_entity, + entity: target.entity, change, }); for effect in damage.effects.iter() { @@ -136,13 +143,13 @@ impl Attack { let impulse = kb.calculate_impulse(dir); if !impulse.is_approx_zero() { emit(ServerEvent::Knockback { - entity: target_entity, + entity: target.entity, impulse, }); } }, CombatEffect::EnergyReward(ec) => { - if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { + if let Some(attacker_entity) = attacker.map(|a| a.entity) { emit(ServerEvent::EnergyChange { entity: attacker_entity, change: EnergyChange { @@ -155,19 +162,19 @@ impl Attack { CombatEffect::Buff(b) => { if thread_rng().gen::() < b.chance { emit(ServerEvent::Buff { - entity: target_entity, + entity: target.entity, buff_change: BuffChange::Add( - b.to_buff(attacker_info.map(|a| a.uid), applied_damage), + b.to_buff(attacker.map(|a| a.uid), applied_damage), ), }); } }, CombatEffect::Lifesteal(l) => { - if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { + if let Some(attacker_entity) = attacker.map(|a| a.entity) { let change = HealthChange { amount: (applied_damage * l) as i32, cause: HealthSource::Heal { - by: attacker_info.map(|a| a.uid), + by: attacker.map(|a| a.uid), }, }; if change.amount != 0 { @@ -179,10 +186,10 @@ impl Attack { } }, CombatEffect::Poise(p) => { - let change = PoiseChange::from_value(*p, target_inventory); + let change = PoiseChange::from_value(*p, target.inventory); if change.amount != 0 { emit(ServerEvent::PoiseChange { - entity: target_entity, + entity: target.entity, change, kb_dir: *dir, }); @@ -192,18 +199,18 @@ impl Attack { let change = HealthChange { amount: *h as i32, cause: HealthSource::Heal { - by: attacker_info.map(|a| a.uid), + by: attacker.map(|a| a.uid), }, }; if change.amount != 0 { emit(ServerEvent::Damage { - entity: target_entity, + entity: target.entity, change, }); } }, CombatEffect::Combo(c) => { - if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { + if let Some(attacker_entity) = attacker.map(|a| a.entity) { emit(ServerEvent::ComboChange { entity: attacker_entity, change: *c, @@ -227,7 +234,7 @@ impl Attack { entity, energy: Some(e), .. - }) = attacker_info + }) = attacker { let sufficient_energy = e.current() as f32 >= *r; if sufficient_energy { @@ -252,13 +259,13 @@ impl Attack { let impulse = kb.calculate_impulse(dir); if !impulse.is_approx_zero() { emit(ServerEvent::Knockback { - entity: target_entity, + entity: target.entity, impulse, }); } }, CombatEffect::EnergyReward(ec) => { - if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { + if let Some(attacker_entity) = attacker.map(|a| a.entity) { emit(ServerEvent::EnergyChange { entity: attacker_entity, change: EnergyChange { @@ -271,19 +278,19 @@ impl Attack { CombatEffect::Buff(b) => { if thread_rng().gen::() < b.chance { emit(ServerEvent::Buff { - entity: target_entity, + entity: target.entity, buff_change: BuffChange::Add( - b.to_buff(attacker_info.map(|a| a.uid), accumulated_damage), + b.to_buff(attacker.map(|a| a.uid), accumulated_damage), ), }); } }, CombatEffect::Lifesteal(l) => { - if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { + if let Some(attacker_entity) = attacker.map(|a| a.entity) { let change = HealthChange { amount: (accumulated_damage * l) as i32, cause: HealthSource::Heal { - by: attacker_info.map(|a| a.uid), + by: attacker.map(|a| a.uid), }, }; if change.amount != 0 { @@ -295,10 +302,10 @@ impl Attack { } }, CombatEffect::Poise(p) => { - let change = PoiseChange::from_value(p, target_inventory); + let change = PoiseChange::from_value(p, target.inventory); if change.amount != 0 { emit(ServerEvent::PoiseChange { - entity: target_entity, + entity: target.entity, change, kb_dir: *dir, }); @@ -308,18 +315,18 @@ impl Attack { let change = HealthChange { amount: h as i32, cause: HealthSource::Heal { - by: attacker_info.map(|a| a.uid), + by: attacker.map(|a| a.uid), }, }; if change.amount != 0 { emit(ServerEvent::Damage { - entity: target_entity, + entity: target.entity, change, }); } }, CombatEffect::Combo(c) => { - if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { + if let Some(attacker_entity) = attacker.map(|a| a.entity) { emit(ServerEvent::ComboChange { entity: attacker_entity, change: c, @@ -423,40 +430,49 @@ pub struct Damage { #[cfg(not(target_arch = "wasm32"))] impl Damage { /// Returns the total damage reduction provided by all equipped items - pub fn compute_damage_reduction(inventory: &Inventory) -> f32 { - let protection = inventory - .equipped_items() - .filter_map(|item| { - if let ItemKind::Armor(armor) = &item.kind() { - Some(armor.get_protection()) - } else { - None - } - }) - .map(|protection| match protection { - Protection::Normal(protection) => Some(protection), - Protection::Invincible => None, - }) - .sum::>(); + pub fn compute_damage_reduction(inventory: Option<&Inventory>, stats: Option<&Stats>) -> f32 { + let inventory_dr = if let Some(inventory) = inventory { + let protection = inventory + .equipped_items() + .filter_map(|item| { + if let ItemKind::Armor(armor) = &item.kind() { + Some(armor.get_protection()) + } else { + None + } + }) + .map(|protection| match protection { + Protection::Normal(protection) => Some(protection), + Protection::Invincible => None, + }) + .sum::>(); - const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; + const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; - match protection { - Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()), - None => 1.0, - } + match protection { + Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()), + None => 1.0, + } + } else { + 0.0 + }; + let stats_dr = if let Some(stats) = stats { + stats.damage_reduction + } else { + 0.0 + }; + 1.0 - (1.0 - inventory_dr) * (1.0 - stats_dr) } pub fn calculate_health_change( self, - inventory: Option<&Inventory>, + damage_reduction: f32, uid: Option, is_crit: bool, crit_mult: f32, damage_modifier: f32, ) -> HealthChange { let mut damage = self.value * damage_modifier; - let damage_reduction = inventory.map_or(0.0, Damage::compute_damage_reduction); match self.source { DamageSource::Melee => { // Critical hit @@ -705,7 +721,7 @@ pub fn combat_rating( let defensive_weighting = 1.0; let offensive_weighting = 1.0; let defensive_rating = health.maximum() as f32 - / (1.0 - Damage::compute_damage_reduction(inventory)).max(0.00001) + / (1.0 - Damage::compute_damage_reduction(Some(inventory), Some(stats))).max(0.00001) / 100.0; let offensive_rating = offensive_rating(inventory, &stats.skill_set, msm).max(0.1) + 0.05 * stats.skill_set.earned_sp(SkillGroupKind::General) as f32; diff --git a/common/src/comp/aura.rs b/common/src/comp/aura.rs index 30162d19ed..eb82775cdb 100644 --- a/common/src/comp/aura.rs +++ b/common/src/comp/aura.rs @@ -91,10 +91,12 @@ pub struct Auras { } impl Auras { - pub fn new(aura: Aura) -> Self { - let mut auras: SlotMap = SlotMap::with_key(); - auras.insert(aura); - Self { auras } + pub fn new(auras: Vec) -> Self { + let mut auras_comp: SlotMap = SlotMap::with_key(); + for aura in auras { + auras_comp.insert(aura); + } + Self { auras: auras_comp } } pub fn insert(&mut self, aura: Aura) { self.auras.insert(aura); } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 5b4ec2d542..7dc9fd0dd0 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -31,6 +31,8 @@ pub enum BuffKind { IncreaseMaxEnergy, /// Raises maximum health IncreaseMaxHealth, + /// Makes you immune to attacks + Invulnerability, } #[cfg(not(target_arch = "wasm32"))] @@ -46,6 +48,7 @@ impl BuffKind { BuffKind::CampfireHeal => true, BuffKind::IncreaseMaxEnergy => true, BuffKind::IncreaseMaxHealth => true, + BuffKind::Invulnerability => true, } } @@ -98,9 +101,16 @@ pub enum BuffEffect { kind: ModifierKind, }, /// Changes maximum health by a certain amount - MaxHealthModifier { value: f32, kind: ModifierKind }, + MaxHealthModifier { + value: f32, + kind: ModifierKind, + }, /// Changes maximum stamina by a certain amount - MaxEnergyModifier { value: f32, kind: ModifierKind }, + MaxEnergyModifier { + value: f32, + kind: ModifierKind, + }, + ImmuneToAttacks, } /// Actual de/buff. @@ -203,6 +213,7 @@ impl Buff { }], data.duration, ), + BuffKind::Invulnerability => (vec![BuffEffect::ImmuneToAttacks], data.duration), }; Buff { kind, diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index e333420e5e..a233b99c12 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -23,7 +23,10 @@ impl Error for StatChangeError {} #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Stats { pub name: String, + // TODO: Make skillset a separate component, probably too heavy for something that will + // potentially be updated every tick (especially as more buffs are added) pub skill_set: SkillSet, + pub damage_reduction: f32, } impl Stats { @@ -31,6 +34,7 @@ impl Stats { Self { name, skill_set: SkillSet::default(), + damage_reduction: 0.0, } } @@ -40,6 +44,7 @@ impl Stats { Self { name: "".to_owned(), skill_set: SkillSet::default(), + damage_reduction: 0.0, } } } diff --git a/common/sys/src/beam.rs b/common/sys/src/beam.rs index c4c2b863ea..0f4cdf130d 100644 --- a/common/sys/src/beam.rs +++ b/common/sys/src/beam.rs @@ -1,8 +1,8 @@ use common::{ - combat::AttackerInfo, + combat::{AttackerInfo, TargetInfo}, comp::{ Beam, BeamSegment, Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, Pos, - Scale, + Scale, Stats, }, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, @@ -33,6 +33,7 @@ pub struct ReadData<'a> { inventories: ReadStorage<'a, Inventory>, groups: ReadStorage<'a, Group>, energies: ReadStorage<'a, Energy>, + stats: ReadStorage<'a, Stats>, } /// This system is responsible for handling beams that heal or do damage @@ -157,11 +158,16 @@ impl<'a> System<'a> for Sys { energy: read_data.energies.get(entity), }); + let target_info = TargetInfo { + entity: target, + inventory: read_data.inventories.get(target), + stats: read_data.stats.get(target), + }; + beam_segment.properties.attack.apply_attack( target_group, attacker_info, - target, - read_data.inventories.get(target), + target_info, ori.look_dir(), false, 1.0, diff --git a/common/sys/src/buff.rs b/common/sys/src/buff.rs index 7e5e6ebe7e..2a2aaa8c05 100644 --- a/common/sys/src/buff.rs +++ b/common/sys/src/buff.rs @@ -1,7 +1,7 @@ use common::{ comp::{ Buff, BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, Energy, Health, - HealthChange, HealthSource, Inventory, ModifierKind, + HealthChange, HealthSource, Inventory, ModifierKind, Stats, }, event::{EventBus, ServerEvent}, resources::DeltaTime, @@ -27,17 +27,29 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Health>, WriteStorage<'a, Energy>, WriteStorage<'a, Buffs>, + WriteStorage<'a, Stats>, ); - fn run(&mut self, (read_data, mut healths, mut energies, mut buffs): Self::SystemData) { + fn run( + &mut self, + (read_data, mut healths, mut energies, mut buffs, mut stats): Self::SystemData, + ) { let mut server_emitter = read_data.server_bus.emitter(); let dt = read_data.dt.0; // Set to false to avoid spamming server buffs.set_event_emission(false); healths.set_event_emission(false); energies.set_event_emission(false); - for (entity, mut buff_comp, mut health, mut energy) in - (&read_data.entities, &mut buffs, &mut healths, &mut energies).join() + healths.set_event_emission(false); + stats.set_event_emission(false); + for (entity, mut buff_comp, mut health, mut energy, mut stat) in ( + &read_data.entities, + &mut buffs, + &mut healths, + &mut energies, + &mut stats, + ) + .join() { let (buff_comp_kinds, buff_comp_buffs) = buff_comp.parts(); let mut expired_buffs = Vec::::new(); @@ -61,13 +73,12 @@ impl<'a> System<'a> for Sys { } } - if let Some(inventory) = read_data.inventories.get(entity) { - let damage_reduction = Damage::compute_damage_reduction(inventory); - if (damage_reduction - 1.0).abs() < f32::EPSILON { - for (id, buff) in buff_comp.buffs.iter() { - if !buff.kind.is_buff() { - expired_buffs.push(*id); - } + let damage_reduction = + Damage::compute_damage_reduction(read_data.inventories.get(entity), Some(&stat)); + if (damage_reduction - 1.0).abs() < f32::EPSILON { + for (id, buff) in buff_comp.buffs.iter() { + if !buff.kind.is_buff() { + expired_buffs.push(*id); } } } @@ -77,6 +88,7 @@ impl<'a> System<'a> for Sys { energy.last_set(); health.reset_max(); energy.reset_max(); + stat.damage_reduction = 0.0; // Iterator over the lists of buffs by kind let buff_comp = &mut *buff_comp; @@ -147,6 +159,9 @@ impl<'a> System<'a> for Sys { energy.set_maximum(new_max); }, }, + BuffEffect::ImmuneToAttacks => { + stat.damage_reduction = 1.0; + }, }; } } @@ -176,6 +191,7 @@ impl<'a> System<'a> for Sys { buffs.set_event_emission(true); healths.set_event_emission(true); energies.set_event_emission(true); + stats.set_event_emission(true); } } diff --git a/common/sys/src/melee.rs b/common/sys/src/melee.rs index d9e248c720..548b76f143 100644 --- a/common/sys/src/melee.rs +++ b/common/sys/src/melee.rs @@ -1,6 +1,6 @@ use common::{ - combat::AttackerInfo, - comp::{Body, CharacterState, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale}, + combat::{AttackerInfo, TargetInfo}, + comp::{Body, CharacterState, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale, Stats}, event::{EventBus, ServerEvent}, metrics::SysMetrics, span, @@ -29,6 +29,7 @@ pub struct ReadData<'a> { char_states: ReadStorage<'a, CharacterState>, server_bus: Read<'a, EventBus>, metrics: ReadExpect<'a, SysMetrics>, + stats: ReadStorage<'a, Stats>, } /// This system is responsible for handling accepted inputs like moving or @@ -114,11 +115,16 @@ impl<'a> System<'a> for Sys { energy: read_data.energies.get(attacker), }); + let target_info = TargetInfo { + entity: target, + inventory: read_data.inventories.get(target), + stats: read_data.stats.get(target), + }; + melee_attack.attack.apply_attack( target_group, attacker_info, - target, - read_data.inventories.get(target), + target_info, dir, is_dodge, 1.0, diff --git a/common/sys/src/projectile.rs b/common/sys/src/projectile.rs index 329ad2df36..45701c3cb3 100644 --- a/common/sys/src/projectile.rs +++ b/common/sys/src/projectile.rs @@ -1,7 +1,8 @@ use common::{ - combat::AttackerInfo, + combat::{AttackerInfo, TargetInfo}, comp::{ - projectile, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile, Vel, + projectile, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile, + Stats, Vel, }, event::{EventBus, ServerEvent}, metrics::SysMetrics, @@ -30,6 +31,7 @@ pub struct ReadData<'a> { inventories: ReadStorage<'a, Inventory>, groups: ReadStorage<'a, Group>, energies: ReadStorage<'a, Energy>, + stats: ReadStorage<'a, Stats>, } /// This system is responsible for handling projectile effect triggers @@ -93,7 +95,7 @@ impl<'a> System<'a> for Sys { for effect in projectile.hit_entity.drain(..) { match effect { projectile::Effect::Attack(attack) => { - if let Some(target_entity) = read_data + if let Some(target) = read_data .uid_allocator .retrieve_entity_internal(other.into()) { @@ -110,11 +112,16 @@ impl<'a> System<'a> for Sys { } }); + let target_info = TargetInfo { + entity: target, + inventory: read_data.inventories.get(target), + stats: read_data.stats.get(target), + }; + attack.apply_attack( target_group, attacker_info, - target_entity, - read_data.inventories.get(target_entity), + target_info, ori.look_dir(), false, 1.0, diff --git a/common/sys/src/shockwave.rs b/common/sys/src/shockwave.rs index 0085125f75..80b5a12efa 100644 --- a/common/sys/src/shockwave.rs +++ b/common/sys/src/shockwave.rs @@ -1,8 +1,8 @@ use common::{ - combat::AttackerInfo, + combat::{AttackerInfo, TargetInfo}, comp::{ Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, PhysicsState, Pos, Scale, - Shockwave, ShockwaveHitEntities, + Shockwave, ShockwaveHitEntities, Stats, }, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, @@ -34,6 +34,7 @@ pub struct ReadData<'a> { groups: ReadStorage<'a, Group>, physics_states: ReadStorage<'a, PhysicsState>, energies: ReadStorage<'a, Energy>, + stats: ReadStorage<'a, Stats>, } /// This system is responsible for handling accepted inputs like moving or @@ -176,11 +177,16 @@ impl<'a> System<'a> for Sys { energy: read_data.energies.get(entity), }); + let target_info = TargetInfo { + entity: target, + inventory: read_data.inventories.get(target), + stats: read_data.stats.get(target), + }; + shockwave.properties.attack.apply_attack( target_group, attacker_info, - target, - read_data.inventories.get(target), + target_info, dir, false, 1.0, diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 0cd6ed1c7d..09a51eeec5 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -111,6 +111,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler { ChatCommand::Players => handle_players, ChatCommand::Region => handle_region, ChatCommand::RemoveLights => handle_remove_lights, + ChatCommand::Safezone => handle_safezone, ChatCommand::Say => handle_say, ChatCommand::SetMotd => handle_set_motd, ChatCommand::SkillPoint => handle_skill_point, @@ -975,7 +976,7 @@ fn handle_spawn_campfire( animated: true, }) .with(WaypointArea::default()) - .with(comp::Auras::new(Aura::new( + .with(comp::Auras::new(vec![Aura::new( AuraKind::Buff { kind: BuffKind::CampfireHeal, data: BuffData::new(0.02, Some(Duration::from_secs(1))), @@ -985,7 +986,7 @@ fn handle_spawn_campfire( 5.0, None, AuraTarget::All, - ))) + )])) .build(); server.notify_client( @@ -1000,6 +1001,45 @@ fn handle_spawn_campfire( } } +fn handle_safezone( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: String, + action: &ChatCommand, +) { + let range = scan_fmt_some!(&args, &action.arg_fmt(), f32); + + match server.state.read_component_copied::(target) { + Some(pos) => { + server + .state + .create_object(pos, comp::object::Body::BoltNature) + .with(comp::Auras::new(vec![Aura::new( + AuraKind::Buff { + kind: BuffKind::Invulnerability, + data: BuffData::new(1.0, Some(Duration::from_secs(1))), + category: BuffCategory::Natural, + source: BuffSource::World, + }, + range.unwrap_or(100.0), + None, + AuraTarget::All, + )])) + .build(); + + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a safe zone"), + ); + }, + None => server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandError, "You have no position!"), + ), + } +} + fn handle_players( server: &mut Server, client: EcsEntity, diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 6f7bab4552..0f02d79cb4 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -191,7 +191,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { }) .with(WaypointArea::default()) .with(comp::Mass(10_f32.powi(10))) - .with(comp::Auras::new(Aura::new( + .with(comp::Auras::new(vec![Aura::new( AuraKind::Buff { kind: BuffKind::CampfireHeal, data: BuffData::new(0.02, Some(Duration::from_secs(1))), @@ -201,6 +201,6 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { 5.0, None, AuraTarget::All, - ))) + )])) .build(); } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index cb4762e5a9..2a42c497ab 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -511,14 +511,16 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) if vel.z <= -30.0 { let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0; let inventories = state.ecs().read_storage::(); + let stats = state.ecs().read_storage::(); // Handle health change if let Some(mut health) = state.ecs().write_storage::().get_mut(entity) { let damage = Damage { source: DamageSource::Falling, value: falldmg, }; - let change = - damage.calculate_health_change(inventories.get(entity), None, false, 0.0, 1.0); + let damage_reduction = + Damage::compute_damage_reduction(inventories.get(entity), stats.get(entity)); + let change = damage.calculate_health_change(damage_reduction, None, false, 0.0, 1.0); health.change_by(change); } // Handle poise change @@ -674,14 +676,15 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o }, RadiusEffect::Attack(attack) => { let energies = &ecs.read_storage::(); - for (entity_b, pos_b, _health_b, inventory_b_maybe) in ( + for (entity_b, pos_b, _health_b, inventory_b_maybe, stats_b_maybe) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), ) .join() - .filter(|(_, _, h, _)| !h.is_dead) + .filter(|(_, _, h, _, _)| !h.is_dead) { // Check if it is a hit let distance_squared = pos.distance_squared(pos_b.0); @@ -714,13 +717,18 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o energy: energies.get(entity), }); + let target_info = combat::TargetInfo { + entity: entity_b, + inventory: inventory_b_maybe, + stats: stats_b_maybe, + }; + let server_eventbus = ecs.read_resource::>(); attack.apply_attack( target_group, attacker_info, - entity_b, - inventory_b_maybe, + target_info, dir, false, strength, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index ad1dae6fe9..6f2c5fc852 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -4,6 +4,7 @@ use crate::{ }; use common::{ character::CharacterId, + combat, comp::{ self, skills::{GeneralSkill, Skill}, @@ -89,8 +90,12 @@ impl StateExt for State { }, Effect::Damage(damage) => { let inventories = self.ecs().read_storage::(); + let stats = self.ecs().read_storage::(); let change = damage.calculate_health_change( - inventories.get(entity), + combat::Damage::compute_damage_reduction( + inventories.get(entity), + stats.get(entity), + ), source, false, 0.0, diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 8fcbe9dd45..9a71b5e5fe 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -696,7 +696,8 @@ impl<'a> Widget for Bag<'a> { }); let protection_txt = format!( "{}%", - (100.0 * Damage::compute_damage_reduction(inventory)) as i32 + (100.0 * Damage::compute_damage_reduction(Some(inventory), Some(self.stats))) + as i32 ); let health_txt = format!("{}", (self.health.maximum() as f32 / 10.0) as usize); let stamina_txt = format!("{}", (self.energy.maximum() as f32 / 10.0) as usize); diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index 62c6056818..ed351b905c 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -3,7 +3,7 @@ use super::{ BUFF_COLOR, DEBUFF_COLOR, TEXT_COLOR, }; use crate::{ - hud::{get_buff_info, BuffPosition}, + hud::{self, BuffPosition}, i18n::Localization, ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, GlobalState, @@ -145,7 +145,7 @@ impl<'a> Widget for BuffsBar<'a> { .set(state.ids.buffs_align, ui); // Buffs and Debuffs - let (buff_count, debuff_count) = buffs.iter_active().map(get_buff_info).fold( + let (buff_count, debuff_count) = buffs.iter_active().map(hud::get_buff_info).fold( (0, 0), |(buff_count, debuff_count), info| { if info.is_buff { @@ -183,7 +183,7 @@ impl<'a> Widget for BuffsBar<'a> { .zip( buffs .iter_active() - .map(get_buff_info) + .map(hud::get_buff_info) .filter(|info| info.is_buff), ) .collect::>(); @@ -201,15 +201,7 @@ impl<'a> Widget for BuffsBar<'a> { max_duration .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0) }) as u32; // Percentage to determine which frame of the timer overlay is displayed - let buff_img = match buff.kind { - BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, - BuffKind::Saturation { .. } => self.imgs.buff_saturation_0, - BuffKind::Potion { .. } => self.imgs.buff_potion_0, - BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0, - BuffKind::IncreaseMaxHealth { .. } => self.imgs.buff_healthplus_0, - BuffKind::IncreaseMaxEnergy { .. } => self.imgs.buff_energyplus_0, - _ => self.imgs.missing_icon, - }; + let buff_img = hud::get_buff_image(buff.kind, self.imgs); let buff_widget = Image::new(buff_img).w_h(40.0, 40.0); // Sort buffs into rows of 11 slots let x = i % 6; @@ -229,46 +221,10 @@ impl<'a> Widget for BuffsBar<'a> { ) .set(*id, ui); // Create Buff tooltip - let title = match buff.kind { - BuffKind::Regeneration { .. } => localized_strings.get("buff.title.heal"), - BuffKind::Saturation { .. } => { - localized_strings.get("buff.title.saturation") - }, - BuffKind::Potion { .. } => localized_strings.get("buff.title.potion"), - BuffKind::CampfireHeal { .. } => { - localized_strings.get("buff.title.campfire_heal") - }, - BuffKind::IncreaseMaxHealth { .. } => { - localized_strings.get("buff.title.IncreaseMaxHealth") - }, - BuffKind::IncreaseMaxEnergy { .. } => { - localized_strings.get("buff.title.staminaup") - }, - _ => localized_strings.get("buff.title.missing"), - }; - let remaining_time = if current_duration.is_none() { - "Permanent".to_string() - } else { - format!("Remaining: {:.0}s", current_duration.unwrap().as_secs_f32()) - }; + let title = hud::get_buff_title(buff.kind, localized_strings); + let desc_txt = hud::get_buff_desc(buff.kind, localized_strings); + let remaining_time = hud::get_buff_time(*buff); let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove")); - let desc_txt = match buff.kind { - BuffKind::Regeneration { .. } => localized_strings.get("buff.desc.heal"), - BuffKind::Saturation { .. } => { - localized_strings.get("buff.desc.saturation") - }, - BuffKind::Potion { .. } => localized_strings.get("buff.desc.potion"), - BuffKind::CampfireHeal { .. } => { - localized_strings.get("buff.desc.campfire_heal") - }, - BuffKind::IncreaseMaxHealth { .. } => { - localized_strings.get("buff.desc.IncreaseMaxHealth") - }, - BuffKind::IncreaseMaxEnergy { .. } => { - localized_strings.get("buff.desc.IncreaseMaxEnergy") - }, - _ => localized_strings.get("buff.desc.missing"), - }; let desc = format!("{}\n\n{}\n\n{}", desc_txt, remaining_time, click_to_remove); // Timer overlay if Button::image(match duration_percentage as u64 { @@ -308,7 +264,7 @@ impl<'a> Widget for BuffsBar<'a> { .zip( buffs .iter_active() - .map(get_buff_info) + .map(hud::get_buff_info) .filter(|info| !info.is_buff), ) .collect::>(); @@ -351,19 +307,9 @@ impl<'a> Widget for BuffsBar<'a> { ) .set(*id, ui); // Create Debuff tooltip - let title = match debuff.kind { - BuffKind::Bleeding { .. } => localized_strings.get("debuff.title.bleed"), - _ => localized_strings.get("buff.title.missing"), - }; - let remaining_time = if current_duration.is_none() { - "Permanent".to_string() - } else { - format!("Remaining: {:.0}s", current_duration.unwrap().as_secs_f32()) - }; - let desc_txt = match debuff.kind { - BuffKind::Bleeding { .. } => localized_strings.get("debuff.desc.bleed"), - _ => localized_strings.get("debuff.desc.missing"), - }; + let title = hud::get_buff_title(debuff.kind, localized_strings); + let desc_txt = hud::get_buff_desc(debuff.kind, localized_strings); + let remaining_time = hud::get_buff_time(*debuff); let desc = format!("{}\n\n{}", desc_txt, remaining_time); Image::new(match duration_percentage as u64 { 875..=1000 => self.imgs.nothing, // 8/8 @@ -420,7 +366,7 @@ impl<'a> Widget for BuffsBar<'a> { .copied() .zip(state.ids.buff_timers.iter().copied()) .zip(state.ids.buff_txts.iter().copied()) - .zip(buffs.iter_active().map(get_buff_info)) + .zip(buffs.iter_active().map(hud::get_buff_info)) .collect::>(); // Sort the buffs by kind @@ -437,16 +383,7 @@ impl<'a> Widget for BuffsBar<'a> { max_duration .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0) }) as u32; - let buff_img = match buff.kind { - BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, - BuffKind::Saturation { .. } => self.imgs.buff_saturation_0, - BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffKind::Cursed { .. } => self.imgs.debuff_skull_0, - BuffKind::Potion { .. } => self.imgs.buff_potion_0, - BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0, - BuffKind::IncreaseMaxEnergy { .. } => self.imgs.buff_energyplus_0, - BuffKind::IncreaseMaxHealth { .. } => self.imgs.buff_healthplus_0, - }; + let buff_img = hud::get_buff_image(buff.kind, &self.imgs); let buff_widget = Image::new(buff_img).w_h(40.0, 40.0); // Sort buffs into rows of 6 slots let x = i % 6; @@ -466,36 +403,10 @@ impl<'a> Widget for BuffsBar<'a> { ) .set(*id, ui); // Create Buff tooltip - let title = match buff.kind { - BuffKind::Regeneration { .. } => localized_strings.get("buff.title.heal"), - BuffKind::Saturation { .. } => { - localized_strings.get("buff.title.saturation") - }, - BuffKind::Potion { .. } => localized_strings.get("buff.title.potion"), - BuffKind::Bleeding { .. } => localized_strings.get("debuff.title.bleed"), - BuffKind::CampfireHeal { .. } => { - localized_strings.get("buff.title.campfire_heal") - }, - _ => localized_strings.get("buff.title.missing"), - }; - let remaining_time = if current_duration.is_none() { - "".to_string() - } else { - format!("{:.0}s", current_duration.unwrap().as_secs_f32()) - }; + let title = hud::get_buff_title(buff.kind, localized_strings); + let desc_txt = hud::get_buff_desc(buff.kind, localized_strings); + let remaining_time = hud::get_buff_time(*buff); let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove")); - let desc_txt = match buff.kind { - BuffKind::Regeneration { .. } => localized_strings.get("buff.desc.heal"), - BuffKind::Saturation { .. } => { - localized_strings.get("buff.desc.saturation") - }, - BuffKind::Potion { .. } => localized_strings.get("buff.desc.potion"), - BuffKind::Bleeding { .. } => localized_strings.get("debuff.desc.bleed"), - BuffKind::CampfireHeal { .. } => { - localized_strings.get("buff.desc.campfire_heal") - }, - _ => localized_strings.get("buff.desc.missing"), - }; let desc = if buff.is_buff { format!("{}\n\n{}", desc_txt, click_to_remove) } else { diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index d199b5c5b2..ab756dd06d 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -6,7 +6,7 @@ use super::{ }; use crate::{ - hud::get_buff_info, + hud, i18n::Localization, settings::Settings, ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, @@ -16,9 +16,7 @@ use crate::{ use client::{self, Client}; use common::{ combat, - comp::{ - group::Role, inventory::item::MaterialStatManifest, invite::InviteKind, BuffKind, Stats, - }, + comp::{group::Role, inventory::item::MaterialStatManifest, invite::InviteKind, Stats}, uid::{Uid, UidAllocator}, }; use common_net::sync::WorldSyncExt; @@ -493,7 +491,7 @@ impl<'a> Widget for Group<'a> { .copied() .zip(state.ids.buff_timers.iter().copied()) .skip(total_buff_count - buff_count) - .zip(buffs.iter_active().map(get_buff_info)) + .zip(buffs.iter_active().map(hud::get_buff_info)) .for_each(|((id, timer_id), buff)| { let max_duration = buff.data.duration; let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); @@ -504,20 +502,7 @@ impl<'a> Widget for Group<'a> { cur.as_secs_f32() / max.as_secs_f32() * 1000.0 }) }) as u32; // Percentage to determine which frame of the timer overlay is displayed - let buff_img = match buff.kind { - BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, - BuffKind::Saturation { .. } => self.imgs.buff_saturation_0, - BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffKind::Cursed { .. } => self.imgs.debuff_skull_0, - BuffKind::Potion { .. } => self.imgs.buff_potion_0, - BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0, - BuffKind::IncreaseMaxEnergy { .. } => { - self.imgs.buff_energyplus_0 - }, - BuffKind::IncreaseMaxHealth { .. } => { - self.imgs.buff_healthplus_0 - }, - }; + let buff_img = hud::get_buff_image(buff.kind, &self.imgs); let buff_widget = Image::new(buff_img).w_h(15.0, 15.0); let buff_widget = if let Some(id) = prev_id { buff_widget.right_from(id, 1.0) @@ -541,38 +526,9 @@ impl<'a> Widget for Group<'a> { ) .set(id, ui); // Create Buff tooltip - let title = match buff.kind { - BuffKind::Regeneration { .. } => { - localized_strings.get("buff.title.heal") - }, - BuffKind::Saturation { .. } => { - localized_strings.get("buff.title.saturation") - }, - BuffKind::Bleeding { .. } => { - localized_strings.get("debuff.title.bleed") - }, - _ => localized_strings.get("buff.title.missing"), - }; - let remaining_time = if current_duration.is_none() { - "Permanent".to_string() - } else { - format!( - "Remaining: {:.0}s", - current_duration.unwrap().as_secs_f32() - ) - }; - let desc_txt = match buff.kind { - BuffKind::Regeneration { .. } => { - localized_strings.get("buff.desc.heal") - }, - BuffKind::Saturation { .. } => { - localized_strings.get("buff.desc.saturation") - }, - BuffKind::Bleeding { .. } => { - localized_strings.get("debuff.desc.bleed") - }, - _ => localized_strings.get("buff.desc.missing"), - }; + let title = hud::get_buff_title(buff.kind, localized_strings); + let desc_txt = hud::get_buff_desc(buff.kind, localized_strings); + let remaining_time = hud::get_buff_time(buff); let desc = format!("{}\n\n{}", desc_txt, remaining_time); Image::new(match duration_percentage as u64 { 875..=1000 => self.imgs.nothing, // 8/8 diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 9c031af1fb..d7da861e52 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -447,6 +447,7 @@ image_ids! { buff_campfire_heal_0: "voxygen.element.icons.de_buffs.buff_campfire_heal_0", buff_energyplus_0: "voxygen.element.icons.de_buffs.buff_energyplus_0", buff_healthplus_0: "voxygen.element.icons.de_buffs.buff_healthplus_0", + buff_invincibility_0: "voxygen.element.icons.de_buffs.buff_invincibility_0", // Debuffs debuff_skull_0: "voxygen.element.icons.de_buffs.debuff_skull_0", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 575c11c33d..706bed30b3 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -3198,3 +3198,59 @@ pub fn cr_color(combat_rating: f32) -> Color { _ => XP_COLOR, } } + +pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id { + match buff { + // Buffs + BuffKind::Regeneration { .. } => imgs.buff_plus_0, + BuffKind::Saturation { .. } => imgs.buff_saturation_0, + BuffKind::Potion { .. } => imgs.buff_potion_0, + BuffKind::CampfireHeal { .. } => imgs.buff_campfire_heal_0, + BuffKind::IncreaseMaxEnergy { .. } => imgs.buff_energyplus_0, + BuffKind::IncreaseMaxHealth { .. } => imgs.buff_healthplus_0, + BuffKind::Invulnerability => imgs.buff_invincibility_0, + // Debuffs + BuffKind::Bleeding { .. } => imgs.debuff_bleed_0, + BuffKind::Cursed { .. } => imgs.debuff_skull_0, + } +} + +pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> &str { + match buff { + // Buffs + BuffKind::Regeneration { .. } => localized_strings.get("buff.title.heal"), + BuffKind::Saturation { .. } => localized_strings.get("buff.title.saturation"), + BuffKind::Potion { .. } => localized_strings.get("buff.title.potion"), + BuffKind::CampfireHeal { .. } => localized_strings.get("buff.title.campfire_heal"), + BuffKind::IncreaseMaxHealth { .. } => localized_strings.get("buff.title.IncreaseMaxHealth"), + BuffKind::IncreaseMaxEnergy { .. } => localized_strings.get("buff.title.staminaup"), + BuffKind::Invulnerability => localized_strings.get("buff.title.invulnerability"), + // Debuffs + BuffKind::Bleeding { .. } => localized_strings.get("buff.title.bleed"), + BuffKind::Cursed { .. } => localized_strings.get("buff.title.cursed"), + } +} + +pub fn get_buff_desc(buff: BuffKind, localized_strings: &Localization) -> &str { + match buff { + // Buffs + BuffKind::Regeneration { .. } => localized_strings.get("buff.desc.heal"), + BuffKind::Saturation { .. } => localized_strings.get("buff.desc.saturation"), + BuffKind::Potion { .. } => localized_strings.get("buff.desc.potion"), + BuffKind::CampfireHeal { .. } => localized_strings.get("buff.desc.campfire_heal"), + BuffKind::IncreaseMaxHealth { .. } => localized_strings.get("buff.desc.IncreaseMaxHealth"), + BuffKind::IncreaseMaxEnergy { .. } => localized_strings.get("buff.desc.IncreaseMaxEnergy"), + BuffKind::Invulnerability => localized_strings.get("buff.desc.invulnerability"), + // Debuffs + BuffKind::Bleeding { .. } => localized_strings.get("buff.desc.bleed"), + BuffKind::Cursed { .. } => localized_strings.get("buff.desc.cursed"), + } +} + +pub fn get_buff_time(buff: BuffInfo) -> String { + if let Some(dur) = buff.dur { + format!("Remaining: {:.0}s", dur.as_secs_f32()) + } else { + "Permanent".to_string() + } +} diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index c5376f0248..d9a62e05e2 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -4,12 +4,12 @@ use super::{ TEXT_COLOR, }; use crate::{ - hud::get_buff_info, + hud::{get_buff_image, get_buff_info}, i18n::Localization, settings::GameplaySettings, ui::{fonts::Fonts, Ingameable}, }; -use common::comp::{BuffKind, Buffs, Energy, Health, SpeechBubble, SpeechBubbleType}; +use common::comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType}; use conrod_core::{ color, position::Align, @@ -236,16 +236,7 @@ impl<'a> Widget for Overhead<'a> { cur.as_secs_f32() / max.as_secs_f32() * 1000.0 }) }) as u32; // Percentage to determine which frame of the timer overlay is displayed - let buff_img = match buff.kind { - BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, - BuffKind::Saturation { .. } => self.imgs.buff_saturation_0, - BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffKind::Cursed { .. } => self.imgs.debuff_skull_0, - BuffKind::Potion { .. } => self.imgs.buff_potion_0, - BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0, - BuffKind::IncreaseMaxEnergy { .. } => self.imgs.buff_energyplus_0, - BuffKind::IncreaseMaxHealth { .. } => self.imgs.buff_healthplus_0, - }; + let buff_img = get_buff_image(buff.kind, &self.imgs); let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); // Sort buffs into rows of 5 slots let x = i % 5;