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