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 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 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 stat_names[],
179 stat_values[],
180 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#[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#[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 let diary_tooltip = Tooltip::new({
397 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 let frame_ani = (self.pulse * 4.0).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 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 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 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 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 let sel_section = &self.show.diary_fields.section;
463
464 let sections_len = DiarySection::iter().enumerate().len();
465
466 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 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 §ion_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 let sel_tab = &self.show.diary_fields.skilltreetab;
535
536 let skill_trees_len = DiarySkillTree::iter().enumerate().len();
537
538 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 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 let locked = !self.skill_set.skill_group_accessible(skill_group);
562
563 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 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 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 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 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 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 let x = 200.0;
767 let y = 100.0;
768 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 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 .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 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 filled_slot: self.imgs.inv_slot,
846 selected_slot: self.imgs.inv_slot_sel,
847 background_color: Some(UI_MAIN),
848 content_size: ContentSize {
849 width_height_ratio: 1.0,
850 max_fraction: 0.9,
851 },
852 selected_content_scale: 1.067,
853 amount_font: self.fonts.cyri.conrod_id,
854 amount_margins: Vec2::new(-4.0, 0.0),
855 amount_font_size: self.fonts.cyri.scale(12),
856 amount_text_color: TEXT_COLOR,
857 content_source: &(
858 self.active_abilities,
859 self.inventory,
860 self.skill_set,
861 self.context,
862 Some(self.char_state),
863 self.stats,
864 ),
865 image_source: self.imgs,
866 slot_manager: Some(self.slot_manager),
867 pulse: 0.0,
868 };
869
870 for i in 0..BASE_ABILITY_LIMIT {
871 let ability_id = self
872 .active_abilities
873 .get_ability(
874 AbilityInput::Auxiliary(i),
875 Some(self.inventory),
876 Some(self.skill_set),
877 self.stats,
878 )
879 .ability_id(
880 Some(self.char_state),
881 Some(self.inventory),
882 Some(self.skill_set),
883 self.context,
884 );
885 let (ability_title, ability_desc) = if let Some(ability_id) = ability_id {
886 util::ability_description(ability_id, self.localized_strings)
887 } else {
888 (
889 Cow::Borrowed("Drag an ability here to use it."),
890 Cow::Borrowed(""),
891 )
892 };
893
894 let image_size = 80.0;
895 let image_offsets = 92.0 * i as f64;
896
897 let slot = AbilitySlot::Slot(i);
898 let mut ability_slot = slot_maker.fabricate(slot, [image_size; 2]);
899
900 if i == 0 {
901 ability_slot = ability_slot.top_left_with_margins_on(
902 state.ids.spellbook_skills_bg,
903 0.0,
904 32.0 + image_offsets,
905 );
906 } else {
907 ability_slot =
908 ability_slot.right_from(state.ids.active_abilities[i - 1], 4.0)
909 }
910 ability_slot
911 .with_tooltip(
912 self.tooltip_manager,
913 &ability_title,
914 &ability_desc,
915 &diary_tooltip,
916 TEXT_COLOR,
917 )
918 .set(state.ids.active_abilities[i], ui);
919
920 let keys = &self.global_state.settings.controls;
922 let ability_key = [
923 GameInput::Slot1,
924 GameInput::Slot2,
925 GameInput::Slot3,
926 GameInput::Slot4,
927 GameInput::Slot5,
928 ]
929 .get(i)
930 .and_then(|input| keys.get_binding(*input))
931 .map(|key| key.display_shortest())
932 .unwrap_or_default();
933
934 Text::new(&ability_key)
935 .top_left_with_margins_on(state.ids.active_abilities[i], 0.0, 4.0)
936 .font_id(self.fonts.cyri.conrod_id)
937 .font_size(self.fonts.cyri.scale(20))
938 .color(TEXT_COLOR)
939 .graphics_for(state.ids.active_abilities[i])
940 .set(state.ids.active_abilities_keys[i], ui);
941 }
942
943 let abilities: Vec<_> = ActiveAbilities::all_available_abilities(
944 Some(self.inventory),
945 Some(self.skill_set),
946 )
947 .into_iter()
948 .map(|a| {
949 (
950 Ability::from(a).ability_id(
951 Some(self.char_state),
952 Some(self.inventory),
953 Some(self.skill_set),
954 self.context,
955 ),
956 a,
957 )
958 })
959 .collect();
960
961 const ABILITIES_PER_PAGE: usize = 12;
962
963 let page_indices = (abilities.len().saturating_sub(1)) / ABILITIES_PER_PAGE;
964
965 if state.ability_page > page_indices {
966 state.update(|s| s.ability_page = 0);
967 }
968
969 state.update(|s| {
970 s.ids
971 .abilities
972 .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
973 });
974 state.update(|s| {
975 s.ids
976 .abilities_dual
977 .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
978 });
979 state.update(|s| {
980 s.ids
981 .ability_titles
982 .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
983 });
984 state.update(|s| {
985 s.ids
986 .ability_frames
987 .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
988 });
989 state.update(|s| {
990 s.ids
991 .ability_descs
992 .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator())
993 });
994
995 let left_arrow = Button::image(if state.ability_page > 0 {
998 self.imgs.arrow_l
999 } else {
1000 self.imgs.arrow_l_inactive
1001 })
1002 .bottom_left_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1003 .w_h(48.0, 55.0);
1004 if state.ability_page > 0 {
1006 if left_arrow
1007 .hover_image(self.imgs.arrow_l_click)
1008 .press_image(self.imgs.arrow_l)
1009 .set(state.ids.ability_page_left, ui)
1010 .was_clicked()
1011 {
1012 state.update(|s| s.ability_page -= 1);
1013 }
1014 } else {
1015 left_arrow.set(state.ids.ability_page_left, ui);
1016 }
1017 let right_arrow = Button::image(if state.ability_page < page_indices {
1019 self.imgs.arrow_r
1020 } else {
1021 self.imgs.arrow_r_inactive
1022 })
1023 .bottom_right_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1024 .w_h(48.0, 55.0);
1025 if state.ability_page < page_indices {
1026 if right_arrow
1028 .hover_image(self.imgs.arrow_r_click)
1029 .press_image(self.imgs.arrow_r)
1030 .set(state.ids.ability_page_right, ui)
1031 .was_clicked()
1032 {
1033 state.update(|s| s.ability_page += 1);
1034 };
1035 } else {
1036 right_arrow.set(state.ids.ability_page_right, ui);
1037 }
1038
1039 let ability_start = state.ability_page * ABILITIES_PER_PAGE;
1040
1041 let mut slot_maker = SlotMaker {
1042 empty_slot: self.imgs.inv_slot,
1043 filled_slot: self.imgs.inv_slot,
1044 selected_slot: self.imgs.inv_slot_sel,
1045 background_color: Some(UI_MAIN),
1046 content_size: ContentSize {
1047 width_height_ratio: 1.0,
1048 max_fraction: 1.0,
1049 },
1050 selected_content_scale: 1.067,
1051 amount_font: self.fonts.cyri.conrod_id,
1052 amount_margins: Vec2::new(-4.0, 0.0),
1053 amount_font_size: self.fonts.cyri.scale(12),
1054 amount_text_color: TEXT_COLOR,
1055 content_source: &(
1056 self.active_abilities,
1057 self.inventory,
1058 self.skill_set,
1059 self.context,
1060 Some(self.char_state),
1061 self.stats,
1062 ),
1063 image_source: self.imgs,
1064 slot_manager: Some(self.slot_manager),
1065 pulse: 0.0,
1066 };
1067
1068 let same_weap_kinds = self
1069 .inventory
1070 .equipped(EquipSlot::ActiveMainhand)
1071 .zip(self.inventory.equipped(EquipSlot::ActiveOffhand))
1072 .is_some_and(|(a, b)| {
1073 if let (ItemKind::Tool(tool_a), ItemKind::Tool(tool_b)) =
1074 (&*a.kind(), &*b.kind())
1075 {
1076 (a.ability_spec(), tool_a.kind) == (b.ability_spec(), tool_b.kind)
1077 } else {
1078 false
1079 }
1080 });
1081
1082 for (id_index, (ability_id, ability)) in abilities
1083 .iter()
1084 .skip(ability_start)
1085 .take(ABILITIES_PER_PAGE)
1086 .enumerate()
1087 {
1088 let (ability_title, ability_desc) =
1089 util::ability_description(ability_id.unwrap_or(""), self.localized_strings);
1090
1091 let (align_state, image_offsets) = if id_index < 6 {
1092 (state.ids.sb_page_left_align, 120.0 * id_index as f64)
1093 } else {
1094 (state.ids.sb_page_right_align, 120.0 * (id_index - 6) as f64)
1095 };
1096
1097 Image::new(if same_weap_kinds {
1098 self.imgs.ability_frame_dual
1099 } else {
1100 self.imgs.ability_frame
1101 })
1102 .w_h(566.0, 108.0)
1103 .top_left_with_margins_on(align_state, 16.0 + image_offsets, 16.0)
1104 .color(Some(UI_HIGHLIGHT_0))
1105 .set(state.ids.ability_frames[id_index], ui);
1106
1107 let slot = AbilitySlot::Ability(*ability);
1108 slot_maker
1109 .fabricate(slot, [100.0; 2])
1110 .top_left_with_margins_on(align_state, 20.0 + image_offsets, 20.0)
1111 .set(state.ids.abilities[id_index], ui);
1112
1113 if same_weap_kinds && let AuxiliaryAbility::MainWeapon(slot) = ability {
1114 let ability = AuxiliaryAbility::OffWeapon(*slot);
1115
1116 let slot = AbilitySlot::Ability(ability);
1117 slot_maker
1118 .fabricate(slot, [100.0; 2])
1119 .top_right_with_margins_on(align_state, 20.0 + image_offsets, 20.0)
1120 .set(state.ids.abilities_dual[id_index], ui);
1121 }
1122 let text_width = 299.0 * 2.0
1124 - if same_weap_kinds && matches!(ability, AuxiliaryAbility::MainWeapon(_)) {
1125 (20.0 + 100.0 + 10.0) * 2.0
1128 } else {
1129 20.0 * 2.0 + 100.0
1132 };
1133 Text::new(&ability_title)
1134 .top_left_with_margins_on(state.ids.abilities[id_index], 5.0, 110.0)
1135 .font_id(self.fonts.cyri.conrod_id)
1136 .font_size(self.fonts.cyri.scale(28))
1137 .color(TEXT_COLOR)
1138 .w(text_width)
1139 .graphics_for(state.ids.abilities[id_index])
1140 .set(state.ids.ability_titles[id_index], ui);
1141 Text::new(&ability_desc)
1142 .top_left_with_margins_on(state.ids.abilities[id_index], 40.0, 110.0)
1143 .font_id(self.fonts.cyri.conrod_id)
1144 .font_size(self.fonts.cyri.scale(13))
1145 .color(TEXT_COLOR)
1146 .w(text_width)
1147 .graphics_for(state.ids.abilities[id_index])
1148 .set(state.ids.ability_descs[id_index], ui);
1149 }
1150
1151 events
1152 },
1153 DiarySection::Character => {
1154 Image::new(self.imgs.book_bg)
1156 .w_h(299.0 * 4.0, 184.0 * 4.0)
1157 .mid_top_with_margin_on(state.ids.content_align, 4.0)
1158 .set(state.ids.spellbook_art, ui);
1159
1160 if state.ids.stat_names.len() < STAT_COUNT {
1161 state.update(|s| {
1162 s.ids
1163 .stat_names
1164 .resize(STAT_COUNT, &mut ui.widget_id_generator());
1165 s.ids
1166 .stat_values
1167 .resize(STAT_COUNT, &mut ui.widget_id_generator());
1168 });
1169 }
1170
1171 for (i, stat) in CharacterStat::iter().enumerate() {
1172 let localized_name = stat.localized_str(self.localized_strings);
1174 let mut txt = Text::new(&localized_name)
1175 .font_id(self.fonts.cyri.conrod_id)
1176 .font_size(self.fonts.cyri.scale(29))
1177 .color(BLACK);
1178
1179 if i == 0 {
1180 txt = txt.top_left_with_margins_on(state.ids.spellbook_art, 20.0, 20.0);
1181 } else {
1182 txt = txt.down_from(state.ids.stat_names[i - 1], 10.0);
1183 };
1184 txt.set(state.ids.stat_names[i], ui);
1185
1186 let main_weap_stats = self
1187 .inventory
1188 .equipped(EquipSlot::ActiveMainhand)
1189 .and_then(|item| match &*item.kind() {
1190 ItemKind::Tool(tool) => {
1191 Some(tool.stats(item.stats_durability_multiplier()))
1192 },
1193 _ => None,
1194 });
1195
1196 let off_weap_stats = self
1197 .inventory
1198 .equipped(EquipSlot::ActiveOffhand)
1199 .and_then(|item| match &*item.kind() {
1200 ItemKind::Tool(tool) => {
1201 Some(tool.stats(item.stats_durability_multiplier()))
1202 },
1203 _ => None,
1204 });
1205
1206 let (name, _gender, battle_mode) = self
1207 .client
1208 .player_list()
1209 .get(self.uid)
1210 .and_then(|info| info.character.as_ref())
1211 .map_or_else(
1212 || ("Unknown".to_string(), None, BattleMode::PvP),
1213 |character_info| {
1214 (
1215 self.localized_strings.get_content(&character_info.name),
1216 character_info.gender,
1217 character_info.battle_mode,
1218 )
1219 },
1220 );
1221
1222 let value = match stat {
1224 CharacterStat::Name => name,
1225 CharacterStat::BattleMode => match battle_mode {
1226 BattleMode::PvP => "PvP".to_string(),
1227 BattleMode::PvE => "PvE".to_string(),
1228 },
1229 CharacterStat::Waypoint => self
1230 .client
1231 .waypoint()
1232 .as_ref()
1233 .cloned()
1234 .unwrap_or_else(|| "Unknown".to_string()),
1235 CharacterStat::Hitpoints => format!("{}", self.health.maximum() as u32),
1236 CharacterStat::Energy => format!("{}", self.energy.maximum() as u32),
1237 CharacterStat::Poise => format!("{}", self.poise.maximum() as u32),
1238 CharacterStat::CombatRating => {
1239 let cr = combat::combat_rating(
1240 self.inventory,
1241 self.health,
1242 self.energy,
1243 self.poise,
1244 self.skill_set,
1245 *self.body,
1246 self.msm,
1247 );
1248 format!("{:.2}", cr * 10.0)
1249 },
1250 CharacterStat::Protection => {
1251 let protection =
1252 combat::compute_protection(Some(self.inventory), self.msm);
1253 match protection {
1254 Some(prot) => format!("{}", prot),
1255 None => String::from("Invincible"),
1256 }
1257 },
1258 CharacterStat::StunResistance => {
1259 let stun_res = Poise::compute_poise_damage_reduction(
1260 Some(self.inventory),
1261 self.msm,
1262 None,
1263 self.stats,
1264 );
1265 format!("{:.2}%", stun_res * 100.0)
1266 },
1267 CharacterStat::PrecisionPower => {
1268 let precision_power =
1269 combat::compute_precision_mult(Some(self.inventory), self.msm);
1270 format!("x{:.2}", precision_power)
1271 },
1272 CharacterStat::EnergyReward => {
1273 let energy_rew =
1274 combat::compute_energy_reward_mod(Some(self.inventory), self.msm);
1275 format!("{:+.0}%", (energy_rew - 1.0) * 100.0)
1276 },
1277 CharacterStat::Stealth => {
1278 let stealth_perception_multiplier =
1279 combat::perception_dist_multiplier_from_stealth(
1280 Some(self.inventory),
1281 None,
1282 self.msm,
1283 );
1284 let txt =
1285 format!("{:+.1}%", (1.0 - stealth_perception_multiplier) * 100.0);
1286
1287 txt
1288 },
1289 CharacterStat::WeaponPower => match (main_weap_stats, off_weap_stats) {
1290 (Some(m_stats), Some(o_stats)) => {
1291 format!("{} {}", m_stats.power * 10.0, o_stats.power * 10.0)
1292 },
1293 (Some(stats), None) | (None, Some(stats)) => {
1294 format!("{}", stats.power * 10.0)
1295 },
1296 (None, None) => String::new(),
1297 },
1298 CharacterStat::WeaponSpeed => {
1299 let spd_fmt = |sp| (sp - 1.0) * 100.0;
1300 match (main_weap_stats, off_weap_stats) {
1301 (Some(m_stats), Some(o_stats)) => format!(
1302 "{:+.0}% {:+.0}%",
1303 spd_fmt(m_stats.speed),
1304 spd_fmt(o_stats.speed)
1305 ),
1306 (Some(stats), None) | (None, Some(stats)) => {
1307 format!("{:+.0}%", spd_fmt(stats.speed))
1308 },
1309 _ => String::new(),
1310 }
1311 },
1312 CharacterStat::WeaponEffectPower => match (main_weap_stats, off_weap_stats)
1313 {
1314 (Some(m_stats), Some(o_stats)) => {
1315 format!(
1316 "{} {}",
1317 m_stats.effect_power * 10.0,
1318 o_stats.effect_power * 10.0
1319 )
1320 },
1321 (Some(stats), None) | (None, Some(stats)) => {
1322 format!("{}", stats.effect_power * 10.0)
1323 },
1324 (None, None) => String::new(),
1325 },
1326 };
1327
1328 let mut number = Text::new(&value)
1329 .font_id(self.fonts.cyri.conrod_id)
1330 .font_size(self.fonts.cyri.scale(29))
1331 .color(BLACK);
1332
1333 if i == 0 {
1334 number = number.right_from(state.ids.stat_names[i], 165.0);
1335 } else {
1336 number = number.down_from(state.ids.stat_values[i - 1], 10.0);
1337 };
1338 number.set(state.ids.stat_values[i], ui);
1339 }
1340
1341 events
1342 },
1343 DiarySection::Recipes => {
1344 Image::new(self.imgs.book_bg)
1346 .w_h(299.0 * 4.0, 184.0 * 4.0)
1347 .mid_top_with_margin_on(state.ids.content_align, 4.0)
1348 .set(state.ids.spellbook_art, ui);
1349
1350 Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1351 .top_left_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1352 .set(state.ids.sb_page_left_align, ui);
1353 Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1354 .top_right_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1355 .set(state.ids.sb_page_right_align, ui);
1356
1357 const RECIPES_PER_PAGE: usize = 36;
1358
1359 let page_index_max =
1360 self.inventory.recipe_groups_iter().len().saturating_sub(1) / RECIPES_PER_PAGE;
1361
1362 if state.recipe_page > page_index_max {
1363 state.update(|s| s.recipe_page = 0);
1364 }
1365
1366 let left_arrow = Button::image(if state.recipe_page > 0 {
1369 self.imgs.arrow_l
1370 } else {
1371 self.imgs.arrow_l_inactive
1372 })
1373 .bottom_left_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1374 .w_h(48.0, 55.0);
1375 if state.recipe_page > 0 {
1377 if left_arrow
1378 .hover_image(self.imgs.arrow_l_click)
1379 .press_image(self.imgs.arrow_l)
1380 .set(state.ids.ability_page_left, ui)
1381 .was_clicked()
1382 {
1383 state.update(|s| s.recipe_page -= 1);
1384 }
1385 } else {
1386 left_arrow.set(state.ids.ability_page_left, ui);
1387 }
1388 let right_arrow = Button::image(if state.recipe_page < page_index_max {
1390 self.imgs.arrow_r
1391 } else {
1392 self.imgs.arrow_r_inactive
1393 })
1394 .bottom_right_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1395 .w_h(48.0, 55.0);
1396 if state.recipe_page < page_index_max {
1397 if right_arrow
1399 .hover_image(self.imgs.arrow_r_click)
1400 .press_image(self.imgs.arrow_r)
1401 .set(state.ids.ability_page_right, ui)
1402 .was_clicked()
1403 {
1404 state.update(|s| s.recipe_page += 1);
1405 };
1406 } else {
1407 right_arrow.set(state.ids.ability_page_right, ui);
1408 }
1409
1410 state.update(|s| {
1411 s.ids
1412 .recipe_groups
1413 .resize(RECIPES_PER_PAGE, &mut ui.widget_id_generator())
1414 });
1415
1416 for (i, rg) in self
1417 .inventory
1418 .recipe_groups_iter()
1419 .skip(state.recipe_page * RECIPES_PER_PAGE)
1420 .take(RECIPES_PER_PAGE)
1421 .enumerate()
1422 {
1423 let (title, _desc) =
1424 util::item_text(rg, self.localized_strings, self.item_i18n);
1425
1426 let mut text = Text::new(&title)
1427 .font_id(self.fonts.cyri.conrod_id)
1428 .font_size(self.fonts.cyri.scale(29))
1429 .color(BLACK);
1430
1431 if i == 0 {
1432 text =
1433 text.top_left_with_margins_on(state.ids.sb_page_left_align, 20.0, 20.0);
1434 } else if i == 18 {
1435 text = text.top_left_with_margins_on(
1436 state.ids.sb_page_right_align,
1437 20.0,
1438 20.0,
1439 );
1440 } else {
1441 text = text.down_from(state.ids.recipe_groups[i - 1], 10.0);
1442 }
1443 text.set(state.ids.recipe_groups[i], ui);
1444 }
1445
1446 events
1447 },
1448 }
1449 }
1450}
1451
1452enum SkillIcon<'a> {
1453 Unlockable {
1454 skill: Skill,
1455 image: image::Id,
1456 position: PositionSpecifier,
1457 id: widget::Id,
1458 },
1459 Descriptive {
1460 title: &'a str,
1461 desc: &'a str,
1462 image: image::Id,
1463 position: PositionSpecifier,
1464 id: widget::Id,
1465 },
1466 Ability {
1467 skill: Skill,
1468 ability_id: &'a str,
1469 position: PositionSpecifier,
1470 },
1471}
1472
1473impl Diary<'_> {
1474 fn handle_general_skills_window(
1475 &mut self,
1476 diary_tooltip: &Tooltip,
1477 state: &mut State<DiaryState>,
1478 ui: &mut UiCell,
1479 mut events: Vec<Event>,
1480 ) -> Vec<Event> {
1481 let tree_title = &self.localized_strings.get_msg("common-weapons-general");
1482 Text::new(tree_title)
1483 .mid_top_with_margin_on(state.ids.content_align, 2.0)
1484 .font_id(self.fonts.cyri.conrod_id)
1485 .font_size(self.fonts.cyri.scale(34))
1486 .color(TEXT_COLOR)
1487 .set(state.ids.tree_title_txt, ui);
1488
1489 let skills_top_l = 6;
1492 let skills_top_r = 0;
1493 let skills_bot_l = 0;
1494 let skills_bot_r = 5;
1495
1496 self.setup_state_for_skill_icons(
1497 state,
1498 ui,
1499 skills_top_l,
1500 skills_top_r,
1501 skills_bot_l,
1502 skills_bot_r,
1503 );
1504
1505 use SkillGroupKind::*;
1506 use ToolKind::*;
1507 Image::new(animate_by_pulse(
1509 &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1510 "example_general_combat_left".to_string(),
1511 )),
1512 self.pulse,
1513 ))
1514 .wh(ART_SIZE)
1515 .middle_of(state.ids.content_align)
1516 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1517 .set(state.ids.general_combat_render_0, ui);
1518
1519 Image::new(animate_by_pulse(
1520 &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1521 "example_general_combat_right".to_string(),
1522 )),
1523 self.pulse,
1524 ))
1525 .wh(ART_SIZE)
1526 .middle_of(state.ids.general_combat_render_0)
1527 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1528 .set(state.ids.general_combat_render_1, ui);
1529
1530 use PositionSpecifier::MidTopWithMarginOn;
1531 let skill_buttons = &[
1532 SkillIcon::Unlockable {
1538 skill: Skill::UnlockGroup(Weapon(Sword)),
1539 image: self.imgs.unlock_sword_skill,
1540 position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
1541 id: state.ids.skill_general_tree_0,
1542 },
1543 SkillIcon::Unlockable {
1544 skill: Skill::UnlockGroup(Weapon(Axe)),
1545 image: self.imgs.unlock_axe_skill,
1546 position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
1547 id: state.ids.skill_general_tree_1,
1548 },
1549 SkillIcon::Unlockable {
1550 skill: Skill::UnlockGroup(Weapon(Hammer)),
1551 image: self.imgs.unlock_hammer_skill,
1552 position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
1553 id: state.ids.skill_general_tree_2,
1554 },
1555 SkillIcon::Unlockable {
1556 skill: Skill::UnlockGroup(Weapon(Bow)),
1557 image: self.imgs.unlock_bow_skill,
1558 position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
1559 id: state.ids.skill_general_tree_3,
1560 },
1561 SkillIcon::Unlockable {
1562 skill: Skill::UnlockGroup(Weapon(Staff)),
1563 image: self.imgs.unlock_staff_skill0,
1564 position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
1565 id: state.ids.skill_general_tree_4,
1566 },
1567 SkillIcon::Unlockable {
1568 skill: Skill::UnlockGroup(Weapon(Sceptre)),
1569 image: self.imgs.unlock_sceptre_skill,
1570 position: MidTopWithMarginOn(state.ids.skills_top_l[5], 3.0),
1571 id: state.ids.skill_general_tree_5,
1572 },
1573 SkillIcon::Descriptive {
1575 title: "hud-skill-climbing_title",
1576 desc: "hud-skill-climbing",
1577 image: self.imgs.skill_climbing_skill,
1578 position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0),
1579 id: state.ids.skill_general_climb_0,
1580 },
1581 SkillIcon::Unlockable {
1582 skill: Skill::Climb(ClimbSkill::Cost),
1583 image: self.imgs.utility_cost_skill,
1584 position: MidTopWithMarginOn(state.ids.skills_bot_r[1], 3.0),
1585 id: state.ids.skill_general_climb_1,
1586 },
1587 SkillIcon::Unlockable {
1588 skill: Skill::Climb(ClimbSkill::Speed),
1589 image: self.imgs.utility_speed_skill,
1590 position: MidTopWithMarginOn(state.ids.skills_bot_r[2], 3.0),
1591 id: state.ids.skill_general_climb_2,
1592 },
1593 SkillIcon::Descriptive {
1594 title: "hud-skill-swim_title",
1595 desc: "hud-skill-swim",
1596 image: self.imgs.skill_swim_skill,
1597 position: MidTopWithMarginOn(state.ids.skills_bot_r[3], 3.0),
1598 id: state.ids.skill_general_swim_0,
1599 },
1600 SkillIcon::Unlockable {
1601 skill: Skill::Swim(SwimSkill::Speed),
1602 image: self.imgs.utility_speed_skill,
1603 position: MidTopWithMarginOn(state.ids.skills_bot_r[4], 3.0),
1604 id: state.ids.skill_general_swim_1,
1605 },
1606 ];
1607
1608 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1609 events
1610 }
1611
1612 fn handle_sword_skills_window(
1613 &mut self,
1614 diary_tooltip: &Tooltip,
1615 state: &mut State<DiaryState>,
1616 ui: &mut UiCell,
1617 mut events: Vec<Event>,
1618 ) -> Vec<Event> {
1619 Image::new(self.imgs.sword_tree_paths)
1620 .wh([1042.0, 636.0])
1621 .mid_top_with_margin_on(state.ids.content_align, 55.0)
1622 .graphics_for(state.ids.content_align)
1623 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1624 .set(state.ids.sword_path_overlay, ui);
1625
1626 Image::new(self.imgs.sword_bg)
1628 .wh([933.0, 615.0])
1629 .mid_top_with_margin_on(state.ids.content_align, 65.0)
1630 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1631 .set(state.ids.sword_bg, ui);
1632
1633 Rectangle::fill_with([169.0, 615.0], color::TRANSPARENT)
1636 .top_left_of(state.ids.sword_bg)
1637 .set(state.ids.sword_stance_left_align, ui);
1638 Rectangle::fill_with([169.0, 615.0], color::TRANSPARENT)
1639 .top_right_of(state.ids.sword_bg)
1640 .set(state.ids.sword_stance_right_align, ui);
1641
1642 Text::new(
1644 &self
1645 .localized_strings
1646 .get_msg("hud-skill-sword_stance_cleaving"),
1647 )
1648 .mid_top_with_margin_on(state.ids.sword_stance_left_align, -7.0)
1649 .font_id(self.fonts.cyri.conrod_id)
1650 .font_size(self.fonts.cyri.scale(34))
1651 .color(Color::Rgba(0.94, 0.54, 0.07, 1.0))
1652 .set(state.ids.sword_stance_cleaving_text, ui);
1653
1654 Text::new(
1655 &self
1656 .localized_strings
1657 .get_msg("hud-skill-sword_stance_cleaving"),
1658 )
1659 .x_y_position_relative_to(
1660 state.ids.sword_stance_cleaving_text,
1661 Relative::Scalar(2.5),
1662 Relative::Scalar(-2.5),
1663 )
1664 .depth(1.0)
1665 .font_id(self.fonts.cyri.conrod_id)
1666 .font_size(self.fonts.cyri.scale(34))
1667 .color(color::BLACK)
1668 .set(state.ids.sword_stance_cleaving_shadow, ui);
1669
1670 Text::new(
1672 &self
1673 .localized_strings
1674 .get_msg("hud-skill-sword_stance_agile"),
1675 )
1676 .mid_top_with_margin_on(state.ids.sword_bg, -7.0)
1677 .font_id(self.fonts.cyri.conrod_id)
1678 .font_size(self.fonts.cyri.scale(34))
1679 .color(Color::Rgba(0.81, 0.70, 0.08, 1.0))
1680 .set(state.ids.sword_stance_agile_text, ui);
1681
1682 Text::new(
1683 &self
1684 .localized_strings
1685 .get_msg("hud-skill-sword_stance_agile"),
1686 )
1687 .x_y_position_relative_to(
1688 state.ids.sword_stance_agile_text,
1689 Relative::Scalar(2.5),
1690 Relative::Scalar(-2.5),
1691 )
1692 .depth(1.0)
1693 .font_id(self.fonts.cyri.conrod_id)
1694 .font_size(self.fonts.cyri.scale(34))
1695 .color(color::BLACK)
1696 .set(state.ids.sword_stance_agile_shadow, ui);
1697
1698 Text::new(
1700 &self
1701 .localized_strings
1702 .get_msg("hud-skill-sword_stance_crippling"),
1703 )
1704 .mid_top_with_margin_on(state.ids.sword_stance_right_align, -7.0)
1705 .font_id(self.fonts.cyri.conrod_id)
1706 .font_size(self.fonts.cyri.scale(34))
1707 .color(Color::Rgba(0.0, 0.52, 0.0, 1.0))
1708 .set(state.ids.sword_stance_crippling_text, ui);
1709
1710 Text::new(
1711 &self
1712 .localized_strings
1713 .get_msg("hud-skill-sword_stance_crippling"),
1714 )
1715 .x_y_position_relative_to(
1716 state.ids.sword_stance_crippling_text,
1717 Relative::Scalar(2.5),
1718 Relative::Scalar(-2.5),
1719 )
1720 .depth(1.0)
1721 .font_id(self.fonts.cyri.conrod_id)
1722 .font_size(self.fonts.cyri.scale(34))
1723 .color(color::BLACK)
1724 .set(state.ids.sword_stance_crippling_shadow, ui);
1725
1726 Text::new(
1728 &self
1729 .localized_strings
1730 .get_msg("hud-skill-sword_stance_heavy"),
1731 )
1732 .mid_bottom_with_margin_on(state.ids.sword_stance_left_align, 272.0)
1733 .font_id(self.fonts.cyri.conrod_id)
1734 .font_size(self.fonts.cyri.scale(34))
1735 .color(Color::Rgba(0.67, 0.0, 0.0, 1.0))
1736 .set(state.ids.sword_stance_heavy_text, ui);
1737
1738 Text::new(
1739 &self
1740 .localized_strings
1741 .get_msg("hud-skill-sword_stance_heavy"),
1742 )
1743 .x_y_position_relative_to(
1744 state.ids.sword_stance_heavy_text,
1745 Relative::Scalar(2.5),
1746 Relative::Scalar(-2.5),
1747 )
1748 .depth(1.0)
1749 .font_id(self.fonts.cyri.conrod_id)
1750 .font_size(self.fonts.cyri.scale(34))
1751 .color(color::BLACK)
1752 .set(state.ids.sword_stance_heavy_shadow, ui);
1753
1754 Text::new(
1756 &self
1757 .localized_strings
1758 .get_msg("hud-skill-sword_stance_defensive"),
1759 )
1760 .mid_bottom_with_margin_on(state.ids.sword_stance_right_align, 272.0)
1761 .font_id(self.fonts.cyri.conrod_id)
1762 .font_size(self.fonts.cyri.scale(34))
1763 .color(Color::Rgba(0.10, 0.40, 0.82, 1.0))
1764 .set(state.ids.sword_stance_defensive_text, ui);
1765
1766 Text::new(
1767 &self
1768 .localized_strings
1769 .get_msg("hud-skill-sword_stance_defensive"),
1770 )
1771 .x_y_position_relative_to(
1772 state.ids.sword_stance_defensive_text,
1773 Relative::Scalar(2.5),
1774 Relative::Scalar(-2.5),
1775 )
1776 .depth(1.0)
1777 .font_id(self.fonts.cyri.conrod_id)
1778 .font_size(self.fonts.cyri.scale(34))
1779 .color(color::BLACK)
1780 .set(state.ids.sword_stance_defensive_shadow, ui);
1781
1782 use PositionSpecifier::TopLeftWithMarginsOn;
1783 let skill_buttons = &[
1784 SkillIcon::Ability {
1785 skill: Skill::Sword(SwordSkill::CrescentSlash),
1786 ability_id: "veloren.core.pseudo_abilities.sword.crescent_slash",
1787 position: TopLeftWithMarginsOn(state.ids.sword_bg, 537.0, 429.0),
1788 },
1789 SkillIcon::Ability {
1790 skill: Skill::Sword(SwordSkill::FellStrike),
1791 ability_id: "veloren.core.pseudo_abilities.sword.fell_strike",
1792 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 527.0),
1793 },
1794 SkillIcon::Ability {
1795 skill: Skill::Sword(SwordSkill::Skewer),
1796 ability_id: "veloren.core.pseudo_abilities.sword.skewer",
1797 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 527.0),
1798 },
1799 SkillIcon::Ability {
1800 skill: Skill::Sword(SwordSkill::Cascade),
1801 ability_id: "veloren.core.pseudo_abilities.sword.cascade",
1802 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 332.0),
1803 },
1804 SkillIcon::Ability {
1805 skill: Skill::Sword(SwordSkill::CrossCut),
1806 ability_id: "veloren.core.pseudo_abilities.sword.cross_cut",
1807 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 332.0),
1808 },
1809 SkillIcon::Ability {
1810 skill: Skill::Sword(SwordSkill::Finisher),
1811 ability_id: "veloren.core.pseudo_abilities.sword.finisher",
1812 position: TopLeftWithMarginsOn(state.ids.sword_bg, 263.0, 429.0),
1813 },
1814 SkillIcon::Ability {
1815 skill: Skill::Sword(SwordSkill::HeavySweep),
1816 ability_id: "common.abilities.sword.heavy_sweep",
1817 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 2.0),
1818 },
1819 SkillIcon::Ability {
1820 skill: Skill::Sword(SwordSkill::HeavyPommelStrike),
1821 ability_id: "common.abilities.sword.heavy_pommel_strike",
1822 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 91.0),
1823 },
1824 SkillIcon::Ability {
1825 skill: Skill::Sword(SwordSkill::AgileQuickDraw),
1826 ability_id: "common.abilities.sword.agile_quick_draw",
1827 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 384.0),
1828 },
1829 SkillIcon::Ability {
1830 skill: Skill::Sword(SwordSkill::AgileFeint),
1831 ability_id: "common.abilities.sword.agile_feint",
1832 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 472.0),
1833 },
1834 SkillIcon::Ability {
1835 skill: Skill::Sword(SwordSkill::DefensiveRiposte),
1836 ability_id: "common.abilities.sword.defensive_riposte",
1837 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 766.0),
1838 },
1839 SkillIcon::Ability {
1840 skill: Skill::Sword(SwordSkill::DefensiveDisengage),
1841 ability_id: "common.abilities.sword.defensive_disengage",
1842 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 855.0),
1843 },
1844 SkillIcon::Ability {
1845 skill: Skill::Sword(SwordSkill::CripplingGouge),
1846 ability_id: "common.abilities.sword.crippling_gouge",
1847 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 766.0),
1848 },
1849 SkillIcon::Ability {
1850 skill: Skill::Sword(SwordSkill::CripplingHamstring),
1851 ability_id: "common.abilities.sword.crippling_hamstring",
1852 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 766.0),
1853 },
1854 SkillIcon::Ability {
1855 skill: Skill::Sword(SwordSkill::CleavingWhirlwindSlice),
1856 ability_id: "common.abilities.sword.cleaving_whirlwind_slice",
1857 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 91.0),
1858 },
1859 SkillIcon::Ability {
1860 skill: Skill::Sword(SwordSkill::CleavingEarthSplitter),
1861 ability_id: "common.abilities.sword.cleaving_earth_splitter",
1862 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 91.0),
1863 },
1864 SkillIcon::Ability {
1865 skill: Skill::Sword(SwordSkill::HeavyFortitude),
1866 ability_id: "common.abilities.sword.heavy_fortitude",
1867 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 2.0),
1868 },
1869 SkillIcon::Ability {
1870 skill: Skill::Sword(SwordSkill::HeavyPillarThrust),
1871 ability_id: "common.abilities.sword.heavy_pillar_thrust",
1872 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 91.0),
1873 },
1874 SkillIcon::Ability {
1875 skill: Skill::Sword(SwordSkill::AgileDancingEdge),
1876 ability_id: "common.abilities.sword.agile_dancing_edge",
1877 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 385.0),
1878 },
1879 SkillIcon::Ability {
1880 skill: Skill::Sword(SwordSkill::AgileFlurry),
1881 ability_id: "common.abilities.sword.agile_flurry",
1882 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 473.0),
1883 },
1884 SkillIcon::Ability {
1885 skill: Skill::Sword(SwordSkill::DefensiveStalwartSword),
1886 ability_id: "common.abilities.sword.defensive_stalwart_sword",
1887 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 766.0),
1888 },
1889 SkillIcon::Ability {
1890 skill: Skill::Sword(SwordSkill::DefensiveDeflect),
1891 ability_id: "common.abilities.sword.defensive_deflect",
1892 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 855.0),
1893 },
1894 SkillIcon::Ability {
1895 skill: Skill::Sword(SwordSkill::CripplingEviscerate),
1896 ability_id: "common.abilities.sword.crippling_eviscerate",
1897 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 855.0),
1898 },
1899 SkillIcon::Ability {
1900 skill: Skill::Sword(SwordSkill::CripplingBloodyGash),
1901 ability_id: "common.abilities.sword.crippling_bloody_gash",
1902 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 855.0),
1903 },
1904 SkillIcon::Ability {
1905 skill: Skill::Sword(SwordSkill::CleavingBladeFever),
1906 ability_id: "common.abilities.sword.cleaving_blade_fever",
1907 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 2.0),
1908 },
1909 SkillIcon::Ability {
1910 skill: Skill::Sword(SwordSkill::CleavingSkySplitter),
1911 ability_id: "common.abilities.sword.cleaving_sky_splitter",
1912 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 2.0),
1913 },
1914 ];
1915
1916 state.update(|s| {
1917 s.ids
1918 .skills
1919 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1920 });
1921 state.update(|s| {
1922 s.ids
1923 .skill_lock_imgs
1924 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1925 });
1926
1927 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1928 events
1929 }
1930
1931 fn handle_axe_skills_window(
1932 &mut self,
1933 diary_tooltip: &Tooltip,
1934 state: &mut State<DiaryState>,
1935 ui: &mut UiCell,
1936 mut events: Vec<Event>,
1937 ) -> Vec<Event> {
1938 Image::new(self.imgs.axe_bg)
1940 .wh([924.0, 619.0])
1941 .mid_top_with_margin_on(state.ids.content_align, 65.0)
1942 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1943 .set(state.ids.axe_bg, ui);
1944
1945 use PositionSpecifier::TopLeftWithMarginsOn;
1946 let skill_buttons = &[
1947 SkillIcon::Ability {
1948 skill: Skill::Axe(AxeSkill::BrutalSwing),
1949 ability_id: "common.abilities.axe.brutal_swing",
1950 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 424.0),
1951 },
1952 SkillIcon::Ability {
1953 skill: Skill::Axe(AxeSkill::Berserk),
1954 ability_id: "common.abilities.axe.berserk",
1955 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 374.0),
1956 },
1957 SkillIcon::Ability {
1958 skill: Skill::Axe(AxeSkill::RisingTide),
1959 ability_id: "common.abilities.axe.rising_tide",
1960 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 474.0),
1961 },
1962 SkillIcon::Ability {
1963 skill: Skill::Axe(AxeSkill::SavageSense),
1964 ability_id: "common.abilities.axe.savage_sense",
1965 position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 324.0),
1966 },
1967 SkillIcon::Ability {
1968 skill: Skill::Axe(AxeSkill::AdrenalineRush),
1969 ability_id: "common.abilities.axe.adrenaline_rush",
1970 position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 524.0),
1971 },
1972 SkillIcon::Ability {
1973 skill: Skill::Axe(AxeSkill::Execute),
1974 ability_id: "common.abilities.axe.execute",
1975 position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 424.0),
1976 },
1977 SkillIcon::Ability {
1978 skill: Skill::Axe(AxeSkill::Maelstrom),
1979 ability_id: "common.abilities.axe.maelstrom",
1980 position: TopLeftWithMarginsOn(state.ids.axe_bg, 4.0, 424.0),
1981 },
1982 SkillIcon::Ability {
1983 skill: Skill::Axe(AxeSkill::Rake),
1984 ability_id: "common.abilities.axe.rake",
1985 position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 325.0),
1986 },
1987 SkillIcon::Ability {
1988 skill: Skill::Axe(AxeSkill::Bloodfeast),
1989 ability_id: "common.abilities.axe.bloodfeast",
1990 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 74.0),
1991 },
1992 SkillIcon::Ability {
1993 skill: Skill::Axe(AxeSkill::FierceRaze),
1994 ability_id: "common.abilities.axe.fierce_raze",
1995 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 174.0),
1996 },
1997 SkillIcon::Ability {
1998 skill: Skill::Axe(AxeSkill::Furor),
1999 ability_id: "common.abilities.axe.furor",
2000 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 24.0),
2001 },
2002 SkillIcon::Ability {
2003 skill: Skill::Axe(AxeSkill::Fracture),
2004 ability_id: "common.abilities.axe.fracture",
2005 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 224.0),
2006 },
2007 SkillIcon::Ability {
2008 skill: Skill::Axe(AxeSkill::Lacerate),
2009 ability_id: "common.abilities.axe.lacerate",
2010 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 124.0),
2011 },
2012 SkillIcon::Ability {
2013 skill: Skill::Axe(AxeSkill::Riptide),
2014 ability_id: "common.abilities.axe.riptide",
2015 position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 124.0),
2016 },
2017 SkillIcon::Ability {
2018 skill: Skill::Axe(AxeSkill::SkullBash),
2019 ability_id: "common.abilities.axe.skull_bash",
2020 position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 523.0),
2021 },
2022 SkillIcon::Ability {
2023 skill: Skill::Axe(AxeSkill::Sunder),
2024 ability_id: "common.abilities.axe.sunder",
2025 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 674.0),
2026 },
2027 SkillIcon::Ability {
2028 skill: Skill::Axe(AxeSkill::Plunder),
2029 ability_id: "common.abilities.axe.plunder",
2030 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 774.0),
2031 },
2032 SkillIcon::Ability {
2033 skill: Skill::Axe(AxeSkill::Defiance),
2034 ability_id: "common.abilities.axe.defiance",
2035 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 624.0),
2036 },
2037 SkillIcon::Ability {
2038 skill: Skill::Axe(AxeSkill::Keelhaul),
2039 ability_id: "common.abilities.axe.keelhaul",
2040 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 824.0),
2041 },
2042 SkillIcon::Ability {
2043 skill: Skill::Axe(AxeSkill::Bulkhead),
2044 ability_id: "common.abilities.axe.bulkhead",
2045 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 724.0),
2046 },
2047 SkillIcon::Ability {
2048 skill: Skill::Axe(AxeSkill::Capsize),
2049 ability_id: "common.abilities.axe.capsize",
2050 position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 724.0),
2051 },
2052 ];
2053
2054 state.update(|s| {
2055 s.ids
2056 .skills
2057 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2058 });
2059 state.update(|s| {
2060 s.ids
2061 .skill_lock_imgs
2062 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2063 });
2064
2065 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2066 events
2067 }
2068
2069 fn handle_hammer_skills_window(
2070 &mut self,
2071 diary_tooltip: &Tooltip,
2072 state: &mut State<DiaryState>,
2073 ui: &mut UiCell,
2074 mut events: Vec<Event>,
2075 ) -> Vec<Event> {
2076 Image::new(self.imgs.hammer_bg)
2078 .wh([924.0, 619.0])
2079 .mid_top_with_margin_on(state.ids.content_align, 65.0)
2080 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2081 .set(state.ids.hammer_bg, ui);
2082
2083 use PositionSpecifier::TopLeftWithMarginsOn;
2084 let skill_buttons = &[
2085 SkillIcon::Ability {
2086 skill: Skill::Hammer(HammerSkill::ScornfulSwipe),
2087 ability_id: "common.abilities.hammer.scornful_swipe",
2088 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 455.0, 424.0),
2089 },
2090 SkillIcon::Ability {
2091 skill: Skill::Hammer(HammerSkill::Tremor),
2092 ability_id: "common.abilities.hammer.tremor",
2093 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 172.0),
2094 },
2095 SkillIcon::Ability {
2096 skill: Skill::Hammer(HammerSkill::VigorousBash),
2097 ability_id: "common.abilities.hammer.vigorous_bash",
2098 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 272.0),
2099 },
2100 SkillIcon::Ability {
2101 skill: Skill::Hammer(HammerSkill::Retaliate),
2102 ability_id: "common.abilities.hammer.retaliate",
2103 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 122.0),
2104 },
2105 SkillIcon::Ability {
2106 skill: Skill::Hammer(HammerSkill::SpineCracker),
2107 ability_id: "common.abilities.hammer.spine_cracker",
2108 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 222.0),
2109 },
2110 SkillIcon::Ability {
2111 skill: Skill::Hammer(HammerSkill::Breach),
2112 ability_id: "common.abilities.hammer.breach",
2113 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 322.0),
2114 },
2115 SkillIcon::Ability {
2116 skill: Skill::Hammer(HammerSkill::IronTempest),
2117 ability_id: "common.abilities.hammer.iron_tempest",
2118 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 172.0),
2119 },
2120 SkillIcon::Ability {
2121 skill: Skill::Hammer(HammerSkill::Upheaval),
2122 ability_id: "common.abilities.hammer.upheaval",
2123 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 272.0),
2124 },
2125 SkillIcon::Ability {
2126 skill: Skill::Hammer(HammerSkill::Thunderclap),
2127 ability_id: "common.abilities.hammer.thunderclap",
2128 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 172.0),
2129 },
2130 SkillIcon::Ability {
2131 skill: Skill::Hammer(HammerSkill::SeismicShock),
2132 ability_id: "common.abilities.hammer.seismic_shock",
2133 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 272.0),
2134 },
2135 SkillIcon::Ability {
2136 skill: Skill::Hammer(HammerSkill::HeavyWhorl),
2137 ability_id: "common.abilities.hammer.heavy_whorl",
2138 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 576.0),
2139 },
2140 SkillIcon::Ability {
2141 skill: Skill::Hammer(HammerSkill::Intercept),
2142 ability_id: "common.abilities.hammer.intercept",
2143 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 676.0),
2144 },
2145 SkillIcon::Ability {
2146 skill: Skill::Hammer(HammerSkill::PileDriver),
2147 ability_id: "common.abilities.hammer.pile_driver",
2148 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 526.0),
2149 },
2150 SkillIcon::Ability {
2151 skill: Skill::Hammer(HammerSkill::LungPummel),
2152 ability_id: "common.abilities.hammer.lung_pummel",
2153 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 626.0),
2154 },
2155 SkillIcon::Ability {
2156 skill: Skill::Hammer(HammerSkill::HelmCrusher),
2157 ability_id: "common.abilities.hammer.helm_crusher",
2158 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 726.0),
2159 },
2160 SkillIcon::Ability {
2161 skill: Skill::Hammer(HammerSkill::Rampart),
2162 ability_id: "common.abilities.hammer.rampart",
2163 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 576.0),
2164 },
2165 SkillIcon::Ability {
2166 skill: Skill::Hammer(HammerSkill::Tenacity),
2167 ability_id: "common.abilities.hammer.tenacity",
2168 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 676.0),
2169 },
2170 SkillIcon::Ability {
2171 skill: Skill::Hammer(HammerSkill::Earthshaker),
2172 ability_id: "common.abilities.hammer.earthshaker",
2173 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 576.0),
2174 },
2175 SkillIcon::Ability {
2176 skill: Skill::Hammer(HammerSkill::Judgement),
2177 ability_id: "common.abilities.hammer.judgement",
2178 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 676.0),
2179 },
2180 ];
2181
2182 state.update(|s| {
2183 s.ids
2184 .skills
2185 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2186 });
2187 state.update(|s| {
2188 s.ids
2189 .skill_lock_imgs
2190 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2191 });
2192
2193 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2194 events
2195 }
2196
2197 fn handle_sceptre_skills_window(
2198 &mut self,
2199 diary_tooltip: &Tooltip,
2200 state: &mut State<DiaryState>,
2201 ui: &mut UiCell,
2202 mut events: Vec<Event>,
2203 ) -> Vec<Event> {
2204 let tree_title = &self.localized_strings.get_msg("common-weapons-sceptre");
2206
2207 Text::new(tree_title)
2208 .mid_top_with_margin_on(state.ids.content_align, 2.0)
2209 .font_id(self.fonts.cyri.conrod_id)
2210 .font_size(self.fonts.cyri.scale(34))
2211 .color(TEXT_COLOR)
2212 .set(state.ids.tree_title_txt, ui);
2213
2214 let skills_top_l = 5;
2217 let skills_top_r = 5;
2218 let skills_bot_l = 5;
2219 let skills_bot_r = 0;
2220
2221 self.setup_state_for_skill_icons(
2222 state,
2223 ui,
2224 skills_top_l,
2225 skills_top_r,
2226 skills_bot_l,
2227 skills_bot_r,
2228 );
2229
2230 use skills::SceptreSkill::*;
2232 Image::new(animate_by_pulse(
2234 &self
2235 .item_imgs
2236 .img_ids_or_not_found_img(ItemKey::Simple("example_sceptre".to_string())),
2237 self.pulse,
2238 ))
2239 .wh(ART_SIZE)
2240 .middle_of(state.ids.content_align)
2241 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2242 .set(state.ids.sceptre_render, ui);
2243 use PositionSpecifier::MidTopWithMarginOn;
2244 let skill_buttons = &[
2245 SkillIcon::Descriptive {
2250 title: "hud-skill-sc_lifesteal_title",
2251 desc: "hud-skill-sc_lifesteal",
2252 image: self.imgs.skill_sceptre_lifesteal,
2253 position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2254 id: state.ids.skill_sceptre_lifesteal_0,
2255 },
2256 SkillIcon::Unlockable {
2257 skill: Skill::Sceptre(LDamage),
2258 image: self.imgs.magic_damage_skill,
2259 position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2260 id: state.ids.skill_sceptre_lifesteal_1,
2261 },
2262 SkillIcon::Unlockable {
2263 skill: Skill::Sceptre(LRange),
2264 image: self.imgs.magic_distance_skill,
2265 position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2266 id: state.ids.skill_sceptre_lifesteal_2,
2267 },
2268 SkillIcon::Unlockable {
2269 skill: Skill::Sceptre(LLifesteal),
2270 image: self.imgs.magic_lifesteal_skill,
2271 position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2272 id: state.ids.skill_sceptre_lifesteal_3,
2273 },
2274 SkillIcon::Unlockable {
2275 skill: Skill::Sceptre(LRegen),
2276 image: self.imgs.magic_energy_regen_skill,
2277 position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
2278 id: state.ids.skill_sceptre_lifesteal_4,
2279 },
2280 SkillIcon::Descriptive {
2282 title: "hud-skill-sc_heal_title",
2283 desc: "hud-skill-sc_heal",
2284 image: self.imgs.skill_sceptre_heal,
2285 position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2286 id: state.ids.skill_sceptre_heal_0,
2287 },
2288 SkillIcon::Unlockable {
2289 skill: Skill::Sceptre(HHeal),
2290 image: self.imgs.heal_heal_skill,
2291 position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2292 id: state.ids.skill_sceptre_heal_1,
2293 },
2294 SkillIcon::Unlockable {
2295 skill: Skill::Sceptre(HDuration),
2296 image: self.imgs.heal_duration_skill,
2297 position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2298 id: state.ids.skill_sceptre_heal_2,
2299 },
2300 SkillIcon::Unlockable {
2301 skill: Skill::Sceptre(HRange),
2302 image: self.imgs.heal_radius_skill,
2303 position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2304 id: state.ids.skill_sceptre_heal_3,
2305 },
2306 SkillIcon::Unlockable {
2307 skill: Skill::Sceptre(HCost),
2308 image: self.imgs.heal_cost_skill,
2309 position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0),
2310 id: state.ids.skill_sceptre_heal_4,
2311 },
2312 SkillIcon::Unlockable {
2314 skill: Skill::Sceptre(UnlockAura),
2315 image: self.imgs.skill_sceptre_aura,
2316 position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2317 id: state.ids.skill_sceptre_aura_0,
2318 },
2319 SkillIcon::Unlockable {
2320 skill: Skill::Sceptre(AStrength),
2321 image: self.imgs.buff_damage_skill,
2322 position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2323 id: state.ids.skill_sceptre_aura_1,
2324 },
2325 SkillIcon::Unlockable {
2326 skill: Skill::Sceptre(ADuration),
2327 image: self.imgs.buff_duration_skill,
2328 position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2329 id: state.ids.skill_sceptre_aura_2,
2330 },
2331 SkillIcon::Unlockable {
2332 skill: Skill::Sceptre(ARange),
2333 image: self.imgs.buff_radius_skill,
2334 position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2335 id: state.ids.skill_sceptre_aura_3,
2336 },
2337 SkillIcon::Unlockable {
2338 skill: Skill::Sceptre(ACost),
2339 image: self.imgs.buff_cost_skill,
2340 position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2341 id: state.ids.skill_sceptre_aura_4,
2342 },
2343 ];
2344
2345 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2346 events
2347 }
2348
2349 fn handle_bow_skills_window(
2350 &mut self,
2351 diary_tooltip: &Tooltip,
2352 state: &mut State<DiaryState>,
2353 ui: &mut UiCell,
2354 mut events: Vec<Event>,
2355 ) -> Vec<Event> {
2356 Image::new(self.imgs.bow_bg)
2357 .wh([924.0, 619.0])
2358 .mid_top_with_margin_on(state.ids.content_align, 65.0)
2359 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2360 .set(state.ids.bow_bg, ui);
2361
2362 use PositionSpecifier::TopLeftWithMarginsOn;
2363 let skill_buttons = &[
2364 SkillIcon::Ability {
2365 skill: Skill::Bow(BowSkill::Foothold),
2366 ability_id: "common.abilities.bow.foothold",
2367 position: TopLeftWithMarginsOn(state.ids.bow_bg, 424.0, 368.0),
2368 },
2369 SkillIcon::Ability {
2370 skill: Skill::Bow(BowSkill::HeavyNock),
2371 ability_id: "common.abilities.bow.heavy_nock",
2372 position: TopLeftWithMarginsOn(state.ids.bow_bg, 424.0, 480.0),
2373 },
2374 SkillIcon::Ability {
2375 skill: Skill::Bow(BowSkill::ArdentHunt),
2376 ability_id: "common.abilities.bow.ardent_hunt",
2377 position: TopLeftWithMarginsOn(state.ids.bow_bg, 310.0, 204.0),
2378 },
2379 SkillIcon::Ability {
2380 skill: Skill::Bow(BowSkill::SepticShot),
2381 ability_id: "common.abilities.bow.septic_shot",
2382 position: TopLeftWithMarginsOn(state.ids.bow_bg, 310.0, 424.0),
2383 },
2384 SkillIcon::Ability {
2385 skill: Skill::Bow(BowSkill::Barrage),
2386 ability_id: "common.abilities.bow.barrage",
2387 position: TopLeftWithMarginsOn(state.ids.bow_bg, 310.0, 644.0),
2388 },
2389 SkillIcon::Ability {
2390 skill: Skill::Bow(BowSkill::OwlTalon),
2391 ability_id: "common.abilities.bow.owl_talon",
2392 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 154.0),
2393 },
2394 SkillIcon::Ability {
2395 skill: Skill::Bow(BowSkill::EagleEye),
2396 ability_id: "common.abilities.bow.eagle_eye",
2397 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 254.0),
2398 },
2399 SkillIcon::Ability {
2400 skill: Skill::Bow(BowSkill::IgniteArrow),
2401 ability_id: "common.abilities.bow.ignite_arrow",
2402 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 374.0),
2403 },
2404 SkillIcon::Ability {
2405 skill: Skill::Bow(BowSkill::DrenchArrow),
2406 ability_id: "common.abilities.bow.drench_arrow",
2407 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 474.0),
2408 },
2409 SkillIcon::Ability {
2410 skill: Skill::Bow(BowSkill::PiercingGale),
2411 ability_id: "common.abilities.bow.piercing_gale",
2412 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 594.0),
2413 },
2414 SkillIcon::Ability {
2415 skill: Skill::Bow(BowSkill::Scatterburst),
2416 ability_id: "common.abilities.bow.scatterburst",
2417 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 694.0),
2418 },
2419 SkillIcon::Ability {
2420 skill: Skill::Bow(BowSkill::Heartseeker),
2421 ability_id: "common.abilities.bow.heartseeker",
2422 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 154.0),
2423 },
2424 SkillIcon::Ability {
2425 skill: Skill::Bow(BowSkill::Hawkstrike),
2426 ability_id: "common.abilities.bow.hawkstrike",
2427 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 254.0),
2428 },
2429 SkillIcon::Ability {
2430 skill: Skill::Bow(BowSkill::FreezeArrow),
2431 ability_id: "common.abilities.bow.freeze_arrow",
2432 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 374.0),
2433 },
2434 SkillIcon::Ability {
2435 skill: Skill::Bow(BowSkill::JoltArrow),
2436 ability_id: "common.abilities.bow.jolt_arrow",
2437 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 474.0),
2438 },
2439 SkillIcon::Ability {
2440 skill: Skill::Bow(BowSkill::Fusillade),
2441 ability_id: "common.abilities.bow.fusillade",
2442 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 594.0),
2443 },
2444 SkillIcon::Ability {
2445 skill: Skill::Bow(BowSkill::DeathVolley),
2446 ability_id: "common.abilities.bow.death_volley",
2447 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 694.0),
2448 },
2449 ];
2450
2451 state.update(|s| {
2452 s.ids
2453 .skills
2454 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2455 });
2456 state.update(|s| {
2457 s.ids
2458 .skill_lock_imgs
2459 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2460 });
2461
2462 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2463 events
2464 }
2465
2466 fn handle_staff_skills_window(
2467 &mut self,
2468 diary_tooltip: &Tooltip,
2469 state: &mut State<DiaryState>,
2470 ui: &mut UiCell,
2471 mut events: Vec<Event>,
2472 ) -> Vec<Event> {
2473 use skills::StaffSkill::*;
2474
2475 Image::new(self.imgs.staff_bg)
2476 .wh([924.0, 619.0])
2477 .mid_top_with_margin_on(state.ids.content_align, 65.0)
2478 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2479 .set(state.ids.staff_bg, ui);
2480
2481 use PositionSpecifier::TopLeftWithMarginsOn;
2482 let skill_buttons = &[
2483 SkillIcon::Ability {
2484 skill: Skill::Staff(FireShockwave),
2485 ability_id: "common.abilities.staff.fireshockwave",
2486 position: TopLeftWithMarginsOn(state.ids.staff_bg, 436.0, 421.0),
2487 },
2488 SkillIcon::Ability {
2489 skill: Skill::Staff(FireDash),
2490 ability_id: "common.abilities.staff.fire_dash",
2491 position: TopLeftWithMarginsOn(state.ids.staff_bg, 319.0, 331.0),
2492 },
2493 SkillIcon::Ability {
2494 skill: Skill::Staff(FlameCloak),
2495 ability_id: "common.abilities.staff.flame_cloak",
2496 position: TopLeftWithMarginsOn(state.ids.staff_bg, 319.0, 515.0),
2497 },
2498 SkillIcon::Ability {
2499 skill: Skill::Staff(FireBreath),
2500 ability_id: "common.abilities.staff.fire_breath",
2501 position: TopLeftWithMarginsOn(state.ids.staff_bg, 200.0, 515.0),
2502 },
2503 SkillIcon::Ability {
2504 skill: Skill::Staff(NapalmStrike),
2505 ability_id: "common.abilities.staff.napalm_strike",
2506 position: TopLeftWithMarginsOn(state.ids.staff_bg, 200.0, 331.0),
2507 },
2508 SkillIcon::Ability {
2509 skill: Skill::Staff(Pyroclasm),
2510 ability_id: "common.abilities.staff.pyroclasm",
2511 position: TopLeftWithMarginsOn(state.ids.staff_bg, 86.0, 422.0),
2512 },
2513 ];
2514
2515 state.update(|s| {
2516 s.ids
2517 .skills
2518 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2519 });
2520 state.update(|s| {
2521 s.ids
2522 .skill_lock_imgs
2523 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2524 });
2525
2526 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2527 events
2528 }
2529
2530 fn handle_mining_skills_window(
2531 &mut self,
2532 diary_tooltip: &Tooltip,
2533 state: &mut State<DiaryState>,
2534 ui: &mut UiCell,
2535 mut events: Vec<Event>,
2536 ) -> Vec<Event> {
2537 let tree_title = &self.localized_strings.get_msg("common-tool-mining");
2539
2540 Text::new(tree_title)
2541 .mid_top_with_margin_on(state.ids.content_align, 2.0)
2542 .font_id(self.fonts.cyri.conrod_id)
2543 .font_size(self.fonts.cyri.scale(34))
2544 .color(TEXT_COLOR)
2545 .set(state.ids.tree_title_txt, ui);
2546
2547 let skills_top_l = 4;
2550 let skills_top_r = 0;
2551 let skills_bot_l = 0;
2552 let skills_bot_r = 0;
2553
2554 self.setup_state_for_skill_icons(
2555 state,
2556 ui,
2557 skills_top_l,
2558 skills_top_r,
2559 skills_bot_l,
2560 skills_bot_r,
2561 );
2562
2563 use skills::MiningSkill::*;
2565 Image::new(animate_by_pulse(
2567 &self
2568 .item_imgs
2569 .img_ids_or_not_found_img(ItemKey::Simple("example_pick".to_string())),
2570 self.pulse,
2571 ))
2572 .wh(ART_SIZE)
2573 .middle_of(state.ids.content_align)
2574 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2575 .set(state.ids.pick_render, ui);
2576
2577 use PositionSpecifier::MidTopWithMarginOn;
2578 let skill_buttons = &[
2579 SkillIcon::Descriptive {
2584 title: "hud-skill-pick_strike_title",
2585 desc: "hud-skill-pick_strike",
2586 image: self.imgs.pickaxe,
2587 position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2588 id: state.ids.skill_pick_m1,
2589 },
2590 SkillIcon::Unlockable {
2591 skill: Skill::Pick(Speed),
2592 image: self.imgs.pickaxe_speed_skill,
2593 position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2594 id: state.ids.skill_pick_m1_0,
2595 },
2596 SkillIcon::Unlockable {
2597 skill: Skill::Pick(OreGain),
2598 image: self.imgs.pickaxe_oregain_skill,
2599 position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2600 id: state.ids.skill_pick_m1_1,
2601 },
2602 SkillIcon::Unlockable {
2603 skill: Skill::Pick(GemGain),
2604 image: self.imgs.pickaxe_gemgain_skill,
2605 position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2606 id: state.ids.skill_pick_m1_2,
2607 },
2608 ];
2609
2610 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2611 events
2612 }
2613
2614 fn handle_skill_buttons(
2615 &mut self,
2616 icons: &[SkillIcon],
2617 ui: &mut UiCell,
2618 events: &mut Vec<Event>,
2619 diary_tooltip: &Tooltip,
2620 state: &mut State<DiaryState>,
2621 ) {
2622 for (i, icon) in icons.iter().enumerate() {
2623 match icon {
2624 SkillIcon::Descriptive {
2625 title,
2626 desc,
2627 image,
2628 position,
2629 id,
2630 } => {
2631 Button::image(*image)
2633 .w_h(74.0, 74.0)
2634 .position(*position)
2635 .with_tooltip(
2636 self.tooltip_manager,
2637 &self.localized_strings.get_msg(title),
2638 &self.localized_strings.get_msg(desc),
2639 diary_tooltip,
2640 TEXT_COLOR,
2641 )
2642 .set(*id, ui);
2643 },
2644 SkillIcon::Unlockable {
2645 skill,
2646 image,
2647 position,
2648 id,
2649 } => self.create_unlock_skill_button(
2650 *skill,
2651 *image,
2652 *position,
2653 *id,
2654 ui,
2655 events,
2656 diary_tooltip,
2657 ),
2658 SkillIcon::Ability {
2659 skill,
2660 ability_id,
2661 position,
2662 } => self.create_unlock_ability_button(
2663 *skill,
2664 ability_id,
2665 *position,
2666 i,
2667 ui,
2668 events,
2669 diary_tooltip,
2670 state,
2671 ),
2672 }
2673 }
2674 }
2675
2676 fn setup_state_for_skill_icons(
2677 &mut self,
2678 state: &mut State<DiaryState>,
2679 ui: &mut UiCell,
2680 skills_top_l: usize,
2681 skills_top_r: usize,
2682 skills_bot_l: usize,
2683 skills_bot_r: usize,
2684 ) {
2685 state.update(|s| {
2687 s.ids
2688 .skills_top_l
2689 .resize(skills_top_l, &mut ui.widget_id_generator())
2690 });
2691 state.update(|s| {
2692 s.ids
2693 .skills_top_r
2694 .resize(skills_top_r, &mut ui.widget_id_generator())
2695 });
2696 state.update(|s| {
2697 s.ids
2698 .skills_bot_l
2699 .resize(skills_bot_l, &mut ui.widget_id_generator())
2700 });
2701 state.update(|s| {
2702 s.ids
2703 .skills_bot_r
2704 .resize(skills_bot_r, &mut ui.widget_id_generator())
2705 });
2706
2707 let offset_0 = 22.0;
2716 let offset_1 = -122.0;
2717 let offset_2 = offset_1 - -20.0;
2718
2719 let skill_pos = |idx, align, central_skill| {
2720 use PositionSpecifier::*;
2721 match idx {
2722 0 => MiddleOf(align),
2724 1 => UpFrom(central_skill, offset_0),
2726 2 => DownFrom(central_skill, offset_0),
2728 3 => LeftFrom(central_skill, offset_0),
2730 4 => RightFrom(central_skill, offset_0),
2732 5 => TopLeftWithMarginsOn(central_skill, offset_1, offset_2),
2734 6 => TopRightWithMarginsOn(central_skill, offset_1, offset_2),
2736 7 => BottomLeftWithMarginsOn(central_skill, offset_1, offset_2),
2738 8 => BottomRightWithMarginsOn(central_skill, offset_1, offset_2),
2740 buttons => {
2741 panic!("{} > 8 position number", buttons);
2742 },
2743 }
2744 };
2745
2746 while self.created_btns_top_l < skills_top_l {
2751 let pos = skill_pos(
2752 self.created_btns_top_l,
2753 state.ids.skills_top_l_align,
2754 state.ids.skills_top_l[0],
2755 );
2756 Button::image(self.imgs.wpn_icon_border_skills)
2757 .w_h(80.0, 100.0)
2758 .position(pos)
2759 .set(state.ids.skills_top_l[self.created_btns_top_l], ui);
2760 self.created_btns_top_l += 1;
2761 }
2762 while self.created_btns_top_r < skills_top_r {
2764 let pos = skill_pos(
2765 self.created_btns_top_r,
2766 state.ids.skills_top_r_align,
2767 state.ids.skills_top_r[0],
2768 );
2769 Button::image(self.imgs.wpn_icon_border_skills)
2770 .w_h(80.0, 100.0)
2771 .position(pos)
2772 .set(state.ids.skills_top_r[self.created_btns_top_r], ui);
2773 self.created_btns_top_r += 1;
2774 }
2775 while self.created_btns_bot_l < skills_bot_l {
2777 let pos = skill_pos(
2778 self.created_btns_bot_l,
2779 state.ids.skills_bot_l_align,
2780 state.ids.skills_bot_l[0],
2781 );
2782 Button::image(self.imgs.wpn_icon_border_skills)
2783 .w_h(80.0, 100.0)
2784 .position(pos)
2785 .set(state.ids.skills_bot_l[self.created_btns_bot_l], ui);
2786 self.created_btns_bot_l += 1;
2787 }
2788 while self.created_btns_bot_r < skills_bot_r {
2790 let pos = skill_pos(
2791 self.created_btns_bot_r,
2792 state.ids.skills_bot_r_align,
2793 state.ids.skills_bot_r[0],
2794 );
2795 Button::image(self.imgs.wpn_icon_border_skills)
2796 .w_h(80.0, 100.0)
2797 .position(pos)
2798 .set(state.ids.skills_bot_r[self.created_btns_bot_r], ui);
2799 self.created_btns_bot_r += 1;
2800 }
2801 }
2802
2803 fn create_unlock_skill_button(
2804 &mut self,
2805 skill: Skill,
2806 skill_image: image::Id,
2807 position: PositionSpecifier,
2808 widget_id: widget::Id,
2809 ui: &mut UiCell,
2810 events: &mut Vec<Event>,
2811 diary_tooltip: &Tooltip,
2812 ) {
2813 let label = if self.skill_set.prerequisites_met(skill) {
2814 let current = self.skill_set.skill_level(skill).unwrap_or(0);
2815 let max = skill.max_level();
2816 format!("{}/{}", current, max)
2817 } else {
2818 "".to_owned()
2819 };
2820
2821 let label_color = if self.skill_set.is_at_max_level(skill) {
2822 TEXT_COLOR
2823 } else if self.skill_set.sufficient_skill_points(skill) {
2824 HP_COLOR
2825 } else {
2826 CRITICAL_HP_COLOR
2827 };
2828
2829 let image_color = if self.skill_set.prerequisites_met(skill) {
2830 TEXT_COLOR
2831 } else {
2832 Color::Rgba(0.41, 0.41, 0.41, 0.7)
2833 };
2834
2835 let skill_strings = skill_strings(skill);
2836 let (title, description) =
2837 skill_strings.localize(self.localized_strings, self.skill_set, skill);
2838
2839 let button = Button::image(skill_image)
2840 .w_h(74.0, 74.0)
2841 .position(position)
2842 .label(&label)
2843 .label_y(conrod_core::position::Relative::Scalar(-47.0))
2844 .label_x(conrod_core::position::Relative::Scalar(0.0))
2845 .label_color(label_color)
2846 .label_font_size(self.fonts.cyri.scale(15))
2847 .label_font_id(self.fonts.cyri.conrod_id)
2848 .image_color(image_color)
2849 .with_tooltip(
2850 self.tooltip_manager,
2851 &title,
2852 &description,
2853 diary_tooltip,
2854 TEXT_COLOR,
2855 )
2856 .set(widget_id, ui);
2857
2858 if button.was_clicked() {
2859 events.push(Event::UnlockSkill(skill));
2860 };
2861 }
2862
2863 fn create_unlock_ability_button(
2864 &mut self,
2865 skill: Skill,
2866 ability_id: &str,
2867 position: PositionSpecifier,
2868 widget_index: usize,
2869 ui: &mut UiCell,
2870 events: &mut Vec<Event>,
2871 diary_tooltip: &Tooltip,
2872 state: &mut State<DiaryState>,
2873 ) {
2874 let locked = !self.skill_set.prerequisites_met(skill);
2875 let owned = self.skill_set.has_skill(skill);
2876 let image_color = if owned {
2877 TEXT_COLOR
2878 } else {
2879 Color::Rgba(0.41, 0.41, 0.41, 0.7)
2880 };
2881
2882 let (title, description) = util::ability_description(ability_id, self.localized_strings);
2883
2884 let sp_cost = sp(self.localized_strings, self.skill_set, skill);
2885
2886 let description = format!("{description}\n{sp_cost}");
2887
2888 let button = Button::image(util::ability_image(self.imgs, ability_id))
2889 .w_h(76.0, 76.0)
2890 .position(position)
2891 .image_color(image_color)
2892 .with_tooltip(
2893 self.tooltip_manager,
2894 &title,
2895 &description,
2896 diary_tooltip,
2897 TEXT_COLOR,
2898 )
2899 .set(state.ids.skills[widget_index], ui);
2900
2901 if locked {
2903 Image::new(self.imgs.lock)
2904 .w_h(76.0, 76.0)
2905 .middle_of(state.ids.skills[widget_index])
2906 .graphics_for(state.ids.skills[widget_index])
2907 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8)))
2908 .set(state.ids.skill_lock_imgs[widget_index], ui);
2909 }
2910
2911 if button.was_clicked() {
2912 events.push(Event::UnlockSkill(skill));
2913 };
2914 }
2915}
2916
2917fn skill_strings(skill: Skill) -> SkillStrings<'static> {
2921 match skill {
2922 Skill::UnlockGroup(s) => unlock_skill_strings(s),
2924 Skill::Sceptre(s) => sceptre_skill_strings(s),
2926 Skill::Climb(s) => climb_skill_strings(s),
2928 Skill::Swim(s) => swim_skill_strings(s),
2929 Skill::Pick(s) => mining_skill_strings(s),
2931 _ => SkillStrings::plain("", ""),
2932 }
2933}
2934
2935fn unlock_skill_strings(group: SkillGroupKind) -> SkillStrings<'static> {
2936 match group {
2937 SkillGroupKind::Weapon(ToolKind::Sword) => {
2938 SkillStrings::plain("hud-skill-unlck_sword_title", "hud-skill-unlck_sword")
2939 },
2940 SkillGroupKind::Weapon(ToolKind::Axe) => {
2941 SkillStrings::plain("hud-skill-unlck_axe_title", "hud-skill-unlck_axe")
2942 },
2943 SkillGroupKind::Weapon(ToolKind::Hammer) => {
2944 SkillStrings::plain("hud-skill-unlck_hammer_title", "hud-skill-unlck_hammer")
2945 },
2946 SkillGroupKind::Weapon(ToolKind::Bow) => {
2947 SkillStrings::plain("hud-skill-unlck_bow_title", "hud-skill-unlck_bow")
2948 },
2949 SkillGroupKind::Weapon(ToolKind::Staff) => {
2950 SkillStrings::plain("hud-skill-unlck_staff_title", "hud-skill-unlck_staff")
2951 },
2952 SkillGroupKind::Weapon(ToolKind::Sceptre) => {
2953 SkillStrings::plain("hud-skill-unlck_sceptre_title", "hud-skill-unlck_sceptre")
2954 },
2955 SkillGroupKind::General
2956 | SkillGroupKind::Weapon(
2957 ToolKind::Dagger
2958 | ToolKind::Shield
2959 | ToolKind::Spear
2960 | ToolKind::Blowgun
2961 | ToolKind::Debug
2962 | ToolKind::Farming
2963 | ToolKind::Instrument
2964 | ToolKind::Throwable
2965 | ToolKind::Pick
2966 | ToolKind::Shovel
2967 | ToolKind::Natural
2968 | ToolKind::Empty,
2969 ) => {
2970 tracing::warn!("Requesting title for unlocking unexpected skill group");
2971 SkillStrings::Empty
2972 },
2973 }
2974}
2975
2976fn sceptre_skill_strings(skill: SceptreSkill) -> SkillStrings<'static> {
2977 let modifiers = SKILL_MODIFIERS.sceptre_tree;
2978 match skill {
2979 SceptreSkill::LDamage => SkillStrings::with_mult(
2981 "hud-skill-sc_lifesteal_damage_title",
2982 "hud-skill-sc_lifesteal_damage",
2983 modifiers.beam.damage,
2984 ),
2985 SceptreSkill::LRange => SkillStrings::with_mult(
2986 "hud-skill-sc_lifesteal_range_title",
2987 "hud-skill-sc_lifesteal_range",
2988 modifiers.beam.range,
2989 ),
2990 SceptreSkill::LLifesteal => SkillStrings::with_mult(
2991 "hud-skill-sc_lifesteal_lifesteal_title",
2992 "hud-skill-sc_lifesteal_lifesteal",
2993 modifiers.beam.lifesteal,
2994 ),
2995 SceptreSkill::LRegen => SkillStrings::with_mult(
2996 "hud-skill-sc_lifesteal_regen_title",
2997 "hud-skill-sc_lifesteal_regen",
2998 modifiers.beam.energy_regen,
2999 ),
3000 SceptreSkill::HHeal => SkillStrings::with_mult(
3002 "hud-skill-sc_heal_heal_title",
3003 "hud-skill-sc_heal_heal",
3004 modifiers.healing_aura.strength,
3005 ),
3006 SceptreSkill::HRange => SkillStrings::with_mult(
3007 "hud-skill-sc_heal_range_title",
3008 "hud-skill-sc_heal_range",
3009 modifiers.healing_aura.range,
3010 ),
3011 SceptreSkill::HDuration => SkillStrings::with_mult(
3012 "hud-skill-sc_heal_duration_title",
3013 "hud-skill-sc_heal_duration",
3014 modifiers.healing_aura.duration,
3015 ),
3016 SceptreSkill::HCost => SkillStrings::with_mult(
3017 "hud-skill-sc_heal_cost_title",
3018 "hud-skill-sc_heal_cost",
3019 modifiers.healing_aura.energy_cost,
3020 ),
3021 SceptreSkill::UnlockAura => SkillStrings::plain(
3023 "hud-skill-sc_wardaura_unlock_title",
3024 "hud-skill-sc_wardaura_unlock",
3025 ),
3026 SceptreSkill::AStrength => SkillStrings::with_mult(
3027 "hud-skill-sc_wardaura_strength_title",
3028 "hud-skill-sc_wardaura_strength",
3029 modifiers.warding_aura.strength,
3030 ),
3031 SceptreSkill::ADuration => SkillStrings::with_mult(
3032 "hud-skill-sc_wardaura_duration_title",
3033 "hud-skill-sc_wardaura_duration",
3034 modifiers.warding_aura.duration,
3035 ),
3036 SceptreSkill::ARange => SkillStrings::with_mult(
3037 "hud-skill-sc_wardaura_range_title",
3038 "hud-skill-sc_wardaura_range",
3039 modifiers.warding_aura.range,
3040 ),
3041 SceptreSkill::ACost => SkillStrings::with_mult(
3042 "hud-skill-sc_wardaura_cost_title",
3043 "hud-skill-sc_wardaura_cost",
3044 modifiers.warding_aura.energy_cost,
3045 ),
3046 }
3047}
3048
3049fn climb_skill_strings(skill: ClimbSkill) -> SkillStrings<'static> {
3050 let modifiers = SKILL_MODIFIERS.general_tree.climb;
3051 match skill {
3052 ClimbSkill::Cost => SkillStrings::with_mult(
3053 "hud-skill-climbing_cost_title",
3054 "hud-skill-climbing_cost",
3055 modifiers.energy_cost,
3056 ),
3057 ClimbSkill::Speed => SkillStrings::with_mult(
3058 "hud-skill-climbing_speed_title",
3059 "hud-skill-climbing_speed",
3060 modifiers.speed,
3061 ),
3062 }
3063}
3064
3065fn swim_skill_strings(skill: SwimSkill) -> SkillStrings<'static> {
3066 let modifiers = SKILL_MODIFIERS.general_tree.swim;
3067 match skill {
3068 SwimSkill::Speed => SkillStrings::with_mult(
3069 "hud-skill-swim_speed_title",
3070 "hud-skill-swim_speed",
3071 modifiers.speed,
3072 ),
3073 }
3074}
3075
3076fn mining_skill_strings(skill: MiningSkill) -> SkillStrings<'static> {
3077 let modifiers = SKILL_MODIFIERS.mining_tree;
3078 match skill {
3079 MiningSkill::Speed => SkillStrings::with_mult(
3080 "hud-skill-pick_strike_speed_title",
3081 "hud-skill-pick_strike_speed",
3082 modifiers.speed,
3083 ),
3084 MiningSkill::OreGain => SkillStrings::with_const(
3085 "hud-skill-pick_strike_oregain_title",
3086 "hud-skill-pick_strike_oregain",
3087 (modifiers.ore_gain * 100.0).round() as u32,
3088 ),
3089 MiningSkill::GemGain => SkillStrings::with_const(
3090 "hud-skill-pick_strike_gemgain_title",
3091 "hud-skill-pick_strike_gemgain",
3092 (modifiers.gem_gain * 100.0).round() as u32,
3093 ),
3094 }
3095}
3096
3097enum SkillStrings<'a> {
3100 Plain {
3101 title: &'a str,
3102 desc: &'a str,
3103 },
3104 WithConst {
3105 title: &'a str,
3106 desc: &'a str,
3107 constant: u32,
3108 },
3109 WithMult {
3110 title: &'a str,
3111 desc: &'a str,
3112 multiplier: f32,
3113 },
3114 Empty,
3115}
3116
3117impl<'a> SkillStrings<'a> {
3118 fn plain(title: &'a str, desc: &'a str) -> Self { Self::Plain { title, desc } }
3119
3120 fn with_const(title: &'a str, desc: &'a str, constant: u32) -> Self {
3121 Self::WithConst {
3122 title,
3123 desc,
3124 constant,
3125 }
3126 }
3127
3128 fn with_mult(title: &'a str, desc: &'a str, multiplier: f32) -> Self {
3129 Self::WithMult {
3130 title,
3131 desc,
3132 multiplier,
3133 }
3134 }
3135
3136 fn localize<'loc>(
3137 &self,
3138 i18n: &'loc Localization,
3139 skill_set: &SkillSet,
3140 skill: Skill,
3141 ) -> (Cow<'loc, str>, Cow<'loc, str>) {
3142 match self {
3143 Self::Plain { title, desc } => {
3144 let title = i18n.get_msg(title);
3145
3146 let args = i18n::fluent_args! {
3147 "SP" => sp(i18n, skill_set, skill),
3148 };
3149 let desc = i18n.get_msg_ctx(desc, &args);
3150
3151 (title, desc)
3152 },
3153 Self::WithConst {
3154 title,
3155 desc,
3156 constant,
3157 } => {
3158 let title = i18n.get_msg(title);
3159 let args = i18n::fluent_args! {
3160 "boost" => constant,
3161 "SP" => sp(i18n, skill_set, skill),
3162 };
3163 let desc = i18n.get_msg_ctx(desc, &args);
3164
3165 (title, desc)
3166 },
3167 Self::WithMult {
3168 title,
3169 desc,
3170 multiplier,
3171 } => {
3172 let percentage = hud::multiplier_to_percentage(*multiplier).abs();
3173
3174 let title = i18n.get_msg(title);
3175
3176 let args = i18n::fluent_args! {
3177 "boost" => format!("{percentage:.0}"),
3178 "SP" => sp(i18n, skill_set, skill),
3179 };
3180 let desc = i18n.get_msg_ctx(desc, &args);
3181
3182 (title, desc)
3183 },
3184 Self::Empty => (Cow::Borrowed(""), Cow::Borrowed("")),
3185 }
3186 }
3187}
3188
3189const STAT_COUNT: usize = 15;
3191
3192#[derive(EnumIter)]
3193enum CharacterStat {
3194 Name,
3195 BattleMode,
3196 Waypoint,
3197 Hitpoints,
3198 Energy,
3199 Poise,
3200 CombatRating,
3201 Protection,
3202 StunResistance,
3203 PrecisionPower,
3204 EnergyReward,
3205 Stealth,
3206 WeaponPower,
3207 WeaponSpeed,
3208 WeaponEffectPower,
3209}
3210
3211impl CharacterStat {
3212 fn localized_str<'a>(&self, i18n: &'a Localization) -> Cow<'a, str> {
3213 use CharacterStat::*;
3214
3215 match self {
3216 Name => i18n.get_msg("character_window-character_name"),
3217 BattleMode => i18n.get_msg("hud-battle-mode"),
3218 Waypoint => i18n.get_msg("hud-waypoint"),
3219 Hitpoints => i18n.get_msg("hud-bag-health"),
3220 Energy => i18n.get_msg("hud-bag-energy"),
3221 CombatRating => i18n.get_msg("hud-bag-combat_rating"),
3222 Protection => i18n.get_msg("hud-bag-protection"),
3223 StunResistance => i18n.get_msg("hud-bag-stun_res"),
3224 Poise => i18n.get_msg("common-stats-poise_res"),
3225 PrecisionPower => i18n.get_msg("common-stats-precision_power"),
3226 EnergyReward => i18n.get_msg("common-stats-energy_reward"),
3227 Stealth => i18n.get_msg("common-stats-stealth"),
3228 WeaponPower => i18n.get_msg("common-stats-power"),
3229 WeaponSpeed => i18n.get_msg("common-stats-speed"),
3230 WeaponEffectPower => i18n.get_msg("common-stats-effect-power"),
3231 }
3232 }
3233}
3234
3235fn sp<'loc>(i18n: &'loc Localization, skill_set: &SkillSet, skill: Skill) -> Cow<'loc, str> {
3236 let current_level = skill_set.skill_level(skill);
3237 if matches!(current_level, Ok(level) if level == skill.max_level()) {
3238 Cow::Borrowed("")
3239 } else {
3240 i18n.get_msg_ctx("hud-skill-req_sp", &i18n::fluent_args! {
3241 "number" => skill_set.skill_cost(skill),
3242 })
3243 }
3244}