veloren_voxygen/hud/
util.rs

1use super::img_ids;
2use common::{
3    comp::{
4        BuffData, BuffKind,
5        inventory::trade_pricing::TradePricing,
6        item::{
7            Effects, Item, ItemDefinitionId, ItemDesc, ItemI18n, ItemKind, MaterialKind,
8            MaterialStatManifest, Quality,
9            armor::{Armor, ArmorKind, Protection},
10            tool::{Hands, Tool, ToolKind},
11        },
12    },
13    effect::Effect,
14    trade::{Good, SitePrices},
15};
16use conrod_core::image;
17use i18n::{FluentValue, Localization, fluent_args};
18use std::{borrow::Cow, fmt::Write};
19
20pub fn price_desc<'a>(
21    prices: &Option<SitePrices>,
22    item_definition_id: ItemDefinitionId<'_>,
23    quality: Quality,
24    i18n: &'a Localization,
25) -> Option<(Cow<'a, str>, Cow<'a, str>, f32)> {
26    let prices = prices.as_ref()?;
27    let materials = TradePricing::get_materials(&item_definition_id)?;
28    let coinprice = prices.values.get(&Good::Coin).cloned().unwrap_or(1.0);
29    let buyprice: f32 = materials
30        .iter()
31        .map(|e| prices.values.get(&e.1).cloned().unwrap_or_default() * e.0)
32        .sum();
33    let sellprice: f32 = materials
34        .iter()
35        .map(|e| {
36            prices.values.get(&e.1).cloned().unwrap_or_default() * e.0 * e.1.sell_discount(quality)
37        })
38        .sum();
39
40    let deal_goodness: f32 = materials
41        .iter()
42        .map(|e| prices.values.get(&e.1).cloned().unwrap_or(0.0))
43        .sum::<f32>()
44        / prices.values.get(&Good::Coin).cloned().unwrap_or(1.0)
45        / (materials.len() as f32);
46    let deal_goodness = deal_goodness.log(2.0);
47
48    let buy_string = i18n.get_msg_ctx("hud-trade-buy", &fluent_args! {
49        "coin_num" => buyprice / coinprice,
50        "coin_formatted" => format!("{:0.1}", buyprice / coinprice),
51    });
52    let sell_string = i18n.get_msg_ctx("hud-trade-sell", &fluent_args! {
53        "coin_num" => sellprice / coinprice,
54        "coin_formatted" => format!("{:0.1}", sellprice / coinprice),
55    });
56
57    let deal_goodness = match deal_goodness {
58        x if x < -2.5 => 0.0,
59        x if x < -1.05 => 0.25,
60        x if x < -0.95 => 0.5,
61        x if x < 0.0 => 0.75,
62        _ => 1.0,
63    };
64    Some((buy_string, sell_string, deal_goodness))
65}
66
67pub fn item_text<'a, I: ItemDesc + ?Sized>(
68    item: &I,
69    i18n: &'a Localization,
70    i18n_spec: &'a ItemI18n,
71) -> (String, String) {
72    let (title, desc) = item.i18n(i18n_spec);
73
74    (i18n.get_content(&title), i18n.get_content(&desc))
75}
76
77pub fn describe<'a, I: ItemDesc + ?Sized>(
78    item: &I,
79    i18n: &'a Localization,
80    i18n_spec: &'a ItemI18n,
81) -> String {
82    let (title, _) = item_text(item, i18n, i18n_spec);
83    let amount = item.amount();
84
85    if amount.get() > 1 {
86        format!("{amount} x {title}")
87    } else {
88        title
89    }
90}
91
92pub fn kind_text<'a>(kind: &ItemKind, i18n: &'a Localization) -> Cow<'a, str> {
93    match kind {
94        ItemKind::Armor(armor) => armor_kind(armor, i18n),
95        ItemKind::Tool(tool) => Cow::Owned(format!(
96            "{} ({})",
97            tool_kind(tool, i18n),
98            tool_hands(tool, i18n)
99        )),
100        ItemKind::ModularComponent(mc) => {
101            if let Some(toolkind) = mc.toolkind() {
102                Cow::Owned(format!(
103                    "{} {}",
104                    i18n.get_msg(&format!("common-weapons-{}", toolkind.identifier_name())),
105                    i18n.get_msg("common-kind-modular_component_partial")
106                ))
107            } else {
108                i18n.get_msg("common-kind-modular_component")
109            }
110        },
111        ItemKind::Glider => i18n.get_msg("common-kind-glider"),
112        ItemKind::Consumable { .. } => i18n.get_msg("common-kind-consumable"),
113        ItemKind::Utility { .. } => i18n.get_msg("common-kind-utility"),
114        ItemKind::Ingredient { .. } => i18n.get_msg("common-kind-ingredient"),
115        ItemKind::Lantern { .. } => i18n.get_msg("common-kind-lantern"),
116        ItemKind::TagExamples { .. } => Cow::Borrowed(""),
117        ItemKind::RecipeGroup { .. } => i18n.get_msg("common-kind-recipegroup"),
118    }
119}
120
121pub fn material_kind_text<'a>(kind: &MaterialKind, i18n: &'a Localization) -> Cow<'a, str> {
122    match kind {
123        MaterialKind::Metal => i18n.get_msg("common-material-metal"),
124        MaterialKind::Gem => i18n.get_msg("common-material-gem"),
125        MaterialKind::Wood => i18n.get_msg("common-material-wood"),
126        MaterialKind::Stone => i18n.get_msg("common-material-stone"),
127        MaterialKind::Cloth => i18n.get_msg("common-material-cloth"),
128        MaterialKind::Hide => i18n.get_msg("common-material-hide"),
129    }
130}
131
132pub fn stats_count(item: &dyn ItemDesc, msm: &MaterialStatManifest) -> usize {
133    let mut count = match &*item.kind() {
134        ItemKind::Armor(armor) => {
135            let armor_stats = armor.stats(msm, item.stats_durability_multiplier());
136            armor_stats.energy_reward.is_some() as usize
137                + armor_stats.energy_max.is_some() as usize
138                + armor_stats.stealth.is_some() as usize
139                + armor_stats.precision_power.is_some() as usize
140                + armor_stats.poise_resilience.is_some() as usize
141                + armor_stats.protection.is_some() as usize
142                + (item.num_slots() > 0) as usize
143        },
144        ItemKind::Tool(_) => 6,
145        ItemKind::Consumable { effects, .. } => match effects {
146            Effects::Any(_) | Effects::One(_) => 1,
147            Effects::All(effects) => effects.len(),
148        },
149        ItemKind::RecipeGroup { recipes } => {
150            /* Add one for recipe known/not known message */
151            recipes.len() + 1
152        },
153        ItemKind::ModularComponent { .. } => 6,
154        _ => 0,
155    };
156    if item.has_durability() {
157        count += 1;
158    }
159    count
160}
161
162pub fn line_count(item: &dyn ItemDesc, msm: &MaterialStatManifest, i18n: &Localization) -> usize {
163    match &*item.kind() {
164        ItemKind::Consumable { effects, .. } => {
165            let descs = consumable_desc(effects, i18n);
166            let mut lines = 0;
167            for desc in descs {
168                lines += desc.matches('\n').count() + 1;
169            }
170
171            lines
172        },
173        _ => stats_count(item, msm),
174    }
175}
176
177/// Returns i18n key for a buff with title, .desc and optionally .stat
178///
179/// NOTE: not to be confused with buff key for buff's kill message
180fn buff_key(buff: BuffKind) -> &'static str {
181    match buff {
182        // Buffs
183        BuffKind::Regeneration => "buff-heal",
184        BuffKind::Saturation => "buff-saturation",
185        BuffKind::Potion => "buff-potion",
186        BuffKind::Agility => "buff-agility",
187        BuffKind::RestingHeal => "buff-resting_heal",
188        BuffKind::EnergyRegen => "buff-energy_regen",
189        BuffKind::ComboGeneration => "buff-combo_generation",
190        BuffKind::IncreaseMaxHealth => "buff-increase_max_health",
191        BuffKind::IncreaseMaxEnergy => "buff-increase_max_energy",
192        BuffKind::Invulnerability => "buff-invulnerability",
193        BuffKind::ProtectingWard => "buff-protectingward",
194        BuffKind::Frenzied => "buff-frenzied",
195        BuffKind::Hastened => "buff-hastened",
196        BuffKind::Fortitude => "buff-fortitude",
197        BuffKind::Reckless => "buff-reckless",
198        // BuffKind::SalamanderAspect => "buff-salamanderaspect",
199        BuffKind::Flame => "buff-burn",
200        BuffKind::Frigid => "buff-frigid",
201        BuffKind::Lifesteal => "buff-lifesteal",
202        BuffKind::ImminentCritical => "buff-imminentcritical",
203        BuffKind::Fury => "buff-fury",
204        BuffKind::Sunderer => "buff-sunderer",
205        BuffKind::Defiance => "buff-defiance",
206        BuffKind::Bloodfeast => "buff-bloodfeast",
207        BuffKind::Berserk => "buff-berserk",
208        BuffKind::ScornfulTaunt => "buff-scornfultaunt",
209        BuffKind::Tenacity => "buff-tenacity",
210        BuffKind::Resilience => "buff-resilience",
211        BuffKind::OwlTalon => "buff-owltalon",
212        BuffKind::HeavyNock => "buff-heavynock",
213        BuffKind::Heartseeker => "buff-heartseeker",
214        BuffKind::EagleEye => "buff-eagleeye",
215        BuffKind::ArdentHunter => "buff-ardenthunter",
216        BuffKind::SepticShot => "buff-septicshot",
217        // Debuffs
218        BuffKind::Bleeding => "buff-bleed",
219        BuffKind::Cursed => "buff-cursed",
220        BuffKind::Burning => "buff-burn",
221        BuffKind::Crippled => "buff-crippled",
222        BuffKind::Frozen => "buff-frozen",
223        BuffKind::Wet => "buff-wet",
224        BuffKind::Ensnared => "buff-ensnared",
225        BuffKind::Poisoned => "buff-poisoned",
226        BuffKind::Parried => "buff-parried",
227        BuffKind::PotionSickness => "buff-potionsickness",
228        BuffKind::Heatstroke => "buff-heatstroke",
229        BuffKind::Rooted => "buff-rooted",
230        BuffKind::Winded => "buff-winded",
231        BuffKind::Amnesia => "buff-amnesia",
232        BuffKind::OffBalance => "buff-offbalance",
233        BuffKind::Chilled => "buff-chilled",
234        BuffKind::ArdentHunted => "buff-ardenthunted",
235        // Neutral
236        BuffKind::Polymorphed => "buff-polymorphed",
237    }
238}
239
240/// Returns localized buff title
241pub fn get_buff_title(buff: BuffKind, i18n: &Localization) -> Cow<'_, str> {
242    let key = buff_key(buff);
243
244    i18n.get_msg(key)
245}
246
247/// Returns localized buff description
248pub fn get_buff_desc(buff: BuffKind, data: BuffData, i18n: &Localization) -> Cow<'_, str> {
249    let key = buff_key(buff);
250    if let BuffKind::RestingHeal = buff {
251        i18n.get_attr_ctx(key, "desc", &i18n::fluent_args! {
252            "rate" => data.strength * 100.0
253        })
254    } else {
255        i18n.get_attr(key, "desc")
256    }
257}
258
259fn almost_integer(number: &f32) -> FluentValue<'_> {
260    let epsilon = 0.001;
261    if number.fract() < epsilon {
262        FluentValue::from(number.round() as usize)
263    } else {
264        FluentValue::from(format!("{:.<3}", number))
265    }
266}
267/// Takes N `effects` and returns N effect descriptions
268/// If effect isn't intended to have description, returns empty string
269///
270/// FIXME: handle which effects should have description in `stats_count`
271/// to not waste space in item box
272pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
273    let mut descriptions = Vec::new();
274    match effects {
275        Effects::Any(_) => {
276            descriptions.push(i18n.get_msg("buff-mysterious").into_owned());
277        },
278        Effects::All(_) | Effects::One(_) => {
279            for effect in effects.effects() {
280                let mut description = String::new();
281                if let Effect::Buff(buff) = effect {
282                    let strength = buff.data.strength;
283                    let duration = buff.data.duration.map(|d| d.0 as f32);
284                    let str_total = duration.map_or(strength, |secs| strength * secs);
285                    let str_duration = duration.unwrap_or(0.0);
286                    let fluent_duration = almost_integer(&str_duration);
287
288                    let format_float =
289                        |input: f32| format!("{:.1}", input).trim_end_matches(".0").to_string();
290
291                    let buff_desc = match buff.kind {
292                        // These share common buff-key and show full possible regen
293                        BuffKind::Saturation | BuffKind::Regeneration | BuffKind::Potion => {
294                            let key = "buff-heal";
295                            i18n.get_attr_ctx(key, "stat", &i18n::fluent_args! {
296                                "str_total" => format_float(str_total),
297                                "duration" => fluent_duration,
298                            })
299                        },
300                        // Shows its full possible regen
301                        BuffKind::EnergyRegen => {
302                            let key = buff_key(buff.kind);
303                            i18n.get_attr_ctx(key, "stat", &i18n::fluent_args! {
304                                "str_total" => format_float(str_total),
305                                "duration" => fluent_duration,
306                            })
307                        },
308                        BuffKind::ComboGeneration => {
309                            let key = buff_key(buff.kind);
310                            i18n.get_attr_ctx(key, "stat", &i18n::fluent_args! {
311                                "str_total" => format_float(str_total),
312                                "duration" => fluent_duration,
313                            })
314                        },
315                        // Show buff strength
316                        BuffKind::IncreaseMaxEnergy
317                        | BuffKind::IncreaseMaxHealth => {
318                            let key = buff_key(buff.kind);
319                            i18n.get_attr_ctx(key, "stat", &i18n::fluent_args! {
320                                "strength" => format_float(strength),
321                                "duration" => fluent_duration,
322                            })
323                        },
324                        // Show percentage
325                        BuffKind::PotionSickness
326                        | BuffKind::Agility => {
327                            let key = buff_key(buff.kind);
328                            i18n.get_attr_ctx(key, "stat", &i18n::fluent_args! {
329                                "strength" => format_float(strength * 100.0),
330                                "duration" => fluent_duration,
331                            })
332                        },
333                        // Independent of strength, still has duration
334                        BuffKind::Invulnerability => {
335                            let key = buff_key(buff.kind);
336                            i18n.get_attr_ctx(key, "stat", &i18n::fluent_args! {
337                                "duration" => fluent_duration,
338                            })
339                        },
340                        // Have no stat description
341                        BuffKind::Bleeding
342                        | BuffKind::Burning
343                        | BuffKind::RestingHeal
344                        | BuffKind::Cursed
345                        | BuffKind::ProtectingWard
346                        | BuffKind::Crippled
347                        | BuffKind::Frenzied
348                        | BuffKind::Frozen
349                        | BuffKind::Wet
350                        | BuffKind::Ensnared
351                        | BuffKind::Poisoned
352                        | BuffKind::Hastened
353                        | BuffKind::Fortitude
354                        | BuffKind::Parried
355                        | BuffKind::Reckless
356                        | BuffKind::Polymorphed
357                        | BuffKind::Flame
358                        | BuffKind::Frigid
359                        | BuffKind::Lifesteal
360                        // | BuffKind::SalamanderAspect
361                        | BuffKind::ImminentCritical
362                        | BuffKind::Fury
363                        | BuffKind::Sunderer
364                        | BuffKind::Defiance
365                        | BuffKind::Bloodfeast
366                        | BuffKind::Berserk
367                        | BuffKind::Heatstroke
368                        | BuffKind::ScornfulTaunt
369                        | BuffKind::Rooted
370                        | BuffKind::Winded
371                        | BuffKind::Amnesia
372                        | BuffKind::OffBalance
373                        | BuffKind::Tenacity
374                        | BuffKind::Resilience
375                        | BuffKind::OwlTalon
376                        | BuffKind::HeavyNock
377                        | BuffKind::Heartseeker
378                        | BuffKind::EagleEye
379                        | BuffKind::Chilled
380                        | BuffKind::ArdentHunter
381                        | BuffKind::ArdentHunted
382                        | BuffKind::SepticShot => Cow::Borrowed(""),
383                    };
384
385                    write!(&mut description, "{}", buff_desc).unwrap();
386                }
387                descriptions.push(description);
388            }
389        },
390    }
391    descriptions
392}
393
394// Armor
395fn armor_kind<'a>(armor: &Armor, i18n: &'a Localization) -> Cow<'a, str> {
396    match armor.kind {
397        ArmorKind::Shoulder => i18n.get_msg("hud-bag-shoulders"),
398        ArmorKind::Chest => i18n.get_msg("hud-bag-chest"),
399        ArmorKind::Belt => i18n.get_msg("hud-bag-belt"),
400        ArmorKind::Hand => i18n.get_msg("hud-bag-hands"),
401        ArmorKind::Pants => i18n.get_msg("hud-bag-legs"),
402        ArmorKind::Foot => i18n.get_msg("hud-bag-feet"),
403        ArmorKind::Back => i18n.get_msg("hud-bag-back"),
404        ArmorKind::Backpack => i18n.get_msg("hud-bag-backpack"),
405        ArmorKind::Ring => i18n.get_msg("hud-bag-ring"),
406        ArmorKind::Neck => i18n.get_msg("hud-bag-neck"),
407        ArmorKind::Head => i18n.get_msg("hud-bag-head"),
408        ArmorKind::Tabard => i18n.get_msg("hud-bag-tabard"),
409        ArmorKind::Bag => i18n.get_msg("hud-bag-bag"),
410    }
411}
412
413// Tool
414fn tool_kind<'a>(tool: &Tool, i18n: &'a Localization) -> Cow<'a, str> {
415    match tool.kind {
416        ToolKind::Sword => i18n.get_msg("common-weapons-sword"),
417        ToolKind::Axe => i18n.get_msg("common-weapons-axe"),
418        ToolKind::Hammer => i18n.get_msg("common-weapons-hammer"),
419        ToolKind::Bow => i18n.get_msg("common-weapons-bow"),
420        ToolKind::Dagger => i18n.get_msg("common-weapons-dagger"),
421        ToolKind::Staff => i18n.get_msg("common-weapons-staff"),
422        ToolKind::Sceptre => i18n.get_msg("common-weapons-sceptre"),
423        ToolKind::Shield => i18n.get_msg("common-weapons-shield"),
424        ToolKind::Spear => i18n.get_msg("common-weapons-spear"),
425        ToolKind::Blowgun => i18n.get_msg("common-weapons-blowgun"),
426        ToolKind::Natural => i18n.get_msg("common-weapons-natural"),
427        ToolKind::Debug => i18n.get_msg("common-tool-debug"),
428        ToolKind::Farming => i18n.get_msg("common-tool-farming"),
429        ToolKind::Instrument => i18n.get_msg("common-tool-instrument"),
430        ToolKind::Throwable => i18n.get_msg("common-tool-throwable"),
431        ToolKind::Pick => i18n.get_msg("common-tool-pick"),
432        ToolKind::Shovel => i18n.get_msg("common-tool-shovel"),
433        ToolKind::Empty => i18n.get_msg("common-empty"),
434    }
435}
436
437/// Output the number of hands needed to hold a tool
438pub fn tool_hands<'a>(tool: &Tool, i18n: &'a Localization) -> Cow<'a, str> {
439    match tool.hands {
440        Hands::One => i18n.get_msg("common-hands-one"),
441        Hands::Two => i18n.get_msg("common-hands-two"),
442    }
443}
444
445/// Compare two type, output a colored character to show comparison
446pub fn comparison<T: PartialOrd>(first: T, other: T) -> (&'static str, conrod_core::Color) {
447    if first == other {
448        ("•", conrod_core::color::GREY)
449    } else if other < first {
450        ("▲", conrod_core::color::GREEN)
451    } else {
452        ("▼", conrod_core::color::RED)
453    }
454}
455
456/// Compare two Option type, output a colored character to show comparison
457pub fn option_comparison<T: PartialOrd>(
458    first: &Option<T>,
459    other: &Option<T>,
460) -> (&'static str, conrod_core::Color) {
461    if let Some(first) = first {
462        if let Some(other) = other {
463            if first == other {
464                ("•", conrod_core::color::GREY)
465            } else if other < first {
466                ("▲", conrod_core::color::GREEN)
467            } else {
468                ("▼", conrod_core::color::RED)
469            }
470        } else {
471            ("▲", conrod_core::color::GREEN)
472        }
473    } else if other.is_some() {
474        ("▼", conrod_core::color::RED)
475    } else {
476        ("•", conrod_core::color::GREY)
477    }
478}
479
480/// Output protection as a string
481pub fn protec2string(stat: Protection) -> String {
482    match stat {
483        Protection::Normal(a) => format!("{:.1}", a),
484        Protection::Invincible => "Inf".to_string(),
485    }
486}
487
488/// Gets the durability of an item in a format more intuitive for UI
489pub fn item_durability(item: &dyn ItemDesc) -> Option<u32> {
490    let durability = item
491        .durability_lost()
492        .or_else(|| item.has_durability().then_some(0));
493    durability.map(|d| Item::MAX_DURABILITY - d)
494}
495
496pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id {
497    match ability_id {
498        // Debug stick
499        "common.abilities.debug.forwardboost" => imgs.flyingrod_m1,
500        "common.abilities.debug.upboost" => imgs.flyingrod_m2,
501        "common.abilities.debug.possess" => imgs.snake_arrow_0,
502        // Sword
503        "veloren.core.pseudo_abilities.sword.heavy_stance" => imgs.sword_heavy_stance,
504        "veloren.core.pseudo_abilities.sword.agile_stance" => imgs.sword_agile_stance,
505        "veloren.core.pseudo_abilities.sword.defensive_stance" => imgs.sword_defensive_stance,
506        "veloren.core.pseudo_abilities.sword.crippling_stance" => imgs.sword_crippling_stance,
507        "veloren.core.pseudo_abilities.sword.cleaving_stance" => imgs.sword_cleaving_stance,
508        "veloren.core.pseudo_abilities.sword.double_slash" => imgs.sword_double_slash,
509        "common.abilities.sword.basic_double_slash" => imgs.sword_basic_double_slash,
510        "common.abilities.sword.heavy_double_slash" => imgs.sword_heavy_double_slash,
511        "common.abilities.sword.agile_double_slash" => imgs.sword_agile_double_slash,
512        "common.abilities.sword.defensive_double_slash" => imgs.sword_defensive_double_slash,
513        "common.abilities.sword.crippling_double_slash" => imgs.sword_crippling_double_slash,
514        "common.abilities.sword.cleaving_double_slash" => imgs.sword_cleaving_double_slash,
515        "veloren.core.pseudo_abilities.sword.secondary_ability" => imgs.sword_secondary_ability,
516        "common.abilities.sword.basic_thrust" => imgs.sword_basic_thrust,
517        "common.abilities.sword.heavy_slam" => imgs.sword_heavy_slam,
518        "common.abilities.sword.agile_perforate" => imgs.sword_agile_perforate,
519        "common.abilities.sword.agile_dual_perforate" => imgs.sword_agile_perforate,
520        "common.abilities.sword.defensive_vital_jab" => imgs.sword_defensive_vital_jab,
521        "common.abilities.sword.crippling_deep_rend" => imgs.sword_crippling_deep_rend,
522        "common.abilities.sword.cleaving_spiral_slash" => imgs.sword_cleaving_spiral_slash,
523        "common.abilities.sword.cleaving_dual_spiral_slash" => imgs.sword_cleaving_spiral_slash,
524        "veloren.core.pseudo_abilities.sword.crescent_slash" => imgs.sword_crescent_slash,
525        "common.abilities.sword.basic_crescent_slash" => imgs.sword_basic_crescent_slash,
526        "common.abilities.sword.heavy_crescent_slash" => imgs.sword_heavy_crescent_slash,
527        "common.abilities.sword.agile_crescent_slash" => imgs.sword_agile_crescent_slash,
528        "common.abilities.sword.defensive_crescent_slash" => imgs.sword_defensive_crescent_slash,
529        "common.abilities.sword.crippling_crescent_slash" => imgs.sword_crippling_crescent_slash,
530        "common.abilities.sword.cleaving_crescent_slash" => imgs.sword_cleaving_crescent_slash,
531        "veloren.core.pseudo_abilities.sword.fell_strike" => imgs.sword_fell_strike,
532        "common.abilities.sword.basic_fell_strike" => imgs.sword_basic_fell_strike,
533        "common.abilities.sword.heavy_fell_strike" => imgs.sword_heavy_fell_strike,
534        "common.abilities.sword.agile_fell_strike" => imgs.sword_agile_fell_strike,
535        "common.abilities.sword.defensive_fell_strike" => imgs.sword_defensive_fell_strike,
536        "common.abilities.sword.crippling_fell_strike" => imgs.sword_crippling_fell_strike,
537        "common.abilities.sword.cleaving_fell_strike" => imgs.sword_cleaving_fell_strike,
538        "veloren.core.pseudo_abilities.sword.skewer" => imgs.sword_skewer,
539        "common.abilities.sword.basic_skewer" => imgs.sword_basic_skewer,
540        "common.abilities.sword.heavy_skewer" => imgs.sword_heavy_skewer,
541        "common.abilities.sword.agile_skewer" => imgs.sword_agile_skewer,
542        "common.abilities.sword.defensive_skewer" => imgs.sword_defensive_skewer,
543        "common.abilities.sword.crippling_skewer" => imgs.sword_crippling_skewer,
544        "common.abilities.sword.cleaving_skewer" => imgs.sword_cleaving_skewer,
545        "veloren.core.pseudo_abilities.sword.cascade" => imgs.sword_cascade,
546        "common.abilities.sword.basic_cascade" => imgs.sword_basic_cascade,
547        "common.abilities.sword.heavy_cascade" => imgs.sword_heavy_cascade,
548        "common.abilities.sword.agile_cascade" => imgs.sword_agile_cascade,
549        "common.abilities.sword.defensive_cascade" => imgs.sword_defensive_cascade,
550        "common.abilities.sword.crippling_cascade" => imgs.sword_crippling_cascade,
551        "common.abilities.sword.cleaving_cascade" => imgs.sword_cleaving_cascade,
552        "veloren.core.pseudo_abilities.sword.cross_cut" => imgs.sword_cross_cut,
553        "common.abilities.sword.basic_cross_cut" => imgs.sword_basic_cross_cut,
554        "common.abilities.sword.heavy_cross_cut" => imgs.sword_heavy_cross_cut,
555        "common.abilities.sword.agile_cross_cut" => imgs.sword_agile_cross_cut,
556        "common.abilities.sword.defensive_cross_cut" => imgs.sword_defensive_cross_cut,
557        "common.abilities.sword.crippling_cross_cut" => imgs.sword_crippling_cross_cut,
558        "common.abilities.sword.cleaving_cross_cut" => imgs.sword_cleaving_cross_cut,
559        "common.abilities.sword.basic_dual_cross_cut" => imgs.sword_basic_cross_cut,
560        "common.abilities.sword.heavy_dual_cross_cut" => imgs.sword_heavy_cross_cut,
561        "common.abilities.sword.agile_dual_cross_cut" => imgs.sword_agile_cross_cut,
562        "common.abilities.sword.defensive_dual_cross_cut" => imgs.sword_defensive_cross_cut,
563        "common.abilities.sword.crippling_dual_cross_cut" => imgs.sword_crippling_cross_cut,
564        "common.abilities.sword.cleaving_dual_cross_cut" => imgs.sword_cleaving_cross_cut,
565        "veloren.core.pseudo_abilities.sword.finisher" => imgs.sword_finisher,
566        "common.abilities.sword.basic_mighty_strike" => imgs.sword_basic_mighty_strike,
567        "common.abilities.sword.heavy_guillotine" => imgs.sword_heavy_guillotine,
568        "common.abilities.sword.agile_hundred_cuts" => imgs.sword_agile_hundred_cuts,
569        "common.abilities.sword.defensive_counter" => imgs.sword_defensive_counter,
570        "common.abilities.sword.crippling_mutilate" => imgs.sword_crippling_mutilate,
571        "common.abilities.sword.cleaving_bladestorm" => imgs.sword_cleaving_bladestorm,
572        "common.abilities.sword.cleaving_dual_bladestorm" => imgs.sword_cleaving_bladestorm,
573        "common.abilities.sword.heavy_sweep" => imgs.sword_heavy_sweep,
574        "common.abilities.sword.heavy_pommel_strike" => imgs.sword_heavy_pommel_strike,
575        "common.abilities.sword.agile_quick_draw" => imgs.sword_agile_quick_draw,
576        "common.abilities.sword.agile_feint" => imgs.sword_agile_feint,
577        "common.abilities.sword.defensive_riposte" => imgs.sword_defensive_riposte,
578        "common.abilities.sword.defensive_disengage" => imgs.sword_defensive_disengage,
579        "common.abilities.sword.crippling_gouge" => imgs.sword_crippling_gouge,
580        "common.abilities.sword.crippling_hamstring" => imgs.sword_crippling_hamstring,
581        "common.abilities.sword.cleaving_whirlwind_slice" => imgs.sword_cleaving_whirlwind_slice,
582        "common.abilities.sword.cleaving_dual_whirlwind_slice" => {
583            imgs.sword_cleaving_whirlwind_slice
584        },
585        "common.abilities.sword.cleaving_earth_splitter" => imgs.sword_cleaving_earth_splitter,
586        "common.abilities.sword.heavy_fortitude" => imgs.sword_heavy_fortitude,
587        "common.abilities.sword.heavy_pillar_thrust" => imgs.sword_heavy_pillar_thrust,
588        "common.abilities.sword.agile_dancing_edge" => imgs.sword_agile_dancing_edge,
589        "common.abilities.sword.agile_flurry" => imgs.sword_agile_flurry,
590        "common.abilities.sword.agile_dual_flurry" => imgs.sword_agile_flurry,
591        "common.abilities.sword.defensive_stalwart_sword" => imgs.sword_defensive_stalwart_sword,
592        "common.abilities.sword.defensive_deflect" => imgs.sword_defensive_deflect,
593        "common.abilities.sword.crippling_eviscerate" => imgs.sword_crippling_eviscerate,
594        "common.abilities.sword.crippling_bloody_gash" => imgs.sword_crippling_bloody_gash,
595        "common.abilities.sword.cleaving_blade_fever" => imgs.sword_cleaving_blade_fever,
596        "common.abilities.sword.cleaving_sky_splitter" => imgs.sword_cleaving_sky_splitter,
597        // Axe
598        "common.abilities.axe.triple_chop" => imgs.axe_triple_chop,
599        "common.abilities.axe.cleave" => imgs.axe_cleave,
600        "common.abilities.axe.brutal_swing" => imgs.axe_brutal_swing,
601        "common.abilities.axe.berserk" => imgs.axe_berserk,
602        "common.abilities.axe.rising_tide" => imgs.axe_rising_tide,
603        "common.abilities.axe.savage_sense" => imgs.axe_savage_sense,
604        "common.abilities.axe.adrenaline_rush" => imgs.axe_adrenaline_rush,
605        "common.abilities.axe.execute" => imgs.axe_execute,
606        "common.abilities.axe.maelstrom" => imgs.axe_maelstrom,
607        "common.abilities.axe.rake" => imgs.axe_rake,
608        "common.abilities.axe.bloodfeast" => imgs.axe_bloodfeast,
609        "common.abilities.axe.fierce_raze" => imgs.axe_fierce_raze,
610        "common.abilities.axe.dual_fierce_raze" => imgs.axe_fierce_raze,
611        "common.abilities.axe.furor" => imgs.axe_furor,
612        "common.abilities.axe.fracture" => imgs.axe_fracture,
613        "common.abilities.axe.lacerate" => imgs.axe_lacerate,
614        "common.abilities.axe.riptide" => imgs.axe_riptide,
615        "common.abilities.axe.skull_bash" => imgs.axe_skull_bash,
616        "common.abilities.axe.sunder" => imgs.axe_sunder,
617        "common.abilities.axe.plunder" => imgs.axe_plunder,
618        "common.abilities.axe.defiance" => imgs.axe_defiance,
619        "common.abilities.axe.keelhaul" => imgs.axe_keelhaul,
620        "common.abilities.axe.bulkhead" => imgs.axe_bulkhead,
621        "common.abilities.axe.capsize" => imgs.axe_capsize,
622        // Hammer
623        "common.abilities.hammer.solid_smash" => imgs.hammer_solid_smash,
624        "common.abilities.hammer.wide_wallop" => imgs.hammer_wide_wallop,
625        "common.abilities.hammer.scornful_swipe" => imgs.hammer_scornful_swipe,
626        "common.abilities.hammer.tremor" => imgs.hammer_tremor,
627        "common.abilities.hammer.vigorous_bash" => imgs.hammer_vigorous_bash,
628        "common.abilities.hammer.heavy_whorl" => imgs.hammer_heavy_whorl,
629        "common.abilities.hammer.dual_heavy_whorl" => imgs.hammer_heavy_whorl,
630        "common.abilities.hammer.intercept" => imgs.hammer_intercept,
631        "common.abilities.hammer.dual_intercept" => imgs.hammer_intercept,
632        "common.abilities.hammer.retaliate" => imgs.hammer_retaliate,
633        "common.abilities.hammer.spine_cracker" => imgs.hammer_spine_cracker,
634        "common.abilities.hammer.breach" => imgs.hammer_breach,
635        "common.abilities.hammer.pile_driver" => imgs.hammer_pile_driver,
636        "common.abilities.hammer.lung_pummel" => imgs.hammer_lung_pummel,
637        "common.abilities.hammer.helm_crusher" => imgs.hammer_helm_crusher,
638        "common.abilities.hammer.iron_tempest" => imgs.hammer_iron_tempest,
639        "common.abilities.hammer.dual_iron_tempest" => imgs.hammer_iron_tempest,
640        "common.abilities.hammer.upheaval" => imgs.hammer_upheaval,
641        "common.abilities.hammer.dual_upheaval" => imgs.hammer_upheaval,
642        "common.abilities.hammer.rampart" => imgs.hammer_rampart,
643        "common.abilities.hammer.tenacity" => imgs.hammer_tenacity,
644        "common.abilities.hammer.thunderclap" => imgs.hammer_thunderclap,
645        "common.abilities.hammer.seismic_shock" => imgs.hammer_seismic_shock,
646        "common.abilities.hammer.earthshaker" => imgs.hammer_earthshaker,
647        "common.abilities.hammer.judgement" => imgs.hammer_judgement,
648        // Bow
649        "common.abilities.bow.arrow_shot" => imgs.bow_arrow_shot,
650        "common.abilities.bow.broadhead" => imgs.bow_broadhead,
651        "common.abilities.bow.foothold" => imgs.bow_foothold,
652        "common.abilities.bow.heavy_nock" => imgs.bow_heavy_nock,
653        "common.abilities.bow.ardent_hunt" => imgs.bow_ardent_hunt,
654        "common.abilities.bow.owl_talon" => imgs.bow_owl_talon,
655        "common.abilities.bow.eagle_eye" => imgs.bow_eagle_eye,
656        "common.abilities.bow.heartseeker" => imgs.bow_heartseeker,
657        "common.abilities.bow.hawkstrike" => imgs.bow_hawkstrike,
658        "common.abilities.bow.hawkstrike_shot" => imgs.bow_hawkstrike,
659        "common.abilities.bow.septic_shot" => imgs.bow_septic_shot,
660        "common.abilities.bow.ignite_arrow" => imgs.bow_ignite_arrow,
661        "common.abilities.bow.burning_arrow" => imgs.bow_burning_arrow,
662        "common.abilities.bow.burning_broadhead" => imgs.bow_burning_broadhead,
663        "common.abilities.bow.drench_arrow" => imgs.bow_drench_arrow,
664        "common.abilities.bow.poison_arrow" => imgs.bow_poison_arrow,
665        "common.abilities.bow.poison_broadhead" => imgs.bow_poison_broadhead,
666        "common.abilities.bow.freeze_arrow" => imgs.bow_freeze_arrow,
667        "common.abilities.bow.freezing_arrow" => imgs.bow_freezing_arrow,
668        "common.abilities.bow.freezing_broadhead" => imgs.bow_freezing_broadhead,
669        "common.abilities.bow.jolt_arrow" => imgs.bow_jolt_arrow,
670        "common.abilities.bow.lightning_arrow" => imgs.bow_lightning_arrow,
671        "common.abilities.bow.lightning_broadhead" => imgs.bow_lightning_broadhead,
672        "common.abilities.bow.barrage" => imgs.bow_barrage,
673        "common.abilities.bow.barrage_shot" => imgs.bow_barrage,
674        "common.abilities.bow.piercing_gale" => imgs.bow_piercing_gale,
675        "common.abilities.bow.piercing_gale_shot" => imgs.bow_piercing_gale,
676        "common.abilities.bow.scatterburst" => imgs.bow_scatterburst,
677        "common.abilities.bow.lesser_scatterburst" => imgs.bow_lesser_scatterburst,
678        "common.abilities.bow.greater_scatterburst" => imgs.bow_greater_scatterburst,
679        "common.abilities.bow.fusillade" => imgs.bow_fusillade,
680        "common.abilities.bow.fusillade_shot" => imgs.bow_fusillade,
681        "common.abilities.bow.death_volley" => imgs.bow_death_volley,
682        "common.abilities.bow.death_volley_shot" => imgs.bow_death_volley,
683        "common.abilities.bow.death_volley_heavy_shot" => imgs.bow_death_volley,
684        // Staff
685        "common.abilities.staff.firebomb" => imgs.fireball,
686        "common.abilities.staff.flamethrower" => imgs.flamethrower,
687        "common.abilities.staff.fireshockwave" => imgs.fire_aoe,
688        // Sceptre
689        "common.abilities.sceptre.lifestealbeam" => imgs.skill_sceptre_lifesteal,
690        "common.abilities.sceptre.healingaura" => imgs.skill_sceptre_heal,
691        "common.abilities.sceptre.wardingaura" => imgs.skill_sceptre_aura,
692        // Shield
693        "common.abilities.shield.singlestrike" => imgs.onehshield_m1,
694        "common.abilities.shield.power_guard" => imgs.onehshield_m1,
695        // Dagger
696        "common.abilities.dagger.tempbasic" => imgs.onehdagger_m1,
697        // Pickaxe
698        "common.abilities.pick.swing" => imgs.mining,
699        // Shovel
700        "common.abilities.shovel.dig" => imgs.dig,
701        // Instruments
702        "common.abilities.music.bass" => imgs.instrument,
703        "common.abilities.music.flute" => imgs.instrument,
704        "common.abilities.music.harp" => imgs.instrument,
705        "common.abilities.music.perc" => imgs.instrument,
706        "common.abilities.music.kalimba" => imgs.instrument,
707        "common.abilities.music.melodica" => imgs.instrument,
708        "common.abilities.music.lute" => imgs.instrument,
709        "common.abilities.music.oud" => imgs.instrument,
710        "common.abilities.music.guitar" => imgs.instrument,
711        "common.abilities.music.dark_guitar" => imgs.instrument,
712        "common.abilities.music.sitar" => imgs.instrument,
713        "common.abilities.music.double_bass" => imgs.instrument,
714        "common.abilities.music.glass_flute" => imgs.instrument,
715        "common.abilities.music.lyre" => imgs.instrument,
716        "common.abilities.music.wildskin_drum" => imgs.instrument,
717        "common.abilities.music.icy_talharpa" => imgs.instrument,
718        "common.abilities.music.washboard" => imgs.instrument,
719        "common.abilities.music.steeltonguedrum" => imgs.instrument,
720        "common.abilities.music.shamisen" => imgs.instrument,
721        "common.abilities.music.kora" => imgs.instrument,
722        "common.abilities.music.banjo" => imgs.instrument,
723        "common.abilities.music.viola_pizzicato" => imgs.instrument,
724        "common.abilities.music.starlight_conch" => imgs.instrument,
725        "common.abilities.music.timbrel_of_chaos" => imgs.instrument,
726        "common.abilities.music.rhythmo" => imgs.instrument,
727        // Glider
728        "common.abilities.debug.glide_boost" => imgs.flyingrod_m2,
729        "common.abilities.debug.glide_speeder" => imgs.flyingrod_m1,
730        _ => imgs.not_found,
731    }
732}
733
734pub fn ability_description<'a>(
735    ability_id: &str,
736    loc: &'a Localization,
737) -> (Cow<'a, str>, Cow<'a, str>) {
738    let ability_i18n_key = ability_id.replace('.', "-");
739    match ability_i18n_key.as_str() {
740        "common-abilities-axe-execute"
741        | "common-abilities-axe-maelstrom"
742        | "common-abilities-axe-lacerate"
743        | "common-abilities-axe-riptide"
744        | "common-abilities-axe-bulkhead"
745        | "common-abilities-axe-capsize" => (
746            loc.get_msg(&ability_i18n_key),
747            loc.get_attr_ctx(&ability_i18n_key, "desc", &i18n::fluent_args! {
748                "min_combo" => 25,
749                "min_combo_upg" => 40,
750            }),
751        ),
752        "common-abilities-hammer-earthshaker"
753        | "common-abilities-hammer-judgement"
754        | "common-abilities-hammer-seismic_shock"
755        | "common-abilities-hammer-thunderclap" => (
756            loc.get_msg(&ability_i18n_key),
757            loc.get_attr_ctx(&ability_i18n_key, "desc", &i18n::fluent_args! {
758                "min_combo" => 20,
759            }),
760        ),
761        "common-abilities-hammer-lung_pummel" | "common-abilities-hammer-spine_cracker" => (
762            loc.get_msg(&ability_i18n_key),
763            loc.get_attr_ctx(&ability_i18n_key, "desc", &i18n::fluent_args! {
764                "min_combo" => 5,
765            }),
766        ),
767        "common-abilities-hammer-helm_crusher" => (
768            loc.get_msg(&ability_i18n_key),
769            loc.get_attr_ctx(&ability_i18n_key, "desc", &i18n::fluent_args! {
770                "min_combo" => 10,
771            }),
772        ),
773        // Default case, no input values
774        ability_i18n_key => (
775            loc.get_msg(ability_i18n_key),
776            loc.get_attr(ability_i18n_key, "desc"),
777        ),
778    }
779}