mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/invulnerability-buff' into 'master'
Invulnerability Buff Closes #965 See merge request veloren/veloren!1834
This commit is contained in:
commit
0497a437a4
BIN
assets/voxygen/element/icons/de_buffs/buff_invincibility_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/de_buffs/buff_invincibility_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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: {
|
||||
|
@ -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.",
|
||||
},
|
||||
|
||||
|
||||
|
@ -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: {
|
||||
}
|
||||
)
|
@ -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.",
|
||||
|
||||
|
||||
},
|
||||
|
@ -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.",
|
||||
},
|
||||
|
||||
|
||||
|
@ -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.",
|
||||
},
|
||||
|
||||
|
||||
|
@ -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.",
|
||||
},
|
||||
|
||||
|
||||
|
@ -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: {
|
||||
}
|
||||
)
|
@ -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": "Наносит переодический урон.",
|
||||
|
||||
},
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
}
|
||||
)
|
@ -14,6 +14,8 @@
|
||||
"buff.desc.saturation": "Поступово відновлює здоров'я з їжі.",
|
||||
"buff.title.campfire_heal": "Відновлення біля ватри",
|
||||
"buff.desc.campfire_heal": "Відпочинок біля ватри лікує на 1% за секунду.",
|
||||
"buff.title.bleed": "Кровотеча",
|
||||
"buff.desc.bleed": "Наносить регулярні ушкодження.",
|
||||
},
|
||||
|
||||
|
||||
|
@ -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: {
|
||||
}
|
||||
)
|
@ -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",
|
||||
|
@ -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<AttackerInfo>,
|
||||
target_entity: EcsEntity,
|
||||
target_inventory: Option<&Inventory>,
|
||||
attacker: Option<AttackerInfo>,
|
||||
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::<f32>() < 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::<f32>() < 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::<Option<f32>>();
|
||||
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::<Option<f32>>();
|
||||
|
||||
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<Uid>,
|
||||
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;
|
||||
|
@ -91,10 +91,12 @@ pub struct Auras {
|
||||
}
|
||||
|
||||
impl Auras {
|
||||
pub fn new(aura: Aura) -> Self {
|
||||
let mut auras: SlotMap<AuraKey, Aura> = SlotMap::with_key();
|
||||
auras.insert(aura);
|
||||
Self { auras }
|
||||
pub fn new(auras: Vec<Aura>) -> Self {
|
||||
let mut auras_comp: SlotMap<AuraKey, Aura> = 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); }
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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::<BuffId>::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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ServerEvent>>,
|
||||
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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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::<comp::Pos>(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,
|
||||
|
@ -191,7 +191,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
||||
})
|
||||
.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<f32>) {
|
||||
5.0,
|
||||
None,
|
||||
AuraTarget::All,
|
||||
)))
|
||||
)]))
|
||||
.build();
|
||||
}
|
||||
|
@ -511,14 +511,16 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
|
||||
if vel.z <= -30.0 {
|
||||
let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0;
|
||||
let inventories = state.ecs().read_storage::<Inventory>();
|
||||
let stats = state.ecs().read_storage::<Stats>();
|
||||
// Handle health change
|
||||
if let Some(mut health) = state.ecs().write_storage::<comp::Health>().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<f32>, explosion: Explosion, o
|
||||
},
|
||||
RadiusEffect::Attack(attack) => {
|
||||
let energies = &ecs.read_storage::<comp::Energy>();
|
||||
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::<comp::Pos>(),
|
||||
&ecs.read_storage::<comp::Health>(),
|
||||
ecs.read_storage::<comp::Inventory>().maybe(),
|
||||
ecs.read_storage::<comp::Stats>().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<f32>, 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::<EventBus<ServerEvent>>();
|
||||
|
||||
attack.apply_attack(
|
||||
target_group,
|
||||
attacker_info,
|
||||
entity_b,
|
||||
inventory_b_maybe,
|
||||
target_info,
|
||||
dir,
|
||||
false,
|
||||
strength,
|
||||
|
@ -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::<Inventory>();
|
||||
let stats = self.ecs().read_storage::<comp::Stats>();
|
||||
let change = damage.calculate_health_change(
|
||||
inventories.get(entity),
|
||||
combat::Damage::compute_damage_reduction(
|
||||
inventories.get(entity),
|
||||
stats.get(entity),
|
||||
),
|
||||
source,
|
||||
false,
|
||||
0.0,
|
||||
|
@ -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);
|
||||
|
@ -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::<Vec<_>>();
|
||||
@ -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::<Vec<_>>();
|
||||
@ -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::<Vec<_>>();
|
||||
|
||||
// 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 {
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user