Skip to main content

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