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 .map(|c| self.localized_strings.get_content(c))
1248 .unwrap_or_else(|| {
1249 self.localized_strings
1250 .get_msg("char_selection-uncanny_valley")
1251 .into_owned()
1252 }),
1253 CharacterStat::Hitpoints => format!("{}", self.health.maximum() as u32),
1254 CharacterStat::Energy => format!("{}", self.energy.maximum() as u32),
1255 CharacterStat::Poise => format!("{}", self.poise.maximum() as u32),
1256 CharacterStat::CombatRating => {
1257 let cr = combat::combat_rating(
1258 self.inventory,
1259 self.health,
1260 self.energy,
1261 self.poise,
1262 self.skill_set,
1263 *self.body,
1264 self.msm,
1265 );
1266 format!("{:.2}", cr * 10.0)
1267 },
1268 CharacterStat::Protection => {
1269 let protection =
1270 combat::compute_protection(Some(self.inventory), self.msm);
1271 match protection {
1272 Some(prot) => format!("{}", prot),
1273 None => String::from("Invincible"),
1274 }
1275 },
1276 CharacterStat::StunResistance => {
1277 let stun_res = Poise::compute_poise_damage_reduction(
1278 Some(self.inventory),
1279 self.msm,
1280 None,
1281 self.stats,
1282 );
1283 format!("{:.2}%", stun_res * 100.0)
1284 },
1285 CharacterStat::PrecisionPower => {
1286 let precision_power =
1287 combat::compute_precision_mult(Some(self.inventory), self.msm);
1288 format!("x{:.2}", precision_power)
1289 },
1290 CharacterStat::EnergyReward => {
1291 let energy_rew =
1292 combat::compute_energy_reward_mod(Some(self.inventory), self.msm);
1293 format!("{:+.0}%", (energy_rew - 1.0) * 100.0)
1294 },
1295 CharacterStat::Stealth => {
1296 let stealth_perception_multiplier =
1297 combat::perception_dist_multiplier_from_stealth(
1298 Some(self.inventory),
1299 None,
1300 self.msm,
1301 );
1302 let txt =
1303 format!("{:+.1}%", (1.0 - stealth_perception_multiplier) * 100.0);
1304
1305 txt
1306 },
1307 CharacterStat::WeaponPower => match (main_weap_stats, off_weap_stats) {
1308 (Some(m_stats), Some(o_stats)) => {
1309 format!("{} {}", m_stats.power * 10.0, o_stats.power * 10.0)
1310 },
1311 (Some(stats), None) | (None, Some(stats)) => {
1312 format!("{}", stats.power * 10.0)
1313 },
1314 (None, None) => String::new(),
1315 },
1316 CharacterStat::WeaponSpeed => {
1317 let spd_fmt = |sp| (sp - 1.0) * 100.0;
1318 match (main_weap_stats, off_weap_stats) {
1319 (Some(m_stats), Some(o_stats)) => format!(
1320 "{:+.0}% {:+.0}%",
1321 spd_fmt(m_stats.speed),
1322 spd_fmt(o_stats.speed)
1323 ),
1324 (Some(stats), None) | (None, Some(stats)) => {
1325 format!("{:+.0}%", spd_fmt(stats.speed))
1326 },
1327 _ => String::new(),
1328 }
1329 },
1330 CharacterStat::WeaponEffectPower => match (main_weap_stats, off_weap_stats)
1331 {
1332 (Some(m_stats), Some(o_stats)) => {
1333 format!(
1334 "{} {}",
1335 m_stats.effect_power * 10.0,
1336 o_stats.effect_power * 10.0
1337 )
1338 },
1339 (Some(stats), None) | (None, Some(stats)) => {
1340 format!("{}", stats.effect_power * 10.0)
1341 },
1342 (None, None) => String::new(),
1343 },
1344 };
1345
1346 let mut number = Text::new(&value)
1347 .font_id(self.fonts.cyri.conrod_id)
1348 .font_size(self.fonts.cyri.scale(29))
1349 .color(BLACK);
1350
1351 if i == 0 {
1352 number = number.right_from(state.ids.stat_names[i], 165.0);
1353 } else {
1354 number = number.down_from(state.ids.stat_values[i - 1], 10.0);
1355 };
1356 number.set(state.ids.stat_values[i], ui);
1357 }
1358
1359 events
1360 },
1361 DiarySection::Recipes => {
1362 Image::new(self.imgs.book_bg)
1364 .w_h(299.0 * 4.0, 184.0 * 4.0)
1365 .mid_top_with_margin_on(state.ids.content_align, 4.0)
1366 .set(state.ids.spellbook_art, ui);
1367
1368 Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1369 .top_left_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1370 .set(state.ids.sb_page_left_align, ui);
1371 Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT)
1372 .top_right_with_margins_on(state.ids.spellbook_art, 0.0, 0.0)
1373 .set(state.ids.sb_page_right_align, ui);
1374
1375 const RECIPES_PER_PAGE: usize = 36;
1376
1377 let page_index_max =
1378 self.inventory.recipe_groups_iter().len().saturating_sub(1) / RECIPES_PER_PAGE;
1379
1380 if state.recipe_page > page_index_max {
1381 state.update(|s| s.recipe_page = 0);
1382 }
1383
1384 let left_arrow = Button::image(if state.recipe_page > 0 {
1387 self.imgs.arrow_l
1388 } else {
1389 self.imgs.arrow_l_inactive
1390 })
1391 .bottom_left_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1392 .w_h(48.0, 55.0);
1393 if state.recipe_page > 0 {
1395 if left_arrow
1396 .hover_image(self.imgs.arrow_l_click)
1397 .press_image(self.imgs.arrow_l)
1398 .set(state.ids.ability_page_left, ui)
1399 .was_clicked()
1400 {
1401 state.update(|s| s.recipe_page -= 1);
1402 }
1403 } else {
1404 left_arrow.set(state.ids.ability_page_left, ui);
1405 }
1406 let right_arrow = Button::image(if state.recipe_page < page_index_max {
1408 self.imgs.arrow_r
1409 } else {
1410 self.imgs.arrow_r_inactive
1411 })
1412 .bottom_right_with_margins_on(state.ids.spellbook_art, -83.0, 10.0)
1413 .w_h(48.0, 55.0);
1414 if state.recipe_page < page_index_max {
1415 if right_arrow
1417 .hover_image(self.imgs.arrow_r_click)
1418 .press_image(self.imgs.arrow_r)
1419 .set(state.ids.ability_page_right, ui)
1420 .was_clicked()
1421 {
1422 state.update(|s| s.recipe_page += 1);
1423 };
1424 } else {
1425 right_arrow.set(state.ids.ability_page_right, ui);
1426 }
1427
1428 state.update(|s| {
1429 s.ids
1430 .recipe_groups
1431 .resize(RECIPES_PER_PAGE, &mut ui.widget_id_generator())
1432 });
1433
1434 for (i, rg) in self
1435 .inventory
1436 .recipe_groups_iter()
1437 .skip(state.recipe_page * RECIPES_PER_PAGE)
1438 .take(RECIPES_PER_PAGE)
1439 .enumerate()
1440 {
1441 let (title, _desc) =
1442 util::item_text(rg, self.localized_strings, self.item_i18n);
1443
1444 let mut text = Text::new(&title)
1445 .font_id(self.fonts.cyri.conrod_id)
1446 .font_size(self.fonts.cyri.scale(29))
1447 .color(BLACK);
1448
1449 if i == 0 {
1450 text =
1451 text.top_left_with_margins_on(state.ids.sb_page_left_align, 20.0, 20.0);
1452 } else if i == 18 {
1453 text = text.top_left_with_margins_on(
1454 state.ids.sb_page_right_align,
1455 20.0,
1456 20.0,
1457 );
1458 } else {
1459 text = text.down_from(state.ids.recipe_groups[i - 1], 10.0);
1460 }
1461 text.set(state.ids.recipe_groups[i], ui);
1462 }
1463
1464 events
1465 },
1466 }
1467 }
1468}
1469
1470enum SkillIcon<'a> {
1471 Unlockable {
1472 skill: Skill,
1473 image: image::Id,
1474 position: PositionSpecifier,
1475 id: widget::Id,
1476 },
1477 Descriptive {
1478 title: &'a str,
1479 desc: &'a str,
1480 image: image::Id,
1481 position: PositionSpecifier,
1482 id: widget::Id,
1483 },
1484 Ability {
1485 skill: Skill,
1486 ability_id: &'a str,
1487 position: PositionSpecifier,
1488 },
1489}
1490
1491impl Diary<'_> {
1492 fn handle_general_skills_window(
1493 &mut self,
1494 diary_tooltip: &Tooltip,
1495 state: &mut State<DiaryState>,
1496 ui: &mut UiCell,
1497 mut events: Vec<Event>,
1498 ) -> Vec<Event> {
1499 let tree_title = &self.localized_strings.get_msg("common-weapons-general");
1500 Text::new(tree_title)
1501 .mid_top_with_margin_on(state.ids.content_align, 2.0)
1502 .font_id(self.fonts.cyri.conrod_id)
1503 .font_size(self.fonts.cyri.scale(34))
1504 .color(TEXT_COLOR)
1505 .set(state.ids.tree_title_txt, ui);
1506
1507 let skills_top_l = 6;
1510 let skills_top_r = 0;
1511 let skills_bot_l = 0;
1512 let skills_bot_r = 5;
1513
1514 self.setup_state_for_skill_icons(
1515 state,
1516 ui,
1517 skills_top_l,
1518 skills_top_r,
1519 skills_bot_l,
1520 skills_bot_r,
1521 );
1522
1523 use SkillGroupKind::*;
1524 use ToolKind::*;
1525 Image::new(animate_by_pulse(
1527 &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1528 "example_general_combat_left".to_string(),
1529 )),
1530 self.pulse,
1531 ))
1532 .wh(ART_SIZE)
1533 .middle_of(state.ids.content_align)
1534 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1535 .set(state.ids.general_combat_render_0, ui);
1536
1537 Image::new(animate_by_pulse(
1538 &self.item_imgs.img_ids_or_not_found_img(ItemKey::Simple(
1539 "example_general_combat_right".to_string(),
1540 )),
1541 self.pulse,
1542 ))
1543 .wh(ART_SIZE)
1544 .middle_of(state.ids.general_combat_render_0)
1545 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1546 .set(state.ids.general_combat_render_1, ui);
1547
1548 use PositionSpecifier::MidTopWithMarginOn;
1549 let skill_buttons = &[
1550 SkillIcon::Unlockable {
1556 skill: Skill::UnlockGroup(Weapon(Sword)),
1557 image: self.imgs.unlock_sword_skill,
1558 position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
1559 id: state.ids.skill_general_tree_0,
1560 },
1561 SkillIcon::Unlockable {
1562 skill: Skill::UnlockGroup(Weapon(Axe)),
1563 image: self.imgs.unlock_axe_skill,
1564 position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
1565 id: state.ids.skill_general_tree_1,
1566 },
1567 SkillIcon::Unlockable {
1568 skill: Skill::UnlockGroup(Weapon(Hammer)),
1569 image: self.imgs.unlock_hammer_skill,
1570 position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
1571 id: state.ids.skill_general_tree_2,
1572 },
1573 SkillIcon::Unlockable {
1574 skill: Skill::UnlockGroup(Weapon(Bow)),
1575 image: self.imgs.unlock_bow_skill,
1576 position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
1577 id: state.ids.skill_general_tree_3,
1578 },
1579 SkillIcon::Unlockable {
1580 skill: Skill::UnlockGroup(Weapon(Staff)),
1581 image: self.imgs.unlock_staff_skill0,
1582 position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
1583 id: state.ids.skill_general_tree_4,
1584 },
1585 SkillIcon::Unlockable {
1586 skill: Skill::UnlockGroup(Weapon(Sceptre)),
1587 image: self.imgs.unlock_sceptre_skill,
1588 position: MidTopWithMarginOn(state.ids.skills_top_l[5], 3.0),
1589 id: state.ids.skill_general_tree_5,
1590 },
1591 SkillIcon::Descriptive {
1593 title: "hud-skill-climbing_title",
1594 desc: "hud-skill-climbing",
1595 image: self.imgs.skill_climbing_skill,
1596 position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0),
1597 id: state.ids.skill_general_climb_0,
1598 },
1599 SkillIcon::Unlockable {
1600 skill: Skill::Climb(ClimbSkill::Cost),
1601 image: self.imgs.utility_cost_skill,
1602 position: MidTopWithMarginOn(state.ids.skills_bot_r[1], 3.0),
1603 id: state.ids.skill_general_climb_1,
1604 },
1605 SkillIcon::Unlockable {
1606 skill: Skill::Climb(ClimbSkill::Speed),
1607 image: self.imgs.utility_speed_skill,
1608 position: MidTopWithMarginOn(state.ids.skills_bot_r[2], 3.0),
1609 id: state.ids.skill_general_climb_2,
1610 },
1611 SkillIcon::Descriptive {
1612 title: "hud-skill-swim_title",
1613 desc: "hud-skill-swim",
1614 image: self.imgs.skill_swim_skill,
1615 position: MidTopWithMarginOn(state.ids.skills_bot_r[3], 3.0),
1616 id: state.ids.skill_general_swim_0,
1617 },
1618 SkillIcon::Unlockable {
1619 skill: Skill::Swim(SwimSkill::Speed),
1620 image: self.imgs.utility_speed_skill,
1621 position: MidTopWithMarginOn(state.ids.skills_bot_r[4], 3.0),
1622 id: state.ids.skill_general_swim_1,
1623 },
1624 ];
1625
1626 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1627 events
1628 }
1629
1630 fn handle_sword_skills_window(
1631 &mut self,
1632 diary_tooltip: &Tooltip,
1633 state: &mut State<DiaryState>,
1634 ui: &mut UiCell,
1635 mut events: Vec<Event>,
1636 ) -> Vec<Event> {
1637 Image::new(self.imgs.sword_tree_paths)
1638 .wh([1042.0, 636.0])
1639 .mid_top_with_margin_on(state.ids.content_align, 55.0)
1640 .graphics_for(state.ids.content_align)
1641 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1642 .set(state.ids.sword_path_overlay, ui);
1643
1644 Image::new(self.imgs.sword_bg)
1646 .wh([933.0, 615.0])
1647 .mid_top_with_margin_on(state.ids.content_align, 65.0)
1648 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1649 .set(state.ids.sword_bg, ui);
1650
1651 Rectangle::fill_with([169.0, 615.0], color::TRANSPARENT)
1654 .top_left_of(state.ids.sword_bg)
1655 .set(state.ids.sword_stance_left_align, ui);
1656 Rectangle::fill_with([169.0, 615.0], color::TRANSPARENT)
1657 .top_right_of(state.ids.sword_bg)
1658 .set(state.ids.sword_stance_right_align, ui);
1659
1660 Text::new(
1662 &self
1663 .localized_strings
1664 .get_msg("hud-skill-sword_stance_cleaving"),
1665 )
1666 .mid_top_with_margin_on(state.ids.sword_stance_left_align, -7.0)
1667 .font_id(self.fonts.cyri.conrod_id)
1668 .font_size(self.fonts.cyri.scale(34))
1669 .color(Color::Rgba(0.94, 0.54, 0.07, 1.0))
1670 .set(state.ids.sword_stance_cleaving_text, ui);
1671
1672 Text::new(
1673 &self
1674 .localized_strings
1675 .get_msg("hud-skill-sword_stance_cleaving"),
1676 )
1677 .x_y_position_relative_to(
1678 state.ids.sword_stance_cleaving_text,
1679 Relative::Scalar(2.5),
1680 Relative::Scalar(-2.5),
1681 )
1682 .depth(1.0)
1683 .font_id(self.fonts.cyri.conrod_id)
1684 .font_size(self.fonts.cyri.scale(34))
1685 .color(color::BLACK)
1686 .set(state.ids.sword_stance_cleaving_shadow, ui);
1687
1688 Text::new(
1690 &self
1691 .localized_strings
1692 .get_msg("hud-skill-sword_stance_agile"),
1693 )
1694 .mid_top_with_margin_on(state.ids.sword_bg, -7.0)
1695 .font_id(self.fonts.cyri.conrod_id)
1696 .font_size(self.fonts.cyri.scale(34))
1697 .color(Color::Rgba(0.81, 0.70, 0.08, 1.0))
1698 .set(state.ids.sword_stance_agile_text, ui);
1699
1700 Text::new(
1701 &self
1702 .localized_strings
1703 .get_msg("hud-skill-sword_stance_agile"),
1704 )
1705 .x_y_position_relative_to(
1706 state.ids.sword_stance_agile_text,
1707 Relative::Scalar(2.5),
1708 Relative::Scalar(-2.5),
1709 )
1710 .depth(1.0)
1711 .font_id(self.fonts.cyri.conrod_id)
1712 .font_size(self.fonts.cyri.scale(34))
1713 .color(color::BLACK)
1714 .set(state.ids.sword_stance_agile_shadow, ui);
1715
1716 Text::new(
1718 &self
1719 .localized_strings
1720 .get_msg("hud-skill-sword_stance_crippling"),
1721 )
1722 .mid_top_with_margin_on(state.ids.sword_stance_right_align, -7.0)
1723 .font_id(self.fonts.cyri.conrod_id)
1724 .font_size(self.fonts.cyri.scale(34))
1725 .color(Color::Rgba(0.0, 0.52, 0.0, 1.0))
1726 .set(state.ids.sword_stance_crippling_text, ui);
1727
1728 Text::new(
1729 &self
1730 .localized_strings
1731 .get_msg("hud-skill-sword_stance_crippling"),
1732 )
1733 .x_y_position_relative_to(
1734 state.ids.sword_stance_crippling_text,
1735 Relative::Scalar(2.5),
1736 Relative::Scalar(-2.5),
1737 )
1738 .depth(1.0)
1739 .font_id(self.fonts.cyri.conrod_id)
1740 .font_size(self.fonts.cyri.scale(34))
1741 .color(color::BLACK)
1742 .set(state.ids.sword_stance_crippling_shadow, ui);
1743
1744 Text::new(
1746 &self
1747 .localized_strings
1748 .get_msg("hud-skill-sword_stance_heavy"),
1749 )
1750 .mid_bottom_with_margin_on(state.ids.sword_stance_left_align, 272.0)
1751 .font_id(self.fonts.cyri.conrod_id)
1752 .font_size(self.fonts.cyri.scale(34))
1753 .color(Color::Rgba(0.67, 0.0, 0.0, 1.0))
1754 .set(state.ids.sword_stance_heavy_text, ui);
1755
1756 Text::new(
1757 &self
1758 .localized_strings
1759 .get_msg("hud-skill-sword_stance_heavy"),
1760 )
1761 .x_y_position_relative_to(
1762 state.ids.sword_stance_heavy_text,
1763 Relative::Scalar(2.5),
1764 Relative::Scalar(-2.5),
1765 )
1766 .depth(1.0)
1767 .font_id(self.fonts.cyri.conrod_id)
1768 .font_size(self.fonts.cyri.scale(34))
1769 .color(color::BLACK)
1770 .set(state.ids.sword_stance_heavy_shadow, ui);
1771
1772 Text::new(
1774 &self
1775 .localized_strings
1776 .get_msg("hud-skill-sword_stance_defensive"),
1777 )
1778 .mid_bottom_with_margin_on(state.ids.sword_stance_right_align, 272.0)
1779 .font_id(self.fonts.cyri.conrod_id)
1780 .font_size(self.fonts.cyri.scale(34))
1781 .color(Color::Rgba(0.10, 0.40, 0.82, 1.0))
1782 .set(state.ids.sword_stance_defensive_text, ui);
1783
1784 Text::new(
1785 &self
1786 .localized_strings
1787 .get_msg("hud-skill-sword_stance_defensive"),
1788 )
1789 .x_y_position_relative_to(
1790 state.ids.sword_stance_defensive_text,
1791 Relative::Scalar(2.5),
1792 Relative::Scalar(-2.5),
1793 )
1794 .depth(1.0)
1795 .font_id(self.fonts.cyri.conrod_id)
1796 .font_size(self.fonts.cyri.scale(34))
1797 .color(color::BLACK)
1798 .set(state.ids.sword_stance_defensive_shadow, ui);
1799
1800 use PositionSpecifier::TopLeftWithMarginsOn;
1801 let skill_buttons = &[
1802 SkillIcon::Ability {
1803 skill: Skill::Sword(SwordSkill::CrescentSlash),
1804 ability_id: "veloren.core.pseudo_abilities.sword.crescent_slash",
1805 position: TopLeftWithMarginsOn(state.ids.sword_bg, 537.0, 429.0),
1806 },
1807 SkillIcon::Ability {
1808 skill: Skill::Sword(SwordSkill::FellStrike),
1809 ability_id: "veloren.core.pseudo_abilities.sword.fell_strike",
1810 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 527.0),
1811 },
1812 SkillIcon::Ability {
1813 skill: Skill::Sword(SwordSkill::Skewer),
1814 ability_id: "veloren.core.pseudo_abilities.sword.skewer",
1815 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 527.0),
1816 },
1817 SkillIcon::Ability {
1818 skill: Skill::Sword(SwordSkill::Cascade),
1819 ability_id: "veloren.core.pseudo_abilities.sword.cascade",
1820 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 332.0),
1821 },
1822 SkillIcon::Ability {
1823 skill: Skill::Sword(SwordSkill::CrossCut),
1824 ability_id: "veloren.core.pseudo_abilities.sword.cross_cut",
1825 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 332.0),
1826 },
1827 SkillIcon::Ability {
1828 skill: Skill::Sword(SwordSkill::Finisher),
1829 ability_id: "veloren.core.pseudo_abilities.sword.finisher",
1830 position: TopLeftWithMarginsOn(state.ids.sword_bg, 263.0, 429.0),
1831 },
1832 SkillIcon::Ability {
1833 skill: Skill::Sword(SwordSkill::HeavySweep),
1834 ability_id: "common.abilities.sword.heavy_sweep",
1835 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 2.0),
1836 },
1837 SkillIcon::Ability {
1838 skill: Skill::Sword(SwordSkill::HeavyPommelStrike),
1839 ability_id: "common.abilities.sword.heavy_pommel_strike",
1840 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 91.0),
1841 },
1842 SkillIcon::Ability {
1843 skill: Skill::Sword(SwordSkill::AgileQuickDraw),
1844 ability_id: "common.abilities.sword.agile_quick_draw",
1845 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 384.0),
1846 },
1847 SkillIcon::Ability {
1848 skill: Skill::Sword(SwordSkill::AgileFeint),
1849 ability_id: "common.abilities.sword.agile_feint",
1850 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 472.0),
1851 },
1852 SkillIcon::Ability {
1853 skill: Skill::Sword(SwordSkill::DefensiveRiposte),
1854 ability_id: "common.abilities.sword.defensive_riposte",
1855 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 766.0),
1856 },
1857 SkillIcon::Ability {
1858 skill: Skill::Sword(SwordSkill::DefensiveDisengage),
1859 ability_id: "common.abilities.sword.defensive_disengage",
1860 position: TopLeftWithMarginsOn(state.ids.sword_bg, 457.0, 855.0),
1861 },
1862 SkillIcon::Ability {
1863 skill: Skill::Sword(SwordSkill::CripplingGouge),
1864 ability_id: "common.abilities.sword.crippling_gouge",
1865 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 766.0),
1866 },
1867 SkillIcon::Ability {
1868 skill: Skill::Sword(SwordSkill::CripplingHamstring),
1869 ability_id: "common.abilities.sword.crippling_hamstring",
1870 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 766.0),
1871 },
1872 SkillIcon::Ability {
1873 skill: Skill::Sword(SwordSkill::CleavingWhirlwindSlice),
1874 ability_id: "common.abilities.sword.cleaving_whirlwind_slice",
1875 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 91.0),
1876 },
1877 SkillIcon::Ability {
1878 skill: Skill::Sword(SwordSkill::CleavingEarthSplitter),
1879 ability_id: "common.abilities.sword.cleaving_earth_splitter",
1880 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 91.0),
1881 },
1882 SkillIcon::Ability {
1883 skill: Skill::Sword(SwordSkill::HeavyFortitude),
1884 ability_id: "common.abilities.sword.heavy_fortitude",
1885 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 2.0),
1886 },
1887 SkillIcon::Ability {
1888 skill: Skill::Sword(SwordSkill::HeavyPillarThrust),
1889 ability_id: "common.abilities.sword.heavy_pillar_thrust",
1890 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 91.0),
1891 },
1892 SkillIcon::Ability {
1893 skill: Skill::Sword(SwordSkill::AgileDancingEdge),
1894 ability_id: "common.abilities.sword.agile_dancing_edge",
1895 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 385.0),
1896 },
1897 SkillIcon::Ability {
1898 skill: Skill::Sword(SwordSkill::AgileFlurry),
1899 ability_id: "common.abilities.sword.agile_flurry",
1900 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 473.0),
1901 },
1902 SkillIcon::Ability {
1903 skill: Skill::Sword(SwordSkill::DefensiveStalwartSword),
1904 ability_id: "common.abilities.sword.defensive_stalwart_sword",
1905 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 766.0),
1906 },
1907 SkillIcon::Ability {
1908 skill: Skill::Sword(SwordSkill::DefensiveDeflect),
1909 ability_id: "common.abilities.sword.defensive_deflect",
1910 position: TopLeftWithMarginsOn(state.ids.sword_bg, 368.0, 855.0),
1911 },
1912 SkillIcon::Ability {
1913 skill: Skill::Sword(SwordSkill::CripplingEviscerate),
1914 ability_id: "common.abilities.sword.crippling_eviscerate",
1915 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 855.0),
1916 },
1917 SkillIcon::Ability {
1918 skill: Skill::Sword(SwordSkill::CripplingBloodyGash),
1919 ability_id: "common.abilities.sword.crippling_bloody_gash",
1920 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 855.0),
1921 },
1922 SkillIcon::Ability {
1923 skill: Skill::Sword(SwordSkill::CleavingBladeFever),
1924 ability_id: "common.abilities.sword.cleaving_blade_fever",
1925 position: TopLeftWithMarginsOn(state.ids.sword_bg, 53.0, 2.0),
1926 },
1927 SkillIcon::Ability {
1928 skill: Skill::Sword(SwordSkill::CleavingSkySplitter),
1929 ability_id: "common.abilities.sword.cleaving_sky_splitter",
1930 position: TopLeftWithMarginsOn(state.ids.sword_bg, 142.0, 2.0),
1931 },
1932 ];
1933
1934 state.update(|s| {
1935 s.ids
1936 .skills
1937 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1938 });
1939 state.update(|s| {
1940 s.ids
1941 .skill_lock_imgs
1942 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
1943 });
1944
1945 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
1946 events
1947 }
1948
1949 fn handle_axe_skills_window(
1950 &mut self,
1951 diary_tooltip: &Tooltip,
1952 state: &mut State<DiaryState>,
1953 ui: &mut UiCell,
1954 mut events: Vec<Event>,
1955 ) -> Vec<Event> {
1956 Image::new(self.imgs.axe_bg)
1958 .wh([924.0, 619.0])
1959 .mid_top_with_margin_on(state.ids.content_align, 65.0)
1960 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
1961 .set(state.ids.axe_bg, ui);
1962
1963 use PositionSpecifier::TopLeftWithMarginsOn;
1964 let skill_buttons = &[
1965 SkillIcon::Ability {
1966 skill: Skill::Axe(AxeSkill::BrutalSwing),
1967 ability_id: "common.abilities.axe.brutal_swing",
1968 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 424.0),
1969 },
1970 SkillIcon::Ability {
1971 skill: Skill::Axe(AxeSkill::Berserk),
1972 ability_id: "common.abilities.axe.berserk",
1973 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 374.0),
1974 },
1975 SkillIcon::Ability {
1976 skill: Skill::Axe(AxeSkill::RisingTide),
1977 ability_id: "common.abilities.axe.rising_tide",
1978 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 474.0),
1979 },
1980 SkillIcon::Ability {
1981 skill: Skill::Axe(AxeSkill::SavageSense),
1982 ability_id: "common.abilities.axe.savage_sense",
1983 position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 324.0),
1984 },
1985 SkillIcon::Ability {
1986 skill: Skill::Axe(AxeSkill::AdrenalineRush),
1987 ability_id: "common.abilities.axe.adrenaline_rush",
1988 position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 524.0),
1989 },
1990 SkillIcon::Ability {
1991 skill: Skill::Axe(AxeSkill::Execute),
1992 ability_id: "common.abilities.axe.execute",
1993 position: TopLeftWithMarginsOn(state.ids.axe_bg, 187.0, 424.0),
1994 },
1995 SkillIcon::Ability {
1996 skill: Skill::Axe(AxeSkill::Maelstrom),
1997 ability_id: "common.abilities.axe.maelstrom",
1998 position: TopLeftWithMarginsOn(state.ids.axe_bg, 4.0, 424.0),
1999 },
2000 SkillIcon::Ability {
2001 skill: Skill::Axe(AxeSkill::Rake),
2002 ability_id: "common.abilities.axe.rake",
2003 position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 325.0),
2004 },
2005 SkillIcon::Ability {
2006 skill: Skill::Axe(AxeSkill::Bloodfeast),
2007 ability_id: "common.abilities.axe.bloodfeast",
2008 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 74.0),
2009 },
2010 SkillIcon::Ability {
2011 skill: Skill::Axe(AxeSkill::FierceRaze),
2012 ability_id: "common.abilities.axe.fierce_raze",
2013 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 174.0),
2014 },
2015 SkillIcon::Ability {
2016 skill: Skill::Axe(AxeSkill::Furor),
2017 ability_id: "common.abilities.axe.furor",
2018 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 24.0),
2019 },
2020 SkillIcon::Ability {
2021 skill: Skill::Axe(AxeSkill::Fracture),
2022 ability_id: "common.abilities.axe.fracture",
2023 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 224.0),
2024 },
2025 SkillIcon::Ability {
2026 skill: Skill::Axe(AxeSkill::Lacerate),
2027 ability_id: "common.abilities.axe.lacerate",
2028 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 124.0),
2029 },
2030 SkillIcon::Ability {
2031 skill: Skill::Axe(AxeSkill::Riptide),
2032 ability_id: "common.abilities.axe.riptide",
2033 position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 124.0),
2034 },
2035 SkillIcon::Ability {
2036 skill: Skill::Axe(AxeSkill::SkullBash),
2037 ability_id: "common.abilities.axe.skull_bash",
2038 position: TopLeftWithMarginsOn(state.ids.axe_bg, 507.0, 523.0),
2039 },
2040 SkillIcon::Ability {
2041 skill: Skill::Axe(AxeSkill::Sunder),
2042 ability_id: "common.abilities.axe.sunder",
2043 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 674.0),
2044 },
2045 SkillIcon::Ability {
2046 skill: Skill::Axe(AxeSkill::Plunder),
2047 ability_id: "common.abilities.axe.plunder",
2048 position: TopLeftWithMarginsOn(state.ids.axe_bg, 387.0, 774.0),
2049 },
2050 SkillIcon::Ability {
2051 skill: Skill::Axe(AxeSkill::Defiance),
2052 ability_id: "common.abilities.axe.defiance",
2053 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 624.0),
2054 },
2055 SkillIcon::Ability {
2056 skill: Skill::Axe(AxeSkill::Keelhaul),
2057 ability_id: "common.abilities.axe.keelhaul",
2058 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 824.0),
2059 },
2060 SkillIcon::Ability {
2061 skill: Skill::Axe(AxeSkill::Bulkhead),
2062 ability_id: "common.abilities.axe.bulkhead",
2063 position: TopLeftWithMarginsOn(state.ids.axe_bg, 287.0, 724.0),
2064 },
2065 SkillIcon::Ability {
2066 skill: Skill::Axe(AxeSkill::Capsize),
2067 ability_id: "common.abilities.axe.capsize",
2068 position: TopLeftWithMarginsOn(state.ids.axe_bg, 104.0, 724.0),
2069 },
2070 ];
2071
2072 state.update(|s| {
2073 s.ids
2074 .skills
2075 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2076 });
2077 state.update(|s| {
2078 s.ids
2079 .skill_lock_imgs
2080 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2081 });
2082
2083 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2084 events
2085 }
2086
2087 fn handle_hammer_skills_window(
2088 &mut self,
2089 diary_tooltip: &Tooltip,
2090 state: &mut State<DiaryState>,
2091 ui: &mut UiCell,
2092 mut events: Vec<Event>,
2093 ) -> Vec<Event> {
2094 Image::new(self.imgs.hammer_bg)
2096 .wh([924.0, 619.0])
2097 .mid_top_with_margin_on(state.ids.content_align, 65.0)
2098 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2099 .set(state.ids.hammer_bg, ui);
2100
2101 use PositionSpecifier::TopLeftWithMarginsOn;
2102 let skill_buttons = &[
2103 SkillIcon::Ability {
2104 skill: Skill::Hammer(HammerSkill::ScornfulSwipe),
2105 ability_id: "common.abilities.hammer.scornful_swipe",
2106 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 455.0, 424.0),
2107 },
2108 SkillIcon::Ability {
2109 skill: Skill::Hammer(HammerSkill::Tremor),
2110 ability_id: "common.abilities.hammer.tremor",
2111 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 172.0),
2112 },
2113 SkillIcon::Ability {
2114 skill: Skill::Hammer(HammerSkill::VigorousBash),
2115 ability_id: "common.abilities.hammer.vigorous_bash",
2116 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 272.0),
2117 },
2118 SkillIcon::Ability {
2119 skill: Skill::Hammer(HammerSkill::Retaliate),
2120 ability_id: "common.abilities.hammer.retaliate",
2121 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 122.0),
2122 },
2123 SkillIcon::Ability {
2124 skill: Skill::Hammer(HammerSkill::SpineCracker),
2125 ability_id: "common.abilities.hammer.spine_cracker",
2126 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 222.0),
2127 },
2128 SkillIcon::Ability {
2129 skill: Skill::Hammer(HammerSkill::Breach),
2130 ability_id: "common.abilities.hammer.breach",
2131 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 322.0),
2132 },
2133 SkillIcon::Ability {
2134 skill: Skill::Hammer(HammerSkill::IronTempest),
2135 ability_id: "common.abilities.hammer.iron_tempest",
2136 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 172.0),
2137 },
2138 SkillIcon::Ability {
2139 skill: Skill::Hammer(HammerSkill::Upheaval),
2140 ability_id: "common.abilities.hammer.upheaval",
2141 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 272.0),
2142 },
2143 SkillIcon::Ability {
2144 skill: Skill::Hammer(HammerSkill::Thunderclap),
2145 ability_id: "common.abilities.hammer.thunderclap",
2146 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 172.0),
2147 },
2148 SkillIcon::Ability {
2149 skill: Skill::Hammer(HammerSkill::SeismicShock),
2150 ability_id: "common.abilities.hammer.seismic_shock",
2151 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 272.0),
2152 },
2153 SkillIcon::Ability {
2154 skill: Skill::Hammer(HammerSkill::HeavyWhorl),
2155 ability_id: "common.abilities.hammer.heavy_whorl",
2156 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 576.0),
2157 },
2158 SkillIcon::Ability {
2159 skill: Skill::Hammer(HammerSkill::Intercept),
2160 ability_id: "common.abilities.hammer.intercept",
2161 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 398.0, 676.0),
2162 },
2163 SkillIcon::Ability {
2164 skill: Skill::Hammer(HammerSkill::PileDriver),
2165 ability_id: "common.abilities.hammer.pile_driver",
2166 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 526.0),
2167 },
2168 SkillIcon::Ability {
2169 skill: Skill::Hammer(HammerSkill::LungPummel),
2170 ability_id: "common.abilities.hammer.lung_pummel",
2171 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 626.0),
2172 },
2173 SkillIcon::Ability {
2174 skill: Skill::Hammer(HammerSkill::HelmCrusher),
2175 ability_id: "common.abilities.hammer.helm_crusher",
2176 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 284.0, 726.0),
2177 },
2178 SkillIcon::Ability {
2179 skill: Skill::Hammer(HammerSkill::Rampart),
2180 ability_id: "common.abilities.hammer.rampart",
2181 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 576.0),
2182 },
2183 SkillIcon::Ability {
2184 skill: Skill::Hammer(HammerSkill::Tenacity),
2185 ability_id: "common.abilities.hammer.tenacity",
2186 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 170.0, 676.0),
2187 },
2188 SkillIcon::Ability {
2189 skill: Skill::Hammer(HammerSkill::Earthshaker),
2190 ability_id: "common.abilities.hammer.earthshaker",
2191 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 576.0),
2192 },
2193 SkillIcon::Ability {
2194 skill: Skill::Hammer(HammerSkill::Judgement),
2195 ability_id: "common.abilities.hammer.judgement",
2196 position: TopLeftWithMarginsOn(state.ids.hammer_bg, 56.0, 676.0),
2197 },
2198 ];
2199
2200 state.update(|s| {
2201 s.ids
2202 .skills
2203 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2204 });
2205 state.update(|s| {
2206 s.ids
2207 .skill_lock_imgs
2208 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2209 });
2210
2211 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2212 events
2213 }
2214
2215 fn handle_sceptre_skills_window(
2216 &mut self,
2217 diary_tooltip: &Tooltip,
2218 state: &mut State<DiaryState>,
2219 ui: &mut UiCell,
2220 mut events: Vec<Event>,
2221 ) -> Vec<Event> {
2222 let tree_title = &self.localized_strings.get_msg("common-weapons-sceptre");
2224
2225 Text::new(tree_title)
2226 .mid_top_with_margin_on(state.ids.content_align, 2.0)
2227 .font_id(self.fonts.cyri.conrod_id)
2228 .font_size(self.fonts.cyri.scale(34))
2229 .color(TEXT_COLOR)
2230 .set(state.ids.tree_title_txt, ui);
2231
2232 let skills_top_l = 5;
2235 let skills_top_r = 5;
2236 let skills_bot_l = 5;
2237 let skills_bot_r = 0;
2238
2239 self.setup_state_for_skill_icons(
2240 state,
2241 ui,
2242 skills_top_l,
2243 skills_top_r,
2244 skills_bot_l,
2245 skills_bot_r,
2246 );
2247
2248 use skills::SceptreSkill::*;
2250 Image::new(animate_by_pulse(
2252 &self
2253 .item_imgs
2254 .img_ids_or_not_found_img(ItemKey::Simple("example_sceptre".to_string())),
2255 self.pulse,
2256 ))
2257 .wh(ART_SIZE)
2258 .middle_of(state.ids.content_align)
2259 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2260 .set(state.ids.sceptre_render, ui);
2261 use PositionSpecifier::MidTopWithMarginOn;
2262 let skill_buttons = &[
2263 SkillIcon::Descriptive {
2268 title: "hud-skill-sc_lifesteal_title",
2269 desc: "hud-skill-sc_lifesteal",
2270 image: self.imgs.skill_sceptre_lifesteal,
2271 position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2272 id: state.ids.skill_sceptre_lifesteal_0,
2273 },
2274 SkillIcon::Unlockable {
2275 skill: Skill::Sceptre(LDamage),
2276 image: self.imgs.magic_damage_skill,
2277 position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2278 id: state.ids.skill_sceptre_lifesteal_1,
2279 },
2280 SkillIcon::Unlockable {
2281 skill: Skill::Sceptre(LRange),
2282 image: self.imgs.magic_distance_skill,
2283 position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2284 id: state.ids.skill_sceptre_lifesteal_2,
2285 },
2286 SkillIcon::Unlockable {
2287 skill: Skill::Sceptre(LLifesteal),
2288 image: self.imgs.magic_lifesteal_skill,
2289 position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2290 id: state.ids.skill_sceptre_lifesteal_3,
2291 },
2292 SkillIcon::Unlockable {
2293 skill: Skill::Sceptre(LRegen),
2294 image: self.imgs.magic_energy_regen_skill,
2295 position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0),
2296 id: state.ids.skill_sceptre_lifesteal_4,
2297 },
2298 SkillIcon::Descriptive {
2300 title: "hud-skill-sc_heal_title",
2301 desc: "hud-skill-sc_heal",
2302 image: self.imgs.skill_sceptre_heal,
2303 position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0),
2304 id: state.ids.skill_sceptre_heal_0,
2305 },
2306 SkillIcon::Unlockable {
2307 skill: Skill::Sceptre(HHeal),
2308 image: self.imgs.heal_heal_skill,
2309 position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0),
2310 id: state.ids.skill_sceptre_heal_1,
2311 },
2312 SkillIcon::Unlockable {
2313 skill: Skill::Sceptre(HDuration),
2314 image: self.imgs.heal_duration_skill,
2315 position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0),
2316 id: state.ids.skill_sceptre_heal_2,
2317 },
2318 SkillIcon::Unlockable {
2319 skill: Skill::Sceptre(HRange),
2320 image: self.imgs.heal_radius_skill,
2321 position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0),
2322 id: state.ids.skill_sceptre_heal_3,
2323 },
2324 SkillIcon::Unlockable {
2325 skill: Skill::Sceptre(HCost),
2326 image: self.imgs.heal_cost_skill,
2327 position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0),
2328 id: state.ids.skill_sceptre_heal_4,
2329 },
2330 SkillIcon::Unlockable {
2332 skill: Skill::Sceptre(UnlockAura),
2333 image: self.imgs.skill_sceptre_aura,
2334 position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0),
2335 id: state.ids.skill_sceptre_aura_0,
2336 },
2337 SkillIcon::Unlockable {
2338 skill: Skill::Sceptre(AStrength),
2339 image: self.imgs.buff_damage_skill,
2340 position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0),
2341 id: state.ids.skill_sceptre_aura_1,
2342 },
2343 SkillIcon::Unlockable {
2344 skill: Skill::Sceptre(ADuration),
2345 image: self.imgs.buff_duration_skill,
2346 position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0),
2347 id: state.ids.skill_sceptre_aura_2,
2348 },
2349 SkillIcon::Unlockable {
2350 skill: Skill::Sceptre(ARange),
2351 image: self.imgs.buff_radius_skill,
2352 position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0),
2353 id: state.ids.skill_sceptre_aura_3,
2354 },
2355 SkillIcon::Unlockable {
2356 skill: Skill::Sceptre(ACost),
2357 image: self.imgs.buff_cost_skill,
2358 position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0),
2359 id: state.ids.skill_sceptre_aura_4,
2360 },
2361 ];
2362
2363 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2364 events
2365 }
2366
2367 fn handle_bow_skills_window(
2368 &mut self,
2369 diary_tooltip: &Tooltip,
2370 state: &mut State<DiaryState>,
2371 ui: &mut UiCell,
2372 mut events: Vec<Event>,
2373 ) -> Vec<Event> {
2374 Image::new(self.imgs.bow_bg)
2375 .wh([924.0, 619.0])
2376 .mid_top_with_margin_on(state.ids.content_align, 65.0)
2377 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2378 .set(state.ids.bow_bg, ui);
2379
2380 use PositionSpecifier::TopLeftWithMarginsOn;
2381 let skill_buttons = &[
2382 SkillIcon::Ability {
2383 skill: Skill::Bow(BowSkill::Foothold),
2384 ability_id: "common.abilities.bow.foothold",
2385 position: TopLeftWithMarginsOn(state.ids.bow_bg, 424.0, 368.0),
2386 },
2387 SkillIcon::Ability {
2388 skill: Skill::Bow(BowSkill::HeavyNock),
2389 ability_id: "common.abilities.bow.heavy_nock",
2390 position: TopLeftWithMarginsOn(state.ids.bow_bg, 424.0, 480.0),
2391 },
2392 SkillIcon::Ability {
2393 skill: Skill::Bow(BowSkill::ArdentHunt),
2394 ability_id: "common.abilities.bow.ardent_hunt",
2395 position: TopLeftWithMarginsOn(state.ids.bow_bg, 310.0, 204.0),
2396 },
2397 SkillIcon::Ability {
2398 skill: Skill::Bow(BowSkill::SepticShot),
2399 ability_id: "common.abilities.bow.septic_shot",
2400 position: TopLeftWithMarginsOn(state.ids.bow_bg, 310.0, 424.0),
2401 },
2402 SkillIcon::Ability {
2403 skill: Skill::Bow(BowSkill::Barrage),
2404 ability_id: "common.abilities.bow.barrage",
2405 position: TopLeftWithMarginsOn(state.ids.bow_bg, 310.0, 644.0),
2406 },
2407 SkillIcon::Ability {
2408 skill: Skill::Bow(BowSkill::StormChaser),
2409 ability_id: "common.abilities.bow.storm_chaser",
2410 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 154.0),
2411 },
2412 SkillIcon::Ability {
2413 skill: Skill::Bow(BowSkill::EagleEye),
2414 ability_id: "common.abilities.bow.eagle_eye",
2415 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 254.0),
2416 },
2417 SkillIcon::Ability {
2418 skill: Skill::Bow(BowSkill::IgniteArrow),
2419 ability_id: "common.abilities.bow.ignite_arrow",
2420 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 374.0),
2421 },
2422 SkillIcon::Ability {
2423 skill: Skill::Bow(BowSkill::DrenchArrow),
2424 ability_id: "common.abilities.bow.drench_arrow",
2425 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 474.0),
2426 },
2427 SkillIcon::Ability {
2428 skill: Skill::Bow(BowSkill::PiercingGale),
2429 ability_id: "common.abilities.bow.piercing_gale",
2430 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 594.0),
2431 },
2432 SkillIcon::Ability {
2433 skill: Skill::Bow(BowSkill::ThornStake),
2434 ability_id: "common.abilities.bow.thorn_stake",
2435 position: TopLeftWithMarginsOn(state.ids.bow_bg, 196.0, 694.0),
2436 },
2437 SkillIcon::Ability {
2438 skill: Skill::Bow(BowSkill::Heartseeker),
2439 ability_id: "common.abilities.bow.heartseeker",
2440 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 154.0),
2441 },
2442 SkillIcon::Ability {
2443 skill: Skill::Bow(BowSkill::Hawkstrike),
2444 ability_id: "common.abilities.bow.hawkstrike",
2445 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 254.0),
2446 },
2447 SkillIcon::Ability {
2448 skill: Skill::Bow(BowSkill::FreezeArrow),
2449 ability_id: "common.abilities.bow.freeze_arrow",
2450 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 374.0),
2451 },
2452 SkillIcon::Ability {
2453 skill: Skill::Bow(BowSkill::JoltArrow),
2454 ability_id: "common.abilities.bow.jolt_arrow",
2455 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 474.0),
2456 },
2457 SkillIcon::Ability {
2458 skill: Skill::Bow(BowSkill::Fusillade),
2459 ability_id: "common.abilities.bow.fusillade",
2460 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 594.0),
2461 },
2462 SkillIcon::Ability {
2463 skill: Skill::Bow(BowSkill::DeathVolley),
2464 ability_id: "common.abilities.bow.death_volley",
2465 position: TopLeftWithMarginsOn(state.ids.bow_bg, 82.0, 694.0),
2466 },
2467 ];
2468
2469 state.update(|s| {
2470 s.ids
2471 .skills
2472 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2473 });
2474 state.update(|s| {
2475 s.ids
2476 .skill_lock_imgs
2477 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2478 });
2479
2480 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2481 events
2482 }
2483
2484 fn handle_staff_skills_window(
2485 &mut self,
2486 diary_tooltip: &Tooltip,
2487 state: &mut State<DiaryState>,
2488 ui: &mut UiCell,
2489 mut events: Vec<Event>,
2490 ) -> Vec<Event> {
2491 use skills::StaffSkill::*;
2492
2493 Image::new(self.imgs.staff_bg)
2494 .wh([924.0, 619.0])
2495 .mid_top_with_margin_on(state.ids.content_align, 65.0)
2496 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2497 .set(state.ids.staff_bg, ui);
2498
2499 use PositionSpecifier::TopLeftWithMarginsOn;
2500 let skill_buttons = &[
2501 SkillIcon::Ability {
2502 skill: Skill::Staff(FireShockwave),
2503 ability_id: "common.abilities.staff.fireshockwave",
2504 position: TopLeftWithMarginsOn(state.ids.staff_bg, 436.0, 421.0),
2505 },
2506 SkillIcon::Ability {
2507 skill: Skill::Staff(FireDash),
2508 ability_id: "common.abilities.staff.fire_dash",
2509 position: TopLeftWithMarginsOn(state.ids.staff_bg, 319.0, 331.0),
2510 },
2511 SkillIcon::Ability {
2512 skill: Skill::Staff(FlameCloak),
2513 ability_id: "common.abilities.staff.flame_cloak",
2514 position: TopLeftWithMarginsOn(state.ids.staff_bg, 319.0, 515.0),
2515 },
2516 SkillIcon::Ability {
2517 skill: Skill::Staff(FireBreath),
2518 ability_id: "common.abilities.staff.fire_breath",
2519 position: TopLeftWithMarginsOn(state.ids.staff_bg, 200.0, 515.0),
2520 },
2521 SkillIcon::Ability {
2522 skill: Skill::Staff(NapalmStrike),
2523 ability_id: "common.abilities.staff.napalm_strike",
2524 position: TopLeftWithMarginsOn(state.ids.staff_bg, 200.0, 331.0),
2525 },
2526 SkillIcon::Ability {
2527 skill: Skill::Staff(Pyroclasm),
2528 ability_id: "common.abilities.staff.pyroclasm",
2529 position: TopLeftWithMarginsOn(state.ids.staff_bg, 86.0, 422.0),
2530 },
2531 ];
2532
2533 state.update(|s| {
2534 s.ids
2535 .skills
2536 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2537 });
2538 state.update(|s| {
2539 s.ids
2540 .skill_lock_imgs
2541 .resize(skill_buttons.len(), &mut ui.widget_id_generator())
2542 });
2543
2544 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2545 events
2546 }
2547
2548 fn handle_mining_skills_window(
2549 &mut self,
2550 diary_tooltip: &Tooltip,
2551 state: &mut State<DiaryState>,
2552 ui: &mut UiCell,
2553 mut events: Vec<Event>,
2554 ) -> Vec<Event> {
2555 let tree_title = &self.localized_strings.get_msg("common-tool-mining");
2557
2558 Text::new(tree_title)
2559 .mid_top_with_margin_on(state.ids.content_align, 2.0)
2560 .font_id(self.fonts.cyri.conrod_id)
2561 .font_size(self.fonts.cyri.scale(34))
2562 .color(TEXT_COLOR)
2563 .set(state.ids.tree_title_txt, ui);
2564
2565 let skills_top_l = 4;
2568 let skills_top_r = 0;
2569 let skills_bot_l = 0;
2570 let skills_bot_r = 0;
2571
2572 self.setup_state_for_skill_icons(
2573 state,
2574 ui,
2575 skills_top_l,
2576 skills_top_r,
2577 skills_bot_l,
2578 skills_bot_r,
2579 );
2580
2581 use skills::MiningSkill::*;
2583 Image::new(animate_by_pulse(
2585 &self
2586 .item_imgs
2587 .img_ids_or_not_found_img(ItemKey::Simple("example_pick".to_string())),
2588 self.pulse,
2589 ))
2590 .wh(ART_SIZE)
2591 .middle_of(state.ids.content_align)
2592 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
2593 .set(state.ids.pick_render, ui);
2594
2595 use PositionSpecifier::MidTopWithMarginOn;
2596 let skill_buttons = &[
2597 SkillIcon::Descriptive {
2602 title: "hud-skill-pick_strike_title",
2603 desc: "hud-skill-pick_strike",
2604 image: self.imgs.pickaxe,
2605 position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0),
2606 id: state.ids.skill_pick_m1,
2607 },
2608 SkillIcon::Unlockable {
2609 skill: Skill::Pick(Speed),
2610 image: self.imgs.pickaxe_speed_skill,
2611 position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0),
2612 id: state.ids.skill_pick_m1_0,
2613 },
2614 SkillIcon::Unlockable {
2615 skill: Skill::Pick(OreGain),
2616 image: self.imgs.pickaxe_oregain_skill,
2617 position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0),
2618 id: state.ids.skill_pick_m1_1,
2619 },
2620 SkillIcon::Unlockable {
2621 skill: Skill::Pick(GemGain),
2622 image: self.imgs.pickaxe_gemgain_skill,
2623 position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0),
2624 id: state.ids.skill_pick_m1_2,
2625 },
2626 ];
2627
2628 self.handle_skill_buttons(skill_buttons, ui, &mut events, diary_tooltip, state);
2629 events
2630 }
2631
2632 fn handle_skill_buttons(
2633 &mut self,
2634 icons: &[SkillIcon],
2635 ui: &mut UiCell,
2636 events: &mut Vec<Event>,
2637 diary_tooltip: &Tooltip,
2638 state: &mut State<DiaryState>,
2639 ) {
2640 for (i, icon) in icons.iter().enumerate() {
2641 match icon {
2642 SkillIcon::Descriptive {
2643 title,
2644 desc,
2645 image,
2646 position,
2647 id,
2648 } => {
2649 Button::image(*image)
2651 .w_h(74.0, 74.0)
2652 .position(*position)
2653 .with_tooltip(
2654 self.tooltip_manager,
2655 &self.localized_strings.get_msg(title),
2656 &self.localized_strings.get_msg(desc),
2657 diary_tooltip,
2658 TEXT_COLOR,
2659 )
2660 .set(*id, ui);
2661 },
2662 SkillIcon::Unlockable {
2663 skill,
2664 image,
2665 position,
2666 id,
2667 } => self.create_unlock_skill_button(
2668 *skill,
2669 *image,
2670 *position,
2671 *id,
2672 ui,
2673 events,
2674 diary_tooltip,
2675 ),
2676 SkillIcon::Ability {
2677 skill,
2678 ability_id,
2679 position,
2680 } => self.create_unlock_ability_button(
2681 *skill,
2682 ability_id,
2683 *position,
2684 i,
2685 ui,
2686 events,
2687 diary_tooltip,
2688 state,
2689 ),
2690 }
2691 }
2692 }
2693
2694 fn setup_state_for_skill_icons(
2695 &mut self,
2696 state: &mut State<DiaryState>,
2697 ui: &mut UiCell,
2698 skills_top_l: usize,
2699 skills_top_r: usize,
2700 skills_bot_l: usize,
2701 skills_bot_r: usize,
2702 ) {
2703 state.update(|s| {
2705 s.ids
2706 .skills_top_l
2707 .resize(skills_top_l, &mut ui.widget_id_generator())
2708 });
2709 state.update(|s| {
2710 s.ids
2711 .skills_top_r
2712 .resize(skills_top_r, &mut ui.widget_id_generator())
2713 });
2714 state.update(|s| {
2715 s.ids
2716 .skills_bot_l
2717 .resize(skills_bot_l, &mut ui.widget_id_generator())
2718 });
2719 state.update(|s| {
2720 s.ids
2721 .skills_bot_r
2722 .resize(skills_bot_r, &mut ui.widget_id_generator())
2723 });
2724
2725 let offset_0 = 22.0;
2734 let offset_1 = -122.0;
2735 let offset_2 = offset_1 - -20.0;
2736
2737 let skill_pos = |idx, align, central_skill| {
2738 use PositionSpecifier::*;
2739 match idx {
2740 0 => MiddleOf(align),
2742 1 => UpFrom(central_skill, offset_0),
2744 2 => DownFrom(central_skill, offset_0),
2746 3 => LeftFrom(central_skill, offset_0),
2748 4 => RightFrom(central_skill, offset_0),
2750 5 => TopLeftWithMarginsOn(central_skill, offset_1, offset_2),
2752 6 => TopRightWithMarginsOn(central_skill, offset_1, offset_2),
2754 7 => BottomLeftWithMarginsOn(central_skill, offset_1, offset_2),
2756 8 => BottomRightWithMarginsOn(central_skill, offset_1, offset_2),
2758 buttons => {
2759 panic!("{} > 8 position number", buttons);
2760 },
2761 }
2762 };
2763
2764 while self.created_btns_top_l < skills_top_l {
2769 let pos = skill_pos(
2770 self.created_btns_top_l,
2771 state.ids.skills_top_l_align,
2772 state.ids.skills_top_l[0],
2773 );
2774 Button::image(self.imgs.wpn_icon_border_skills)
2775 .w_h(80.0, 100.0)
2776 .position(pos)
2777 .set(state.ids.skills_top_l[self.created_btns_top_l], ui);
2778 self.created_btns_top_l += 1;
2779 }
2780 while self.created_btns_top_r < skills_top_r {
2782 let pos = skill_pos(
2783 self.created_btns_top_r,
2784 state.ids.skills_top_r_align,
2785 state.ids.skills_top_r[0],
2786 );
2787 Button::image(self.imgs.wpn_icon_border_skills)
2788 .w_h(80.0, 100.0)
2789 .position(pos)
2790 .set(state.ids.skills_top_r[self.created_btns_top_r], ui);
2791 self.created_btns_top_r += 1;
2792 }
2793 while self.created_btns_bot_l < skills_bot_l {
2795 let pos = skill_pos(
2796 self.created_btns_bot_l,
2797 state.ids.skills_bot_l_align,
2798 state.ids.skills_bot_l[0],
2799 );
2800 Button::image(self.imgs.wpn_icon_border_skills)
2801 .w_h(80.0, 100.0)
2802 .position(pos)
2803 .set(state.ids.skills_bot_l[self.created_btns_bot_l], ui);
2804 self.created_btns_bot_l += 1;
2805 }
2806 while self.created_btns_bot_r < skills_bot_r {
2808 let pos = skill_pos(
2809 self.created_btns_bot_r,
2810 state.ids.skills_bot_r_align,
2811 state.ids.skills_bot_r[0],
2812 );
2813 Button::image(self.imgs.wpn_icon_border_skills)
2814 .w_h(80.0, 100.0)
2815 .position(pos)
2816 .set(state.ids.skills_bot_r[self.created_btns_bot_r], ui);
2817 self.created_btns_bot_r += 1;
2818 }
2819 }
2820
2821 fn create_unlock_skill_button(
2822 &mut self,
2823 skill: Skill,
2824 skill_image: image::Id,
2825 position: PositionSpecifier,
2826 widget_id: widget::Id,
2827 ui: &mut UiCell,
2828 events: &mut Vec<Event>,
2829 diary_tooltip: &Tooltip,
2830 ) {
2831 let label = if self.skill_set.prerequisites_met(skill) {
2832 let current = self.skill_set.skill_level(skill).unwrap_or(0);
2833 let max = skill.max_level();
2834 format!("{}/{}", current, max)
2835 } else {
2836 "".to_owned()
2837 };
2838
2839 let label_color = if self.skill_set.is_at_max_level(skill) {
2840 TEXT_COLOR
2841 } else if self.skill_set.sufficient_skill_points(skill) {
2842 HP_COLOR
2843 } else {
2844 CRITICAL_HP_COLOR
2845 };
2846
2847 let image_color = if self.skill_set.prerequisites_met(skill) {
2848 TEXT_COLOR
2849 } else {
2850 Color::Rgba(0.41, 0.41, 0.41, 0.7)
2851 };
2852
2853 let skill_strings = skill_strings(skill);
2854 let (title, description) =
2855 skill_strings.localize(self.localized_strings, self.skill_set, skill);
2856
2857 let button = Button::image(skill_image)
2858 .w_h(74.0, 74.0)
2859 .position(position)
2860 .label(&label)
2861 .label_y(conrod_core::position::Relative::Scalar(-47.0))
2862 .label_x(conrod_core::position::Relative::Scalar(0.0))
2863 .label_color(label_color)
2864 .label_font_size(self.fonts.cyri.scale(15))
2865 .label_font_id(self.fonts.cyri.conrod_id)
2866 .image_color(image_color)
2867 .with_tooltip(
2868 self.tooltip_manager,
2869 &title,
2870 &description,
2871 diary_tooltip,
2872 TEXT_COLOR,
2873 )
2874 .set(widget_id, ui);
2875
2876 if button.was_clicked() {
2877 events.push(Event::UnlockSkill(skill));
2878 };
2879 }
2880
2881 fn create_unlock_ability_button(
2882 &mut self,
2883 skill: Skill,
2884 ability_id: &str,
2885 position: PositionSpecifier,
2886 widget_index: usize,
2887 ui: &mut UiCell,
2888 events: &mut Vec<Event>,
2889 diary_tooltip: &Tooltip,
2890 state: &mut State<DiaryState>,
2891 ) {
2892 let locked = !self.skill_set.prerequisites_met(skill);
2893 let owned = self.skill_set.has_skill(skill);
2894 let image_color = if owned {
2895 TEXT_COLOR
2896 } else {
2897 Color::Rgba(0.41, 0.41, 0.41, 0.7)
2898 };
2899
2900 let (title, description) = util::ability_description(ability_id, self.localized_strings);
2901
2902 let sp_cost = sp(self.localized_strings, self.skill_set, skill);
2903
2904 let description = format!("{description}\n{sp_cost}");
2905
2906 let button = Button::image(util::ability_image(self.imgs, ability_id))
2907 .w_h(76.0, 76.0)
2908 .position(position)
2909 .image_color(image_color)
2910 .with_tooltip(
2911 self.tooltip_manager,
2912 &title,
2913 &description,
2914 diary_tooltip,
2915 TEXT_COLOR,
2916 )
2917 .set(state.ids.skills[widget_index], ui);
2918
2919 if locked {
2921 Image::new(self.imgs.lock)
2922 .w_h(76.0, 76.0)
2923 .middle_of(state.ids.skills[widget_index])
2924 .graphics_for(state.ids.skills[widget_index])
2925 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8)))
2926 .set(state.ids.skill_lock_imgs[widget_index], ui);
2927 }
2928
2929 if button.was_clicked() {
2930 events.push(Event::UnlockSkill(skill));
2931 };
2932 }
2933}
2934
2935fn skill_strings(skill: Skill) -> SkillStrings<'static> {
2939 match skill {
2940 Skill::UnlockGroup(s) => unlock_skill_strings(s),
2942 Skill::Sceptre(s) => sceptre_skill_strings(s),
2944 Skill::Climb(s) => climb_skill_strings(s),
2946 Skill::Swim(s) => swim_skill_strings(s),
2947 Skill::Pick(s) => mining_skill_strings(s),
2949 _ => SkillStrings::plain("", ""),
2950 }
2951}
2952
2953fn unlock_skill_strings(group: SkillGroupKind) -> SkillStrings<'static> {
2954 match group {
2955 SkillGroupKind::Weapon(ToolKind::Sword) => {
2956 SkillStrings::plain("hud-skill-unlck_sword_title", "hud-skill-unlck_sword")
2957 },
2958 SkillGroupKind::Weapon(ToolKind::Axe) => {
2959 SkillStrings::plain("hud-skill-unlck_axe_title", "hud-skill-unlck_axe")
2960 },
2961 SkillGroupKind::Weapon(ToolKind::Hammer) => {
2962 SkillStrings::plain("hud-skill-unlck_hammer_title", "hud-skill-unlck_hammer")
2963 },
2964 SkillGroupKind::Weapon(ToolKind::Bow) => {
2965 SkillStrings::plain("hud-skill-unlck_bow_title", "hud-skill-unlck_bow")
2966 },
2967 SkillGroupKind::Weapon(ToolKind::Staff) => {
2968 SkillStrings::plain("hud-skill-unlck_staff_title", "hud-skill-unlck_staff")
2969 },
2970 SkillGroupKind::Weapon(ToolKind::Sceptre) => {
2971 SkillStrings::plain("hud-skill-unlck_sceptre_title", "hud-skill-unlck_sceptre")
2972 },
2973 SkillGroupKind::General
2974 | SkillGroupKind::Weapon(
2975 ToolKind::Dagger
2976 | ToolKind::Shield
2977 | ToolKind::Spear
2978 | ToolKind::Blowgun
2979 | ToolKind::Debug
2980 | ToolKind::Farming
2981 | ToolKind::Instrument
2982 | ToolKind::Throwable
2983 | ToolKind::Pick
2984 | ToolKind::Shovel
2985 | ToolKind::Natural
2986 | ToolKind::Empty,
2987 ) => {
2988 tracing::warn!("Requesting title for unlocking unexpected skill group");
2989 SkillStrings::Empty
2990 },
2991 }
2992}
2993
2994fn sceptre_skill_strings(skill: SceptreSkill) -> SkillStrings<'static> {
2995 let modifiers = SKILL_MODIFIERS.sceptre_tree;
2996 match skill {
2997 SceptreSkill::LDamage => SkillStrings::with_mult(
2999 "hud-skill-sc_lifesteal_damage_title",
3000 "hud-skill-sc_lifesteal_damage",
3001 modifiers.beam.damage,
3002 ),
3003 SceptreSkill::LRange => SkillStrings::with_mult(
3004 "hud-skill-sc_lifesteal_range_title",
3005 "hud-skill-sc_lifesteal_range",
3006 modifiers.beam.range,
3007 ),
3008 SceptreSkill::LLifesteal => SkillStrings::with_mult(
3009 "hud-skill-sc_lifesteal_lifesteal_title",
3010 "hud-skill-sc_lifesteal_lifesteal",
3011 modifiers.beam.lifesteal,
3012 ),
3013 SceptreSkill::LRegen => SkillStrings::with_mult(
3014 "hud-skill-sc_lifesteal_regen_title",
3015 "hud-skill-sc_lifesteal_regen",
3016 modifiers.beam.energy_regen,
3017 ),
3018 SceptreSkill::HHeal => SkillStrings::with_mult(
3020 "hud-skill-sc_heal_heal_title",
3021 "hud-skill-sc_heal_heal",
3022 modifiers.healing_aura.strength,
3023 ),
3024 SceptreSkill::HRange => SkillStrings::with_mult(
3025 "hud-skill-sc_heal_range_title",
3026 "hud-skill-sc_heal_range",
3027 modifiers.healing_aura.range,
3028 ),
3029 SceptreSkill::HDuration => SkillStrings::with_mult(
3030 "hud-skill-sc_heal_duration_title",
3031 "hud-skill-sc_heal_duration",
3032 modifiers.healing_aura.duration,
3033 ),
3034 SceptreSkill::HCost => SkillStrings::with_mult(
3035 "hud-skill-sc_heal_cost_title",
3036 "hud-skill-sc_heal_cost",
3037 modifiers.healing_aura.energy_cost,
3038 ),
3039 SceptreSkill::UnlockAura => SkillStrings::plain(
3041 "hud-skill-sc_wardaura_unlock_title",
3042 "hud-skill-sc_wardaura_unlock",
3043 ),
3044 SceptreSkill::AStrength => SkillStrings::with_mult(
3045 "hud-skill-sc_wardaura_strength_title",
3046 "hud-skill-sc_wardaura_strength",
3047 modifiers.warding_aura.strength,
3048 ),
3049 SceptreSkill::ADuration => SkillStrings::with_mult(
3050 "hud-skill-sc_wardaura_duration_title",
3051 "hud-skill-sc_wardaura_duration",
3052 modifiers.warding_aura.duration,
3053 ),
3054 SceptreSkill::ARange => SkillStrings::with_mult(
3055 "hud-skill-sc_wardaura_range_title",
3056 "hud-skill-sc_wardaura_range",
3057 modifiers.warding_aura.range,
3058 ),
3059 SceptreSkill::ACost => SkillStrings::with_mult(
3060 "hud-skill-sc_wardaura_cost_title",
3061 "hud-skill-sc_wardaura_cost",
3062 modifiers.warding_aura.energy_cost,
3063 ),
3064 }
3065}
3066
3067fn climb_skill_strings(skill: ClimbSkill) -> SkillStrings<'static> {
3068 let modifiers = SKILL_MODIFIERS.general_tree.climb;
3069 match skill {
3070 ClimbSkill::Cost => SkillStrings::with_mult(
3071 "hud-skill-climbing_cost_title",
3072 "hud-skill-climbing_cost",
3073 modifiers.energy_cost,
3074 ),
3075 ClimbSkill::Speed => SkillStrings::with_mult(
3076 "hud-skill-climbing_speed_title",
3077 "hud-skill-climbing_speed",
3078 modifiers.speed,
3079 ),
3080 }
3081}
3082
3083fn swim_skill_strings(skill: SwimSkill) -> SkillStrings<'static> {
3084 let modifiers = SKILL_MODIFIERS.general_tree.swim;
3085 match skill {
3086 SwimSkill::Speed => SkillStrings::with_mult(
3087 "hud-skill-swim_speed_title",
3088 "hud-skill-swim_speed",
3089 modifiers.speed,
3090 ),
3091 }
3092}
3093
3094fn mining_skill_strings(skill: MiningSkill) -> SkillStrings<'static> {
3095 let modifiers = SKILL_MODIFIERS.mining_tree;
3096 match skill {
3097 MiningSkill::Speed => SkillStrings::with_mult(
3098 "hud-skill-pick_strike_speed_title",
3099 "hud-skill-pick_strike_speed",
3100 modifiers.speed,
3101 ),
3102 MiningSkill::OreGain => SkillStrings::with_const(
3103 "hud-skill-pick_strike_oregain_title",
3104 "hud-skill-pick_strike_oregain",
3105 (modifiers.ore_gain * 100.0).round() as u32,
3106 ),
3107 MiningSkill::GemGain => SkillStrings::with_const(
3108 "hud-skill-pick_strike_gemgain_title",
3109 "hud-skill-pick_strike_gemgain",
3110 (modifiers.gem_gain * 100.0).round() as u32,
3111 ),
3112 }
3113}
3114
3115enum SkillStrings<'a> {
3118 Plain {
3119 title: &'a str,
3120 desc: &'a str,
3121 },
3122 WithConst {
3123 title: &'a str,
3124 desc: &'a str,
3125 constant: u32,
3126 },
3127 WithMult {
3128 title: &'a str,
3129 desc: &'a str,
3130 multiplier: f32,
3131 },
3132 Empty,
3133}
3134
3135impl<'a> SkillStrings<'a> {
3136 fn plain(title: &'a str, desc: &'a str) -> Self { Self::Plain { title, desc } }
3137
3138 fn with_const(title: &'a str, desc: &'a str, constant: u32) -> Self {
3139 Self::WithConst {
3140 title,
3141 desc,
3142 constant,
3143 }
3144 }
3145
3146 fn with_mult(title: &'a str, desc: &'a str, multiplier: f32) -> Self {
3147 Self::WithMult {
3148 title,
3149 desc,
3150 multiplier,
3151 }
3152 }
3153
3154 fn localize<'loc>(
3155 &self,
3156 i18n: &'loc Localization,
3157 skill_set: &SkillSet,
3158 skill: Skill,
3159 ) -> (Cow<'loc, str>, Cow<'loc, str>) {
3160 match self {
3161 Self::Plain { title, desc } => {
3162 let title = i18n.get_msg(title);
3163
3164 let args = i18n::fluent_args! {
3165 "SP" => sp(i18n, skill_set, skill),
3166 };
3167 let desc = i18n.get_msg_ctx(desc, &args);
3168
3169 (title, desc)
3170 },
3171 Self::WithConst {
3172 title,
3173 desc,
3174 constant,
3175 } => {
3176 let title = i18n.get_msg(title);
3177 let args = i18n::fluent_args! {
3178 "boost" => constant,
3179 "SP" => sp(i18n, skill_set, skill),
3180 };
3181 let desc = i18n.get_msg_ctx(desc, &args);
3182
3183 (title, desc)
3184 },
3185 Self::WithMult {
3186 title,
3187 desc,
3188 multiplier,
3189 } => {
3190 let percentage = hud::multiplier_to_percentage(*multiplier).abs();
3191
3192 let title = i18n.get_msg(title);
3193
3194 let args = i18n::fluent_args! {
3195 "boost" => format!("{percentage:.0}"),
3196 "SP" => sp(i18n, skill_set, skill),
3197 };
3198 let desc = i18n.get_msg_ctx(desc, &args);
3199
3200 (title, desc)
3201 },
3202 Self::Empty => (Cow::Borrowed(""), Cow::Borrowed("")),
3203 }
3204 }
3205}
3206
3207const STAT_COUNT: usize = 15;
3209
3210#[derive(EnumIter)]
3211enum CharacterStat {
3212 Name,
3213 BattleMode,
3214 Waypoint,
3215 Hitpoints,
3216 Energy,
3217 Poise,
3218 CombatRating,
3219 Protection,
3220 StunResistance,
3221 PrecisionPower,
3222 EnergyReward,
3223 Stealth,
3224 WeaponPower,
3225 WeaponSpeed,
3226 WeaponEffectPower,
3227}
3228
3229impl CharacterStat {
3230 fn localized_str<'a>(&self, i18n: &'a Localization) -> Cow<'a, str> {
3231 use CharacterStat::*;
3232
3233 match self {
3234 Name => i18n.get_msg("character_window-character_name"),
3235 BattleMode => i18n.get_msg("hud-battle-mode"),
3236 Waypoint => i18n.get_msg("hud-waypoint"),
3237 Hitpoints => i18n.get_msg("hud-bag-health"),
3238 Energy => i18n.get_msg("hud-bag-energy"),
3239 CombatRating => i18n.get_msg("hud-bag-combat_rating"),
3240 Protection => i18n.get_msg("hud-bag-protection"),
3241 StunResistance => i18n.get_msg("hud-bag-stun_res"),
3242 Poise => i18n.get_msg("common-stats-poise_res"),
3243 PrecisionPower => i18n.get_msg("common-stats-precision_power"),
3244 EnergyReward => i18n.get_msg("common-stats-energy_reward"),
3245 Stealth => i18n.get_msg("common-stats-stealth"),
3246 WeaponPower => i18n.get_msg("common-stats-power"),
3247 WeaponSpeed => i18n.get_msg("common-stats-speed"),
3248 WeaponEffectPower => i18n.get_msg("common-stats-effect-power"),
3249 }
3250 }
3251}
3252
3253fn sp<'loc>(i18n: &'loc Localization, skill_set: &SkillSet, skill: Skill) -> Cow<'loc, str> {
3254 let current_level = skill_set.skill_level(skill);
3255 if matches!(current_level, Ok(level) if level == skill.max_level()) {
3256 Cow::Borrowed("")
3257 } else {
3258 i18n.get_msg_ctx("hud-skill-req_sp", &i18n::fluent_args! {
3259 "number" => skill_set.skill_cost(skill),
3260 })
3261 }
3262}