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