veloren_voxygen/hud/
diary.rs

1use super::{
2    BLACK, CRITICAL_HP_COLOR, HP_COLOR, Position, PositionSpecifier, Show, TEXT_COLOR,
3    UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR,
4    img_ids::{Imgs, ImgsRot},
5    item_imgs::{ItemImgs, animate_by_pulse},
6};
7use crate::{
8    GlobalState,
9    game_input::GameInput,
10    hud::{
11        self,
12        slots::{AbilitySlot, SlotManager},
13        util,
14    },
15    ui::{
16        ImageFrame, Tooltip, TooltipManager, Tooltipable,
17        fonts::Fonts,
18        slot::{ContentSize, SlotMaker},
19    },
20};
21use client::{self, Client};
22use common::{
23    combat,
24    comp::{
25        self, Body, CharacterState, Energy, Health, Inventory, Poise, Stats,
26        ability::{Ability, ActiveAbilities, AuxiliaryAbility, BASE_ABILITY_LIMIT},
27        inventory::{
28            item::{
29                ItemI18n, ItemKind, MaterialStatManifest,
30                item_key::ItemKey,
31                tool::{AbilityContext, ToolKind},
32            },
33            slot::EquipSlot,
34        },
35        skills::{
36            self, AxeSkill, BowSkill, ClimbSkill, HammerSkill, MiningSkill, SKILL_MODIFIERS,
37            SceptreSkill, Skill, StaffSkill, SwimSkill, SwordSkill,
38        },
39        skillset::{SkillGroupKind, SkillSet},
40    },
41    resources::BattleMode,
42    uid::Uid,
43};
44use conrod_core::{
45    Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, color,
46    image,
47    widget::{self, Button, Image, Rectangle, State, Text},
48    widget_ids,
49};
50use i18n::Localization;
51use std::borrow::Cow;
52use strum::{EnumIter, IntoEnumIterator};
53use vek::*;
54const ART_SIZE: [f64; 2] = [320.0, 320.0];
55
56widget_ids! {
57    pub struct Ids {
58        frame,
59        bg,
60        icon,
61        close,
62        title,
63        content_align,
64        section_imgs[],
65        section_btns[],
66        // Skill tree stuffs
67        exp_bar_bg,
68        exp_bar_frame,
69        exp_bar_content_align,
70        exp_bar_content,
71        exp_bar_rank,
72        exp_bar_txt,
73        active_bar_checkbox,
74        active_bar_checkbox_label,
75        tree_title_txt,
76        lock_imgs[],
77        available_pts_txt,
78        weapon_imgs[],
79        weapon_btns[],
80        skills_top_l_align,
81        skills_top_r_align,
82        skills_bot_l_align,
83        skills_bot_r_align,
84        skills_top_l[],
85        skills_top_r[],
86        skills_bot_l[],
87        skills_bot_r[],
88        skills[],
89        skill_lock_imgs[],
90        sword_bg,
91        axe_bg,
92        hammer_bg,
93        bow_render,
94        skill_bow_charged_0,
95        skill_bow_charged_1,
96        skill_bow_charged_2,
97        skill_bow_charged_3,
98        skill_bow_charged_4,
99        skill_bow_charged_5,
100        skill_bow_repeater_0,
101        skill_bow_repeater_1,
102        skill_bow_repeater_2,
103        skill_bow_repeater_3,
104        skill_bow_shotgun_0,
105        skill_bow_shotgun_1,
106        skill_bow_shotgun_2,
107        skill_bow_shotgun_3,
108        skill_bow_shotgun_4,
109        skill_bow_passive_0,
110        staff_render,
111        skill_staff_basic_0,
112        skill_staff_basic_1,
113        skill_staff_basic_2,
114        skill_staff_basic_3,
115        skill_staff_basic_4,
116        skill_staff_beam_0,
117        skill_staff_beam_1,
118        skill_staff_beam_2,
119        skill_staff_beam_3,
120        skill_staff_beam_4,
121        skill_staff_shockwave_0,
122        skill_staff_shockwave_1,
123        skill_staff_shockwave_2,
124        skill_staff_shockwave_3,
125        skill_staff_shockwave_4,
126        sceptre_render,
127        skill_sceptre_lifesteal_0,
128        skill_sceptre_lifesteal_1,
129        skill_sceptre_lifesteal_2,
130        skill_sceptre_lifesteal_3,
131        skill_sceptre_lifesteal_4,
132        skill_sceptre_heal_0,
133        skill_sceptre_heal_1,
134        skill_sceptre_heal_2,
135        skill_sceptre_heal_3,
136        skill_sceptre_heal_4,
137        skill_sceptre_aura_0,
138        skill_sceptre_aura_1,
139        skill_sceptre_aura_2,
140        skill_sceptre_aura_3,
141        skill_sceptre_aura_4,
142        pick_render,
143        skill_pick_m1,
144        skill_pick_m1_0,
145        skill_pick_m1_1,
146        skill_pick_m1_2,
147        general_combat_render_0,
148        general_combat_render_1,
149        skill_general_stat_0,
150        skill_general_stat_1,
151        skill_general_tree_0,
152        skill_general_tree_1,
153        skill_general_tree_2,
154        skill_general_tree_3,
155        skill_general_tree_4,
156        skill_general_tree_5,
157        skill_general_roll_0,
158        skill_general_roll_1,
159        skill_general_roll_2,
160        skill_general_roll_3,
161        skill_general_climb_0,
162        skill_general_climb_1,
163        skill_general_climb_2,
164        skill_general_swim_0,
165        skill_general_swim_1,
166        sword_path_overlay,
167        // Ability selection
168        spellbook_art,
169        sb_page_left_align,
170        sb_page_right_align,
171        spellbook_skills_bg,
172        spellbook_btn,
173        spellbook_btn_bg,
174        ability_select_title,
175        ability_page_left,
176        ability_page_right,
177        active_abilities[],
178        active_abilities_keys[],
179        main_weap_select,
180        off_weap_select,
181        abilities[],
182        ability_frames[],
183        abilities_dual[],
184        ability_titles[],
185        ability_descs[],
186        dragged_ability,
187        // Stats
188        stat_names[],
189        stat_values[],
190        // Recipes
191        recipe_groups[],
192    }
193}
194
195#[derive(WidgetCommon)]
196pub struct Diary<'a> {
197    show: &'a Show,
198    client: &'a Client,
199    global_state: &'a GlobalState,
200    skill_set: &'a SkillSet,
201    active_abilities: &'a ActiveAbilities,
202    inventory: &'a Inventory,
203    char_state: &'a CharacterState,
204    health: &'a Health,
205    energy: &'a Energy,
206    poise: &'a Poise,
207    body: &'a Body,
208    uid: &'a Uid,
209    msm: &'a MaterialStatManifest,
210    imgs: &'a Imgs,
211    item_imgs: &'a ItemImgs,
212    fonts: &'a Fonts,
213    localized_strings: &'a Localization,
214    item_i18n: &'a ItemI18n,
215    rot_imgs: &'a ImgsRot,
216    tooltip_manager: &'a mut TooltipManager,
217    slot_manager: &'a mut SlotManager,
218    pulse: f32,
219    context: &'a AbilityContext,
220    stats: Option<&'a Stats>,
221
222    #[conrod(common_builder)]
223    common: widget::CommonBuilder,
224    created_btns_top_l: usize,
225    created_btns_top_r: usize,
226    created_btns_bot_l: usize,
227    created_btns_bot_r: usize,
228}
229
230pub struct DiaryShow {
231    pub skilltreetab: SelectedSkillTree,
232    pub section: DiarySection,
233}
234
235impl Default for DiaryShow {
236    fn default() -> Self {
237        Self {
238            skilltreetab: SelectedSkillTree::General,
239            section: DiarySection::SkillTrees,
240        }
241    }
242}
243
244#[expect(clippy::too_many_arguments)]
245impl<'a> Diary<'a> {
246    pub fn new(
247        show: &'a Show,
248        client: &'a Client,
249        global_state: &'a GlobalState,
250        skill_set: &'a SkillSet,
251        active_abilities: &'a ActiveAbilities,
252        inventory: &'a Inventory,
253        char_state: &'a CharacterState,
254        health: &'a Health,
255        energy: &'a Energy,
256        poise: &'a Poise,
257        body: &'a Body,
258        uid: &'a Uid,
259        msm: &'a MaterialStatManifest,
260        imgs: &'a Imgs,
261        item_imgs: &'a ItemImgs,
262        fonts: &'a Fonts,
263        localized_strings: &'a Localization,
264        item_i18n: &'a ItemI18n,
265        rot_imgs: &'a ImgsRot,
266        tooltip_manager: &'a mut TooltipManager,
267        slot_manager: &'a mut SlotManager,
268        pulse: f32,
269        context: &'a AbilityContext,
270        stats: Option<&'a Stats>,
271    ) -> Self {
272        Self {
273            show,
274            client,
275            global_state,
276            skill_set,
277            active_abilities,
278            inventory,
279            char_state,
280            health,
281            energy,
282            poise,
283            body,
284            uid,
285            msm,
286            imgs,
287            item_imgs,
288            fonts,
289            localized_strings,
290            item_i18n,
291            rot_imgs,
292            tooltip_manager,
293            slot_manager,
294            pulse,
295            context,
296            stats,
297            common: widget::CommonBuilder::default(),
298            created_btns_top_l: 0,
299            created_btns_top_r: 0,
300            created_btns_bot_l: 0,
301            created_btns_bot_r: 0,
302        }
303    }
304}
305
306pub type SelectedSkillTree = SkillGroupKind;
307
308pub enum Event {
309    Close,
310    ChangeSkillTree(SelectedSkillTree),
311    UnlockSkill(Skill),
312    ChangeSection(DiarySection),
313    SelectExpBar(Option<SkillGroupKind>),
314}
315
316// Possible future sections: Bestiary ("Pokedex" of fought enemies), Weapon and
317// armour catalogue, Achievements...
318#[derive(EnumIter, PartialEq, Eq)]
319pub enum DiarySection {
320    SkillTrees,
321    AbilitySelection,
322    Character,
323    Recipes,
324}
325
326impl DiarySection {
327    fn title_key(&self) -> &'static str {
328        match self {
329            DiarySection::SkillTrees => "hud-diary-sections-skill_trees-title",
330            DiarySection::AbilitySelection => "hud-diary-sections-abilities-title",
331            DiarySection::Character => "hud-diary-sections-character-title",
332            DiarySection::Recipes => "hud-diary-sections-recipes-title",
333        }
334    }
335}
336
337// Represents the SkillGroupKind items
338// that have a skill tree in the diary
339#[derive(EnumIter, PartialEq, Eq)]
340pub enum DiarySkillTree {
341    General,
342    Sword,
343    Axe,
344    Hammer,
345    Bow,
346    Staff,
347    Sceptre,
348    Pick,
349}
350
351impl DiarySkillTree {
352    fn title_key(&self) -> &'static str {
353        match self {
354            DiarySkillTree::General => "hud-skill_tree-general",
355            DiarySkillTree::Sword => "hud-skill_tree-sword",
356            DiarySkillTree::Axe => "hud-skill_tree-axe",
357            DiarySkillTree::Hammer => "hud-skill_tree-hammer",
358            DiarySkillTree::Bow => "hud-skill_tree-bow",
359            DiarySkillTree::Staff => "hud-skill_tree-staff",
360            DiarySkillTree::Sceptre => "hud-skill_tree-sceptre",
361            DiarySkillTree::Pick => "hud-skill_tree-mining",
362        }
363    }
364
365    fn to_skill_group(&self) -> SkillGroupKind {
366        match self {
367            DiarySkillTree::General => SkillGroupKind::General,
368            DiarySkillTree::Sword => SkillGroupKind::Weapon(ToolKind::Sword),
369            DiarySkillTree::Axe => SkillGroupKind::Weapon(ToolKind::Axe),
370            DiarySkillTree::Hammer => SkillGroupKind::Weapon(ToolKind::Hammer),
371            DiarySkillTree::Bow => SkillGroupKind::Weapon(ToolKind::Bow),
372            DiarySkillTree::Staff => SkillGroupKind::Weapon(ToolKind::Staff),
373            DiarySkillTree::Sceptre => SkillGroupKind::Weapon(ToolKind::Sceptre),
374            DiarySkillTree::Pick => SkillGroupKind::Weapon(ToolKind::Pick),
375        }
376    }
377}
378
379pub struct DiaryState {
380    ids: Ids,
381    ability_page: usize,
382    recipe_page: usize,
383}
384
385impl Widget for Diary<'_> {
386    type Event = Vec<Event>;
387    type State = DiaryState;
388    type Style = ();
389
390    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
391        DiaryState {
392            ids: Ids::new(id_gen),
393            ability_page: 0,
394            recipe_page: 0,
395        }
396    }
397
398    fn style(&self) -> Self::Style {}
399
400    fn update(mut self, args: widget::UpdateArgs<Self>) -> Self::Event {
401        common_base::prof_span!("Diary::update");
402        let widget::UpdateArgs { state, ui, .. } = args;
403        let mut events = Vec::new();
404
405        // Tooltips
406        let diary_tooltip = Tooltip::new({
407            // Edge images [t, b, r, l]
408            // Corner images [tr, tl, br, bl]
409            let edge = &self.rot_imgs.tt_side;
410            let corner = &self.rot_imgs.tt_corner;
411            ImageFrame::new(
412                [edge.cw180, edge.none, edge.cw270, edge.cw90],
413                [corner.none, corner.cw270, corner.cw90, corner.cw180],
414                Color::Rgba(0.08, 0.07, 0.04, 1.0),
415                5.0,
416            )
417        })
418        .title_font_size(self.fonts.cyri.scale(15))
419        .parent(ui.window)
420        .desc_font_size(self.fonts.cyri.scale(12))
421        .font_id(self.fonts.cyri.conrod_id)
422        .desc_text_color(TEXT_COLOR);
423
424        //Animation timer Frame
425        let frame_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8;
426
427        Image::new(self.imgs.diary_bg)
428            .w_h(1202.0, 886.0)
429            .mid_top_with_margin_on(ui.window, 5.0)
430            .color(Some(UI_MAIN))
431            .set(state.ids.bg, ui);
432
433        Image::new(self.imgs.diary_frame)
434            .w_h(1202.0, 886.0)
435            .middle_of(state.ids.bg)
436            .color(Some(UI_HIGHLIGHT_0))
437            .set(state.ids.frame, ui);
438
439        // Icon
440        Image::new(self.imgs.spellbook_button)
441            .w_h(30.0, 27.0)
442            .top_left_with_margins_on(state.ids.frame, 8.0, 8.0)
443            .set(state.ids.icon, ui);
444
445        // X-Button
446        if Button::image(self.imgs.close_button)
447            .w_h(24.0, 25.0)
448            .hover_image(self.imgs.close_btn_hover)
449            .press_image(self.imgs.close_btn_press)
450            .top_right_with_margins_on(state.ids.frame, 0.0, 0.0)
451            .set(state.ids.close, ui)
452            .was_clicked()
453        {
454            events.push(Event::Close);
455        }
456
457        // Title
458        Text::new(&self.localized_strings.get_msg("hud-diary"))
459            .mid_top_with_margin_on(state.ids.frame, 3.0)
460            .font_id(self.fonts.cyri.conrod_id)
461            .font_size(self.fonts.cyri.scale(29))
462            .color(TEXT_COLOR)
463            .set(state.ids.title, ui);
464
465        // Content Alignment
466        Rectangle::fill_with([599.0 * 2.0, 419.0 * 2.0], color::TRANSPARENT)
467            .mid_top_with_margin_on(state.ids.frame, 46.0)
468            .set(state.ids.content_align, ui);
469
470        // Contents
471        // Section buttons
472        let sel_section = &self.show.diary_fields.section;
473
474        let sections_len = DiarySection::iter().enumerate().len();
475
476        // Update len
477        state.update(|s| {
478            s.ids
479                .section_imgs
480                .resize(sections_len, &mut ui.widget_id_generator())
481        });
482        state.update(|s| {
483            s.ids
484                .section_btns
485                .resize(sections_len, &mut ui.widget_id_generator())
486        });
487
488        for (i, section) in DiarySection::iter().enumerate() {
489            let section_name = self.localized_strings.get_msg(section.title_key());
490
491            let btn_img = {
492                let img = match section {
493                    DiarySection::AbilitySelection => self.imgs.spellbook_ico,
494                    DiarySection::SkillTrees => self.imgs.skilltree_ico,
495                    DiarySection::Character => self.imgs.stats_ico,
496                    DiarySection::Recipes => self.imgs.crafting_icon,
497                };
498
499                if i == 0 {
500                    Image::new(img).top_left_with_margins_on(state.ids.content_align, 0.0, -50.0)
501                } else {
502                    Image::new(img).down_from(state.ids.section_btns[i - 1], 5.0)
503                }
504            };
505            btn_img.w_h(50.0, 50.0).set(state.ids.section_imgs[i], ui);
506            // Section Buttons
507            let border_image = if section == *sel_section {
508                self.imgs.wpn_icon_border_pressed
509            } else {
510                self.imgs.wpn_icon_border
511            };
512
513            let hover_image = if section == *sel_section {
514                self.imgs.wpn_icon_border_pressed
515            } else {
516                self.imgs.wpn_icon_border_mo
517            };
518
519            let press_image = if section == *sel_section {
520                self.imgs.wpn_icon_border_pressed
521            } else {
522                self.imgs.wpn_icon_border_press
523            };
524            let section_buttons = Button::image(border_image)
525                .w_h(50.0, 50.0)
526                .hover_image(hover_image)
527                .press_image(press_image)
528                .middle_of(state.ids.section_imgs[i])
529                .with_tooltip(
530                    self.tooltip_manager,
531                    &section_name,
532                    "",
533                    &diary_tooltip,
534                    TEXT_COLOR,
535                )
536                .set(state.ids.section_btns[i], ui);
537            if section_buttons.was_clicked() {
538                events.push(Event::ChangeSection(section))
539            }
540        }
541        match self.show.diary_fields.section {
542            DiarySection::SkillTrees => {
543                // Skill Trees
544                let sel_tab = &self.show.diary_fields.skilltreetab;
545
546                let skill_trees_len = DiarySkillTree::iter().enumerate().len();
547
548                // Skill Tree Selection
549                state.update(|s| {
550                    s.ids
551                        .weapon_btns
552                        .resize(skill_trees_len, &mut ui.widget_id_generator())
553                });
554                state.update(|s| {
555                    s.ids
556                        .weapon_imgs
557                        .resize(skill_trees_len, &mut ui.widget_id_generator())
558                });
559                state.update(|s| {
560                    s.ids
561                        .lock_imgs
562                        .resize(skill_trees_len, &mut ui.widget_id_generator())
563                });
564
565                // Draw skillgroup tab's icons
566                for (i, skill_tree) in DiarySkillTree::iter().enumerate() {
567                    let skill_tree_name = self.localized_strings.get_msg(skill_tree.title_key());
568                    let skill_group = skill_tree.to_skill_group();
569
570                    // Check if we have this skill tree unlocked
571                    let locked = !self.skill_set.skill_group_accessible(skill_group);
572
573                    // Weapon button image
574                    let btn_img = {
575                        let img = match skill_tree {
576                            DiarySkillTree::General => self.imgs.swords_crossed,
577                            DiarySkillTree::Sword => self.imgs.sword,
578                            DiarySkillTree::Axe => self.imgs.axe,
579                            DiarySkillTree::Hammer => self.imgs.hammer,
580                            DiarySkillTree::Bow => self.imgs.bow,
581                            DiarySkillTree::Staff => self.imgs.staff,
582                            DiarySkillTree::Sceptre => self.imgs.sceptre,
583                            DiarySkillTree::Pick => self.imgs.mining,
584                        };
585
586                        if i == 0 {
587                            Image::new(img).top_left_with_margins_on(
588                                state.ids.content_align,
589                                10.0,
590                                5.0,
591                            )
592                        } else {
593                            Image::new(img).down_from(state.ids.weapon_btns[i - 1], 5.0)
594                        }
595                    };
596                    btn_img.w_h(50.0, 50.0).set(state.ids.weapon_imgs[i], ui);
597
598                    // Lock Image
599                    if locked {
600                        Image::new(self.imgs.lock)
601                            .w_h(50.0, 50.0)
602                            .middle_of(state.ids.weapon_imgs[i])
603                            .graphics_for(state.ids.weapon_imgs[i])
604                            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8)))
605                            .set(state.ids.lock_imgs[i], ui);
606                    }
607
608                    // Weapon icons
609                    let have_points = {
610                        let available = self.skill_set.available_sp(skill_group);
611
612                        let earned = self.skill_set.earned_sp(skill_group);
613                        let total_cost = skill_group.total_skill_point_cost();
614
615                        available > 0 && (earned - available) < total_cost
616                    };
617
618                    let border_image = if skill_group == *sel_tab || have_points {
619                        self.imgs.wpn_icon_border_pressed
620                    } else {
621                        self.imgs.wpn_icon_border
622                    };
623
624                    let hover_image = if skill_group == *sel_tab {
625                        self.imgs.wpn_icon_border_pressed
626                    } else {
627                        self.imgs.wpn_icon_border_mo
628                    };
629
630                    let press_image = if skill_group == *sel_tab {
631                        self.imgs.wpn_icon_border_pressed
632                    } else {
633                        self.imgs.wpn_icon_border_press
634                    };
635
636                    let color = if skill_group != *sel_tab && have_points {
637                        Color::Rgba(0.92, 0.76, 0.0, frame_ani)
638                    } else {
639                        TEXT_COLOR
640                    };
641
642                    let tooltip_txt = if locked {
643                        self.localized_strings.get_msg("hud-skill-not_unlocked")
644                    } else {
645                        Cow::Borrowed("")
646                    };
647
648                    let wpn_button = Button::image(border_image)
649                        .w_h(50.0, 50.0)
650                        .hover_image(hover_image)
651                        .press_image(press_image)
652                        .middle_of(state.ids.weapon_imgs[i])
653                        .image_color(color)
654                        .with_tooltip(
655                            self.tooltip_manager,
656                            &skill_tree_name,
657                            &tooltip_txt,
658                            &diary_tooltip,
659                            TEXT_COLOR,
660                        )
661                        .set(state.ids.weapon_btns[i], ui);
662                    if wpn_button.was_clicked() {
663                        events.push(Event::ChangeSkillTree(skill_group))
664                    }
665                }
666
667                // Exp Bars and Rank Display
668                let current_exp = self.skill_set.available_experience(*sel_tab) as f64;
669                let max_exp = self.skill_set.skill_point_cost(*sel_tab) as f64;
670                let exp_percentage = current_exp / max_exp;
671                let rank = self.skill_set.earned_sp(*sel_tab);
672                let rank_txt = format!("{}", rank);
673                let exp_txt = format!("{}/{}", current_exp, max_exp);
674                let available_pts = self.skill_set.available_sp(*sel_tab);
675                Image::new(self.imgs.diary_exp_bg)
676                    .w_h(480.0, 76.0)
677                    .mid_bottom_with_margin_on(state.ids.content_align, 10.0)
678                    .set(state.ids.exp_bar_bg, ui);
679                Rectangle::fill_with([400.0, 40.0], color::TRANSPARENT)
680                    .top_left_with_margins_on(state.ids.exp_bar_bg, 32.0, 40.0)
681                    .set(state.ids.exp_bar_content_align, ui);
682                Image::new(self.imgs.bar_content)
683                    .w_h(400.0 * exp_percentage, 40.0)
684                    .top_left_with_margins_on(state.ids.exp_bar_content_align, 0.0, 0.0)
685                    .color(Some(XP_COLOR))
686                    .set(state.ids.exp_bar_content, ui);
687                Image::new(self.imgs.diary_exp_frame)
688                    .w_h(480.0, 76.0)
689                    .color(Some(UI_HIGHLIGHT_0))
690                    .middle_of(state.ids.exp_bar_bg)
691                    .set(state.ids.exp_bar_frame, ui);
692                // Show as Exp bar below skillbar
693                let exp_selected =
694                    self.global_state.settings.interface.xp_bar_skillgroup == Some(*sel_tab);
695                if Button::image(if !exp_selected {
696                    self.imgs.checkbox
697                } else {
698                    self.imgs.checkbox_checked
699                })
700                .w_h(18.0, 18.0)
701                .hover_image(if !exp_selected {
702                    self.imgs.checkbox_mo
703                } else {
704                    self.imgs.checkbox_checked_mo
705                })
706                .press_image(if !exp_selected {
707                    self.imgs.checkbox_press
708                } else {
709                    self.imgs.checkbox_checked
710                })
711                .top_right_with_margins_on(state.ids.exp_bar_frame, 50.0, -30.0)
712                .set(state.ids.active_bar_checkbox, ui)
713                .was_clicked()
714                {
715                    if self.global_state.settings.interface.xp_bar_skillgroup != Some(*sel_tab) {
716                        events.push(Event::SelectExpBar(Some(*sel_tab)));
717                    } else {
718                        events.push(Event::SelectExpBar(None));
719                    }
720                }
721
722                Text::new(&self.localized_strings.get_msg("hud-skill-set_as_exp_bar"))
723                    .right_from(state.ids.active_bar_checkbox, 10.0)
724                    .font_size(self.fonts.cyri.scale(14))
725                    .font_id(self.fonts.cyri.conrod_id)
726                    .graphics_for(state.ids.active_bar_checkbox)
727                    .color(TEXT_COLOR)
728                    .set(state.ids.active_bar_checkbox_label, ui);
729
730                // Show EXP bar text on hover
731                if ui
732                    .widget_input(state.ids.exp_bar_frame)
733                    .mouse()
734                    .is_some_and(|m| m.is_over())
735                {
736                    Text::new(&exp_txt)
737                        .mid_top_with_margin_on(state.ids.exp_bar_frame, 47.0)
738                        .font_id(self.fonts.cyri.conrod_id)
739                        .font_size(self.fonts.cyri.scale(14))
740                        .color(TEXT_COLOR)
741                        .graphics_for(state.ids.exp_bar_frame)
742                        .set(state.ids.exp_bar_txt, ui);
743                }
744                Text::new(&rank_txt)
745                    .mid_top_with_margin_on(state.ids.exp_bar_frame, match rank {
746                        0..=99 => 5.0,
747                        100..=999 => 8.0,
748                        _ => 10.0,
749                    })
750                    .font_id(self.fonts.cyri.conrod_id)
751                    .font_size(self.fonts.cyri.scale(match rank {
752                        0..=99 => 28,
753                        100..=999 => 21,
754                        _ => 15,
755                    }))
756                    .color(TEXT_COLOR)
757                    .set(state.ids.exp_bar_rank, ui);
758
759                Text::new(&self.localized_strings.get_msg_ctx(
760                    "hud-skill-sp_available",
761                    &i18n::fluent_args! {
762                        "number" => available_pts,
763                    },
764                ))
765                .mid_top_with_margin_on(state.ids.content_align, 700.0)
766                .font_id(self.fonts.cyri.conrod_id)
767                .font_size(self.fonts.cyri.scale(28))
768                .color(if available_pts > 0 {
769                    Color::Rgba(0.92, 0.76, 0.0, frame_ani)
770                } else {
771                    TEXT_COLOR
772                })
773                .set(state.ids.available_pts_txt, ui);
774                // Skill Trees
775                // Alignment Placing
776                let x = 200.0;
777                let y = 100.0;
778                // Alignment rectangles for skills
779                Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT)
780                    .top_left_with_margins_on(state.ids.content_align, y, x)
781                    .set(state.ids.skills_top_l_align, ui);
782                Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT)
783                    .top_right_with_margins_on(state.ids.content_align, y, x)
784                    .set(state.ids.skills_top_r_align, ui);
785                Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT)
786                    .bottom_left_with_margins_on(state.ids.content_align, y, x)
787                    .set(state.ids.skills_bot_l_align, ui);
788                Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT)
789                    .bottom_right_with_margins_on(state.ids.content_align, y, x)
790                    .set(state.ids.skills_bot_r_align, ui);
791
792                match sel_tab {
793                    SelectedSkillTree::General => {
794                        self.handle_general_skills_window(&diary_tooltip, state, ui, events)
795                    },
796                    SelectedSkillTree::Weapon(ToolKind::Sword) => {
797                        self.handle_sword_skills_window(&diary_tooltip, state, ui, events)
798                    },
799                    SelectedSkillTree::Weapon(ToolKind::Axe) => {
800                        self.handle_axe_skills_window(&diary_tooltip, state, ui, events)
801                    },
802                    SelectedSkillTree::Weapon(ToolKind::Hammer) => {
803                        self.handle_hammer_skills_window(&diary_tooltip, state, ui, events)
804                    },
805                    SelectedSkillTree::Weapon(ToolKind::Bow) => {
806                        self.handle_bow_skills_window(&diary_tooltip, state, ui, events)
807                    },
808                    SelectedSkillTree::Weapon(ToolKind::Staff) => {
809                        self.handle_staff_skills_window(&diary_tooltip, state, ui, events)
810                    },
811                    SelectedSkillTree::Weapon(ToolKind::Sceptre) => {
812                        self.handle_sceptre_skills_window(&diary_tooltip, state, ui, events)
813                    },
814                    SelectedSkillTree::Weapon(ToolKind::Pick) => {
815                        self.handle_mining_skills_window(&diary_tooltip, state, ui, events)
816                    },
817                    _ => events,
818                }
819            },
820            DiarySection::AbilitySelection => {
821                use comp::ability::AbilityInput;
822
823                // Background Art
824                Image::new(self.imgs.book_bg)
825                    .w_h(299.0 * 4.0, 184.0 * 4.0)
826                    .mid_top_with_margin_on(state.ids.content_align, 4.0)
827                    //.graphics_for(state.ids.content_align)
828                    .set(state.ids.spellbook_art, ui);
829                Image::new(self.imgs.skills_bg)
830                    .w_h(240.0 * 2.0, 40.0 * 2.0)
831                    .mid_bottom_with_margin_on(state.ids.content_align, 8.0)
832                    .set(state.ids.spellbook_skills_bg, ui);
833
834                Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
835                    .top_left_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
836                    .set(state.ids.sb_page_left_align, ui);
837                Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
838                    .top_right_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
839                    .set(state.ids.sb_page_right_align, ui);
840
841                // Display all active abilities on bottom of window
842                state.update(|s| {
843                    s.ids
844                        .active_abilities
845                        .resize(BASE_ABILITY_LIMIT, &mut ui.widget_id_generator())
846                });
847                state.update(|s| {
848                    s.ids
849                        .active_abilities_keys
850                        .resize(BASE_ABILITY_LIMIT, &mut ui.widget_id_generator())
851                });
852
853                let mut slot_maker = SlotMaker {
854                    empty_slot: self.imgs.inv_slot,
855                    filled_slot: self.imgs.inv_slot,
856                    selected_slot: self.imgs.inv_slot_sel,
857                    background_color: Some(UI_MAIN),
858                    content_size: ContentSize {
859                        width_height_ratio: 1.0,
860                        max_fraction: 0.9,
861                    },
862                    selected_content_scale: 1.067,
863                    amount_font: self.fonts.cyri.conrod_id,
864                    amount_margins: Vec2::new(-4.0, 0.0),
865                    amount_font_size: self.fonts.cyri.scale(12),
866                    amount_text_color: TEXT_COLOR,
867                    content_source: &(
868                        self.active_abilities,
869                        self.inventory,
870                        self.skill_set,
871                        self.context,
872                        Some(self.char_state),
873                        self.stats,
874                    ),
875                    image_source: self.imgs,
876                    slot_manager: Some(self.slot_manager),
877                    pulse: 0.0,
878                };
879
880                for i in 0..BASE_ABILITY_LIMIT {
881                    let ability_id = self
882                        .active_abilities
883                        .get_ability(
884                            AbilityInput::Auxiliary(i),
885                            Some(self.inventory),
886                            Some(self.skill_set),
887                            self.stats,
888                        )
889                        .ability_id(
890                            Some(self.char_state),
891                            Some(self.inventory),
892                            Some(self.skill_set),
893                            self.context,
894                        );
895                    let (ability_title, ability_desc) = if let Some(ability_id) = ability_id {
896                        util::ability_description(ability_id, self.localized_strings)
897                    } else {
898                        (
899                            Cow::Borrowed("Drag an ability here to use it."),
900                            Cow::Borrowed(""),
901                        )
902                    };
903
904                    let image_size = 80.0;
905                    let image_offsets = 92.0 * i as f64;
906
907                    let slot = AbilitySlot::Slot(i);
908                    let mut ability_slot = slot_maker.fabricate(slot, [image_size; 2]);
909
910                    if i == 0 {
911                        ability_slot = ability_slot.top_left_with_margins_on(
912                            state.ids.spellbook_skills_bg,
913                            0.0,
914                            32.0 + image_offsets,
915                        );
916                    } else {
917                        ability_slot =
918                            ability_slot.right_from(state.ids.active_abilities[i - 1], 4.0)
919                    }
920                    ability_slot
921                        .with_tooltip(
922                            self.tooltip_manager,
923                            &ability_title,
924                            &ability_desc,
925                            &diary_tooltip,
926                            TEXT_COLOR,
927                        )
928                        .set(state.ids.active_abilities[i], ui);
929
930                    // Display Slot Keybinding
931                    let keys = &self.global_state.settings.controls;
932                    let ability_key = [
933                        GameInput::Slot1,
934                        GameInput::Slot2,
935                        GameInput::Slot3,
936                        GameInput::Slot4,
937                        GameInput::Slot5,
938                    ]
939                    .get(i)
940                    .and_then(|input| keys.get_binding(*input))
941                    .map(|key| key.display_shortest())
942                    .unwrap_or_default();
943
944                    Text::new(&ability_key)
945                        .top_left_with_margins_on(state.ids.active_abilities[i], 0.0, 4.0)
946                        .font_id(self.fonts.cyri.conrod_id)
947                        .font_size(self.fonts.cyri.scale(20))
948                        .color(TEXT_COLOR)
949                        .graphics_for(state.ids.active_abilities[i])
950                        .set(state.ids.active_abilities_keys[i], ui);
951                }
952
953                let abilities: Vec<_> = ActiveAbilities::all_available_abilities(
954                    Some(self.inventory),
955                    Some(self.skill_set),
956                )
957                .into_iter()
958                .map(|a| {
959                    (
960                        Ability::from(a).ability_id(
961                            Some(self.char_state),
962                            Some(self.inventory),
963                            Some(self.skill_set),
964                            self.context,
965                        ),
966                        a,
967                    )
968                })
969                .collect();
970
971                const ABILITIES_PER_PAGE: usize = 12;
972
973                let page_indices = (abilities.len().saturating_sub(1)) / ABILITIES_PER_PAGE;
974
975                if state.ability_page > page_indices {
976                    state.update(|s| s.ability_page = 0);
977                }
978
979                state.update(|s| {
980                    s.ids
981                        .abilities
982                        .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
983                });
984                state.update(|s| {
985                    s.ids
986                        .abilities_dual
987                        .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
988                });
989                state.update(|s| {
990                    s.ids
991                        .ability_titles
992                        .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
993                });
994                state.update(|s| {
995                    s.ids
996                        .ability_frames
997                        .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
998                });
999                state.update(|s| {
1000                    s.ids
1001                        .ability_descs
1002                        .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
1003                });
1004
1005                // Page button
1006                // Left Arrow
1007                let left_arrow = Button::image(if state.ability_page > 0 {
1008                    self.imgs.arrow_l
1009                } else {
1010                    self.imgs.arrow_l_inactive
1011                })
1012                .bottom_left_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1013                .w_h(48.0, 55.0);
1014                // Grey out arrows when inactive
1015                if state.ability_page > 0 {
1016                    if left_arrow
1017                        .hover_image(self.imgs.arrow_l_click)
1018                        .press_image(self.imgs.arrow_l)
1019                        .set(state.ids.ability_page_left, ui)
1020                        .was_clicked()
1021                    {
1022                        state.update(|s| s.ability_page -= 1);
1023                    }
1024                } else {
1025                    left_arrow.set(state.ids.ability_page_left, ui);
1026                }
1027                // Right Arrow
1028                let right_arrow = Button::image(if state.ability_page < page_indices {
1029                    self.imgs.arrow_r
1030                } else {
1031                    self.imgs.arrow_r_inactive
1032                })
1033                .bottom_right_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1034                .w_h(48.0, 55.0);
1035                if state.ability_page < page_indices {
1036                    // Only show right button if not on last page
1037                    if right_arrow
1038                        .hover_image(self.imgs.arrow_r_click)
1039                        .press_image(self.imgs.arrow_r)
1040                        .set(state.ids.ability_page_right, ui)
1041                        .was_clicked()
1042                    {
1043                        state.update(|s| s.ability_page += 1);
1044                    };
1045                } else {
1046                    right_arrow.set(state.ids.ability_page_right, ui);
1047                }
1048
1049                let ability_start = state.ability_page * ABILITIES_PER_PAGE;
1050
1051                let mut slot_maker = SlotMaker {
1052                    empty_slot: self.imgs.inv_slot,
1053                    filled_slot: self.imgs.inv_slot,
1054                    selected_slot: self.imgs.inv_slot_sel,
1055                    background_color: Some(UI_MAIN),
1056                    content_size: ContentSize {
1057                        width_height_ratio: 1.0,
1058                        max_fraction: 1.0,
1059                    },
1060                    selected_content_scale: 1.067,
1061                    amount_font: self.fonts.cyri.conrod_id,
1062                    amount_margins: Vec2::new(-4.0, 0.0),
1063                    amount_font_size: self.fonts.cyri.scale(12),
1064                    amount_text_color: TEXT_COLOR,
1065                    content_source: &(
1066                        self.active_abilities,
1067                        self.inventory,
1068                        self.skill_set,
1069                        self.context,
1070                        Some(self.char_state),
1071                        self.stats,
1072                    ),
1073                    image_source: self.imgs,
1074                    slot_manager: Some(self.slot_manager),
1075                    pulse: 0.0,
1076                };
1077
1078                let same_weap_kinds = self
1079                    .inventory
1080                    .equipped(EquipSlot::ActiveMainhand)
1081                    .zip(self.inventory.equipped(EquipSlot::ActiveOffhand))
1082                    .is_some_and(|(a, b)| {
1083                        if let (ItemKind::Tool(tool_a), ItemKind::Tool(tool_b)) =
1084                            (&*a.kind(), &*b.kind())
1085                        {
1086                            (a.ability_spec(), tool_a.kind) == (b.ability_spec(), tool_b.kind)
1087                        } else {
1088                            false
1089                        }
1090                    });
1091
1092                for (id_index, (ability_id, ability)) in abilities
1093                    .iter()
1094                    .skip(ability_start)
1095                    .take(ABILITIES_PER_PAGE)
1096                    .enumerate()
1097                {
1098                    let (ability_title, ability_desc) =
1099                        util::ability_description(ability_id.unwrap_or(""), self.localized_strings);
1100
1101                    let (align_state, image_offsets) = if id_index < 6 {
1102                        (state.ids.sb_page_left_align, 120.0 * id_index as f64)
1103                    } else {
1104                        (state.ids.sb_page_right_align, 120.0 * (id_index - 6) as f64)
1105                    };
1106
1107                    Image::new(if same_weap_kinds {
1108                        self.imgs.ability_frame_dual
1109                    } else {
1110                        self.imgs.ability_frame
1111                    })
1112                    .w_h(566.0, 108.0)
1113                    .top_left_with_margins_on(align_state, 16.0 + image_offsets, 16.0)
1114                    .color(Some(UI_HIGHLIGHT_0))
1115                    .set(state.ids.ability_frames[id_index], ui);
1116
1117                    let slot = AbilitySlot::Ability(*ability);
1118                    slot_maker
1119                        .fabricate(slot, [100.0; 2])
1120                        .top_left_with_margins_on(align_state, 20.0 + image_offsets, 20.0)
1121                        .set(state.ids.abilities[id_index], ui);
1122
1123                    if same_weap_kinds {
1124                        if let AuxiliaryAbility::MainWeapon(slot) = ability {
1125                            let ability = AuxiliaryAbility::OffWeapon(*slot);
1126
1127                            let slot = AbilitySlot::Ability(ability);
1128                            slot_maker
1129                                .fabricate(slot, [100.0; 2])
1130                                .top_right_with_margins_on(align_state, 20.0 + image_offsets, 20.0)
1131                                .set(state.ids.abilities_dual[id_index], ui);
1132                        }
1133                    }
1134                    // The page width...
1135                    let text_width = 299.0 * 2.0
1136                        - if same_weap_kinds && matches!(ability, AuxiliaryAbility::MainWeapon(_)) {
1137                            // with double the width of an ability image and some padding subtracted
1138                            // if dual wielding two of the same weapon kind
1139                            (20.0 + 100.0 + 10.0) * 2.0
1140                        } else {
1141                            // or the width of an ability image and some padding subtracted
1142                            // otherwise
1143                            20.0 * 2.0 + 100.0
1144                        };
1145                    Text::new(&ability_title)
1146                        .top_left_with_margins_on(state.ids.abilities[id_index], 5.0, 110.0)
1147                        .font_id(self.fonts.cyri.conrod_id)
1148                        .font_size(self.fonts.cyri.scale(28))
1149                        .color(TEXT_COLOR)
1150                        .w(text_width)
1151                        .graphics_for(state.ids.abilities[id_index])
1152                        .set(state.ids.ability_titles[id_index], ui);
1153                    Text::new(&ability_desc)
1154                        .top_left_with_margins_on(state.ids.abilities[id_index], 40.0, 110.0)
1155                        .font_id(self.fonts.cyri.conrod_id)
1156                        .font_size(self.fonts.cyri.scale(13))
1157                        .color(TEXT_COLOR)
1158                        .w(text_width)
1159                        .graphics_for(state.ids.abilities[id_index])
1160                        .set(state.ids.ability_descs[id_index], ui);
1161                }
1162
1163                events
1164            },
1165            DiarySection::Character => {
1166                // Background Art
1167                Image::new(self.imgs.book_bg)
1168                    .w_h(299.0 * 4.0, 184.0 * 4.0)
1169                    .mid_top_with_margin_on(state.ids.content_align, 4.0)
1170                    .set(state.ids.spellbook_art, ui);
1171
1172                if state.ids.stat_names.len() < STAT_COUNT {
1173                    state.update(|s| {
1174                        s.ids
1175                            .stat_names
1176                            .resize(STAT_COUNT, &mut ui.widget_id_generator());
1177                        s.ids
1178                            .stat_values
1179                            .resize(STAT_COUNT, &mut ui.widget_id_generator());
1180                    });
1181                }
1182
1183                for (i, stat) in CharacterStat::iter().enumerate() {
1184                    // Stat names
1185                    let localized_name = stat.localized_str(self.localized_strings);
1186                    let mut txt = Text::new(&localized_name)
1187                        .font_id(self.fonts.cyri.conrod_id)
1188                        .font_size(self.fonts.cyri.scale(29))
1189                        .color(BLACK);
1190
1191                    if i == 0 {
1192                        txt = txt.top_left_with_margins_on(state.ids.spellbook_art, 20.0, 20.0);
1193                    } else {
1194                        txt = txt.down_from(state.ids.stat_names[i - 1], 10.0);
1195                    };
1196                    txt.set(state.ids.stat_names[i], ui);
1197
1198                    let main_weap_stats = self
1199                        .inventory
1200                        .equipped(EquipSlot::ActiveMainhand)
1201                        .and_then(|item| match &*item.kind() {
1202                            ItemKind::Tool(tool) => {
1203                                Some(tool.stats(item.stats_durability_multiplier()))
1204                            },
1205                            _ => None,
1206                        });
1207
1208                    let off_weap_stats = self
1209                        .inventory
1210                        .equipped(EquipSlot::ActiveOffhand)
1211                        .and_then(|item| match &*item.kind() {
1212                            ItemKind::Tool(tool) => {
1213                                Some(tool.stats(item.stats_durability_multiplier()))
1214                            },
1215                            _ => None,
1216                        });
1217
1218                    let (name, _gender, battle_mode) = self
1219                        .client
1220                        .player_list()
1221                        .get(self.uid)
1222                        .and_then(|info| info.character.as_ref())
1223                        .map_or_else(
1224                            || ("Unknown".to_string(), None, BattleMode::PvP),
1225                            |character_info| {
1226                                (
1227                                    self.localized_strings.get_content(&character_info.name),
1228                                    character_info.gender,
1229                                    character_info.battle_mode,
1230                                )
1231                            },
1232                        );
1233
1234                    // Stat values
1235                    let value = match stat {
1236                        CharacterStat::Name => name,
1237                        CharacterStat::BattleMode => match battle_mode {
1238                            BattleMode::PvP => "PvP".to_string(),
1239                            BattleMode::PvE => "PvE".to_string(),
1240                        },
1241                        CharacterStat::Waypoint => self
1242                            .client
1243                            .waypoint()
1244                            .as_ref()
1245                            .cloned()
1246                            .unwrap_or_else(|| "Unknown".to_string()),
1247                        CharacterStat::Hitpoints => format!("{}", self.health.base_max() as u32),
1248                        CharacterStat::Energy => format!("{}", self.energy.base_max() as u32),
1249                        CharacterStat::Poise => format!("{}", self.poise.base_max() as u32),
1250                        CharacterStat::CombatRating => {
1251                            let cr = combat::combat_rating(
1252                                self.inventory,
1253                                self.health,
1254                                self.energy,
1255                                self.poise,
1256                                self.skill_set,
1257                                *self.body,
1258                                self.msm,
1259                            );
1260                            format!("{:.2}", cr * 10.0)
1261                        },
1262                        CharacterStat::Protection => {
1263                            let protection =
1264                                combat::compute_protection(Some(self.inventory), self.msm);
1265                            match protection {
1266                                Some(prot) => format!("{}", prot),
1267                                None => String::from("Invincible"),
1268                            }
1269                        },
1270                        CharacterStat::StunResistance => {
1271                            let stun_res = Poise::compute_poise_damage_reduction(
1272                                Some(self.inventory),
1273                                self.msm,
1274                                None,
1275                                self.stats,
1276                            );
1277                            format!("{:.2}%", stun_res * 100.0)
1278                        },
1279                        CharacterStat::PrecisionPower => {
1280                            let precision_power =
1281                                combat::compute_precision_mult(Some(self.inventory), self.msm);
1282                            format!("x{:.2}", precision_power)
1283                        },
1284                        CharacterStat::EnergyReward => {
1285                            let energy_rew =
1286                                combat::compute_energy_reward_mod(Some(self.inventory), self.msm);
1287                            format!("{:+.0}%", (energy_rew - 1.0) * 100.0)
1288                        },
1289                        CharacterStat::Stealth => {
1290                            let stealth_perception_multiplier =
1291                                combat::perception_dist_multiplier_from_stealth(
1292                                    Some(self.inventory),
1293                                    None,
1294                                    self.msm,
1295                                );
1296                            let txt =
1297                                format!("{:+.1}%", (1.0 - stealth_perception_multiplier) * 100.0);
1298
1299                            txt
1300                        },
1301                        CharacterStat::WeaponPower => match (main_weap_stats, off_weap_stats) {
1302                            (Some(m_stats), Some(o_stats)) => {
1303                                format!("{}   {}", m_stats.power * 10.0, o_stats.power * 10.0)
1304                            },
1305                            (Some(stats), None) | (None, Some(stats)) => {
1306                                format!("{}", stats.power * 10.0)
1307                            },
1308                            (None, None) => String::new(),
1309                        },
1310                        CharacterStat::WeaponSpeed => {
1311                            let spd_fmt = |sp| (sp - 1.0) * 100.0;
1312                            match (main_weap_stats, off_weap_stats) {
1313                                (Some(m_stats), Some(o_stats)) => format!(
1314                                    "{:+.0}%   {:+.0}%",
1315                                    spd_fmt(m_stats.speed),
1316                                    spd_fmt(o_stats.speed)
1317                                ),
1318                                (Some(stats), None) | (None, Some(stats)) => {
1319                                    format!("{:+.0}%", spd_fmt(stats.speed))
1320                                },
1321                                _ => String::new(),
1322                            }
1323                        },
1324                        CharacterStat::WeaponEffectPower => match (main_weap_stats, off_weap_stats)
1325                        {
1326                            (Some(m_stats), Some(o_stats)) => {
1327                                format!(
1328                                    "{}   {}",
1329                                    m_stats.effect_power * 10.0,
1330                                    o_stats.effect_power * 10.0
1331                                )
1332                            },
1333                            (Some(stats), None) | (None, Some(stats)) => {
1334                                format!("{}", stats.effect_power * 10.0)
1335                            },
1336                            (None, None) => String::new(),
1337                        },
1338                    };
1339
1340                    let mut number = Text::new(&value)
1341                        .font_id(self.fonts.cyri.conrod_id)
1342                        .font_size(self.fonts.cyri.scale(29))
1343                        .color(BLACK);
1344
1345                    if i == 0 {
1346                        number = number.right_from(state.ids.stat_names[i], 165.0);
1347                    } else {
1348                        number = number.down_from(state.ids.stat_values[i - 1], 10.0);
1349                    };
1350                    number.set(state.ids.stat_values[i], ui);
1351                }
1352
1353                events
1354            },
1355            DiarySection::Recipes => {
1356                // Background Art
1357                Image::new(self.imgs.book_bg)
1358                    .w_h(299.0 * 4.0, 184.0 * 4.0)
1359                    .mid_top_with_margin_on(state.ids.content_align, 4.0)
1360                    .set(state.ids.spellbook_art, ui);
1361
1362                Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1363                    .top_left_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1364                    .set(state.ids.sb_page_left_align, ui);
1365                Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1366                    .top_right_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1367                    .set(state.ids.sb_page_right_align, ui);
1368
1369                const RECIPES_PER_PAGE: usize = 36;
1370
1371                let page_index_max =
1372                    self.inventory.recipe_groups_iter().len().saturating_sub(1) / RECIPES_PER_PAGE;
1373
1374                if state.recipe_page > page_index_max {
1375                    state.update(|s| s.recipe_page = 0);
1376                }
1377
1378                // Page button
1379                // Left Arrow
1380                let left_arrow = Button::image(if state.recipe_page > 0 {
1381                    self.imgs.arrow_l
1382                } else {
1383                    self.imgs.arrow_l_inactive
1384                })
1385                .bottom_left_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1386                .w_h(48.0, 55.0);
1387                // Grey out arrows when inactive
1388                if state.recipe_page > 0 {
1389                    if left_arrow
1390                        .hover_image(self.imgs.arrow_l_click)
1391                        .press_image(self.imgs.arrow_l)
1392                        .set(state.ids.ability_page_left, ui)
1393                        .was_clicked()
1394                    {
1395                        state.update(|s| s.recipe_page -= 1);
1396                    }
1397                } else {
1398                    left_arrow.set(state.ids.ability_page_left, ui);
1399                }
1400                // Right Arrow
1401                let right_arrow = Button::image(if state.recipe_page < page_index_max {
1402                    self.imgs.arrow_r
1403                } else {
1404                    self.imgs.arrow_r_inactive
1405                })
1406                .bottom_right_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1407                .w_h(48.0, 55.0);
1408                if state.recipe_page < page_index_max {
1409                    // Only show right button if not on last page
1410                    if right_arrow
1411                        .hover_image(self.imgs.arrow_r_click)
1412                        .press_image(self.imgs.arrow_r)
1413                        .set(state.ids.ability_page_right, ui)
1414                        .was_clicked()
1415                    {
1416                        state.update(|s| s.recipe_page += 1);
1417                    };
1418                } else {
1419                    right_arrow.set(state.ids.ability_page_right, ui);
1420                }
1421
1422                state.update(|s| {
1423                    s.ids
1424                        .recipe_groups
1425                        .resize(RECIPES_PER_PAGE, &mut ui.widget_id_generator())
1426                });
1427
1428                for (i, rg) in self
1429                    .inventory
1430                    .recipe_groups_iter()
1431                    .skip(state.recipe_page * RECIPES_PER_PAGE)
1432                    .take(RECIPES_PER_PAGE)
1433                    .enumerate()
1434                {
1435                    let (title, _desc) =
1436                        util::item_text(rg, self.localized_strings, self.item_i18n);
1437
1438                    let mut text = Text::new(&title)
1439                        .font_id(self.fonts.cyri.conrod_id)
1440                        .font_size(self.fonts.cyri.scale(29))
1441                        .color(BLACK);
1442
1443                    if i == 0 {
1444                        text =
1445                            text.top_left_with_margins_on(state.ids.sb_page_left_align, 20.0, 20.0);
1446                    } else if i == 18 {
1447                        text = text.top_left_with_margins_on(
1448                            state.ids.sb_page_right_align,
1449                            20.0,
1450                            20.0,
1451                        );
1452                    } else {
1453                        text = text.down_from(state.ids.recipe_groups[i - 1], 10.0);
1454                    }
1455                    text.set(state.ids.recipe_groups[i], ui);
1456                }
1457
1458                events
1459            },
1460        }
1461    }
1462}
1463
1464enum SkillIcon<'a> {
1465    Unlockable {
1466        skill: Skill,
1467        image: image::Id,
1468        position: PositionSpecifier,
1469        id: widget::Id,
1470    },
1471    Descriptive {
1472        title: &'a str,
1473        desc: &'a str,
1474        image: image::Id,
1475        position: PositionSpecifier,
1476        id: widget::Id,
1477    },
1478    Ability {
1479        skill: Skill,
1480        ability_id: &'a str,
1481        position: PositionSpecifier,
1482    },
1483}
1484
1485impl Diary<'_> {
1486    fn handle_general_skills_window(
1487        &mut self,
1488        diary_tooltip: &Tooltip,
1489        state: &mut State<DiaryState>,
1490        ui: &mut UiCell,
1491        mut events: Vec<Event>,
1492    ) -> Vec<Event> {
1493        let tree_title = &self.localized_strings.get_msg("common-weapons-general");
1494        Text::new(tree_title)
1495            .mid_top_with_margin_on(state.ids.content_align, 2.0)
1496            .font_id(self.fonts.cyri.conrod_id)
1497            .font_size(self.fonts.cyri.scale(34))
1498            .color(TEXT_COLOR)
1499            .set(state.ids.tree_title_txt, ui);
1500
1501        // Number of skills per rectangle per weapon, start counting at 0
1502        // Maximum of 9 skills/8 indices
1503        let skills_top_l = 6;
1504        let skills_top_r = 0;
1505        let skills_bot_l = 0;
1506        let skills_bot_r = 5;
1507
1508        self.setup_state_for_skill_icons(
1509            state,
1510            ui,
1511            skills_top_l,
1512            skills_top_r,
1513            skills_bot_l,
1514            skills_bot_r,
1515        );
1516
1517        use SkillGroupKind::*;
1518        use ToolKind::*;
1519        // General Combat
1520        Image::new(animate_by_pulse(
1521            &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1522                "example_general_combat_left".to_string(),
1523            )),
1524            self.pulse,
1525        ))
1526        .wh(ART_SIZE)
1527        .middle_of(state.ids.content_align)
1528        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1529        .set(state.ids.general_combat_render_0, ui);
1530
1531        Image::new(animate_by_pulse(
1532            &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1533                "example_general_combat_right".to_string(),
1534            )),
1535            self.pulse,
1536        ))
1537        .wh(ART_SIZE)
1538        .middle_of(state.ids.general_combat_render_0)
1539        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1540        .set(state.ids.general_combat_render_1, ui);
1541
1542        use PositionSpecifier::MidTopWithMarginOn;
1543        let skill_buttons = &[
1544            // Top Left skills
1545            //        5 1 6
1546            //        3 0 4
1547            //        8 2 7
1548            // Bottom left skills
1549            SkillIcon::Unlockable {
1550                skill: Skill::UnlockGroup(Weapon(Sword)),
1551                image: self.imgs.unlock_sword_skill,
1552                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
1553                id: state.ids.skill_general_tree_0,
1554            },
1555            SkillIcon::Unlockable {
1556                skill: Skill::UnlockGroup(Weapon(Axe)),
1557                image: self.imgs.unlock_axe_skill,
1558                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
1559                id: state.ids.skill_general_tree_1,
1560            },
1561            SkillIcon::Unlockable {
1562                skill: Skill::UnlockGroup(Weapon(Hammer)),
1563                image: self.imgs.unlock_hammer_skill,
1564                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
1565                id: state.ids.skill_general_tree_2,
1566            },
1567            SkillIcon::Unlockable {
1568                skill: Skill::UnlockGroup(Weapon(Bow)),
1569                image: self.imgs.unlock_bow_skill,
1570                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
1571                id: state.ids.skill_general_tree_3,
1572            },
1573            SkillIcon::Unlockable {
1574                skill: Skill::UnlockGroup(Weapon(Staff)),
1575                image: self.imgs.unlock_staff_skill0,
1576                position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
1577                id: state.ids.skill_general_tree_4,
1578            },
1579            SkillIcon::Unlockable {
1580                skill: Skill::UnlockGroup(Weapon(Sceptre)),
1581                image: self.imgs.unlock_sceptre_skill,
1582                position: MidTopWithMarginOn(state.ids.skills_top_l[5], 3.0),
1583                id: state.ids.skill_general_tree_5,
1584            },
1585            // Bottom right skills
1586            SkillIcon::Descriptive {
1587                title: "hud-skill-climbing_title",
1588                desc: "hud-skill-climbing",
1589                image: self.imgs.skill_climbing_skill,
1590                position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0),
1591                id: state.ids.skill_general_climb_0,
1592            },
1593            SkillIcon::Unlockable {
1594                skill: Skill::Climb(ClimbSkill::Cost),
1595                image: self.imgs.utility_cost_skill,
1596                position: MidTopWithMarginOn(state.ids.skills_bot_r[1], 3.0),
1597                id: state.ids.skill_general_climb_1,
1598            },
1599            SkillIcon::Unlockable {
1600                skill: Skill::Climb(ClimbSkill::Speed),
1601                image: self.imgs.utility_speed_skill,
1602                position: MidTopWithMarginOn(state.ids.skills_bot_r[2], 3.0),
1603                id: state.ids.skill_general_climb_2,
1604            },
1605            SkillIcon::Descriptive {
1606                title: "hud-skill-swim_title",
1607                desc: "hud-skill-swim",
1608                image: self.imgs.skill_swim_skill,
1609                position: MidTopWithMarginOn(state.ids.skills_bot_r[3], 3.0),
1610                id: state.ids.skill_general_swim_0,
1611            },
1612            SkillIcon::Unlockable {
1613                skill: Skill::Swim(SwimSkill::Speed),
1614                image: self.imgs.utility_speed_skill,
1615                position: MidTopWithMarginOn(state.ids.skills_bot_r[4], 3.0),
1616                id: state.ids.skill_general_swim_1,
1617            },
1618        ];
1619
1620        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1621        events
1622    }
1623
1624    fn handle_sword_skills_window(
1625        &mut self,
1626        diary_tooltip: &Tooltip,
1627        state: &mut State<DiaryState>,
1628        ui: &mut UiCell,
1629        mut events: Vec<Event>,
1630    ) -> Vec<Event> {
1631        Image::new(self.imgs.sword_tree_paths)
1632            .wh([1042.0, 636.0])
1633            .mid_top_with_margin_on(state.ids.content_align, 55.0)
1634            .graphics_for(state.ids.content_align)
1635            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1636            .set(state.ids.sword_path_overlay, ui);
1637
1638        // Sword
1639        Image::new(self.imgs.sword_bg)
1640            .wh([933.0, 615.0])
1641            .mid_top_with_margin_on(state.ids.content_align, 65.0)
1642            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1643            .set(state.ids.sword_bg, ui);
1644
1645        use PositionSpecifier::TopLeftWithMarginsOn;
1646        let skill_buttons = &[
1647            SkillIcon::Ability {
1648                skill: Skill::Sword(SwordSkill::CrescentSlash),
1649                ability_id: "veloren.core.pseudo_abilities.sword.crescent_slash",
1650                position: TopLeftWithMarginsOn(state.ids.sword_bg, 537.0, 429.0),
1651            },
1652            SkillIcon::Ability {
1653                skill: Skill::Sword(SwordSkill::FellStrike),
1654                ability_id: "veloren.core.pseudo_abilities.sword.fell_strike",
1655                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 527.0),
1656            },
1657            SkillIcon::Ability {
1658                skill: Skill::Sword(SwordSkill::Skewer),
1659                ability_id: "veloren.core.pseudo_abilities.sword.skewer",
1660                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 527.0),
1661            },
1662            SkillIcon::Ability {
1663                skill: Skill::Sword(SwordSkill::Cascade),
1664                ability_id: "veloren.core.pseudo_abilities.sword.cascade",
1665                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 332.0),
1666            },
1667            SkillIcon::Ability {
1668                skill: Skill::Sword(SwordSkill::CrossCut),
1669                ability_id: "veloren.core.pseudo_abilities.sword.cross_cut",
1670                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 332.0),
1671            },
1672            SkillIcon::Ability {
1673                skill: Skill::Sword(SwordSkill::Finisher),
1674                ability_id: "veloren.core.pseudo_abilities.sword.finisher",
1675                position: TopLeftWithMarginsOn(state.ids.sword_bg, 263.0, 429.0),
1676            },
1677            SkillIcon::Ability {
1678                skill: Skill::Sword(SwordSkill::HeavySweep),
1679                ability_id: "common.abilities.sword.heavy_sweep",
1680                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 2.0),
1681            },
1682            SkillIcon::Ability {
1683                skill: Skill::Sword(SwordSkill::HeavyPommelStrike),
1684                ability_id: "common.abilities.sword.heavy_pommel_strike",
1685                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 91.0),
1686            },
1687            SkillIcon::Ability {
1688                skill: Skill::Sword(SwordSkill::AgileQuickDraw),
1689                ability_id: "common.abilities.sword.agile_quick_draw",
1690                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 384.0),
1691            },
1692            SkillIcon::Ability {
1693                skill: Skill::Sword(SwordSkill::AgileFeint),
1694                ability_id: "common.abilities.sword.agile_feint",
1695                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 472.0),
1696            },
1697            SkillIcon::Ability {
1698                skill: Skill::Sword(SwordSkill::DefensiveRiposte),
1699                ability_id: "common.abilities.sword.defensive_riposte",
1700                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 766.0),
1701            },
1702            SkillIcon::Ability {
1703                skill: Skill::Sword(SwordSkill::DefensiveDisengage),
1704                ability_id: "common.abilities.sword.defensive_disengage",
1705                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 855.0),
1706            },
1707            SkillIcon::Ability {
1708                skill: Skill::Sword(SwordSkill::CripplingGouge),
1709                ability_id: "common.abilities.sword.crippling_gouge",
1710                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 766.0),
1711            },
1712            SkillIcon::Ability {
1713                skill: Skill::Sword(SwordSkill::CripplingHamstring),
1714                ability_id: "common.abilities.sword.crippling_hamstring",
1715                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 766.0),
1716            },
1717            SkillIcon::Ability {
1718                skill: Skill::Sword(SwordSkill::CleavingWhirlwindSlice),
1719                ability_id: "common.abilities.sword.cleaving_whirlwind_slice",
1720                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 91.0),
1721            },
1722            SkillIcon::Ability {
1723                skill: Skill::Sword(SwordSkill::CleavingEarthSplitter),
1724                ability_id: "common.abilities.sword.cleaving_earth_splitter",
1725                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 91.0),
1726            },
1727            SkillIcon::Ability {
1728                skill: Skill::Sword(SwordSkill::HeavyFortitude),
1729                ability_id: "common.abilities.sword.heavy_fortitude",
1730                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 2.0),
1731            },
1732            SkillIcon::Ability {
1733                skill: Skill::Sword(SwordSkill::HeavyPillarThrust),
1734                ability_id: "common.abilities.sword.heavy_pillar_thrust",
1735                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 91.0),
1736            },
1737            SkillIcon::Ability {
1738                skill: Skill::Sword(SwordSkill::AgileDancingEdge),
1739                ability_id: "common.abilities.sword.agile_dancing_edge",
1740                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 385.0),
1741            },
1742            SkillIcon::Ability {
1743                skill: Skill::Sword(SwordSkill::AgileFlurry),
1744                ability_id: "common.abilities.sword.agile_flurry",
1745                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 473.0),
1746            },
1747            SkillIcon::Ability {
1748                skill: Skill::Sword(SwordSkill::DefensiveStalwartSword),
1749                ability_id: "common.abilities.sword.defensive_stalwart_sword",
1750                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 766.0),
1751            },
1752            SkillIcon::Ability {
1753                skill: Skill::Sword(SwordSkill::DefensiveDeflect),
1754                ability_id: "common.abilities.sword.defensive_deflect",
1755                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 855.0),
1756            },
1757            SkillIcon::Ability {
1758                skill: Skill::Sword(SwordSkill::CripplingEviscerate),
1759                ability_id: "common.abilities.sword.crippling_eviscerate",
1760                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 855.0),
1761            },
1762            SkillIcon::Ability {
1763                skill: Skill::Sword(SwordSkill::CripplingBloodyGash),
1764                ability_id: "common.abilities.sword.crippling_bloody_gash",
1765                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 855.0),
1766            },
1767            SkillIcon::Ability {
1768                skill: Skill::Sword(SwordSkill::CleavingBladeFever),
1769                ability_id: "common.abilities.sword.cleaving_blade_fever",
1770                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 2.0),
1771            },
1772            SkillIcon::Ability {
1773                skill: Skill::Sword(SwordSkill::CleavingSkySplitter),
1774                ability_id: "common.abilities.sword.cleaving_sky_splitter",
1775                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 2.0),
1776            },
1777        ];
1778
1779        state.update(|s| {
1780            s.ids
1781                .skills
1782                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1783        });
1784        state.update(|s| {
1785            s.ids
1786                .skill_lock_imgs
1787                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1788        });
1789
1790        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1791        events
1792    }
1793
1794    fn handle_axe_skills_window(
1795        &mut self,
1796        diary_tooltip: &Tooltip,
1797        state: &mut State<DiaryState>,
1798        ui: &mut UiCell,
1799        mut events: Vec<Event>,
1800    ) -> Vec<Event> {
1801        // Axe
1802        Image::new(self.imgs.axe_bg)
1803            .wh([924.0, 619.0])
1804            .mid_top_with_margin_on(state.ids.content_align, 65.0)
1805            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1806            .set(state.ids.axe_bg, ui);
1807
1808        use PositionSpecifier::TopLeftWithMarginsOn;
1809        let skill_buttons = &[
1810            SkillIcon::Ability {
1811                skill: Skill::Axe(AxeSkill::BrutalSwing),
1812                ability_id: "common.abilities.axe.brutal_swing",
1813                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 424.0),
1814            },
1815            SkillIcon::Ability {
1816                skill: Skill::Axe(AxeSkill::Berserk),
1817                ability_id: "common.abilities.axe.berserk",
1818                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 374.0),
1819            },
1820            SkillIcon::Ability {
1821                skill: Skill::Axe(AxeSkill::RisingTide),
1822                ability_id: "common.abilities.axe.rising_tide",
1823                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 474.0),
1824            },
1825            SkillIcon::Ability {
1826                skill: Skill::Axe(AxeSkill::SavageSense),
1827                ability_id: "common.abilities.axe.savage_sense",
1828                position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 324.0),
1829            },
1830            SkillIcon::Ability {
1831                skill: Skill::Axe(AxeSkill::AdrenalineRush),
1832                ability_id: "common.abilities.axe.adrenaline_rush",
1833                position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 524.0),
1834            },
1835            SkillIcon::Ability {
1836                skill: Skill::Axe(AxeSkill::Execute),
1837                ability_id: "common.abilities.axe.execute",
1838                position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 424.0),
1839            },
1840            SkillIcon::Ability {
1841                skill: Skill::Axe(AxeSkill::Maelstrom),
1842                ability_id: "common.abilities.axe.maelstrom",
1843                position: TopLeftWithMarginsOn(state.ids.axe_bg, 4.0, 424.0),
1844            },
1845            SkillIcon::Ability {
1846                skill: Skill::Axe(AxeSkill::Rake),
1847                ability_id: "common.abilities.axe.rake",
1848                position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 325.0),
1849            },
1850            SkillIcon::Ability {
1851                skill: Skill::Axe(AxeSkill::Bloodfeast),
1852                ability_id: "common.abilities.axe.bloodfeast",
1853                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 74.0),
1854            },
1855            SkillIcon::Ability {
1856                skill: Skill::Axe(AxeSkill::FierceRaze),
1857                ability_id: "common.abilities.axe.fierce_raze",
1858                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 174.0),
1859            },
1860            SkillIcon::Ability {
1861                skill: Skill::Axe(AxeSkill::Furor),
1862                ability_id: "common.abilities.axe.furor",
1863                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 24.0),
1864            },
1865            SkillIcon::Ability {
1866                skill: Skill::Axe(AxeSkill::Fracture),
1867                ability_id: "common.abilities.axe.fracture",
1868                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 224.0),
1869            },
1870            SkillIcon::Ability {
1871                skill: Skill::Axe(AxeSkill::Lacerate),
1872                ability_id: "common.abilities.axe.lacerate",
1873                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 124.0),
1874            },
1875            SkillIcon::Ability {
1876                skill: Skill::Axe(AxeSkill::Riptide),
1877                ability_id: "common.abilities.axe.riptide",
1878                position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 124.0),
1879            },
1880            SkillIcon::Ability {
1881                skill: Skill::Axe(AxeSkill::SkullBash),
1882                ability_id: "common.abilities.axe.skull_bash",
1883                position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 523.0),
1884            },
1885            SkillIcon::Ability {
1886                skill: Skill::Axe(AxeSkill::Sunder),
1887                ability_id: "common.abilities.axe.sunder",
1888                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 674.0),
1889            },
1890            SkillIcon::Ability {
1891                skill: Skill::Axe(AxeSkill::Plunder),
1892                ability_id: "common.abilities.axe.plunder",
1893                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 774.0),
1894            },
1895            SkillIcon::Ability {
1896                skill: Skill::Axe(AxeSkill::Defiance),
1897                ability_id: "common.abilities.axe.defiance",
1898                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 624.0),
1899            },
1900            SkillIcon::Ability {
1901                skill: Skill::Axe(AxeSkill::Keelhaul),
1902                ability_id: "common.abilities.axe.keelhaul",
1903                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 824.0),
1904            },
1905            SkillIcon::Ability {
1906                skill: Skill::Axe(AxeSkill::Bulkhead),
1907                ability_id: "common.abilities.axe.bulkhead",
1908                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 724.0),
1909            },
1910            SkillIcon::Ability {
1911                skill: Skill::Axe(AxeSkill::Capsize),
1912                ability_id: "common.abilities.axe.capsize",
1913                position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 724.0),
1914            },
1915        ];
1916
1917        state.update(|s| {
1918            s.ids
1919                .skills
1920                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1921        });
1922        state.update(|s| {
1923            s.ids
1924                .skill_lock_imgs
1925                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1926        });
1927
1928        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1929        events
1930    }
1931
1932    fn handle_hammer_skills_window(
1933        &mut self,
1934        diary_tooltip: &Tooltip,
1935        state: &mut State<DiaryState>,
1936        ui: &mut UiCell,
1937        mut events: Vec<Event>,
1938    ) -> Vec<Event> {
1939        // Hammer
1940        Image::new(self.imgs.hammer_bg)
1941            .wh([924.0, 619.0])
1942            .mid_top_with_margin_on(state.ids.content_align, 65.0)
1943            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1944            .set(state.ids.hammer_bg, ui);
1945
1946        use PositionSpecifier::TopLeftWithMarginsOn;
1947        let skill_buttons = &[
1948            SkillIcon::Ability {
1949                skill: Skill::Hammer(HammerSkill::ScornfulSwipe),
1950                ability_id: "common.abilities.hammer.scornful_swipe",
1951                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 455.0, 424.0),
1952            },
1953            SkillIcon::Ability {
1954                skill: Skill::Hammer(HammerSkill::Tremor),
1955                ability_id: "common.abilities.hammer.tremor",
1956                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 172.0),
1957            },
1958            SkillIcon::Ability {
1959                skill: Skill::Hammer(HammerSkill::VigorousBash),
1960                ability_id: "common.abilities.hammer.vigorous_bash",
1961                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 272.0),
1962            },
1963            SkillIcon::Ability {
1964                skill: Skill::Hammer(HammerSkill::Retaliate),
1965                ability_id: "common.abilities.hammer.retaliate",
1966                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 122.0),
1967            },
1968            SkillIcon::Ability {
1969                skill: Skill::Hammer(HammerSkill::SpineCracker),
1970                ability_id: "common.abilities.hammer.spine_cracker",
1971                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 222.0),
1972            },
1973            SkillIcon::Ability {
1974                skill: Skill::Hammer(HammerSkill::Breach),
1975                ability_id: "common.abilities.hammer.breach",
1976                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 322.0),
1977            },
1978            SkillIcon::Ability {
1979                skill: Skill::Hammer(HammerSkill::IronTempest),
1980                ability_id: "common.abilities.hammer.iron_tempest",
1981                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 172.0),
1982            },
1983            SkillIcon::Ability {
1984                skill: Skill::Hammer(HammerSkill::Upheaval),
1985                ability_id: "common.abilities.hammer.upheaval",
1986                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 272.0),
1987            },
1988            SkillIcon::Ability {
1989                skill: Skill::Hammer(HammerSkill::Thunderclap),
1990                ability_id: "common.abilities.hammer.thunderclap",
1991                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 172.0),
1992            },
1993            SkillIcon::Ability {
1994                skill: Skill::Hammer(HammerSkill::SeismicShock),
1995                ability_id: "common.abilities.hammer.seismic_shock",
1996                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 272.0),
1997            },
1998            SkillIcon::Ability {
1999                skill: Skill::Hammer(HammerSkill::HeavyWhorl),
2000                ability_id: "common.abilities.hammer.heavy_whorl",
2001                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 576.0),
2002            },
2003            SkillIcon::Ability {
2004                skill: Skill::Hammer(HammerSkill::Intercept),
2005                ability_id: "common.abilities.hammer.intercept",
2006                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 676.0),
2007            },
2008            SkillIcon::Ability {
2009                skill: Skill::Hammer(HammerSkill::PileDriver),
2010                ability_id: "common.abilities.hammer.pile_driver",
2011                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 526.0),
2012            },
2013            SkillIcon::Ability {
2014                skill: Skill::Hammer(HammerSkill::LungPummel),
2015                ability_id: "common.abilities.hammer.lung_pummel",
2016                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 626.0),
2017            },
2018            SkillIcon::Ability {
2019                skill: Skill::Hammer(HammerSkill::HelmCrusher),
2020                ability_id: "common.abilities.hammer.helm_crusher",
2021                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 726.0),
2022            },
2023            SkillIcon::Ability {
2024                skill: Skill::Hammer(HammerSkill::Rampart),
2025                ability_id: "common.abilities.hammer.rampart",
2026                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 576.0),
2027            },
2028            SkillIcon::Ability {
2029                skill: Skill::Hammer(HammerSkill::Tenacity),
2030                ability_id: "common.abilities.hammer.tenacity",
2031                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 676.0),
2032            },
2033            SkillIcon::Ability {
2034                skill: Skill::Hammer(HammerSkill::Earthshaker),
2035                ability_id: "common.abilities.hammer.earthshaker",
2036                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 576.0),
2037            },
2038            SkillIcon::Ability {
2039                skill: Skill::Hammer(HammerSkill::Judgement),
2040                ability_id: "common.abilities.hammer.judgement",
2041                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 676.0),
2042            },
2043        ];
2044
2045        state.update(|s| {
2046            s.ids
2047                .skills
2048                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2049        });
2050        state.update(|s| {
2051            s.ids
2052                .skill_lock_imgs
2053                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2054        });
2055
2056        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2057        events
2058    }
2059
2060    fn handle_sceptre_skills_window(
2061        &mut self,
2062        diary_tooltip: &Tooltip,
2063        state: &mut State<DiaryState>,
2064        ui: &mut UiCell,
2065        mut events: Vec<Event>,
2066    ) -> Vec<Event> {
2067        // Title text
2068        let tree_title = &self.localized_strings.get_msg("common-weapons-sceptre");
2069
2070        Text::new(tree_title)
2071            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2072            .font_id(self.fonts.cyri.conrod_id)
2073            .font_size(self.fonts.cyri.scale(34))
2074            .color(TEXT_COLOR)
2075            .set(state.ids.tree_title_txt, ui);
2076
2077        // Number of skills per rectangle per weapon, start counting at 0
2078        // Maximum of 9 skills/8 indices
2079        let skills_top_l = 5;
2080        let skills_top_r = 5;
2081        let skills_bot_l = 5;
2082        let skills_bot_r = 0;
2083
2084        self.setup_state_for_skill_icons(
2085            state,
2086            ui,
2087            skills_top_l,
2088            skills_top_r,
2089            skills_bot_l,
2090            skills_bot_r,
2091        );
2092
2093        // Skill icons and buttons
2094        use skills::SceptreSkill::*;
2095        // Sceptre
2096        Image::new(animate_by_pulse(
2097            &self
2098                .item_imgs
2099                .img_ids_or_not_found_img(ItemKey::Simple("example_sceptre".to_string())),
2100            self.pulse,
2101        ))
2102        .wh(ART_SIZE)
2103        .middle_of(state.ids.content_align)
2104        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2105        .set(state.ids.sceptre_render, ui);
2106        use PositionSpecifier::MidTopWithMarginOn;
2107        let skill_buttons = &[
2108            // Top Left skills
2109            //        5 1 6
2110            //        3 0 4
2111            //        8 2 7
2112            SkillIcon::Descriptive {
2113                title: "hud-skill-sc_lifesteal_title",
2114                desc: "hud-skill-sc_lifesteal",
2115                image: self.imgs.skill_sceptre_lifesteal,
2116                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2117                id: state.ids.skill_sceptre_lifesteal_0,
2118            },
2119            SkillIcon::Unlockable {
2120                skill: Skill::Sceptre(LDamage),
2121                image: self.imgs.magic_damage_skill,
2122                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2123                id: state.ids.skill_sceptre_lifesteal_1,
2124            },
2125            SkillIcon::Unlockable {
2126                skill: Skill::Sceptre(LRange),
2127                image: self.imgs.magic_distance_skill,
2128                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2129                id: state.ids.skill_sceptre_lifesteal_2,
2130            },
2131            SkillIcon::Unlockable {
2132                skill: Skill::Sceptre(LLifesteal),
2133                image: self.imgs.magic_lifesteal_skill,
2134                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2135                id: state.ids.skill_sceptre_lifesteal_3,
2136            },
2137            SkillIcon::Unlockable {
2138                skill: Skill::Sceptre(LRegen),
2139                image: self.imgs.magic_energy_regen_skill,
2140                position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
2141                id: state.ids.skill_sceptre_lifesteal_4,
2142            },
2143            // Top right skills
2144            SkillIcon::Descriptive {
2145                title: "hud-skill-sc_heal_title",
2146                desc: "hud-skill-sc_heal",
2147                image: self.imgs.skill_sceptre_heal,
2148                position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2149                id: state.ids.skill_sceptre_heal_0,
2150            },
2151            SkillIcon::Unlockable {
2152                skill: Skill::Sceptre(HHeal),
2153                image: self.imgs.heal_heal_skill,
2154                position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2155                id: state.ids.skill_sceptre_heal_1,
2156            },
2157            SkillIcon::Unlockable {
2158                skill: Skill::Sceptre(HDuration),
2159                image: self.imgs.heal_duration_skill,
2160                position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2161                id: state.ids.skill_sceptre_heal_2,
2162            },
2163            SkillIcon::Unlockable {
2164                skill: Skill::Sceptre(HRange),
2165                image: self.imgs.heal_radius_skill,
2166                position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2167                id: state.ids.skill_sceptre_heal_3,
2168            },
2169            SkillIcon::Unlockable {
2170                skill: Skill::Sceptre(HCost),
2171                image: self.imgs.heal_cost_skill,
2172                position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0),
2173                id: state.ids.skill_sceptre_heal_4,
2174            },
2175            // Bottom left skills
2176            SkillIcon::Unlockable {
2177                skill: Skill::Sceptre(UnlockAura),
2178                image: self.imgs.skill_sceptre_aura,
2179                position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2180                id: state.ids.skill_sceptre_aura_0,
2181            },
2182            SkillIcon::Unlockable {
2183                skill: Skill::Sceptre(AStrength),
2184                image: self.imgs.buff_damage_skill,
2185                position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2186                id: state.ids.skill_sceptre_aura_1,
2187            },
2188            SkillIcon::Unlockable {
2189                skill: Skill::Sceptre(ADuration),
2190                image: self.imgs.buff_duration_skill,
2191                position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2192                id: state.ids.skill_sceptre_aura_2,
2193            },
2194            SkillIcon::Unlockable {
2195                skill: Skill::Sceptre(ARange),
2196                image: self.imgs.buff_radius_skill,
2197                position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2198                id: state.ids.skill_sceptre_aura_3,
2199            },
2200            SkillIcon::Unlockable {
2201                skill: Skill::Sceptre(ACost),
2202                image: self.imgs.buff_cost_skill,
2203                position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2204                id: state.ids.skill_sceptre_aura_4,
2205            },
2206        ];
2207
2208        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2209        events
2210    }
2211
2212    fn handle_bow_skills_window(
2213        &mut self,
2214        diary_tooltip: &Tooltip,
2215        state: &mut State<DiaryState>,
2216        ui: &mut UiCell,
2217        mut events: Vec<Event>,
2218    ) -> Vec<Event> {
2219        // Title text
2220        let tree_title = &self.localized_strings.get_msg("common-weapons-bow");
2221
2222        Text::new(tree_title)
2223            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2224            .font_id(self.fonts.cyri.conrod_id)
2225            .font_size(self.fonts.cyri.scale(34))
2226            .color(TEXT_COLOR)
2227            .set(state.ids.tree_title_txt, ui);
2228
2229        // Number of skills per rectangle per weapon, start counting at 0
2230        // Maximum of 9 skills/8 indices
2231        let skills_top_l = 6;
2232        let skills_top_r = 4;
2233        let skills_bot_l = 5;
2234        let skills_bot_r = 1;
2235
2236        self.setup_state_for_skill_icons(
2237            state,
2238            ui,
2239            skills_top_l,
2240            skills_top_r,
2241            skills_bot_l,
2242            skills_bot_r,
2243        );
2244
2245        // Skill icons and buttons
2246        use skills::BowSkill::*;
2247        // Bow
2248        Image::new(animate_by_pulse(
2249            &self
2250                .item_imgs
2251                .img_ids_or_not_found_img(ItemKey::Simple("example_bow".to_string())),
2252            self.pulse,
2253        ))
2254        .wh(ART_SIZE)
2255        .middle_of(state.ids.content_align)
2256        .set(state.ids.bow_render, ui);
2257        use PositionSpecifier::MidTopWithMarginOn;
2258        let skill_buttons = &[
2259            // Top Left skills
2260            //        5 1 6
2261            //        3 0 4
2262            //        8 2 7
2263            SkillIcon::Descriptive {
2264                title: "hud-skill-bow_charged_title",
2265                desc: "hud-skill-bow_charged",
2266                image: self.imgs.bow_m1,
2267                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2268                id: state.ids.skill_bow_charged_0,
2269            },
2270            SkillIcon::Unlockable {
2271                skill: Skill::Bow(CDamage),
2272                image: self.imgs.physical_damage_skill,
2273                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2274                id: state.ids.skill_bow_charged_1,
2275            },
2276            SkillIcon::Unlockable {
2277                skill: Skill::Bow(CRegen),
2278                image: self.imgs.physical_energy_regen_skill,
2279                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2280                id: state.ids.skill_bow_charged_2,
2281            },
2282            SkillIcon::Unlockable {
2283                skill: Skill::Bow(CKnockback),
2284                image: self.imgs.physical_knockback_skill,
2285                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2286                id: state.ids.skill_bow_charged_3,
2287            },
2288            SkillIcon::Unlockable {
2289                skill: Skill::Bow(CSpeed),
2290                image: self.imgs.physical_speed_skill,
2291                position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
2292                id: state.ids.skill_bow_charged_4,
2293            },
2294            SkillIcon::Unlockable {
2295                skill: Skill::Bow(CMove),
2296                image: self.imgs.physical_speed_skill,
2297                position: MidTopWithMarginOn(state.ids.skills_top_l[5], 3.0),
2298                id: state.ids.skill_bow_charged_5,
2299            },
2300            // Top right skills
2301            SkillIcon::Descriptive {
2302                title: "hud-skill-bow_repeater_title",
2303                desc: "hud-skill-bow_repeater",
2304                image: self.imgs.bow_m2,
2305                position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2306                id: state.ids.skill_bow_repeater_0,
2307            },
2308            SkillIcon::Unlockable {
2309                skill: Skill::Bow(RDamage),
2310                image: self.imgs.physical_damage_skill,
2311                position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2312                id: state.ids.skill_bow_repeater_1,
2313            },
2314            SkillIcon::Unlockable {
2315                skill: Skill::Bow(RCost),
2316                image: self.imgs.physical_cost_skill,
2317                position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2318                id: state.ids.skill_bow_repeater_2,
2319            },
2320            SkillIcon::Unlockable {
2321                skill: Skill::Bow(RSpeed),
2322                image: self.imgs.physical_speed_skill,
2323                position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2324                id: state.ids.skill_bow_repeater_3,
2325            },
2326            // Bottom left skills
2327            SkillIcon::Unlockable {
2328                skill: Skill::Bow(UnlockShotgun),
2329                image: self.imgs.skill_bow_jump_burst,
2330                position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2331                id: state.ids.skill_bow_shotgun_0,
2332            },
2333            SkillIcon::Unlockable {
2334                skill: Skill::Bow(SDamage),
2335                image: self.imgs.physical_damage_skill,
2336                position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2337                id: state.ids.skill_bow_shotgun_1,
2338            },
2339            SkillIcon::Unlockable {
2340                skill: Skill::Bow(SCost),
2341                image: self.imgs.physical_cost_skill,
2342                position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2343                id: state.ids.skill_bow_shotgun_2,
2344            },
2345            SkillIcon::Unlockable {
2346                skill: Skill::Bow(SArrows),
2347                image: self.imgs.physical_amount_skill,
2348                position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2349                id: state.ids.skill_bow_shotgun_3,
2350            },
2351            SkillIcon::Unlockable {
2352                skill: Skill::Bow(SSpread),
2353                image: self.imgs.physical_explosion_skill,
2354                position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2355                id: state.ids.skill_bow_shotgun_4,
2356            },
2357            // Bottom right skills
2358            SkillIcon::Unlockable {
2359                skill: Skill::Bow(ProjSpeed),
2360                image: self.imgs.physical_projectile_speed_skill,
2361                position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0),
2362                id: state.ids.skill_bow_passive_0,
2363            },
2364        ];
2365
2366        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2367        events
2368    }
2369
2370    fn handle_staff_skills_window(
2371        &mut self,
2372        diary_tooltip: &Tooltip,
2373        state: &mut State<DiaryState>,
2374        ui: &mut UiCell,
2375        mut events: Vec<Event>,
2376    ) -> Vec<Event> {
2377        // Title text
2378        let tree_title = &self.localized_strings.get_msg("common-weapons-staff");
2379
2380        Text::new(tree_title)
2381            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2382            .font_id(self.fonts.cyri.conrod_id)
2383            .font_size(self.fonts.cyri.scale(34))
2384            .color(TEXT_COLOR)
2385            .set(state.ids.tree_title_txt, ui);
2386
2387        // Number of skills per rectangle per weapon, start counting at 0
2388        // Maximum of 9 skills/8 indices
2389        let skills_top_l = 4;
2390        let skills_top_r = 5;
2391        let skills_bot_l = 5;
2392        let skills_bot_r = 0;
2393
2394        self.setup_state_for_skill_icons(
2395            state,
2396            ui,
2397            skills_top_l,
2398            skills_top_r,
2399            skills_bot_l,
2400            skills_bot_r,
2401        );
2402
2403        // Skill icons and buttons
2404        use skills::StaffSkill::*;
2405        // Staff
2406        Image::new(animate_by_pulse(
2407            &self
2408                .item_imgs
2409                .img_ids_or_not_found_img(ItemKey::Simple("example_staff_fire".to_string())),
2410            self.pulse,
2411        ))
2412        .wh(ART_SIZE)
2413        .middle_of(state.ids.content_align)
2414        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2415        .set(state.ids.staff_render, ui);
2416
2417        use PositionSpecifier::MidTopWithMarginOn;
2418        let skill_buttons = &[
2419            // Top Left skills
2420            //        5 1 6
2421            //        3 0 4
2422            //        8 2 7
2423            SkillIcon::Descriptive {
2424                title: "hud-skill-st_fireball_title",
2425                desc: "hud-skill-st_fireball",
2426                image: self.imgs.fireball,
2427                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2428                id: state.ids.skill_staff_basic_0,
2429            },
2430            SkillIcon::Unlockable {
2431                skill: Skill::Staff(BDamage),
2432                image: self.imgs.magic_damage_skill,
2433                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2434                id: state.ids.skill_staff_basic_1,
2435            },
2436            SkillIcon::Unlockable {
2437                skill: Skill::Staff(BRegen),
2438                image: self.imgs.magic_energy_regen_skill,
2439                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2440                id: state.ids.skill_staff_basic_2,
2441            },
2442            SkillIcon::Unlockable {
2443                skill: Skill::Staff(BRadius),
2444                image: self.imgs.magic_radius_skill,
2445                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2446                id: state.ids.skill_staff_basic_3,
2447            },
2448            // Top right skills
2449            SkillIcon::Descriptive {
2450                title: "hud-skill-st_flamethrower_title",
2451                desc: "hud-skill-st_flamethrower",
2452                image: self.imgs.flamethrower,
2453                position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2454                id: state.ids.skill_staff_beam_0,
2455            },
2456            SkillIcon::Unlockable {
2457                skill: Skill::Staff(FDamage),
2458                image: self.imgs.magic_damage_skill,
2459                position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2460                id: state.ids.skill_staff_beam_1,
2461            },
2462            SkillIcon::Unlockable {
2463                skill: Skill::Staff(FDrain),
2464                image: self.imgs.magic_energy_drain_skill,
2465                position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2466                id: state.ids.skill_staff_beam_2,
2467            },
2468            SkillIcon::Unlockable {
2469                skill: Skill::Staff(FRange),
2470                image: self.imgs.magic_radius_skill,
2471                position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2472                id: state.ids.skill_staff_beam_3,
2473            },
2474            SkillIcon::Unlockable {
2475                skill: Skill::Staff(FVelocity),
2476                image: self.imgs.magic_projectile_speed_skill,
2477                position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0),
2478                id: state.ids.skill_staff_beam_4,
2479            },
2480            // Bottom left skills
2481            SkillIcon::Unlockable {
2482                skill: Skill::Staff(UnlockShockwave),
2483                image: self.imgs.fire_aoe,
2484                position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2485                id: state.ids.skill_staff_shockwave_0,
2486            },
2487            SkillIcon::Unlockable {
2488                skill: Skill::Staff(SDamage),
2489                image: self.imgs.magic_damage_skill,
2490                position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2491                id: state.ids.skill_staff_shockwave_1,
2492            },
2493            SkillIcon::Unlockable {
2494                skill: Skill::Staff(SKnockback),
2495                image: self.imgs.magic_knockback_skill,
2496                position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2497                id: state.ids.skill_staff_shockwave_2,
2498            },
2499            SkillIcon::Unlockable {
2500                skill: Skill::Staff(SCost),
2501                image: self.imgs.magic_cost_skill,
2502                position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2503                id: state.ids.skill_staff_shockwave_3,
2504            },
2505            SkillIcon::Unlockable {
2506                skill: Skill::Staff(SRange),
2507                image: self.imgs.magic_radius_skill,
2508                position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2509                id: state.ids.skill_staff_shockwave_4,
2510            },
2511        ];
2512
2513        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2514        events
2515    }
2516
2517    fn handle_mining_skills_window(
2518        &mut self,
2519        diary_tooltip: &Tooltip,
2520        state: &mut State<DiaryState>,
2521        ui: &mut UiCell,
2522        mut events: Vec<Event>,
2523    ) -> Vec<Event> {
2524        // Title text
2525        let tree_title = &self.localized_strings.get_msg("common-tool-mining");
2526
2527        Text::new(tree_title)
2528            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2529            .font_id(self.fonts.cyri.conrod_id)
2530            .font_size(self.fonts.cyri.scale(34))
2531            .color(TEXT_COLOR)
2532            .set(state.ids.tree_title_txt, ui);
2533
2534        // Number of skills per rectangle per weapon, start counting at 0
2535        // Maximum of 9 skills/8 indices
2536        let skills_top_l = 4;
2537        let skills_top_r = 0;
2538        let skills_bot_l = 0;
2539        let skills_bot_r = 0;
2540
2541        self.setup_state_for_skill_icons(
2542            state,
2543            ui,
2544            skills_top_l,
2545            skills_top_r,
2546            skills_bot_l,
2547            skills_bot_r,
2548        );
2549
2550        // Skill icons and buttons
2551        use skills::MiningSkill::*;
2552        // Mining
2553        Image::new(animate_by_pulse(
2554            &self
2555                .item_imgs
2556                .img_ids_or_not_found_img(ItemKey::Simple("example_pick".to_string())),
2557            self.pulse,
2558        ))
2559        .wh(ART_SIZE)
2560        .middle_of(state.ids.content_align)
2561        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2562        .set(state.ids.pick_render, ui);
2563
2564        use PositionSpecifier::MidTopWithMarginOn;
2565        let skill_buttons = &[
2566            // Top Left skills
2567            //        5 1 6
2568            //        3 0 4
2569            //        8 2 7
2570            SkillIcon::Descriptive {
2571                title: "hud-skill-pick_strike_title",
2572                desc: "hud-skill-pick_strike",
2573                image: self.imgs.pickaxe,
2574                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2575                id: state.ids.skill_pick_m1,
2576            },
2577            SkillIcon::Unlockable {
2578                skill: Skill::Pick(Speed),
2579                image: self.imgs.pickaxe_speed_skill,
2580                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2581                id: state.ids.skill_pick_m1_0,
2582            },
2583            SkillIcon::Unlockable {
2584                skill: Skill::Pick(OreGain),
2585                image: self.imgs.pickaxe_oregain_skill,
2586                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2587                id: state.ids.skill_pick_m1_1,
2588            },
2589            SkillIcon::Unlockable {
2590                skill: Skill::Pick(GemGain),
2591                image: self.imgs.pickaxe_gemgain_skill,
2592                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2593                id: state.ids.skill_pick_m1_2,
2594            },
2595        ];
2596
2597        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2598        events
2599    }
2600
2601    fn handle_skill_buttons(
2602        &mut self,
2603        icons: &[SkillIcon],
2604        ui: &mut UiCell,
2605        events: &mut Vec<Event>,
2606        diary_tooltip: &Tooltip,
2607        state: &mut State<DiaryState>,
2608    ) {
2609        for (i, icon) in icons.iter().enumerate() {
2610            match icon {
2611                SkillIcon::Descriptive {
2612                    title,
2613                    desc,
2614                    image,
2615                    position,
2616                    id,
2617                } => {
2618                    // TODO: shouldn't this be a `Image::new`?
2619                    Button::image(*image)
2620                        .w_h(74.0, 74.0)
2621                        .position(*position)
2622                        .with_tooltip(
2623                            self.tooltip_manager,
2624                            &self.localized_strings.get_msg(title),
2625                            &self.localized_strings.get_msg(desc),
2626                            diary_tooltip,
2627                            TEXT_COLOR,
2628                        )
2629                        .set(*id, ui);
2630                },
2631                SkillIcon::Unlockable {
2632                    skill,
2633                    image,
2634                    position,
2635                    id,
2636                } => self.create_unlock_skill_button(
2637                    *skill,
2638                    *image,
2639                    *position,
2640                    *id,
2641                    ui,
2642                    events,
2643                    diary_tooltip,
2644                ),
2645                SkillIcon::Ability {
2646                    skill,
2647                    ability_id,
2648                    position,
2649                } => self.create_unlock_ability_button(
2650                    *skill,
2651                    ability_id,
2652                    *position,
2653                    i,
2654                    ui,
2655                    events,
2656                    diary_tooltip,
2657                    state,
2658                ),
2659            }
2660        }
2661    }
2662
2663    fn setup_state_for_skill_icons(
2664        &mut self,
2665        state: &mut State<DiaryState>,
2666        ui: &mut UiCell,
2667        skills_top_l: usize,
2668        skills_top_r: usize,
2669        skills_bot_l: usize,
2670        skills_bot_r: usize,
2671    ) {
2672        // Update widget id array len
2673        state.update(|s| {
2674            s.ids
2675                .skills_top_l
2676                .resize(skills_top_l, &mut ui.widget_id_generator())
2677        });
2678        state.update(|s| {
2679            s.ids
2680                .skills_top_r
2681                .resize(skills_top_r, &mut ui.widget_id_generator())
2682        });
2683        state.update(|s| {
2684            s.ids
2685                .skills_bot_l
2686                .resize(skills_bot_l, &mut ui.widget_id_generator())
2687        });
2688        state.update(|s| {
2689            s.ids
2690                .skills_bot_r
2691                .resize(skills_bot_r, &mut ui.widget_id_generator())
2692        });
2693
2694        // Create Background Images to place skill icons on them later
2695        // Create central skill first, others around it:
2696        //
2697        //        5 1 6
2698        //        3 0 4
2699        //        8 2 7
2700        //
2701        //
2702        let offset_0 = 22.0;
2703        let offset_1 = -122.0;
2704        let offset_2 = offset_1 - -20.0;
2705
2706        let skill_pos = |idx, align, central_skill| {
2707            use PositionSpecifier::*;
2708            match idx {
2709                // Central skill
2710                0 => MiddleOf(align),
2711                // 12:00
2712                1 => UpFrom(central_skill, offset_0),
2713                // 6:00
2714                2 => DownFrom(central_skill, offset_0),
2715                // 3:00
2716                3 => LeftFrom(central_skill, offset_0),
2717                // 9:00
2718                4 => RightFrom(central_skill, offset_0),
2719                // 10:30
2720                5 => TopLeftWithMarginsOn(central_skill, offset_1, offset_2),
2721                // 1:30
2722                6 => TopRightWithMarginsOn(central_skill, offset_1, offset_2),
2723                // 4:30
2724                7 => BottomLeftWithMarginsOn(central_skill, offset_1, offset_2),
2725                // 7:30
2726                8 => BottomRightWithMarginsOn(central_skill, offset_1, offset_2),
2727                buttons => {
2728                    panic!("{} > 8 position number", buttons);
2729                },
2730            }
2731        };
2732
2733        // TOP-LEFT Skills
2734        //
2735        // TODO: Why this uses while loop on field of struct and not just
2736        // `for i in 0..skils_top_l`?
2737        while self.created_btns_top_l < skills_top_l {
2738            let pos = skill_pos(
2739                self.created_btns_top_l,
2740                state.ids.skills_top_l_align,
2741                state.ids.skills_top_l[0],
2742            );
2743            Button::image(self.imgs.wpn_icon_border_skills)
2744                .w_h(80.0, 100.0)
2745                .position(pos)
2746                .set(state.ids.skills_top_l[self.created_btns_top_l], ui);
2747            self.created_btns_top_l += 1;
2748        }
2749        // TOP-RIGHT Skills
2750        while self.created_btns_top_r < skills_top_r {
2751            let pos = skill_pos(
2752                self.created_btns_top_r,
2753                state.ids.skills_top_r_align,
2754                state.ids.skills_top_r[0],
2755            );
2756            Button::image(self.imgs.wpn_icon_border_skills)
2757                .w_h(80.0, 100.0)
2758                .position(pos)
2759                .set(state.ids.skills_top_r[self.created_btns_top_r], ui);
2760            self.created_btns_top_r += 1;
2761        }
2762        // BOTTOM-LEFT Skills
2763        while self.created_btns_bot_l < skills_bot_l {
2764            let pos = skill_pos(
2765                self.created_btns_bot_l,
2766                state.ids.skills_bot_l_align,
2767                state.ids.skills_bot_l[0],
2768            );
2769            Button::image(self.imgs.wpn_icon_border_skills)
2770                .w_h(80.0, 100.0)
2771                .position(pos)
2772                .set(state.ids.skills_bot_l[self.created_btns_bot_l], ui);
2773            self.created_btns_bot_l += 1;
2774        }
2775        // BOTTOM-RIGHT Skills
2776        while self.created_btns_bot_r < skills_bot_r {
2777            let pos = skill_pos(
2778                self.created_btns_bot_r,
2779                state.ids.skills_bot_r_align,
2780                state.ids.skills_bot_r[0],
2781            );
2782            Button::image(self.imgs.wpn_icon_border_skills)
2783                .w_h(80.0, 100.0)
2784                .position(pos)
2785                .set(state.ids.skills_bot_r[self.created_btns_bot_r], ui);
2786            self.created_btns_bot_r += 1;
2787        }
2788    }
2789
2790    fn create_unlock_skill_button(
2791        &mut self,
2792        skill: Skill,
2793        skill_image: image::Id,
2794        position: PositionSpecifier,
2795        widget_id: widget::Id,
2796        ui: &mut UiCell,
2797        events: &mut Vec<Event>,
2798        diary_tooltip: &Tooltip,
2799    ) {
2800        let label = if self.skill_set.prerequisites_met(skill) {
2801            let current = self.skill_set.skill_level(skill).unwrap_or(0);
2802            let max = skill.max_level();
2803            format!("{}/{}", current, max)
2804        } else {
2805            "".to_owned()
2806        };
2807
2808        let label_color = if self.skill_set.is_at_max_level(skill) {
2809            TEXT_COLOR
2810        } else if self.skill_set.sufficient_skill_points(skill) {
2811            HP_COLOR
2812        } else {
2813            CRITICAL_HP_COLOR
2814        };
2815
2816        let image_color = if self.skill_set.prerequisites_met(skill) {
2817            TEXT_COLOR
2818        } else {
2819            Color::Rgba(0.41, 0.41, 0.41, 0.7)
2820        };
2821
2822        let skill_strings = skill_strings(skill);
2823        let (title, description) =
2824            skill_strings.localize(self.localized_strings, self.skill_set, skill);
2825
2826        let button = Button::image(skill_image)
2827            .w_h(74.0, 74.0)
2828            .position(position)
2829            .label(&label)
2830            .label_y(conrod_core::position::Relative::Scalar(-47.0))
2831            .label_x(conrod_core::position::Relative::Scalar(0.0))
2832            .label_color(label_color)
2833            .label_font_size(self.fonts.cyri.scale(15))
2834            .label_font_id(self.fonts.cyri.conrod_id)
2835            .image_color(image_color)
2836            .with_tooltip(
2837                self.tooltip_manager,
2838                &title,
2839                &description,
2840                diary_tooltip,
2841                TEXT_COLOR,
2842            )
2843            .set(widget_id, ui);
2844
2845        if button.was_clicked() {
2846            events.push(Event::UnlockSkill(skill));
2847        };
2848    }
2849
2850    fn create_unlock_ability_button(
2851        &mut self,
2852        skill: Skill,
2853        ability_id: &str,
2854        position: PositionSpecifier,
2855        widget_index: usize,
2856        ui: &mut UiCell,
2857        events: &mut Vec<Event>,
2858        diary_tooltip: &Tooltip,
2859        state: &mut State<DiaryState>,
2860    ) {
2861        let locked = !self.skill_set.prerequisites_met(skill);
2862        let owned = self.skill_set.has_skill(skill);
2863        let image_color = if owned {
2864            TEXT_COLOR
2865        } else {
2866            Color::Rgba(0.41, 0.41, 0.41, 0.7)
2867        };
2868
2869        let (title, description) = util::ability_description(ability_id, self.localized_strings);
2870
2871        let sp_cost = sp(self.localized_strings, self.skill_set, skill);
2872
2873        let description = format!("{description}\n{sp_cost}");
2874
2875        let button = Button::image(util::ability_image(self.imgs, ability_id))
2876            .w_h(76.0, 76.0)
2877            .position(position)
2878            .image_color(image_color)
2879            .with_tooltip(
2880                self.tooltip_manager,
2881                &title,
2882                &description,
2883                diary_tooltip,
2884                TEXT_COLOR,
2885            )
2886            .set(state.ids.skills[widget_index], ui);
2887
2888        // Lock Image
2889        if locked {
2890            Image::new(self.imgs.lock)
2891                .w_h(76.0, 76.0)
2892                .middle_of(state.ids.skills[widget_index])
2893                .graphics_for(state.ids.skills[widget_index])
2894                .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8)))
2895                .set(state.ids.skill_lock_imgs[widget_index], ui);
2896        }
2897
2898        if button.was_clicked() {
2899            events.push(Event::UnlockSkill(skill));
2900        };
2901    }
2902}
2903
2904/// Returns skill info as a tuple of title and description.
2905///
2906/// If you want to get localized version, use `SkillStrings::localize` method
2907fn skill_strings(skill: Skill) -> SkillStrings<'static> {
2908    match skill {
2909        // general tree
2910        Skill::UnlockGroup(s) => unlock_skill_strings(s),
2911        // weapon trees
2912        Skill::Bow(s) => bow_skill_strings(s),
2913        Skill::Staff(s) => staff_skill_strings(s),
2914        Skill::Sceptre(s) => sceptre_skill_strings(s),
2915        // movement trees
2916        Skill::Climb(s) => climb_skill_strings(s),
2917        Skill::Swim(s) => swim_skill_strings(s),
2918        // mining
2919        Skill::Pick(s) => mining_skill_strings(s),
2920        _ => SkillStrings::plain("", ""),
2921    }
2922}
2923
2924fn unlock_skill_strings(group: SkillGroupKind) -> SkillStrings<'static> {
2925    match group {
2926        SkillGroupKind::Weapon(ToolKind::Sword) => {
2927            SkillStrings::plain("hud-skill-unlck_sword_title", "hud-skill-unlck_sword")
2928        },
2929        SkillGroupKind::Weapon(ToolKind::Axe) => {
2930            SkillStrings::plain("hud-skill-unlck_axe_title", "hud-skill-unlck_axe")
2931        },
2932        SkillGroupKind::Weapon(ToolKind::Hammer) => {
2933            SkillStrings::plain("hud-skill-unlck_hammer_title", "hud-skill-unlck_hammer")
2934        },
2935        SkillGroupKind::Weapon(ToolKind::Bow) => {
2936            SkillStrings::plain("hud-skill-unlck_bow_title", "hud-skill-unlck_bow")
2937        },
2938        SkillGroupKind::Weapon(ToolKind::Staff) => {
2939            SkillStrings::plain("hud-skill-unlck_staff_title", "hud-skill-unlck_staff")
2940        },
2941        SkillGroupKind::Weapon(ToolKind::Sceptre) => {
2942            SkillStrings::plain("hud-skill-unlck_sceptre_title", "hud-skill-unlck_sceptre")
2943        },
2944        SkillGroupKind::General
2945        | SkillGroupKind::Weapon(
2946            ToolKind::Dagger
2947            | ToolKind::Shield
2948            | ToolKind::Spear
2949            | ToolKind::Blowgun
2950            | ToolKind::Debug
2951            | ToolKind::Farming
2952            | ToolKind::Instrument
2953            | ToolKind::Throwable
2954            | ToolKind::Pick
2955            | ToolKind::Shovel
2956            | ToolKind::Natural
2957            | ToolKind::Empty,
2958        ) => {
2959            tracing::warn!("Requesting title for unlocking unexpected skill group");
2960            SkillStrings::Empty
2961        },
2962    }
2963}
2964
2965fn bow_skill_strings(skill: BowSkill) -> SkillStrings<'static> {
2966    let modifiers = SKILL_MODIFIERS.bow_tree;
2967    match skill {
2968        // Passives
2969        BowSkill::ProjSpeed => SkillStrings::with_mult(
2970            "hud-skill-bow_projectile_speed_title",
2971            "hud-skill-bow_projectile_speed",
2972            modifiers.universal.projectile_speed,
2973        ),
2974        // Charged upgrades
2975        BowSkill::CDamage => SkillStrings::with_mult(
2976            "hud-skill-bow_charged_damage_title",
2977            "hud-skill-bow_charged_damage",
2978            modifiers.charged.damage_scaling,
2979        ),
2980        BowSkill::CRegen => SkillStrings::with_mult(
2981            "hud-skill-bow_charged_energy_regen_title",
2982            "hud-skill-bow_charged_energy_regen",
2983            modifiers.charged.regen_scaling,
2984        ),
2985        BowSkill::CKnockback => SkillStrings::with_mult(
2986            "hud-skill-bow_charged_knockback_title",
2987            "hud-skill-bow_charged_knockback",
2988            modifiers.charged.knockback_scaling,
2989        ),
2990        BowSkill::CSpeed => SkillStrings::with_mult(
2991            "hud-skill-bow_charged_speed_title",
2992            "hud-skill-bow_charged_speed",
2993            modifiers.charged.charge_rate,
2994        ),
2995        BowSkill::CMove => SkillStrings::with_mult(
2996            "hud-skill-bow_charged_move_title",
2997            "hud-skill-bow_charged_move",
2998            modifiers.charged.move_speed,
2999        ),
3000        // Repeater upgrades
3001        BowSkill::RDamage => SkillStrings::with_mult(
3002            "hud-skill-bow_repeater_damage_title",
3003            "hud-skill-bow_repeater_damage",
3004            modifiers.repeater.power,
3005        ),
3006        BowSkill::RCost => SkillStrings::with_mult(
3007            "hud-skill-bow_repeater_cost_title",
3008            "hud-skill-bow_repeater_cost",
3009            modifiers.repeater.energy_cost,
3010        ),
3011        BowSkill::RSpeed => SkillStrings::with_mult(
3012            "hud-skill-bow_repeater_speed_title",
3013            "hud-skill-bow_repeater_speed",
3014            modifiers.repeater.max_speed,
3015        ),
3016        // Shotgun upgrades
3017        BowSkill::UnlockShotgun => SkillStrings::plain(
3018            "hud-skill-bow_shotgun_unlock_title",
3019            "hud-skill-bow_shotgun_unlock",
3020        ),
3021        BowSkill::SDamage => SkillStrings::with_mult(
3022            "hud-skill-bow_shotgun_damage_title",
3023            "hud-skill-bow_shotgun_damage",
3024            modifiers.shotgun.power,
3025        ),
3026        BowSkill::SCost => SkillStrings::with_mult(
3027            "hud-skill-bow_shotgun_cost_title",
3028            "hud-skill-bow_shotgun_cost",
3029            modifiers.shotgun.energy_cost,
3030        ),
3031        BowSkill::SArrows => SkillStrings::with_const(
3032            "hud-skill-bow_shotgun_arrow_count_title",
3033            "hud-skill-bow_shotgun_arrow_count",
3034            modifiers.shotgun.num_projectiles,
3035        ),
3036        BowSkill::SSpread => SkillStrings::with_mult(
3037            "hud-skill-bow_shotgun_spread_title",
3038            "hud-skill-bow_shotgun_spread",
3039            modifiers.shotgun.spread,
3040        ),
3041    }
3042}
3043
3044fn staff_skill_strings(skill: StaffSkill) -> SkillStrings<'static> {
3045    let modifiers = SKILL_MODIFIERS.staff_tree;
3046    match skill {
3047        // Basic ranged upgrades
3048        StaffSkill::BDamage => SkillStrings::with_mult(
3049            "hud-skill-st_damage_title",
3050            "hud-skill-st_damage",
3051            modifiers.fireball.power,
3052        ),
3053        StaffSkill::BRegen => SkillStrings::with_mult(
3054            "hud-skill-st_energy_regen_title",
3055            "hud-skill-st_energy_regen",
3056            modifiers.fireball.regen,
3057        ),
3058        StaffSkill::BRadius => SkillStrings::with_mult(
3059            "hud-skill-st_explosion_radius_title",
3060            "hud-skill-st_explosion_radius",
3061            modifiers.fireball.range,
3062        ),
3063        // Flamethrower upgrades
3064        StaffSkill::FDamage => SkillStrings::with_mult(
3065            "hud-skill-st_flamethrower_damage_title",
3066            "hud-skill-st_flamethrower_damage",
3067            modifiers.flamethrower.damage,
3068        ),
3069        StaffSkill::FRange => SkillStrings::with_mult(
3070            "hud-skill-st_flamethrower_range_title",
3071            "hud-skill-st_flamethrower_range",
3072            modifiers.flamethrower.range,
3073        ),
3074        StaffSkill::FDrain => SkillStrings::with_mult(
3075            "hud-skill-st_energy_drain_title",
3076            "hud-skill-st_energy_drain",
3077            modifiers.flamethrower.energy_drain,
3078        ),
3079        StaffSkill::FVelocity => SkillStrings::with_mult(
3080            "hud-skill-st_flame_velocity_title",
3081            "hud-skill-st_flame_velocity",
3082            modifiers.flamethrower.velocity,
3083        ),
3084        // Shockwave upgrades
3085        StaffSkill::UnlockShockwave => SkillStrings::plain(
3086            "hud-skill-st_shockwave_unlock_title",
3087            "hud-skill-st_shockwave_unlock",
3088        ),
3089        StaffSkill::SDamage => SkillStrings::with_mult(
3090            "hud-skill-st_shockwave_damage_title",
3091            "hud-skill-st_shockwave_damage",
3092            modifiers.shockwave.damage,
3093        ),
3094        StaffSkill::SKnockback => SkillStrings::with_mult(
3095            "hud-skill-st_shockwave_knockback_title",
3096            "hud-skill-st_shockwave_knockback",
3097            modifiers.shockwave.knockback,
3098        ),
3099        StaffSkill::SRange => SkillStrings::with_mult(
3100            "hud-skill-st_shockwave_range_title",
3101            "hud-skill-st_shockwave_range",
3102            modifiers.shockwave.duration,
3103        ),
3104        StaffSkill::SCost => SkillStrings::with_mult(
3105            "hud-skill-st_shockwave_cost_title",
3106            "hud-skill-st_shockwave_cost",
3107            modifiers.shockwave.energy_cost,
3108        ),
3109    }
3110}
3111
3112fn sceptre_skill_strings(skill: SceptreSkill) -> SkillStrings<'static> {
3113    let modifiers = SKILL_MODIFIERS.sceptre_tree;
3114    match skill {
3115        // Lifesteal beam upgrades
3116        SceptreSkill::LDamage => SkillStrings::with_mult(
3117            "hud-skill-sc_lifesteal_damage_title",
3118            "hud-skill-sc_lifesteal_damage",
3119            modifiers.beam.damage,
3120        ),
3121        SceptreSkill::LRange => SkillStrings::with_mult(
3122            "hud-skill-sc_lifesteal_range_title",
3123            "hud-skill-sc_lifesteal_range",
3124            modifiers.beam.range,
3125        ),
3126        SceptreSkill::LLifesteal => SkillStrings::with_mult(
3127            "hud-skill-sc_lifesteal_lifesteal_title",
3128            "hud-skill-sc_lifesteal_lifesteal",
3129            modifiers.beam.lifesteal,
3130        ),
3131        SceptreSkill::LRegen => SkillStrings::with_mult(
3132            "hud-skill-sc_lifesteal_regen_title",
3133            "hud-skill-sc_lifesteal_regen",
3134            modifiers.beam.energy_regen,
3135        ),
3136        // Healing aura upgrades
3137        SceptreSkill::HHeal => SkillStrings::with_mult(
3138            "hud-skill-sc_heal_heal_title",
3139            "hud-skill-sc_heal_heal",
3140            modifiers.healing_aura.strength,
3141        ),
3142        SceptreSkill::HRange => SkillStrings::with_mult(
3143            "hud-skill-sc_heal_range_title",
3144            "hud-skill-sc_heal_range",
3145            modifiers.healing_aura.range,
3146        ),
3147        SceptreSkill::HDuration => SkillStrings::with_mult(
3148            "hud-skill-sc_heal_duration_title",
3149            "hud-skill-sc_heal_duration",
3150            modifiers.healing_aura.duration,
3151        ),
3152        SceptreSkill::HCost => SkillStrings::with_mult(
3153            "hud-skill-sc_heal_cost_title",
3154            "hud-skill-sc_heal_cost",
3155            modifiers.healing_aura.energy_cost,
3156        ),
3157        // Warding aura upgrades
3158        SceptreSkill::UnlockAura => SkillStrings::plain(
3159            "hud-skill-sc_wardaura_unlock_title",
3160            "hud-skill-sc_wardaura_unlock",
3161        ),
3162        SceptreSkill::AStrength => SkillStrings::with_mult(
3163            "hud-skill-sc_wardaura_strength_title",
3164            "hud-skill-sc_wardaura_strength",
3165            modifiers.warding_aura.strength,
3166        ),
3167        SceptreSkill::ADuration => SkillStrings::with_mult(
3168            "hud-skill-sc_wardaura_duration_title",
3169            "hud-skill-sc_wardaura_duration",
3170            modifiers.warding_aura.duration,
3171        ),
3172        SceptreSkill::ARange => SkillStrings::with_mult(
3173            "hud-skill-sc_wardaura_range_title",
3174            "hud-skill-sc_wardaura_range",
3175            modifiers.warding_aura.range,
3176        ),
3177        SceptreSkill::ACost => SkillStrings::with_mult(
3178            "hud-skill-sc_wardaura_cost_title",
3179            "hud-skill-sc_wardaura_cost",
3180            modifiers.warding_aura.energy_cost,
3181        ),
3182    }
3183}
3184
3185fn climb_skill_strings(skill: ClimbSkill) -> SkillStrings<'static> {
3186    let modifiers = SKILL_MODIFIERS.general_tree.climb;
3187    match skill {
3188        ClimbSkill::Cost => SkillStrings::with_mult(
3189            "hud-skill-climbing_cost_title",
3190            "hud-skill-climbing_cost",
3191            modifiers.energy_cost,
3192        ),
3193        ClimbSkill::Speed => SkillStrings::with_mult(
3194            "hud-skill-climbing_speed_title",
3195            "hud-skill-climbing_speed",
3196            modifiers.speed,
3197        ),
3198    }
3199}
3200
3201fn swim_skill_strings(skill: SwimSkill) -> SkillStrings<'static> {
3202    let modifiers = SKILL_MODIFIERS.general_tree.swim;
3203    match skill {
3204        SwimSkill::Speed => SkillStrings::with_mult(
3205            "hud-skill-swim_speed_title",
3206            "hud-skill-swim_speed",
3207            modifiers.speed,
3208        ),
3209    }
3210}
3211
3212fn mining_skill_strings(skill: MiningSkill) -> SkillStrings<'static> {
3213    let modifiers = SKILL_MODIFIERS.mining_tree;
3214    match skill {
3215        MiningSkill::Speed => SkillStrings::with_mult(
3216            "hud-skill-pick_strike_speed_title",
3217            "hud-skill-pick_strike_speed",
3218            modifiers.speed,
3219        ),
3220        MiningSkill::OreGain => SkillStrings::with_const(
3221            "hud-skill-pick_strike_oregain_title",
3222            "hud-skill-pick_strike_oregain",
3223            (modifiers.ore_gain * 100.0).round() as u32,
3224        ),
3225        MiningSkill::GemGain => SkillStrings::with_const(
3226            "hud-skill-pick_strike_gemgain_title",
3227            "hud-skill-pick_strike_gemgain",
3228            (modifiers.gem_gain * 100.0).round() as u32,
3229        ),
3230    }
3231}
3232
3233/// Helper object used returned by `skill_strings` as source for
3234/// later internationalization and formatting.
3235enum SkillStrings<'a> {
3236    Plain {
3237        title: &'a str,
3238        desc: &'a str,
3239    },
3240    WithConst {
3241        title: &'a str,
3242        desc: &'a str,
3243        constant: u32,
3244    },
3245    WithMult {
3246        title: &'a str,
3247        desc: &'a str,
3248        multiplier: f32,
3249    },
3250    Empty,
3251}
3252
3253impl<'a> SkillStrings<'a> {
3254    fn plain(title: &'a str, desc: &'a str) -> Self { Self::Plain { title, desc } }
3255
3256    fn with_const(title: &'a str, desc: &'a str, constant: u32) -> Self {
3257        Self::WithConst {
3258            title,
3259            desc,
3260            constant,
3261        }
3262    }
3263
3264    fn with_mult(title: &'a str, desc: &'a str, multiplier: f32) -> Self {
3265        Self::WithMult {
3266            title,
3267            desc,
3268            multiplier,
3269        }
3270    }
3271
3272    fn localize<'loc>(
3273        &self,
3274        i18n: &'loc Localization,
3275        skill_set: &SkillSet,
3276        skill: Skill,
3277    ) -> (Cow<'loc, str>, Cow<'loc, str>) {
3278        match self {
3279            Self::Plain { title, desc } => {
3280                let title = i18n.get_msg(title);
3281
3282                let args = i18n::fluent_args! {
3283                    "SP" => sp(i18n, skill_set, skill),
3284                };
3285                let desc = i18n.get_msg_ctx(desc, &args);
3286
3287                (title, desc)
3288            },
3289            Self::WithConst {
3290                title,
3291                desc,
3292                constant,
3293            } => {
3294                let title = i18n.get_msg(title);
3295                let args = i18n::fluent_args! {
3296                    "boost" => constant,
3297                    "SP" => sp(i18n, skill_set, skill),
3298                };
3299                let desc = i18n.get_msg_ctx(desc, &args);
3300
3301                (title, desc)
3302            },
3303            Self::WithMult {
3304                title,
3305                desc,
3306                multiplier,
3307            } => {
3308                let percentage = hud::multiplier_to_percentage(*multiplier).abs();
3309
3310                let title = i18n.get_msg(title);
3311
3312                let args = i18n::fluent_args! {
3313                    "boost" => format!("{percentage:.0}"),
3314                    "SP" => sp(i18n, skill_set, skill),
3315                };
3316                let desc = i18n.get_msg_ctx(desc, &args);
3317
3318                (title, desc)
3319            },
3320            Self::Empty => (Cow::Borrowed(""), Cow::Borrowed("")),
3321        }
3322    }
3323}
3324
3325/// The number of variants of the [`CharacterStat`] enum.
3326const STAT_COUNT: usize = 15;
3327
3328#[derive(EnumIter)]
3329enum CharacterStat {
3330    Name,
3331    BattleMode,
3332    Waypoint,
3333    Hitpoints,
3334    Energy,
3335    Poise,
3336    CombatRating,
3337    Protection,
3338    StunResistance,
3339    PrecisionPower,
3340    EnergyReward,
3341    Stealth,
3342    WeaponPower,
3343    WeaponSpeed,
3344    WeaponEffectPower,
3345}
3346
3347impl CharacterStat {
3348    fn localized_str<'a>(&self, i18n: &'a Localization) -> Cow<'a, str> {
3349        use CharacterStat::*;
3350
3351        match self {
3352            Name => i18n.get_msg("character_window-character_name"),
3353            BattleMode => i18n.get_msg("hud-battle-mode"),
3354            Waypoint => i18n.get_msg("hud-waypoint"),
3355            Hitpoints => i18n.get_msg("hud-bag-health"),
3356            Energy => i18n.get_msg("hud-bag-energy"),
3357            CombatRating => i18n.get_msg("hud-bag-combat_rating"),
3358            Protection => i18n.get_msg("hud-bag-protection"),
3359            StunResistance => i18n.get_msg("hud-bag-stun_res"),
3360            Poise => i18n.get_msg("common-stats-poise_res"),
3361            PrecisionPower => i18n.get_msg("common-stats-precision_power"),
3362            EnergyReward => i18n.get_msg("common-stats-energy_reward"),
3363            Stealth => i18n.get_msg("common-stats-stealth"),
3364            WeaponPower => i18n.get_msg("common-stats-power"),
3365            WeaponSpeed => i18n.get_msg("common-stats-speed"),
3366            WeaponEffectPower => i18n.get_msg("common-stats-effect-power"),
3367        }
3368    }
3369}
3370
3371fn sp<'loc>(i18n: &'loc Localization, skill_set: &SkillSet, skill: Skill) -> Cow<'loc, str> {
3372    let current_level = skill_set.skill_level(skill);
3373    if matches!(current_level, Ok(level) if level == skill.max_level()) {
3374        Cow::Borrowed("")
3375    } else {
3376        i18n.get_msg_ctx("hud-skill-req_sp", &i18n::fluent_args! {
3377            "number" => skill_set.skill_cost(skill),
3378        })
3379    }
3380}