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