veloren_voxygen/hud/
diary.rs

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