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