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