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 && let AuxiliaryAbility::MainWeapon(slot) = ability {
1124                        let ability = AuxiliaryAbility::OffWeapon(*slot);
1125
1126                        let slot = AbilitySlot::Ability(ability);
1127                        slot_maker
1128                            .fabricate(slot, [100.0; 2])
1129                            .top_right_with_margins_on(align_state, 20.0 + image_offsets, 20.0)
1130                            .set(state.ids.abilities_dual[id_index], ui);
1131                    }
1132                    // The page width...
1133                    let text_width = 299.0 * 2.0
1134                        - if same_weap_kinds && matches!(ability, AuxiliaryAbility::MainWeapon(_)) {
1135                            // with double the width of an ability image and some padding subtracted
1136                            // if dual wielding two of the same weapon kind
1137                            (20.0 + 100.0 + 10.0) * 2.0
1138                        } else {
1139                            // or the width of an ability image and some padding subtracted
1140                            // otherwise
1141                            20.0 * 2.0 + 100.0
1142                        };
1143                    Text::new(&ability_title)
1144                        .top_left_with_margins_on(state.ids.abilities[id_index], 5.0, 110.0)
1145                        .font_id(self.fonts.cyri.conrod_id)
1146                        .font_size(self.fonts.cyri.scale(28))
1147                        .color(TEXT_COLOR)
1148                        .w(text_width)
1149                        .graphics_for(state.ids.abilities[id_index])
1150                        .set(state.ids.ability_titles[id_index], ui);
1151                    Text::new(&ability_desc)
1152                        .top_left_with_margins_on(state.ids.abilities[id_index], 40.0, 110.0)
1153                        .font_id(self.fonts.cyri.conrod_id)
1154                        .font_size(self.fonts.cyri.scale(13))
1155                        .color(TEXT_COLOR)
1156                        .w(text_width)
1157                        .graphics_for(state.ids.abilities[id_index])
1158                        .set(state.ids.ability_descs[id_index], ui);
1159                }
1160
1161                events
1162            },
1163            DiarySection::Character => {
1164                // Background Art
1165                Image::new(self.imgs.book_bg)
1166                    .w_h(299.0 * 4.0, 184.0 * 4.0)
1167                    .mid_top_with_margin_on(state.ids.content_align, 4.0)
1168                    .set(state.ids.spellbook_art, ui);
1169
1170                if state.ids.stat_names.len() < STAT_COUNT {
1171                    state.update(|s| {
1172                        s.ids
1173                            .stat_names
1174                            .resize(STAT_COUNT, &mut ui.widget_id_generator());
1175                        s.ids
1176                            .stat_values
1177                            .resize(STAT_COUNT, &mut ui.widget_id_generator());
1178                    });
1179                }
1180
1181                for (i, stat) in CharacterStat::iter().enumerate() {
1182                    // Stat names
1183                    let localized_name = stat.localized_str(self.localized_strings);
1184                    let mut txt = Text::new(&localized_name)
1185                        .font_id(self.fonts.cyri.conrod_id)
1186                        .font_size(self.fonts.cyri.scale(29))
1187                        .color(BLACK);
1188
1189                    if i == 0 {
1190                        txt = txt.top_left_with_margins_on(state.ids.spellbook_art, 20.0, 20.0);
1191                    } else {
1192                        txt = txt.down_from(state.ids.stat_names[i - 1], 10.0);
1193                    };
1194                    txt.set(state.ids.stat_names[i], ui);
1195
1196                    let main_weap_stats = self
1197                        .inventory
1198                        .equipped(EquipSlot::ActiveMainhand)
1199                        .and_then(|item| match &*item.kind() {
1200                            ItemKind::Tool(tool) => {
1201                                Some(tool.stats(item.stats_durability_multiplier()))
1202                            },
1203                            _ => None,
1204                        });
1205
1206                    let off_weap_stats = self
1207                        .inventory
1208                        .equipped(EquipSlot::ActiveOffhand)
1209                        .and_then(|item| match &*item.kind() {
1210                            ItemKind::Tool(tool) => {
1211                                Some(tool.stats(item.stats_durability_multiplier()))
1212                            },
1213                            _ => None,
1214                        });
1215
1216                    let (name, _gender, battle_mode) = self
1217                        .client
1218                        .player_list()
1219                        .get(self.uid)
1220                        .and_then(|info| info.character.as_ref())
1221                        .map_or_else(
1222                            || ("Unknown".to_string(), None, BattleMode::PvP),
1223                            |character_info| {
1224                                (
1225                                    self.localized_strings.get_content(&character_info.name),
1226                                    character_info.gender,
1227                                    character_info.battle_mode,
1228                                )
1229                            },
1230                        );
1231
1232                    // Stat values
1233                    let value = match stat {
1234                        CharacterStat::Name => name,
1235                        CharacterStat::BattleMode => match battle_mode {
1236                            BattleMode::PvP => "PvP".to_string(),
1237                            BattleMode::PvE => "PvE".to_string(),
1238                        },
1239                        CharacterStat::Waypoint => self
1240                            .client
1241                            .waypoint()
1242                            .as_ref()
1243                            .cloned()
1244                            .unwrap_or_else(|| "Unknown".to_string()),
1245                        CharacterStat::Hitpoints => format!("{}", self.health.base_max() as u32),
1246                        CharacterStat::Energy => format!("{}", self.energy.base_max() as u32),
1247                        CharacterStat::Poise => format!("{}", self.poise.base_max() as u32),
1248                        CharacterStat::CombatRating => {
1249                            let cr = combat::combat_rating(
1250                                self.inventory,
1251                                self.health,
1252                                self.energy,
1253                                self.poise,
1254                                self.skill_set,
1255                                *self.body,
1256                                self.msm,
1257                            );
1258                            format!("{:.2}", cr * 10.0)
1259                        },
1260                        CharacterStat::Protection => {
1261                            let protection =
1262                                combat::compute_protection(Some(self.inventory), self.msm);
1263                            match protection {
1264                                Some(prot) => format!("{}", prot),
1265                                None => String::from("Invincible"),
1266                            }
1267                        },
1268                        CharacterStat::StunResistance => {
1269                            let stun_res = Poise::compute_poise_damage_reduction(
1270                                Some(self.inventory),
1271                                self.msm,
1272                                None,
1273                                self.stats,
1274                            );
1275                            format!("{:.2}%", stun_res * 100.0)
1276                        },
1277                        CharacterStat::PrecisionPower => {
1278                            let precision_power =
1279                                combat::compute_precision_mult(Some(self.inventory), self.msm);
1280                            format!("x{:.2}", precision_power)
1281                        },
1282                        CharacterStat::EnergyReward => {
1283                            let energy_rew =
1284                                combat::compute_energy_reward_mod(Some(self.inventory), self.msm);
1285                            format!("{:+.0}%", (energy_rew - 1.0) * 100.0)
1286                        },
1287                        CharacterStat::Stealth => {
1288                            let stealth_perception_multiplier =
1289                                combat::perception_dist_multiplier_from_stealth(
1290                                    Some(self.inventory),
1291                                    None,
1292                                    self.msm,
1293                                );
1294                            let txt =
1295                                format!("{:+.1}%", (1.0 - stealth_perception_multiplier) * 100.0);
1296
1297                            txt
1298                        },
1299                        CharacterStat::WeaponPower => match (main_weap_stats, off_weap_stats) {
1300                            (Some(m_stats), Some(o_stats)) => {
1301                                format!("{}   {}", m_stats.power * 10.0, o_stats.power * 10.0)
1302                            },
1303                            (Some(stats), None) | (None, Some(stats)) => {
1304                                format!("{}", stats.power * 10.0)
1305                            },
1306                            (None, None) => String::new(),
1307                        },
1308                        CharacterStat::WeaponSpeed => {
1309                            let spd_fmt = |sp| (sp - 1.0) * 100.0;
1310                            match (main_weap_stats, off_weap_stats) {
1311                                (Some(m_stats), Some(o_stats)) => format!(
1312                                    "{:+.0}%   {:+.0}%",
1313                                    spd_fmt(m_stats.speed),
1314                                    spd_fmt(o_stats.speed)
1315                                ),
1316                                (Some(stats), None) | (None, Some(stats)) => {
1317                                    format!("{:+.0}%", spd_fmt(stats.speed))
1318                                },
1319                                _ => String::new(),
1320                            }
1321                        },
1322                        CharacterStat::WeaponEffectPower => match (main_weap_stats, off_weap_stats)
1323                        {
1324                            (Some(m_stats), Some(o_stats)) => {
1325                                format!(
1326                                    "{}   {}",
1327                                    m_stats.effect_power * 10.0,
1328                                    o_stats.effect_power * 10.0
1329                                )
1330                            },
1331                            (Some(stats), None) | (None, Some(stats)) => {
1332                                format!("{}", stats.effect_power * 10.0)
1333                            },
1334                            (None, None) => String::new(),
1335                        },
1336                    };
1337
1338                    let mut number = Text::new(&value)
1339                        .font_id(self.fonts.cyri.conrod_id)
1340                        .font_size(self.fonts.cyri.scale(29))
1341                        .color(BLACK);
1342
1343                    if i == 0 {
1344                        number = number.right_from(state.ids.stat_names[i], 165.0);
1345                    } else {
1346                        number = number.down_from(state.ids.stat_values[i - 1], 10.0);
1347                    };
1348                    number.set(state.ids.stat_values[i], ui);
1349                }
1350
1351                events
1352            },
1353            DiarySection::Recipes => {
1354                // Background Art
1355                Image::new(self.imgs.book_bg)
1356                    .w_h(299.0 * 4.0, 184.0 * 4.0)
1357                    .mid_top_with_margin_on(state.ids.content_align, 4.0)
1358                    .set(state.ids.spellbook_art, ui);
1359
1360                Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1361                    .top_left_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1362                    .set(state.ids.sb_page_left_align, ui);
1363                Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1364                    .top_right_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1365                    .set(state.ids.sb_page_right_align, ui);
1366
1367                const RECIPES_PER_PAGE: usize = 36;
1368
1369                let page_index_max =
1370                    self.inventory.recipe_groups_iter().len().saturating_sub(1) / RECIPES_PER_PAGE;
1371
1372                if state.recipe_page > page_index_max {
1373                    state.update(|s| s.recipe_page = 0);
1374                }
1375
1376                // Page button
1377                // Left Arrow
1378                let left_arrow = Button::image(if state.recipe_page > 0 {
1379                    self.imgs.arrow_l
1380                } else {
1381                    self.imgs.arrow_l_inactive
1382                })
1383                .bottom_left_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1384                .w_h(48.0, 55.0);
1385                // Grey out arrows when inactive
1386                if state.recipe_page > 0 {
1387                    if left_arrow
1388                        .hover_image(self.imgs.arrow_l_click)
1389                        .press_image(self.imgs.arrow_l)
1390                        .set(state.ids.ability_page_left, ui)
1391                        .was_clicked()
1392                    {
1393                        state.update(|s| s.recipe_page -= 1);
1394                    }
1395                } else {
1396                    left_arrow.set(state.ids.ability_page_left, ui);
1397                }
1398                // Right Arrow
1399                let right_arrow = Button::image(if state.recipe_page < page_index_max {
1400                    self.imgs.arrow_r
1401                } else {
1402                    self.imgs.arrow_r_inactive
1403                })
1404                .bottom_right_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1405                .w_h(48.0, 55.0);
1406                if state.recipe_page < page_index_max {
1407                    // Only show right button if not on last page
1408                    if right_arrow
1409                        .hover_image(self.imgs.arrow_r_click)
1410                        .press_image(self.imgs.arrow_r)
1411                        .set(state.ids.ability_page_right, ui)
1412                        .was_clicked()
1413                    {
1414                        state.update(|s| s.recipe_page += 1);
1415                    };
1416                } else {
1417                    right_arrow.set(state.ids.ability_page_right, ui);
1418                }
1419
1420                state.update(|s| {
1421                    s.ids
1422                        .recipe_groups
1423                        .resize(RECIPES_PER_PAGE, &mut ui.widget_id_generator())
1424                });
1425
1426                for (i, rg) in self
1427                    .inventory
1428                    .recipe_groups_iter()
1429                    .skip(state.recipe_page * RECIPES_PER_PAGE)
1430                    .take(RECIPES_PER_PAGE)
1431                    .enumerate()
1432                {
1433                    let (title, _desc) =
1434                        util::item_text(rg, self.localized_strings, self.item_i18n);
1435
1436                    let mut text = Text::new(&title)
1437                        .font_id(self.fonts.cyri.conrod_id)
1438                        .font_size(self.fonts.cyri.scale(29))
1439                        .color(BLACK);
1440
1441                    if i == 0 {
1442                        text =
1443                            text.top_left_with_margins_on(state.ids.sb_page_left_align, 20.0, 20.0);
1444                    } else if i == 18 {
1445                        text = text.top_left_with_margins_on(
1446                            state.ids.sb_page_right_align,
1447                            20.0,
1448                            20.0,
1449                        );
1450                    } else {
1451                        text = text.down_from(state.ids.recipe_groups[i - 1], 10.0);
1452                    }
1453                    text.set(state.ids.recipe_groups[i], ui);
1454                }
1455
1456                events
1457            },
1458        }
1459    }
1460}
1461
1462enum SkillIcon<'a> {
1463    Unlockable {
1464        skill: Skill,
1465        image: image::Id,
1466        position: PositionSpecifier,
1467        id: widget::Id,
1468    },
1469    Descriptive {
1470        title: &'a str,
1471        desc: &'a str,
1472        image: image::Id,
1473        position: PositionSpecifier,
1474        id: widget::Id,
1475    },
1476    Ability {
1477        skill: Skill,
1478        ability_id: &'a str,
1479        position: PositionSpecifier,
1480    },
1481}
1482
1483impl Diary<'_> {
1484    fn handle_general_skills_window(
1485        &mut self,
1486        diary_tooltip: &Tooltip,
1487        state: &mut State<DiaryState>,
1488        ui: &mut UiCell,
1489        mut events: Vec<Event>,
1490    ) -> Vec<Event> {
1491        let tree_title = &self.localized_strings.get_msg("common-weapons-general");
1492        Text::new(tree_title)
1493            .mid_top_with_margin_on(state.ids.content_align, 2.0)
1494            .font_id(self.fonts.cyri.conrod_id)
1495            .font_size(self.fonts.cyri.scale(34))
1496            .color(TEXT_COLOR)
1497            .set(state.ids.tree_title_txt, ui);
1498
1499        // Number of skills per rectangle per weapon, start counting at 0
1500        // Maximum of 9 skills/8 indices
1501        let skills_top_l = 6;
1502        let skills_top_r = 0;
1503        let skills_bot_l = 0;
1504        let skills_bot_r = 5;
1505
1506        self.setup_state_for_skill_icons(
1507            state,
1508            ui,
1509            skills_top_l,
1510            skills_top_r,
1511            skills_bot_l,
1512            skills_bot_r,
1513        );
1514
1515        use SkillGroupKind::*;
1516        use ToolKind::*;
1517        // General Combat
1518        Image::new(animate_by_pulse(
1519            &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1520                "example_general_combat_left".to_string(),
1521            )),
1522            self.pulse,
1523        ))
1524        .wh(ART_SIZE)
1525        .middle_of(state.ids.content_align)
1526        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1527        .set(state.ids.general_combat_render_0, ui);
1528
1529        Image::new(animate_by_pulse(
1530            &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1531                "example_general_combat_right".to_string(),
1532            )),
1533            self.pulse,
1534        ))
1535        .wh(ART_SIZE)
1536        .middle_of(state.ids.general_combat_render_0)
1537        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1538        .set(state.ids.general_combat_render_1, ui);
1539
1540        use PositionSpecifier::MidTopWithMarginOn;
1541        let skill_buttons = &[
1542            // Top Left skills
1543            //        5 1 6
1544            //        3 0 4
1545            //        8 2 7
1546            // Bottom left skills
1547            SkillIcon::Unlockable {
1548                skill: Skill::UnlockGroup(Weapon(Sword)),
1549                image: self.imgs.unlock_sword_skill,
1550                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
1551                id: state.ids.skill_general_tree_0,
1552            },
1553            SkillIcon::Unlockable {
1554                skill: Skill::UnlockGroup(Weapon(Axe)),
1555                image: self.imgs.unlock_axe_skill,
1556                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
1557                id: state.ids.skill_general_tree_1,
1558            },
1559            SkillIcon::Unlockable {
1560                skill: Skill::UnlockGroup(Weapon(Hammer)),
1561                image: self.imgs.unlock_hammer_skill,
1562                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
1563                id: state.ids.skill_general_tree_2,
1564            },
1565            SkillIcon::Unlockable {
1566                skill: Skill::UnlockGroup(Weapon(Bow)),
1567                image: self.imgs.unlock_bow_skill,
1568                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
1569                id: state.ids.skill_general_tree_3,
1570            },
1571            SkillIcon::Unlockable {
1572                skill: Skill::UnlockGroup(Weapon(Staff)),
1573                image: self.imgs.unlock_staff_skill0,
1574                position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
1575                id: state.ids.skill_general_tree_4,
1576            },
1577            SkillIcon::Unlockable {
1578                skill: Skill::UnlockGroup(Weapon(Sceptre)),
1579                image: self.imgs.unlock_sceptre_skill,
1580                position: MidTopWithMarginOn(state.ids.skills_top_l[5], 3.0),
1581                id: state.ids.skill_general_tree_5,
1582            },
1583            // Bottom right skills
1584            SkillIcon::Descriptive {
1585                title: "hud-skill-climbing_title",
1586                desc: "hud-skill-climbing",
1587                image: self.imgs.skill_climbing_skill,
1588                position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0),
1589                id: state.ids.skill_general_climb_0,
1590            },
1591            SkillIcon::Unlockable {
1592                skill: Skill::Climb(ClimbSkill::Cost),
1593                image: self.imgs.utility_cost_skill,
1594                position: MidTopWithMarginOn(state.ids.skills_bot_r[1], 3.0),
1595                id: state.ids.skill_general_climb_1,
1596            },
1597            SkillIcon::Unlockable {
1598                skill: Skill::Climb(ClimbSkill::Speed),
1599                image: self.imgs.utility_speed_skill,
1600                position: MidTopWithMarginOn(state.ids.skills_bot_r[2], 3.0),
1601                id: state.ids.skill_general_climb_2,
1602            },
1603            SkillIcon::Descriptive {
1604                title: "hud-skill-swim_title",
1605                desc: "hud-skill-swim",
1606                image: self.imgs.skill_swim_skill,
1607                position: MidTopWithMarginOn(state.ids.skills_bot_r[3], 3.0),
1608                id: state.ids.skill_general_swim_0,
1609            },
1610            SkillIcon::Unlockable {
1611                skill: Skill::Swim(SwimSkill::Speed),
1612                image: self.imgs.utility_speed_skill,
1613                position: MidTopWithMarginOn(state.ids.skills_bot_r[4], 3.0),
1614                id: state.ids.skill_general_swim_1,
1615            },
1616        ];
1617
1618        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1619        events
1620    }
1621
1622    fn handle_sword_skills_window(
1623        &mut self,
1624        diary_tooltip: &Tooltip,
1625        state: &mut State<DiaryState>,
1626        ui: &mut UiCell,
1627        mut events: Vec<Event>,
1628    ) -> Vec<Event> {
1629        Image::new(self.imgs.sword_tree_paths)
1630            .wh([1042.0, 636.0])
1631            .mid_top_with_margin_on(state.ids.content_align, 55.0)
1632            .graphics_for(state.ids.content_align)
1633            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1634            .set(state.ids.sword_path_overlay, ui);
1635
1636        // Sword
1637        Image::new(self.imgs.sword_bg)
1638            .wh([933.0, 615.0])
1639            .mid_top_with_margin_on(state.ids.content_align, 65.0)
1640            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1641            .set(state.ids.sword_bg, ui);
1642
1643        use PositionSpecifier::TopLeftWithMarginsOn;
1644        let skill_buttons = &[
1645            SkillIcon::Ability {
1646                skill: Skill::Sword(SwordSkill::CrescentSlash),
1647                ability_id: "veloren.core.pseudo_abilities.sword.crescent_slash",
1648                position: TopLeftWithMarginsOn(state.ids.sword_bg, 537.0, 429.0),
1649            },
1650            SkillIcon::Ability {
1651                skill: Skill::Sword(SwordSkill::FellStrike),
1652                ability_id: "veloren.core.pseudo_abilities.sword.fell_strike",
1653                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 527.0),
1654            },
1655            SkillIcon::Ability {
1656                skill: Skill::Sword(SwordSkill::Skewer),
1657                ability_id: "veloren.core.pseudo_abilities.sword.skewer",
1658                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 527.0),
1659            },
1660            SkillIcon::Ability {
1661                skill: Skill::Sword(SwordSkill::Cascade),
1662                ability_id: "veloren.core.pseudo_abilities.sword.cascade",
1663                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 332.0),
1664            },
1665            SkillIcon::Ability {
1666                skill: Skill::Sword(SwordSkill::CrossCut),
1667                ability_id: "veloren.core.pseudo_abilities.sword.cross_cut",
1668                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 332.0),
1669            },
1670            SkillIcon::Ability {
1671                skill: Skill::Sword(SwordSkill::Finisher),
1672                ability_id: "veloren.core.pseudo_abilities.sword.finisher",
1673                position: TopLeftWithMarginsOn(state.ids.sword_bg, 263.0, 429.0),
1674            },
1675            SkillIcon::Ability {
1676                skill: Skill::Sword(SwordSkill::HeavySweep),
1677                ability_id: "common.abilities.sword.heavy_sweep",
1678                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 2.0),
1679            },
1680            SkillIcon::Ability {
1681                skill: Skill::Sword(SwordSkill::HeavyPommelStrike),
1682                ability_id: "common.abilities.sword.heavy_pommel_strike",
1683                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 91.0),
1684            },
1685            SkillIcon::Ability {
1686                skill: Skill::Sword(SwordSkill::AgileQuickDraw),
1687                ability_id: "common.abilities.sword.agile_quick_draw",
1688                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 384.0),
1689            },
1690            SkillIcon::Ability {
1691                skill: Skill::Sword(SwordSkill::AgileFeint),
1692                ability_id: "common.abilities.sword.agile_feint",
1693                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 472.0),
1694            },
1695            SkillIcon::Ability {
1696                skill: Skill::Sword(SwordSkill::DefensiveRiposte),
1697                ability_id: "common.abilities.sword.defensive_riposte",
1698                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 766.0),
1699            },
1700            SkillIcon::Ability {
1701                skill: Skill::Sword(SwordSkill::DefensiveDisengage),
1702                ability_id: "common.abilities.sword.defensive_disengage",
1703                position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 855.0),
1704            },
1705            SkillIcon::Ability {
1706                skill: Skill::Sword(SwordSkill::CripplingGouge),
1707                ability_id: "common.abilities.sword.crippling_gouge",
1708                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 766.0),
1709            },
1710            SkillIcon::Ability {
1711                skill: Skill::Sword(SwordSkill::CripplingHamstring),
1712                ability_id: "common.abilities.sword.crippling_hamstring",
1713                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 766.0),
1714            },
1715            SkillIcon::Ability {
1716                skill: Skill::Sword(SwordSkill::CleavingWhirlwindSlice),
1717                ability_id: "common.abilities.sword.cleaving_whirlwind_slice",
1718                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 91.0),
1719            },
1720            SkillIcon::Ability {
1721                skill: Skill::Sword(SwordSkill::CleavingEarthSplitter),
1722                ability_id: "common.abilities.sword.cleaving_earth_splitter",
1723                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 91.0),
1724            },
1725            SkillIcon::Ability {
1726                skill: Skill::Sword(SwordSkill::HeavyFortitude),
1727                ability_id: "common.abilities.sword.heavy_fortitude",
1728                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 2.0),
1729            },
1730            SkillIcon::Ability {
1731                skill: Skill::Sword(SwordSkill::HeavyPillarThrust),
1732                ability_id: "common.abilities.sword.heavy_pillar_thrust",
1733                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 91.0),
1734            },
1735            SkillIcon::Ability {
1736                skill: Skill::Sword(SwordSkill::AgileDancingEdge),
1737                ability_id: "common.abilities.sword.agile_dancing_edge",
1738                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 385.0),
1739            },
1740            SkillIcon::Ability {
1741                skill: Skill::Sword(SwordSkill::AgileFlurry),
1742                ability_id: "common.abilities.sword.agile_flurry",
1743                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 473.0),
1744            },
1745            SkillIcon::Ability {
1746                skill: Skill::Sword(SwordSkill::DefensiveStalwartSword),
1747                ability_id: "common.abilities.sword.defensive_stalwart_sword",
1748                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 766.0),
1749            },
1750            SkillIcon::Ability {
1751                skill: Skill::Sword(SwordSkill::DefensiveDeflect),
1752                ability_id: "common.abilities.sword.defensive_deflect",
1753                position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 855.0),
1754            },
1755            SkillIcon::Ability {
1756                skill: Skill::Sword(SwordSkill::CripplingEviscerate),
1757                ability_id: "common.abilities.sword.crippling_eviscerate",
1758                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 855.0),
1759            },
1760            SkillIcon::Ability {
1761                skill: Skill::Sword(SwordSkill::CripplingBloodyGash),
1762                ability_id: "common.abilities.sword.crippling_bloody_gash",
1763                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 855.0),
1764            },
1765            SkillIcon::Ability {
1766                skill: Skill::Sword(SwordSkill::CleavingBladeFever),
1767                ability_id: "common.abilities.sword.cleaving_blade_fever",
1768                position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 2.0),
1769            },
1770            SkillIcon::Ability {
1771                skill: Skill::Sword(SwordSkill::CleavingSkySplitter),
1772                ability_id: "common.abilities.sword.cleaving_sky_splitter",
1773                position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 2.0),
1774            },
1775        ];
1776
1777        state.update(|s| {
1778            s.ids
1779                .skills
1780                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1781        });
1782        state.update(|s| {
1783            s.ids
1784                .skill_lock_imgs
1785                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1786        });
1787
1788        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1789        events
1790    }
1791
1792    fn handle_axe_skills_window(
1793        &mut self,
1794        diary_tooltip: &Tooltip,
1795        state: &mut State<DiaryState>,
1796        ui: &mut UiCell,
1797        mut events: Vec<Event>,
1798    ) -> Vec<Event> {
1799        // Axe
1800        Image::new(self.imgs.axe_bg)
1801            .wh([924.0, 619.0])
1802            .mid_top_with_margin_on(state.ids.content_align, 65.0)
1803            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1804            .set(state.ids.axe_bg, ui);
1805
1806        use PositionSpecifier::TopLeftWithMarginsOn;
1807        let skill_buttons = &[
1808            SkillIcon::Ability {
1809                skill: Skill::Axe(AxeSkill::BrutalSwing),
1810                ability_id: "common.abilities.axe.brutal_swing",
1811                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 424.0),
1812            },
1813            SkillIcon::Ability {
1814                skill: Skill::Axe(AxeSkill::Berserk),
1815                ability_id: "common.abilities.axe.berserk",
1816                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 374.0),
1817            },
1818            SkillIcon::Ability {
1819                skill: Skill::Axe(AxeSkill::RisingTide),
1820                ability_id: "common.abilities.axe.rising_tide",
1821                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 474.0),
1822            },
1823            SkillIcon::Ability {
1824                skill: Skill::Axe(AxeSkill::SavageSense),
1825                ability_id: "common.abilities.axe.savage_sense",
1826                position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 324.0),
1827            },
1828            SkillIcon::Ability {
1829                skill: Skill::Axe(AxeSkill::AdrenalineRush),
1830                ability_id: "common.abilities.axe.adrenaline_rush",
1831                position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 524.0),
1832            },
1833            SkillIcon::Ability {
1834                skill: Skill::Axe(AxeSkill::Execute),
1835                ability_id: "common.abilities.axe.execute",
1836                position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 424.0),
1837            },
1838            SkillIcon::Ability {
1839                skill: Skill::Axe(AxeSkill::Maelstrom),
1840                ability_id: "common.abilities.axe.maelstrom",
1841                position: TopLeftWithMarginsOn(state.ids.axe_bg, 4.0, 424.0),
1842            },
1843            SkillIcon::Ability {
1844                skill: Skill::Axe(AxeSkill::Rake),
1845                ability_id: "common.abilities.axe.rake",
1846                position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 325.0),
1847            },
1848            SkillIcon::Ability {
1849                skill: Skill::Axe(AxeSkill::Bloodfeast),
1850                ability_id: "common.abilities.axe.bloodfeast",
1851                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 74.0),
1852            },
1853            SkillIcon::Ability {
1854                skill: Skill::Axe(AxeSkill::FierceRaze),
1855                ability_id: "common.abilities.axe.fierce_raze",
1856                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 174.0),
1857            },
1858            SkillIcon::Ability {
1859                skill: Skill::Axe(AxeSkill::Furor),
1860                ability_id: "common.abilities.axe.furor",
1861                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 24.0),
1862            },
1863            SkillIcon::Ability {
1864                skill: Skill::Axe(AxeSkill::Fracture),
1865                ability_id: "common.abilities.axe.fracture",
1866                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 224.0),
1867            },
1868            SkillIcon::Ability {
1869                skill: Skill::Axe(AxeSkill::Lacerate),
1870                ability_id: "common.abilities.axe.lacerate",
1871                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 124.0),
1872            },
1873            SkillIcon::Ability {
1874                skill: Skill::Axe(AxeSkill::Riptide),
1875                ability_id: "common.abilities.axe.riptide",
1876                position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 124.0),
1877            },
1878            SkillIcon::Ability {
1879                skill: Skill::Axe(AxeSkill::SkullBash),
1880                ability_id: "common.abilities.axe.skull_bash",
1881                position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 523.0),
1882            },
1883            SkillIcon::Ability {
1884                skill: Skill::Axe(AxeSkill::Sunder),
1885                ability_id: "common.abilities.axe.sunder",
1886                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 674.0),
1887            },
1888            SkillIcon::Ability {
1889                skill: Skill::Axe(AxeSkill::Plunder),
1890                ability_id: "common.abilities.axe.plunder",
1891                position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 774.0),
1892            },
1893            SkillIcon::Ability {
1894                skill: Skill::Axe(AxeSkill::Defiance),
1895                ability_id: "common.abilities.axe.defiance",
1896                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 624.0),
1897            },
1898            SkillIcon::Ability {
1899                skill: Skill::Axe(AxeSkill::Keelhaul),
1900                ability_id: "common.abilities.axe.keelhaul",
1901                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 824.0),
1902            },
1903            SkillIcon::Ability {
1904                skill: Skill::Axe(AxeSkill::Bulkhead),
1905                ability_id: "common.abilities.axe.bulkhead",
1906                position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 724.0),
1907            },
1908            SkillIcon::Ability {
1909                skill: Skill::Axe(AxeSkill::Capsize),
1910                ability_id: "common.abilities.axe.capsize",
1911                position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 724.0),
1912            },
1913        ];
1914
1915        state.update(|s| {
1916            s.ids
1917                .skills
1918                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1919        });
1920        state.update(|s| {
1921            s.ids
1922                .skill_lock_imgs
1923                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1924        });
1925
1926        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1927        events
1928    }
1929
1930    fn handle_hammer_skills_window(
1931        &mut self,
1932        diary_tooltip: &Tooltip,
1933        state: &mut State<DiaryState>,
1934        ui: &mut UiCell,
1935        mut events: Vec<Event>,
1936    ) -> Vec<Event> {
1937        // Hammer
1938        Image::new(self.imgs.hammer_bg)
1939            .wh([924.0, 619.0])
1940            .mid_top_with_margin_on(state.ids.content_align, 65.0)
1941            .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1942            .set(state.ids.hammer_bg, ui);
1943
1944        use PositionSpecifier::TopLeftWithMarginsOn;
1945        let skill_buttons = &[
1946            SkillIcon::Ability {
1947                skill: Skill::Hammer(HammerSkill::ScornfulSwipe),
1948                ability_id: "common.abilities.hammer.scornful_swipe",
1949                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 455.0, 424.0),
1950            },
1951            SkillIcon::Ability {
1952                skill: Skill::Hammer(HammerSkill::Tremor),
1953                ability_id: "common.abilities.hammer.tremor",
1954                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 172.0),
1955            },
1956            SkillIcon::Ability {
1957                skill: Skill::Hammer(HammerSkill::VigorousBash),
1958                ability_id: "common.abilities.hammer.vigorous_bash",
1959                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 272.0),
1960            },
1961            SkillIcon::Ability {
1962                skill: Skill::Hammer(HammerSkill::Retaliate),
1963                ability_id: "common.abilities.hammer.retaliate",
1964                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 122.0),
1965            },
1966            SkillIcon::Ability {
1967                skill: Skill::Hammer(HammerSkill::SpineCracker),
1968                ability_id: "common.abilities.hammer.spine_cracker",
1969                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 222.0),
1970            },
1971            SkillIcon::Ability {
1972                skill: Skill::Hammer(HammerSkill::Breach),
1973                ability_id: "common.abilities.hammer.breach",
1974                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 322.0),
1975            },
1976            SkillIcon::Ability {
1977                skill: Skill::Hammer(HammerSkill::IronTempest),
1978                ability_id: "common.abilities.hammer.iron_tempest",
1979                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 172.0),
1980            },
1981            SkillIcon::Ability {
1982                skill: Skill::Hammer(HammerSkill::Upheaval),
1983                ability_id: "common.abilities.hammer.upheaval",
1984                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 272.0),
1985            },
1986            SkillIcon::Ability {
1987                skill: Skill::Hammer(HammerSkill::Thunderclap),
1988                ability_id: "common.abilities.hammer.thunderclap",
1989                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 172.0),
1990            },
1991            SkillIcon::Ability {
1992                skill: Skill::Hammer(HammerSkill::SeismicShock),
1993                ability_id: "common.abilities.hammer.seismic_shock",
1994                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 272.0),
1995            },
1996            SkillIcon::Ability {
1997                skill: Skill::Hammer(HammerSkill::HeavyWhorl),
1998                ability_id: "common.abilities.hammer.heavy_whorl",
1999                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 576.0),
2000            },
2001            SkillIcon::Ability {
2002                skill: Skill::Hammer(HammerSkill::Intercept),
2003                ability_id: "common.abilities.hammer.intercept",
2004                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 676.0),
2005            },
2006            SkillIcon::Ability {
2007                skill: Skill::Hammer(HammerSkill::PileDriver),
2008                ability_id: "common.abilities.hammer.pile_driver",
2009                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 526.0),
2010            },
2011            SkillIcon::Ability {
2012                skill: Skill::Hammer(HammerSkill::LungPummel),
2013                ability_id: "common.abilities.hammer.lung_pummel",
2014                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 626.0),
2015            },
2016            SkillIcon::Ability {
2017                skill: Skill::Hammer(HammerSkill::HelmCrusher),
2018                ability_id: "common.abilities.hammer.helm_crusher",
2019                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 726.0),
2020            },
2021            SkillIcon::Ability {
2022                skill: Skill::Hammer(HammerSkill::Rampart),
2023                ability_id: "common.abilities.hammer.rampart",
2024                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 576.0),
2025            },
2026            SkillIcon::Ability {
2027                skill: Skill::Hammer(HammerSkill::Tenacity),
2028                ability_id: "common.abilities.hammer.tenacity",
2029                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 676.0),
2030            },
2031            SkillIcon::Ability {
2032                skill: Skill::Hammer(HammerSkill::Earthshaker),
2033                ability_id: "common.abilities.hammer.earthshaker",
2034                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 576.0),
2035            },
2036            SkillIcon::Ability {
2037                skill: Skill::Hammer(HammerSkill::Judgement),
2038                ability_id: "common.abilities.hammer.judgement",
2039                position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 676.0),
2040            },
2041        ];
2042
2043        state.update(|s| {
2044            s.ids
2045                .skills
2046                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2047        });
2048        state.update(|s| {
2049            s.ids
2050                .skill_lock_imgs
2051                .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2052        });
2053
2054        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2055        events
2056    }
2057
2058    fn handle_sceptre_skills_window(
2059        &mut self,
2060        diary_tooltip: &Tooltip,
2061        state: &mut State<DiaryState>,
2062        ui: &mut UiCell,
2063        mut events: Vec<Event>,
2064    ) -> Vec<Event> {
2065        // Title text
2066        let tree_title = &self.localized_strings.get_msg("common-weapons-sceptre");
2067
2068        Text::new(tree_title)
2069            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2070            .font_id(self.fonts.cyri.conrod_id)
2071            .font_size(self.fonts.cyri.scale(34))
2072            .color(TEXT_COLOR)
2073            .set(state.ids.tree_title_txt, ui);
2074
2075        // Number of skills per rectangle per weapon, start counting at 0
2076        // Maximum of 9 skills/8 indices
2077        let skills_top_l = 5;
2078        let skills_top_r = 5;
2079        let skills_bot_l = 5;
2080        let skills_bot_r = 0;
2081
2082        self.setup_state_for_skill_icons(
2083            state,
2084            ui,
2085            skills_top_l,
2086            skills_top_r,
2087            skills_bot_l,
2088            skills_bot_r,
2089        );
2090
2091        // Skill icons and buttons
2092        use skills::SceptreSkill::*;
2093        // Sceptre
2094        Image::new(animate_by_pulse(
2095            &self
2096                .item_imgs
2097                .img_ids_or_not_found_img(ItemKey::Simple("example_sceptre".to_string())),
2098            self.pulse,
2099        ))
2100        .wh(ART_SIZE)
2101        .middle_of(state.ids.content_align)
2102        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2103        .set(state.ids.sceptre_render, ui);
2104        use PositionSpecifier::MidTopWithMarginOn;
2105        let skill_buttons = &[
2106            // Top Left skills
2107            //        5 1 6
2108            //        3 0 4
2109            //        8 2 7
2110            SkillIcon::Descriptive {
2111                title: "hud-skill-sc_lifesteal_title",
2112                desc: "hud-skill-sc_lifesteal",
2113                image: self.imgs.skill_sceptre_lifesteal,
2114                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2115                id: state.ids.skill_sceptre_lifesteal_0,
2116            },
2117            SkillIcon::Unlockable {
2118                skill: Skill::Sceptre(LDamage),
2119                image: self.imgs.magic_damage_skill,
2120                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2121                id: state.ids.skill_sceptre_lifesteal_1,
2122            },
2123            SkillIcon::Unlockable {
2124                skill: Skill::Sceptre(LRange),
2125                image: self.imgs.magic_distance_skill,
2126                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2127                id: state.ids.skill_sceptre_lifesteal_2,
2128            },
2129            SkillIcon::Unlockable {
2130                skill: Skill::Sceptre(LLifesteal),
2131                image: self.imgs.magic_lifesteal_skill,
2132                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2133                id: state.ids.skill_sceptre_lifesteal_3,
2134            },
2135            SkillIcon::Unlockable {
2136                skill: Skill::Sceptre(LRegen),
2137                image: self.imgs.magic_energy_regen_skill,
2138                position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
2139                id: state.ids.skill_sceptre_lifesteal_4,
2140            },
2141            // Top right skills
2142            SkillIcon::Descriptive {
2143                title: "hud-skill-sc_heal_title",
2144                desc: "hud-skill-sc_heal",
2145                image: self.imgs.skill_sceptre_heal,
2146                position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2147                id: state.ids.skill_sceptre_heal_0,
2148            },
2149            SkillIcon::Unlockable {
2150                skill: Skill::Sceptre(HHeal),
2151                image: self.imgs.heal_heal_skill,
2152                position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2153                id: state.ids.skill_sceptre_heal_1,
2154            },
2155            SkillIcon::Unlockable {
2156                skill: Skill::Sceptre(HDuration),
2157                image: self.imgs.heal_duration_skill,
2158                position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2159                id: state.ids.skill_sceptre_heal_2,
2160            },
2161            SkillIcon::Unlockable {
2162                skill: Skill::Sceptre(HRange),
2163                image: self.imgs.heal_radius_skill,
2164                position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2165                id: state.ids.skill_sceptre_heal_3,
2166            },
2167            SkillIcon::Unlockable {
2168                skill: Skill::Sceptre(HCost),
2169                image: self.imgs.heal_cost_skill,
2170                position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0),
2171                id: state.ids.skill_sceptre_heal_4,
2172            },
2173            // Bottom left skills
2174            SkillIcon::Unlockable {
2175                skill: Skill::Sceptre(UnlockAura),
2176                image: self.imgs.skill_sceptre_aura,
2177                position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2178                id: state.ids.skill_sceptre_aura_0,
2179            },
2180            SkillIcon::Unlockable {
2181                skill: Skill::Sceptre(AStrength),
2182                image: self.imgs.buff_damage_skill,
2183                position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2184                id: state.ids.skill_sceptre_aura_1,
2185            },
2186            SkillIcon::Unlockable {
2187                skill: Skill::Sceptre(ADuration),
2188                image: self.imgs.buff_duration_skill,
2189                position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2190                id: state.ids.skill_sceptre_aura_2,
2191            },
2192            SkillIcon::Unlockable {
2193                skill: Skill::Sceptre(ARange),
2194                image: self.imgs.buff_radius_skill,
2195                position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2196                id: state.ids.skill_sceptre_aura_3,
2197            },
2198            SkillIcon::Unlockable {
2199                skill: Skill::Sceptre(ACost),
2200                image: self.imgs.buff_cost_skill,
2201                position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2202                id: state.ids.skill_sceptre_aura_4,
2203            },
2204        ];
2205
2206        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2207        events
2208    }
2209
2210    fn handle_bow_skills_window(
2211        &mut self,
2212        diary_tooltip: &Tooltip,
2213        state: &mut State<DiaryState>,
2214        ui: &mut UiCell,
2215        mut events: Vec<Event>,
2216    ) -> Vec<Event> {
2217        // Title text
2218        let tree_title = &self.localized_strings.get_msg("common-weapons-bow");
2219
2220        Text::new(tree_title)
2221            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2222            .font_id(self.fonts.cyri.conrod_id)
2223            .font_size(self.fonts.cyri.scale(34))
2224            .color(TEXT_COLOR)
2225            .set(state.ids.tree_title_txt, ui);
2226
2227        // Number of skills per rectangle per weapon, start counting at 0
2228        // Maximum of 9 skills/8 indices
2229        let skills_top_l = 6;
2230        let skills_top_r = 4;
2231        let skills_bot_l = 5;
2232        let skills_bot_r = 1;
2233
2234        self.setup_state_for_skill_icons(
2235            state,
2236            ui,
2237            skills_top_l,
2238            skills_top_r,
2239            skills_bot_l,
2240            skills_bot_r,
2241        );
2242
2243        // Skill icons and buttons
2244        use skills::BowSkill::*;
2245        // Bow
2246        Image::new(animate_by_pulse(
2247            &self
2248                .item_imgs
2249                .img_ids_or_not_found_img(ItemKey::Simple("example_bow".to_string())),
2250            self.pulse,
2251        ))
2252        .wh(ART_SIZE)
2253        .middle_of(state.ids.content_align)
2254        .set(state.ids.bow_render, ui);
2255        use PositionSpecifier::MidTopWithMarginOn;
2256        let skill_buttons = &[
2257            // Top Left skills
2258            //        5 1 6
2259            //        3 0 4
2260            //        8 2 7
2261            SkillIcon::Descriptive {
2262                title: "hud-skill-bow_charged_title",
2263                desc: "hud-skill-bow_charged",
2264                image: self.imgs.bow_m1,
2265                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2266                id: state.ids.skill_bow_charged_0,
2267            },
2268            SkillIcon::Unlockable {
2269                skill: Skill::Bow(CDamage),
2270                image: self.imgs.physical_damage_skill,
2271                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2272                id: state.ids.skill_bow_charged_1,
2273            },
2274            SkillIcon::Unlockable {
2275                skill: Skill::Bow(CRegen),
2276                image: self.imgs.physical_energy_regen_skill,
2277                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2278                id: state.ids.skill_bow_charged_2,
2279            },
2280            SkillIcon::Unlockable {
2281                skill: Skill::Bow(CKnockback),
2282                image: self.imgs.physical_knockback_skill,
2283                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2284                id: state.ids.skill_bow_charged_3,
2285            },
2286            SkillIcon::Unlockable {
2287                skill: Skill::Bow(CSpeed),
2288                image: self.imgs.physical_speed_skill,
2289                position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
2290                id: state.ids.skill_bow_charged_4,
2291            },
2292            SkillIcon::Unlockable {
2293                skill: Skill::Bow(CMove),
2294                image: self.imgs.physical_speed_skill,
2295                position: MidTopWithMarginOn(state.ids.skills_top_l[5], 3.0),
2296                id: state.ids.skill_bow_charged_5,
2297            },
2298            // Top right skills
2299            SkillIcon::Descriptive {
2300                title: "hud-skill-bow_repeater_title",
2301                desc: "hud-skill-bow_repeater",
2302                image: self.imgs.bow_m2,
2303                position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2304                id: state.ids.skill_bow_repeater_0,
2305            },
2306            SkillIcon::Unlockable {
2307                skill: Skill::Bow(RDamage),
2308                image: self.imgs.physical_damage_skill,
2309                position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2310                id: state.ids.skill_bow_repeater_1,
2311            },
2312            SkillIcon::Unlockable {
2313                skill: Skill::Bow(RCost),
2314                image: self.imgs.physical_cost_skill,
2315                position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2316                id: state.ids.skill_bow_repeater_2,
2317            },
2318            SkillIcon::Unlockable {
2319                skill: Skill::Bow(RSpeed),
2320                image: self.imgs.physical_speed_skill,
2321                position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2322                id: state.ids.skill_bow_repeater_3,
2323            },
2324            // Bottom left skills
2325            SkillIcon::Unlockable {
2326                skill: Skill::Bow(UnlockShotgun),
2327                image: self.imgs.skill_bow_jump_burst,
2328                position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2329                id: state.ids.skill_bow_shotgun_0,
2330            },
2331            SkillIcon::Unlockable {
2332                skill: Skill::Bow(SDamage),
2333                image: self.imgs.physical_damage_skill,
2334                position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2335                id: state.ids.skill_bow_shotgun_1,
2336            },
2337            SkillIcon::Unlockable {
2338                skill: Skill::Bow(SCost),
2339                image: self.imgs.physical_cost_skill,
2340                position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2341                id: state.ids.skill_bow_shotgun_2,
2342            },
2343            SkillIcon::Unlockable {
2344                skill: Skill::Bow(SArrows),
2345                image: self.imgs.physical_amount_skill,
2346                position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2347                id: state.ids.skill_bow_shotgun_3,
2348            },
2349            SkillIcon::Unlockable {
2350                skill: Skill::Bow(SSpread),
2351                image: self.imgs.physical_explosion_skill,
2352                position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2353                id: state.ids.skill_bow_shotgun_4,
2354            },
2355            // Bottom right skills
2356            SkillIcon::Unlockable {
2357                skill: Skill::Bow(ProjSpeed),
2358                image: self.imgs.physical_projectile_speed_skill,
2359                position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0),
2360                id: state.ids.skill_bow_passive_0,
2361            },
2362        ];
2363
2364        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2365        events
2366    }
2367
2368    fn handle_staff_skills_window(
2369        &mut self,
2370        diary_tooltip: &Tooltip,
2371        state: &mut State<DiaryState>,
2372        ui: &mut UiCell,
2373        mut events: Vec<Event>,
2374    ) -> Vec<Event> {
2375        // Title text
2376        let tree_title = &self.localized_strings.get_msg("common-weapons-staff");
2377
2378        Text::new(tree_title)
2379            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2380            .font_id(self.fonts.cyri.conrod_id)
2381            .font_size(self.fonts.cyri.scale(34))
2382            .color(TEXT_COLOR)
2383            .set(state.ids.tree_title_txt, ui);
2384
2385        // Number of skills per rectangle per weapon, start counting at 0
2386        // Maximum of 9 skills/8 indices
2387        let skills_top_l = 4;
2388        let skills_top_r = 5;
2389        let skills_bot_l = 5;
2390        let skills_bot_r = 0;
2391
2392        self.setup_state_for_skill_icons(
2393            state,
2394            ui,
2395            skills_top_l,
2396            skills_top_r,
2397            skills_bot_l,
2398            skills_bot_r,
2399        );
2400
2401        // Skill icons and buttons
2402        use skills::StaffSkill::*;
2403        // Staff
2404        Image::new(animate_by_pulse(
2405            &self
2406                .item_imgs
2407                .img_ids_or_not_found_img(ItemKey::Simple("example_staff_fire".to_string())),
2408            self.pulse,
2409        ))
2410        .wh(ART_SIZE)
2411        .middle_of(state.ids.content_align)
2412        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2413        .set(state.ids.staff_render, ui);
2414
2415        use PositionSpecifier::MidTopWithMarginOn;
2416        let skill_buttons = &[
2417            // Top Left skills
2418            //        5 1 6
2419            //        3 0 4
2420            //        8 2 7
2421            SkillIcon::Descriptive {
2422                title: "hud-skill-st_fireball_title",
2423                desc: "hud-skill-st_fireball",
2424                image: self.imgs.fireball,
2425                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2426                id: state.ids.skill_staff_basic_0,
2427            },
2428            SkillIcon::Unlockable {
2429                skill: Skill::Staff(BDamage),
2430                image: self.imgs.magic_damage_skill,
2431                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2432                id: state.ids.skill_staff_basic_1,
2433            },
2434            SkillIcon::Unlockable {
2435                skill: Skill::Staff(BRegen),
2436                image: self.imgs.magic_energy_regen_skill,
2437                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2438                id: state.ids.skill_staff_basic_2,
2439            },
2440            SkillIcon::Unlockable {
2441                skill: Skill::Staff(BRadius),
2442                image: self.imgs.magic_radius_skill,
2443                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2444                id: state.ids.skill_staff_basic_3,
2445            },
2446            // Top right skills
2447            SkillIcon::Descriptive {
2448                title: "hud-skill-st_flamethrower_title",
2449                desc: "hud-skill-st_flamethrower",
2450                image: self.imgs.flamethrower,
2451                position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2452                id: state.ids.skill_staff_beam_0,
2453            },
2454            SkillIcon::Unlockable {
2455                skill: Skill::Staff(FDamage),
2456                image: self.imgs.magic_damage_skill,
2457                position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2458                id: state.ids.skill_staff_beam_1,
2459            },
2460            SkillIcon::Unlockable {
2461                skill: Skill::Staff(FDrain),
2462                image: self.imgs.magic_energy_drain_skill,
2463                position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2464                id: state.ids.skill_staff_beam_2,
2465            },
2466            SkillIcon::Unlockable {
2467                skill: Skill::Staff(FRange),
2468                image: self.imgs.magic_radius_skill,
2469                position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2470                id: state.ids.skill_staff_beam_3,
2471            },
2472            SkillIcon::Unlockable {
2473                skill: Skill::Staff(FVelocity),
2474                image: self.imgs.magic_projectile_speed_skill,
2475                position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0),
2476                id: state.ids.skill_staff_beam_4,
2477            },
2478            // Bottom left skills
2479            SkillIcon::Unlockable {
2480                skill: Skill::Staff(UnlockShockwave),
2481                image: self.imgs.fire_aoe,
2482                position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2483                id: state.ids.skill_staff_shockwave_0,
2484            },
2485            SkillIcon::Unlockable {
2486                skill: Skill::Staff(SDamage),
2487                image: self.imgs.magic_damage_skill,
2488                position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2489                id: state.ids.skill_staff_shockwave_1,
2490            },
2491            SkillIcon::Unlockable {
2492                skill: Skill::Staff(SKnockback),
2493                image: self.imgs.magic_knockback_skill,
2494                position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2495                id: state.ids.skill_staff_shockwave_2,
2496            },
2497            SkillIcon::Unlockable {
2498                skill: Skill::Staff(SCost),
2499                image: self.imgs.magic_cost_skill,
2500                position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2501                id: state.ids.skill_staff_shockwave_3,
2502            },
2503            SkillIcon::Unlockable {
2504                skill: Skill::Staff(SRange),
2505                image: self.imgs.magic_radius_skill,
2506                position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2507                id: state.ids.skill_staff_shockwave_4,
2508            },
2509        ];
2510
2511        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2512        events
2513    }
2514
2515    fn handle_mining_skills_window(
2516        &mut self,
2517        diary_tooltip: &Tooltip,
2518        state: &mut State<DiaryState>,
2519        ui: &mut UiCell,
2520        mut events: Vec<Event>,
2521    ) -> Vec<Event> {
2522        // Title text
2523        let tree_title = &self.localized_strings.get_msg("common-tool-mining");
2524
2525        Text::new(tree_title)
2526            .mid_top_with_margin_on(state.ids.content_align, 2.0)
2527            .font_id(self.fonts.cyri.conrod_id)
2528            .font_size(self.fonts.cyri.scale(34))
2529            .color(TEXT_COLOR)
2530            .set(state.ids.tree_title_txt, ui);
2531
2532        // Number of skills per rectangle per weapon, start counting at 0
2533        // Maximum of 9 skills/8 indices
2534        let skills_top_l = 4;
2535        let skills_top_r = 0;
2536        let skills_bot_l = 0;
2537        let skills_bot_r = 0;
2538
2539        self.setup_state_for_skill_icons(
2540            state,
2541            ui,
2542            skills_top_l,
2543            skills_top_r,
2544            skills_bot_l,
2545            skills_bot_r,
2546        );
2547
2548        // Skill icons and buttons
2549        use skills::MiningSkill::*;
2550        // Mining
2551        Image::new(animate_by_pulse(
2552            &self
2553                .item_imgs
2554                .img_ids_or_not_found_img(ItemKey::Simple("example_pick".to_string())),
2555            self.pulse,
2556        ))
2557        .wh(ART_SIZE)
2558        .middle_of(state.ids.content_align)
2559        .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2560        .set(state.ids.pick_render, ui);
2561
2562        use PositionSpecifier::MidTopWithMarginOn;
2563        let skill_buttons = &[
2564            // Top Left skills
2565            //        5 1 6
2566            //        3 0 4
2567            //        8 2 7
2568            SkillIcon::Descriptive {
2569                title: "hud-skill-pick_strike_title",
2570                desc: "hud-skill-pick_strike",
2571                image: self.imgs.pickaxe,
2572                position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2573                id: state.ids.skill_pick_m1,
2574            },
2575            SkillIcon::Unlockable {
2576                skill: Skill::Pick(Speed),
2577                image: self.imgs.pickaxe_speed_skill,
2578                position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2579                id: state.ids.skill_pick_m1_0,
2580            },
2581            SkillIcon::Unlockable {
2582                skill: Skill::Pick(OreGain),
2583                image: self.imgs.pickaxe_oregain_skill,
2584                position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2585                id: state.ids.skill_pick_m1_1,
2586            },
2587            SkillIcon::Unlockable {
2588                skill: Skill::Pick(GemGain),
2589                image: self.imgs.pickaxe_gemgain_skill,
2590                position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2591                id: state.ids.skill_pick_m1_2,
2592            },
2593        ];
2594
2595        self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2596        events
2597    }
2598
2599    fn handle_skill_buttons(
2600        &mut self,
2601        icons: &[SkillIcon],
2602        ui: &mut UiCell,
2603        events: &mut Vec<Event>,
2604        diary_tooltip: &Tooltip,
2605        state: &mut State<DiaryState>,
2606    ) {
2607        for (i, icon) in icons.iter().enumerate() {
2608            match icon {
2609                SkillIcon::Descriptive {
2610                    title,
2611                    desc,
2612                    image,
2613                    position,
2614                    id,
2615                } => {
2616                    // TODO: shouldn't this be a `Image::new`?
2617                    Button::image(*image)
2618                        .w_h(74.0, 74.0)
2619                        .position(*position)
2620                        .with_tooltip(
2621                            self.tooltip_manager,
2622                            &self.localized_strings.get_msg(title),
2623                            &self.localized_strings.get_msg(desc),
2624                            diary_tooltip,
2625                            TEXT_COLOR,
2626                        )
2627                        .set(*id, ui);
2628                },
2629                SkillIcon::Unlockable {
2630                    skill,
2631                    image,
2632                    position,
2633                    id,
2634                } => self.create_unlock_skill_button(
2635                    *skill,
2636                    *image,
2637                    *position,
2638                    *id,
2639                    ui,
2640                    events,
2641                    diary_tooltip,
2642                ),
2643                SkillIcon::Ability {
2644                    skill,
2645                    ability_id,
2646                    position,
2647                } => self.create_unlock_ability_button(
2648                    *skill,
2649                    ability_id,
2650                    *position,
2651                    i,
2652                    ui,
2653                    events,
2654                    diary_tooltip,
2655                    state,
2656                ),
2657            }
2658        }
2659    }
2660
2661    fn setup_state_for_skill_icons(
2662        &mut self,
2663        state: &mut State<DiaryState>,
2664        ui: &mut UiCell,
2665        skills_top_l: usize,
2666        skills_top_r: usize,
2667        skills_bot_l: usize,
2668        skills_bot_r: usize,
2669    ) {
2670        // Update widget id array len
2671        state.update(|s| {
2672            s.ids
2673                .skills_top_l
2674                .resize(skills_top_l, &mut ui.widget_id_generator())
2675        });
2676        state.update(|s| {
2677            s.ids
2678                .skills_top_r
2679                .resize(skills_top_r, &mut ui.widget_id_generator())
2680        });
2681        state.update(|s| {
2682            s.ids
2683                .skills_bot_l
2684                .resize(skills_bot_l, &mut ui.widget_id_generator())
2685        });
2686        state.update(|s| {
2687            s.ids
2688                .skills_bot_r
2689                .resize(skills_bot_r, &mut ui.widget_id_generator())
2690        });
2691
2692        // Create Background Images to place skill icons on them later
2693        // Create central skill first, others around it:
2694        //
2695        //        5 1 6
2696        //        3 0 4
2697        //        8 2 7
2698        //
2699        //
2700        let offset_0 = 22.0;
2701        let offset_1 = -122.0;
2702        let offset_2 = offset_1 - -20.0;
2703
2704        let skill_pos = |idx, align, central_skill| {
2705            use PositionSpecifier::*;
2706            match idx {
2707                // Central skill
2708                0 => MiddleOf(align),
2709                // 12:00
2710                1 => UpFrom(central_skill, offset_0),
2711                // 6:00
2712                2 => DownFrom(central_skill, offset_0),
2713                // 3:00
2714                3 => LeftFrom(central_skill, offset_0),
2715                // 9:00
2716                4 => RightFrom(central_skill, offset_0),
2717                // 10:30
2718                5 => TopLeftWithMarginsOn(central_skill, offset_1, offset_2),
2719                // 1:30
2720                6 => TopRightWithMarginsOn(central_skill, offset_1, offset_2),
2721                // 4:30
2722                7 => BottomLeftWithMarginsOn(central_skill, offset_1, offset_2),
2723                // 7:30
2724                8 => BottomRightWithMarginsOn(central_skill, offset_1, offset_2),
2725                buttons => {
2726                    panic!("{} > 8 position number", buttons);
2727                },
2728            }
2729        };
2730
2731        // TOP-LEFT Skills
2732        //
2733        // TODO: Why this uses while loop on field of struct and not just
2734        // `for i in 0..skils_top_l`?
2735        while self.created_btns_top_l < skills_top_l {
2736            let pos = skill_pos(
2737                self.created_btns_top_l,
2738                state.ids.skills_top_l_align,
2739                state.ids.skills_top_l[0],
2740            );
2741            Button::image(self.imgs.wpn_icon_border_skills)
2742                .w_h(80.0, 100.0)
2743                .position(pos)
2744                .set(state.ids.skills_top_l[self.created_btns_top_l], ui);
2745            self.created_btns_top_l += 1;
2746        }
2747        // TOP-RIGHT Skills
2748        while self.created_btns_top_r < skills_top_r {
2749            let pos = skill_pos(
2750                self.created_btns_top_r,
2751                state.ids.skills_top_r_align,
2752                state.ids.skills_top_r[0],
2753            );
2754            Button::image(self.imgs.wpn_icon_border_skills)
2755                .w_h(80.0, 100.0)
2756                .position(pos)
2757                .set(state.ids.skills_top_r[self.created_btns_top_r], ui);
2758            self.created_btns_top_r += 1;
2759        }
2760        // BOTTOM-LEFT Skills
2761        while self.created_btns_bot_l < skills_bot_l {
2762            let pos = skill_pos(
2763                self.created_btns_bot_l,
2764                state.ids.skills_bot_l_align,
2765                state.ids.skills_bot_l[0],
2766            );
2767            Button::image(self.imgs.wpn_icon_border_skills)
2768                .w_h(80.0, 100.0)
2769                .position(pos)
2770                .set(state.ids.skills_bot_l[self.created_btns_bot_l], ui);
2771            self.created_btns_bot_l += 1;
2772        }
2773        // BOTTOM-RIGHT Skills
2774        while self.created_btns_bot_r < skills_bot_r {
2775            let pos = skill_pos(
2776                self.created_btns_bot_r,
2777                state.ids.skills_bot_r_align,
2778                state.ids.skills_bot_r[0],
2779            );
2780            Button::image(self.imgs.wpn_icon_border_skills)
2781                .w_h(80.0, 100.0)
2782                .position(pos)
2783                .set(state.ids.skills_bot_r[self.created_btns_bot_r], ui);
2784            self.created_btns_bot_r += 1;
2785        }
2786    }
2787
2788    fn create_unlock_skill_button(
2789        &mut self,
2790        skill: Skill,
2791        skill_image: image::Id,
2792        position: PositionSpecifier,
2793        widget_id: widget::Id,
2794        ui: &mut UiCell,
2795        events: &mut Vec<Event>,
2796        diary_tooltip: &Tooltip,
2797    ) {
2798        let label = if self.skill_set.prerequisites_met(skill) {
2799            let current = self.skill_set.skill_level(skill).unwrap_or(0);
2800            let max = skill.max_level();
2801            format!("{}/{}", current, max)
2802        } else {
2803            "".to_owned()
2804        };
2805
2806        let label_color = if self.skill_set.is_at_max_level(skill) {
2807            TEXT_COLOR
2808        } else if self.skill_set.sufficient_skill_points(skill) {
2809            HP_COLOR
2810        } else {
2811            CRITICAL_HP_COLOR
2812        };
2813
2814        let image_color = if self.skill_set.prerequisites_met(skill) {
2815            TEXT_COLOR
2816        } else {
2817            Color::Rgba(0.41, 0.41, 0.41, 0.7)
2818        };
2819
2820        let skill_strings = skill_strings(skill);
2821        let (title, description) =
2822            skill_strings.localize(self.localized_strings, self.skill_set, skill);
2823
2824        let button = Button::image(skill_image)
2825            .w_h(74.0, 74.0)
2826            .position(position)
2827            .label(&label)
2828            .label_y(conrod_core::position::Relative::Scalar(-47.0))
2829            .label_x(conrod_core::position::Relative::Scalar(0.0))
2830            .label_color(label_color)
2831            .label_font_size(self.fonts.cyri.scale(15))
2832            .label_font_id(self.fonts.cyri.conrod_id)
2833            .image_color(image_color)
2834            .with_tooltip(
2835                self.tooltip_manager,
2836                &title,
2837                &description,
2838                diary_tooltip,
2839                TEXT_COLOR,
2840            )
2841            .set(widget_id, ui);
2842
2843        if button.was_clicked() {
2844            events.push(Event::UnlockSkill(skill));
2845        };
2846    }
2847
2848    fn create_unlock_ability_button(
2849        &mut self,
2850        skill: Skill,
2851        ability_id: &str,
2852        position: PositionSpecifier,
2853        widget_index: usize,
2854        ui: &mut UiCell,
2855        events: &mut Vec<Event>,
2856        diary_tooltip: &Tooltip,
2857        state: &mut State<DiaryState>,
2858    ) {
2859        let locked = !self.skill_set.prerequisites_met(skill);
2860        let owned = self.skill_set.has_skill(skill);
2861        let image_color = if owned {
2862            TEXT_COLOR
2863        } else {
2864            Color::Rgba(0.41, 0.41, 0.41, 0.7)
2865        };
2866
2867        let (title, description) = util::ability_description(ability_id, self.localized_strings);
2868
2869        let sp_cost = sp(self.localized_strings, self.skill_set, skill);
2870
2871        let description = format!("{description}\n{sp_cost}");
2872
2873        let button = Button::image(util::ability_image(self.imgs, ability_id))
2874            .w_h(76.0, 76.0)
2875            .position(position)
2876            .image_color(image_color)
2877            .with_tooltip(
2878                self.tooltip_manager,
2879                &title,
2880                &description,
2881                diary_tooltip,
2882                TEXT_COLOR,
2883            )
2884            .set(state.ids.skills[widget_index], ui);
2885
2886        // Lock Image
2887        if locked {
2888            Image::new(self.imgs.lock)
2889                .w_h(76.0, 76.0)
2890                .middle_of(state.ids.skills[widget_index])
2891                .graphics_for(state.ids.skills[widget_index])
2892                .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8)))
2893                .set(state.ids.skill_lock_imgs[widget_index], ui);
2894        }
2895
2896        if button.was_clicked() {
2897            events.push(Event::UnlockSkill(skill));
2898        };
2899    }
2900}
2901
2902/// Returns skill info as a tuple of title and description.
2903///
2904/// If you want to get localized version, use `SkillStrings::localize` method
2905fn skill_strings(skill: Skill) -> SkillStrings<'static> {
2906    match skill {
2907        // general tree
2908        Skill::UnlockGroup(s) => unlock_skill_strings(s),
2909        // weapon trees
2910        Skill::Bow(s) => bow_skill_strings(s),
2911        Skill::Staff(s) => staff_skill_strings(s),
2912        Skill::Sceptre(s) => sceptre_skill_strings(s),
2913        // movement trees
2914        Skill::Climb(s) => climb_skill_strings(s),
2915        Skill::Swim(s) => swim_skill_strings(s),
2916        // mining
2917        Skill::Pick(s) => mining_skill_strings(s),
2918        _ => SkillStrings::plain("", ""),
2919    }
2920}
2921
2922fn unlock_skill_strings(group: SkillGroupKind) -> SkillStrings<'static> {
2923    match group {
2924        SkillGroupKind::Weapon(ToolKind::Sword) => {
2925            SkillStrings::plain("hud-skill-unlck_sword_title", "hud-skill-unlck_sword")
2926        },
2927        SkillGroupKind::Weapon(ToolKind::Axe) => {
2928            SkillStrings::plain("hud-skill-unlck_axe_title", "hud-skill-unlck_axe")
2929        },
2930        SkillGroupKind::Weapon(ToolKind::Hammer) => {
2931            SkillStrings::plain("hud-skill-unlck_hammer_title", "hud-skill-unlck_hammer")
2932        },
2933        SkillGroupKind::Weapon(ToolKind::Bow) => {
2934            SkillStrings::plain("hud-skill-unlck_bow_title", "hud-skill-unlck_bow")
2935        },
2936        SkillGroupKind::Weapon(ToolKind::Staff) => {
2937            SkillStrings::plain("hud-skill-unlck_staff_title", "hud-skill-unlck_staff")
2938        },
2939        SkillGroupKind::Weapon(ToolKind::Sceptre) => {
2940            SkillStrings::plain("hud-skill-unlck_sceptre_title", "hud-skill-unlck_sceptre")
2941        },
2942        SkillGroupKind::General
2943        | SkillGroupKind::Weapon(
2944            ToolKind::Dagger
2945            | ToolKind::Shield
2946            | ToolKind::Spear
2947            | ToolKind::Blowgun
2948            | ToolKind::Debug
2949            | ToolKind::Farming
2950            | ToolKind::Instrument
2951            | ToolKind::Throwable
2952            | ToolKind::Pick
2953            | ToolKind::Shovel
2954            | ToolKind::Natural
2955            | ToolKind::Empty,
2956        ) => {
2957            tracing::warn!("Requesting title for unlocking unexpected skill group");
2958            SkillStrings::Empty
2959        },
2960    }
2961}
2962
2963fn bow_skill_strings(skill: BowSkill) -> SkillStrings<'static> {
2964    let modifiers = SKILL_MODIFIERS.bow_tree;
2965    match skill {
2966        // Passives
2967        BowSkill::ProjSpeed => SkillStrings::with_mult(
2968            "hud-skill-bow_projectile_speed_title",
2969            "hud-skill-bow_projectile_speed",
2970            modifiers.universal.projectile_speed,
2971        ),
2972        // Charged upgrades
2973        BowSkill::CDamage => SkillStrings::with_mult(
2974            "hud-skill-bow_charged_damage_title",
2975            "hud-skill-bow_charged_damage",
2976            modifiers.charged.damage_scaling,
2977        ),
2978        BowSkill::CRegen => SkillStrings::with_mult(
2979            "hud-skill-bow_charged_energy_regen_title",
2980            "hud-skill-bow_charged_energy_regen",
2981            modifiers.charged.regen_scaling,
2982        ),
2983        BowSkill::CKnockback => SkillStrings::with_mult(
2984            "hud-skill-bow_charged_knockback_title",
2985            "hud-skill-bow_charged_knockback",
2986            modifiers.charged.knockback_scaling,
2987        ),
2988        BowSkill::CSpeed => SkillStrings::with_mult(
2989            "hud-skill-bow_charged_speed_title",
2990            "hud-skill-bow_charged_speed",
2991            modifiers.charged.charge_rate,
2992        ),
2993        BowSkill::CMove => SkillStrings::with_mult(
2994            "hud-skill-bow_charged_move_title",
2995            "hud-skill-bow_charged_move",
2996            modifiers.charged.move_speed,
2997        ),
2998        // Repeater upgrades
2999        BowSkill::RDamage => SkillStrings::with_mult(
3000            "hud-skill-bow_repeater_damage_title",
3001            "hud-skill-bow_repeater_damage",
3002            modifiers.repeater.power,
3003        ),
3004        BowSkill::RCost => SkillStrings::with_mult(
3005            "hud-skill-bow_repeater_cost_title",
3006            "hud-skill-bow_repeater_cost",
3007            modifiers.repeater.energy_cost,
3008        ),
3009        BowSkill::RSpeed => SkillStrings::with_mult(
3010            "hud-skill-bow_repeater_speed_title",
3011            "hud-skill-bow_repeater_speed",
3012            modifiers.repeater.max_speed,
3013        ),
3014        // Shotgun upgrades
3015        BowSkill::UnlockShotgun => SkillStrings::plain(
3016            "hud-skill-bow_shotgun_unlock_title",
3017            "hud-skill-bow_shotgun_unlock",
3018        ),
3019        BowSkill::SDamage => SkillStrings::with_mult(
3020            "hud-skill-bow_shotgun_damage_title",
3021            "hud-skill-bow_shotgun_damage",
3022            modifiers.shotgun.power,
3023        ),
3024        BowSkill::SCost => SkillStrings::with_mult(
3025            "hud-skill-bow_shotgun_cost_title",
3026            "hud-skill-bow_shotgun_cost",
3027            modifiers.shotgun.energy_cost,
3028        ),
3029        BowSkill::SArrows => SkillStrings::with_const(
3030            "hud-skill-bow_shotgun_arrow_count_title",
3031            "hud-skill-bow_shotgun_arrow_count",
3032            modifiers.shotgun.num_projectiles,
3033        ),
3034        BowSkill::SSpread => SkillStrings::with_mult(
3035            "hud-skill-bow_shotgun_spread_title",
3036            "hud-skill-bow_shotgun_spread",
3037            modifiers.shotgun.spread,
3038        ),
3039    }
3040}
3041
3042fn staff_skill_strings(skill: StaffSkill) -> SkillStrings<'static> {
3043    let modifiers = SKILL_MODIFIERS.staff_tree;
3044    match skill {
3045        // Basic ranged upgrades
3046        StaffSkill::BDamage => SkillStrings::with_mult(
3047            "hud-skill-st_damage_title",
3048            "hud-skill-st_damage",
3049            modifiers.fireball.power,
3050        ),
3051        StaffSkill::BRegen => SkillStrings::with_mult(
3052            "hud-skill-st_energy_regen_title",
3053            "hud-skill-st_energy_regen",
3054            modifiers.fireball.regen,
3055        ),
3056        StaffSkill::BRadius => SkillStrings::with_mult(
3057            "hud-skill-st_explosion_radius_title",
3058            "hud-skill-st_explosion_radius",
3059            modifiers.fireball.range,
3060        ),
3061        // Flamethrower upgrades
3062        StaffSkill::FDamage => SkillStrings::with_mult(
3063            "hud-skill-st_flamethrower_damage_title",
3064            "hud-skill-st_flamethrower_damage",
3065            modifiers.flamethrower.damage,
3066        ),
3067        StaffSkill::FRange => SkillStrings::with_mult(
3068            "hud-skill-st_flamethrower_range_title",
3069            "hud-skill-st_flamethrower_range",
3070            modifiers.flamethrower.range,
3071        ),
3072        StaffSkill::FDrain => SkillStrings::with_mult(
3073            "hud-skill-st_energy_drain_title",
3074            "hud-skill-st_energy_drain",
3075            modifiers.flamethrower.energy_drain,
3076        ),
3077        StaffSkill::FVelocity => SkillStrings::with_mult(
3078            "hud-skill-st_flame_velocity_title",
3079            "hud-skill-st_flame_velocity",
3080            modifiers.flamethrower.velocity,
3081        ),
3082        // Shockwave upgrades
3083        StaffSkill::UnlockShockwave => SkillStrings::plain(
3084            "hud-skill-st_shockwave_unlock_title",
3085            "hud-skill-st_shockwave_unlock",
3086        ),
3087        StaffSkill::SDamage => SkillStrings::with_mult(
3088            "hud-skill-st_shockwave_damage_title",
3089            "hud-skill-st_shockwave_damage",
3090            modifiers.shockwave.damage,
3091        ),
3092        StaffSkill::SKnockback => SkillStrings::with_mult(
3093            "hud-skill-st_shockwave_knockback_title",
3094            "hud-skill-st_shockwave_knockback",
3095            modifiers.shockwave.knockback,
3096        ),
3097        StaffSkill::SRange => SkillStrings::with_mult(
3098            "hud-skill-st_shockwave_range_title",
3099            "hud-skill-st_shockwave_range",
3100            modifiers.shockwave.duration,
3101        ),
3102        StaffSkill::SCost => SkillStrings::with_mult(
3103            "hud-skill-st_shockwave_cost_title",
3104            "hud-skill-st_shockwave_cost",
3105            modifiers.shockwave.energy_cost,
3106        ),
3107    }
3108}
3109
3110fn sceptre_skill_strings(skill: SceptreSkill) -> SkillStrings<'static> {
3111    let modifiers = SKILL_MODIFIERS.sceptre_tree;
3112    match skill {
3113        // Lifesteal beam upgrades
3114        SceptreSkill::LDamage => SkillStrings::with_mult(
3115            "hud-skill-sc_lifesteal_damage_title",
3116            "hud-skill-sc_lifesteal_damage",
3117            modifiers.beam.damage,
3118        ),
3119        SceptreSkill::LRange => SkillStrings::with_mult(
3120            "hud-skill-sc_lifesteal_range_title",
3121            "hud-skill-sc_lifesteal_range",
3122            modifiers.beam.range,
3123        ),
3124        SceptreSkill::LLifesteal => SkillStrings::with_mult(
3125            "hud-skill-sc_lifesteal_lifesteal_title",
3126            "hud-skill-sc_lifesteal_lifesteal",
3127            modifiers.beam.lifesteal,
3128        ),
3129        SceptreSkill::LRegen => SkillStrings::with_mult(
3130            "hud-skill-sc_lifesteal_regen_title",
3131            "hud-skill-sc_lifesteal_regen",
3132            modifiers.beam.energy_regen,
3133        ),
3134        // Healing aura upgrades
3135        SceptreSkill::HHeal => SkillStrings::with_mult(
3136            "hud-skill-sc_heal_heal_title",
3137            "hud-skill-sc_heal_heal",
3138            modifiers.healing_aura.strength,
3139        ),
3140        SceptreSkill::HRange => SkillStrings::with_mult(
3141            "hud-skill-sc_heal_range_title",
3142            "hud-skill-sc_heal_range",
3143            modifiers.healing_aura.range,
3144        ),
3145        SceptreSkill::HDuration => SkillStrings::with_mult(
3146            "hud-skill-sc_heal_duration_title",
3147            "hud-skill-sc_heal_duration",
3148            modifiers.healing_aura.duration,
3149        ),
3150        SceptreSkill::HCost => SkillStrings::with_mult(
3151            "hud-skill-sc_heal_cost_title",
3152            "hud-skill-sc_heal_cost",
3153            modifiers.healing_aura.energy_cost,
3154        ),
3155        // Warding aura upgrades
3156        SceptreSkill::UnlockAura => SkillStrings::plain(
3157            "hud-skill-sc_wardaura_unlock_title",
3158            "hud-skill-sc_wardaura_unlock",
3159        ),
3160        SceptreSkill::AStrength => SkillStrings::with_mult(
3161            "hud-skill-sc_wardaura_strength_title",
3162            "hud-skill-sc_wardaura_strength",
3163            modifiers.warding_aura.strength,
3164        ),
3165        SceptreSkill::ADuration => SkillStrings::with_mult(
3166            "hud-skill-sc_wardaura_duration_title",
3167            "hud-skill-sc_wardaura_duration",
3168            modifiers.warding_aura.duration,
3169        ),
3170        SceptreSkill::ARange => SkillStrings::with_mult(
3171            "hud-skill-sc_wardaura_range_title",
3172            "hud-skill-sc_wardaura_range",
3173            modifiers.warding_aura.range,
3174        ),
3175        SceptreSkill::ACost => SkillStrings::with_mult(
3176            "hud-skill-sc_wardaura_cost_title",
3177            "hud-skill-sc_wardaura_cost",
3178            modifiers.warding_aura.energy_cost,
3179        ),
3180    }
3181}
3182
3183fn climb_skill_strings(skill: ClimbSkill) -> SkillStrings<'static> {
3184    let modifiers = SKILL_MODIFIERS.general_tree.climb;
3185    match skill {
3186        ClimbSkill::Cost => SkillStrings::with_mult(
3187            "hud-skill-climbing_cost_title",
3188            "hud-skill-climbing_cost",
3189            modifiers.energy_cost,
3190        ),
3191        ClimbSkill::Speed => SkillStrings::with_mult(
3192            "hud-skill-climbing_speed_title",
3193            "hud-skill-climbing_speed",
3194            modifiers.speed,
3195        ),
3196    }
3197}
3198
3199fn swim_skill_strings(skill: SwimSkill) -> SkillStrings<'static> {
3200    let modifiers = SKILL_MODIFIERS.general_tree.swim;
3201    match skill {
3202        SwimSkill::Speed => SkillStrings::with_mult(
3203            "hud-skill-swim_speed_title",
3204            "hud-skill-swim_speed",
3205            modifiers.speed,
3206        ),
3207    }
3208}
3209
3210fn mining_skill_strings(skill: MiningSkill) -> SkillStrings<'static> {
3211    let modifiers = SKILL_MODIFIERS.mining_tree;
3212    match skill {
3213        MiningSkill::Speed => SkillStrings::with_mult(
3214            "hud-skill-pick_strike_speed_title",
3215            "hud-skill-pick_strike_speed",
3216            modifiers.speed,
3217        ),
3218        MiningSkill::OreGain => SkillStrings::with_const(
3219            "hud-skill-pick_strike_oregain_title",
3220            "hud-skill-pick_strike_oregain",
3221            (modifiers.ore_gain * 100.0).round() as u32,
3222        ),
3223        MiningSkill::GemGain => SkillStrings::with_const(
3224            "hud-skill-pick_strike_gemgain_title",
3225            "hud-skill-pick_strike_gemgain",
3226            (modifiers.gem_gain * 100.0).round() as u32,
3227        ),
3228    }
3229}
3230
3231/// Helper object used returned by `skill_strings` as source for
3232/// later internationalization and formatting.
3233enum SkillStrings<'a> {
3234    Plain {
3235        title: &'a str,
3236        desc: &'a str,
3237    },
3238    WithConst {
3239        title: &'a str,
3240        desc: &'a str,
3241        constant: u32,
3242    },
3243    WithMult {
3244        title: &'a str,
3245        desc: &'a str,
3246        multiplier: f32,
3247    },
3248    Empty,
3249}
3250
3251impl<'a> SkillStrings<'a> {
3252    fn plain(title: &'a str, desc: &'a str) -> Self { Self::Plain { title, desc } }
3253
3254    fn with_const(title: &'a str, desc: &'a str, constant: u32) -> Self {
3255        Self::WithConst {
3256            title,
3257            desc,
3258            constant,
3259        }
3260    }
3261
3262    fn with_mult(title: &'a str, desc: &'a str, multiplier: f32) -> Self {
3263        Self::WithMult {
3264            title,
3265            desc,
3266            multiplier,
3267        }
3268    }
3269
3270    fn localize<'loc>(
3271        &self,
3272        i18n: &'loc Localization,
3273        skill_set: &SkillSet,
3274        skill: Skill,
3275    ) -> (Cow<'loc, str>, Cow<'loc, str>) {
3276        match self {
3277            Self::Plain { title, desc } => {
3278                let title = i18n.get_msg(title);
3279
3280                let args = i18n::fluent_args! {
3281                    "SP" => sp(i18n, skill_set, skill),
3282                };
3283                let desc = i18n.get_msg_ctx(desc, &args);
3284
3285                (title, desc)
3286            },
3287            Self::WithConst {
3288                title,
3289                desc,
3290                constant,
3291            } => {
3292                let title = i18n.get_msg(title);
3293                let args = i18n::fluent_args! {
3294                    "boost" => constant,
3295                    "SP" => sp(i18n, skill_set, skill),
3296                };
3297                let desc = i18n.get_msg_ctx(desc, &args);
3298
3299                (title, desc)
3300            },
3301            Self::WithMult {
3302                title,
3303                desc,
3304                multiplier,
3305            } => {
3306                let percentage = hud::multiplier_to_percentage(*multiplier).abs();
3307
3308                let title = i18n.get_msg(title);
3309
3310                let args = i18n::fluent_args! {
3311                    "boost" => format!("{percentage:.0}"),
3312                    "SP" => sp(i18n, skill_set, skill),
3313                };
3314                let desc = i18n.get_msg_ctx(desc, &args);
3315
3316                (title, desc)
3317            },
3318            Self::Empty => (Cow::Borrowed(""), Cow::Borrowed("")),
3319        }
3320    }
3321}
3322
3323/// The number of variants of the [`CharacterStat`] enum.
3324const STAT_COUNT: usize = 15;
3325
3326#[derive(EnumIter)]
3327enum CharacterStat {
3328    Name,
3329    BattleMode,
3330    Waypoint,
3331    Hitpoints,
3332    Energy,
3333    Poise,
3334    CombatRating,
3335    Protection,
3336    StunResistance,
3337    PrecisionPower,
3338    EnergyReward,
3339    Stealth,
3340    WeaponPower,
3341    WeaponSpeed,
3342    WeaponEffectPower,
3343}
3344
3345impl CharacterStat {
3346    fn localized_str<'a>(&self, i18n: &'a Localization) -> Cow<'a, str> {
3347        use CharacterStat::*;
3348
3349        match self {
3350            Name => i18n.get_msg("character_window-character_name"),
3351            BattleMode => i18n.get_msg("hud-battle-mode"),
3352            Waypoint => i18n.get_msg("hud-waypoint"),
3353            Hitpoints => i18n.get_msg("hud-bag-health"),
3354            Energy => i18n.get_msg("hud-bag-energy"),
3355            CombatRating => i18n.get_msg("hud-bag-combat_rating"),
3356            Protection => i18n.get_msg("hud-bag-protection"),
3357            StunResistance => i18n.get_msg("hud-bag-stun_res"),
3358            Poise => i18n.get_msg("common-stats-poise_res"),
3359            PrecisionPower => i18n.get_msg("common-stats-precision_power"),
3360            EnergyReward => i18n.get_msg("common-stats-energy_reward"),
3361            Stealth => i18n.get_msg("common-stats-stealth"),
3362            WeaponPower => i18n.get_msg("common-stats-power"),
3363            WeaponSpeed => i18n.get_msg("common-stats-speed"),
3364            WeaponEffectPower => i18n.get_msg("common-stats-effect-power"),
3365        }
3366    }
3367}
3368
3369fn sp<'loc>(i18n: &'loc Localization, skill_set: &SkillSet, skill: Skill) -> Cow<'loc, str> {
3370    let current_level = skill_set.skill_level(skill);
3371    if matches!(current_level, Ok(level) if level == skill.max_level()) {
3372        Cow::Borrowed("")
3373    } else {
3374        i18n.get_msg_ctx("hud-skill-req_sp", &i18n::fluent_args! {
3375            "number" => skill_set.skill_cost(skill),
3376        })
3377    }
3378}