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