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