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