Wired up skill UI to check for prerequisites being met and sufficient skill points.

available SP check in general HUD, more skill icons

Trimmed 2000 lines from a file.

UI tweaks
This commit is contained in:
Sam 2021-01-05 01:03:25 -05:00
parent 4b52574750
commit 986c05621a
22 changed files with 1844 additions and 2142 deletions

BIN
assets/voxygen/element/buttons/arrow_down_gold.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/leap_cost.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/leap_damage.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/leap_distance.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/leap_knockback.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/leap_radius.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/spin_amount.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/spin_cost.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/spin_damage.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/spin_helicopter.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/spin_infinite.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skilltree/spin_speed.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -280,11 +280,7 @@ magically infused items?"#,
"hud.bag.feet": "Feet", "hud.bag.feet": "Feet",
"hud.bag.mainhand": "Mainhand", "hud.bag.mainhand": "Mainhand",
"hud.bag.offhand": "Offhand", "hud.bag.offhand": "Offhand",
// Diary
"hud.diary": "Diary",
// Map and Questlog // Map and Questlog
"hud.map.map_title": "Map", "hud.map.map_title": "Map",
"hud.map.qlog_title": "Quests", "hud.map.qlog_title": "Quests",
@ -397,7 +393,7 @@ magically infused items?"#,
"hud.crafting.recipes": "Recipes", "hud.crafting.recipes": "Recipes",
"hud.crafting.ingredients": "Ingredients:", "hud.crafting.ingredients": "Ingredients:",
"hud.crafting.craft": "Craft", "hud.crafting.craft": "Craft",
"hud.crafting.tool_cata": "Requires:", "hud.crafting.tool_cata": "Requires:",
"hud.group": "Group", "hud.group": "Group",
"hud.group.invite_to_join": "{name} invited you to their group!", "hud.group.invite_to_join": "{name} invited you to their group!",

View File

@ -8,6 +8,7 @@
"hud.quests": "Quests", "hud.quests": "Quests",
"hud.you_died": "You Died", "hud.you_died": "You Died",
"hud.waypoint_saved": "Waypoint Saved", "hud.waypoint_saved": "Waypoint Saved",
"hud.sp_arrow_txt": "SP",
"hud.press_key_to_show_keybindings_fmt": "[{key}] Keybindings", "hud.press_key_to_show_keybindings_fmt": "[{key}] Keybindings",
"hud.press_key_to_toggle_lantern_fmt": "[{key}] Lantern", "hud.press_key_to_toggle_lantern_fmt": "[{key}] Lantern",
@ -64,6 +65,8 @@ Maybe you can even obtain one of their
magically infused items?"#, magically infused items?"#,
"hud.spell": "Spells", "hud.spell": "Spells",
// Diary
"hud.diary": "Diary",
"hud.free_look_indicator": "Free look active. Press {key} to disable.", "hud.free_look_indicator": "Free look active. Press {key} to disable.",
"hud.auto_walk_indicator": "Auto walk active", "hud.auto_walk_indicator": "Auto walk active",

View File

@ -6,7 +6,7 @@ use hashbrown::{HashMap, HashSet};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::hash::Hash; use std::hash::Hash;
use tracing::warn; use tracing::{trace, warn};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SkillTreeMap(HashMap<SkillGroupType, HashSet<Skill>>); pub struct SkillTreeMap(HashMap<SkillGroupType, HashSet<Skill>>);
@ -338,8 +338,8 @@ impl SkillSet {
} else { } else {
skill.get_max_level().map(|_| 1) skill.get_max_level().map(|_| 1)
}; };
let prerequisites_met = self.prerequisites_met(skill, next_level); let prerequisites_met = self.prerequisites_met(skill);
if !matches!(self.skills.get(&skill), Some(&None)) { if !matches!(self.skills.get(&skill), Some(level) if *level == skill.get_max_level()) {
if let Some(mut skill_group) = self if let Some(mut skill_group) = self
.skill_groups .skill_groups
.iter_mut() .iter_mut()
@ -359,16 +359,16 @@ impl SkillSet {
} }
self.skills.insert(skill, next_level); self.skills.insert(skill, next_level);
} else { } else {
warn!("Tried to unlock skill for skill group with insufficient SP"); trace!("Tried to unlock skill for skill group with insufficient SP");
} }
} else { } else {
warn!("Tried to unlock skill without meeting prerequisite skills"); trace!("Tried to unlock skill without meeting prerequisite skills");
} }
} else { } else {
warn!("Tried to unlock skill for a skill group that player does not have"); trace!("Tried to unlock skill for a skill group that player does not have");
} }
} else { } else {
warn!("Tried to unlock skill the player already has") trace!("Tried to unlock skill the player already has")
} }
} else { } else {
warn!( warn!(
@ -488,8 +488,13 @@ impl SkillSet {
/// Checks that the skill set contains all prerequisite skills for a /// Checks that the skill set contains all prerequisite skills for a
/// particular skill /// particular skill
pub fn prerequisites_met(&self, skill: Skill, level: Level) -> bool { pub fn prerequisites_met(&self, skill: Skill) -> bool {
skill.prerequisite_skills(level).iter().all(|(s, l)| { let next_level = if self.skills.contains_key(&skill) {
self.skills.get(&skill).copied().flatten().map(|l| l + 1)
} else {
skill.get_max_level().map(|_| 1)
};
skill.prerequisite_skills(next_level).iter().all(|(s, l)| {
self.skills.contains_key(s) && self.skills.get(s).map_or(false, |l_b| l_b >= l) self.skills.contains_key(s) && self.skills.get(s).map_or(false, |l_b| l_b >= l)
}) })
} }
@ -520,6 +525,37 @@ impl SkillSet {
.filter(|s_g| s_g.skill_group_type == skill_group); .filter(|s_g| s_g.skill_group_type == skill_group);
skill_groups.next().map_or(0, |s_g| s_g.exp) skill_groups.next().map_or(0, |s_g| s_g.exp)
} }
/// Checks if player has sufficient skill points to purchase a skill
pub fn sufficient_skill_points(&self, skill: Skill) -> bool {
if let Some(skill_group_type) = SkillSet::get_skill_group_type_for_skill(&skill) {
if let Some(skill_group) = self
.skill_groups
.iter()
.find(|x| x.skill_group_type == skill_group_type)
{
let next_level = if self.skills.contains_key(&skill) {
self.skills.get(&skill).copied().flatten().map(|l| l + 1)
} else {
skill.get_max_level().map(|_| 1)
};
let needed_sp = skill.skill_cost(next_level);
skill_group.available_sp > needed_sp
} else {
false
}
} else {
false
}
}
/// Checks if the player has available SP to spend
pub fn has_available_sp(&self) -> bool {
self.skill_groups.iter().any(|sg| {
sg.available_sp > 0
&& (sg.earned_sp - sg.available_sp) < sg.skill_group_type.get_max_skill_points()
})
}
} }
impl Skill { impl Skill {
@ -528,10 +564,7 @@ impl Skill {
pub fn prerequisite_skills(self, level: Level) -> HashMap<Skill, Level> { pub fn prerequisite_skills(self, level: Level) -> HashMap<Skill, Level> {
let mut prerequisites = HashMap::new(); let mut prerequisites = HashMap::new();
if let Some(level) = level { if let Some(level) = level {
if level > self.get_max_level().unwrap_or(0) { if level > 1 {
// Sets a prerequisite of itself for skills beyond the max level
prerequisites.insert(self, Some(level));
} else if level > 1 {
// For skills above level 1, sets prerequisite of skill of lower level // For skills above level 1, sets prerequisite of skill of lower level
prerequisites.insert(self, Some(level - 1)); prerequisites.insert(self, Some(level - 1));
} }

View File

@ -1,6 +1,6 @@
use super::{ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
BLACK, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, BLACK, CRITICAL_HP_COLOR, LOW_HP_COLOR, QUALITY_LEGENDARY, TEXT_COLOR,
}; };
use crate::{ use crate::{
i18n::Localization, i18n::Localization,
@ -11,10 +11,10 @@ use crate::{
use client::Client; use client::Client;
use common::comp::Stats; use common::comp::Stats;
use conrod_core::{ use conrod_core::{
widget::{self, Button, Text}, widget::{self, Button, Image, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use inline_tweak::*;
widget_ids! { widget_ids! {
struct Ids { struct Ids {
bag, bag,
@ -42,6 +42,9 @@ widget_ids! {
crafting_text, crafting_text,
crafting_text_bg, crafting_text_bg,
group_button, group_button,
sp_arrow,
sp_arrow_txt_bg,
sp_arrow_txt,
} }
} }
#[derive(WidgetCommon)] #[derive(WidgetCommon)]
@ -57,6 +60,7 @@ pub struct Buttons<'a> {
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
stats: &'a Stats, stats: &'a Stats,
pulse: f32,
} }
impl<'a> Buttons<'a> { impl<'a> Buttons<'a> {
@ -71,6 +75,7 @@ impl<'a> Buttons<'a> {
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
stats: &'a Stats, stats: &'a Stats,
pulse: f32,
) -> Self { ) -> Self {
Self { Self {
client, client,
@ -83,6 +88,7 @@ impl<'a> Buttons<'a> {
tooltip_manager, tooltip_manager,
localized_strings, localized_strings,
stats, stats,
pulse,
} }
} }
} }
@ -122,6 +128,9 @@ impl<'a> Widget for Buttons<'a> {
None => return None, None => return None,
}; };
let localized_strings = self.localized_strings; let localized_strings = self.localized_strings;
let arrow_ani =
(self.pulse * tweak!(4.0)/* speed factor */).cos() * tweak!(0.5) + tweak!(0.8); //Animation timer
let button_tooltip = Tooltip::new({ let button_tooltip = Tooltip::new({
// Edge images [t, b, r, l] // Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl] // Corner images [tr, tl, br, bl]
@ -326,22 +335,26 @@ impl<'a> Widget for Buttons<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.map_text, ui); .set(state.ids.map_text, ui);
} }
// Diary
// Spellbook let unspent_sp = self.stats.skill_set.has_available_sp();
if Button::image(self.imgs.spellbook_button) if Button::image(if !unspent_sp {
.w_h(28.0, 25.0) self.imgs.spellbook_button
.left_from(state.ids.map_button, 10.0) } else {
.hover_image(self.imgs.spellbook_hover) self.imgs.spellbook_hover
.press_image(self.imgs.spellbook_press) })
.with_tooltip( .w_h(28.0, 25.0)
self.tooltip_manager, .left_from(state.ids.map_button, 10.0)
&localized_strings.get("hud.diary"), .hover_image(self.imgs.spellbook_hover)
"", .press_image(self.imgs.spellbook_press)
&button_tooltip, .with_tooltip(
TEXT_COLOR, self.tooltip_manager,
) &localized_strings.get("hud.diary"),
.set(state.ids.spellbook_button, ui) "",
.was_clicked() &button_tooltip,
TEXT_COLOR,
)
.set(state.ids.spellbook_button, ui)
.was_clicked()
{ {
return Some(Event::ToggleSpell); return Some(Event::ToggleSpell);
} }
@ -364,7 +377,32 @@ impl<'a> Widget for Buttons<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.spellbook_text, ui); .set(state.ids.spellbook_text, ui);
} }
// Unspent SP indicator
if unspent_sp {
Image::new(self.imgs.sp_indicator_arrow)
.w_h(20.0, 11.0)
.graphics_for(state.ids.spellbook_button)
.mid_top_with_margin_on(
state.ids.spellbook_button,
tweak!(-12.0) + arrow_ani as f64,
)
.color(Some(QUALITY_LEGENDARY))
.set(state.ids.sp_arrow, ui);
Text::new(&localized_strings.get("hud.sp_arrow_txt"))
.mid_top_with_margin_on(state.ids.sp_arrow, tweak!(-18.0))
.graphics_for(state.ids.spellbook_button)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(tweak!(14)))
.color(BLACK)
.set(state.ids.sp_arrow_txt_bg, ui);
Text::new(&localized_strings.get("hud.sp_arrow_txt"))
.graphics_for(state.ids.spellbook_button)
.bottom_right_with_margins_on(state.ids.sp_arrow_txt_bg, 1.0, 1.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(tweak!(14)))
.color(QUALITY_LEGENDARY)
.set(state.ids.sp_arrow_txt, ui);
}
// Crafting // Crafting
if Button::image(self.imgs.crafting_icon) if Button::image(self.imgs.crafting_icon)
.w_h(25.0, 25.0) .w_h(25.0, 25.0)

File diff suppressed because it is too large Load Diff

View File

@ -71,27 +71,47 @@ impl State {
use specs::WorldExt; use specs::WorldExt;
let inventories = client.state().ecs().read_storage::<Inventory>(); let inventories = client.state().ecs().read_storage::<Inventory>();
let inventory = inventories.get(client.entity()); let inventory = inventories.get(client.entity());
let should_be_present = if let Some(inventory) = inventory { let stats = client.state().ecs().read_storage::<common::comp::Stats>();
let stat = stats.get(client.entity());
let should_be_present = if let (Some(inventory), Some(stat)) = (inventory, stat) {
inventory inventory
.equipped(EquipSlot::Mainhand) .equipped(EquipSlot::Mainhand)
.map(|i| i.kind()) .map(|i| i.kind())
.filter(|kind| { .filter(|kind| {
use common::comp::item::{ use common::comp::{
tool::{ToolKind, UniqueKind}, item::{
ItemKind, tool::{ToolKind, UniqueKind},
ItemKind,
},
skills::{self, Skill},
}; };
if let ItemKind::Tool(kind) = kind { if let ItemKind::Tool(tool) = kind {
matches!( match tool.kind {
&kind.kind, ToolKind::Sword => stat
ToolKind::Staff .skill_set
| ToolKind::Debug .skills
| ToolKind::Sword .contains_key(&Skill::Sword(skills::SwordSkill::SUnlockSpin)),
| ToolKind::Hammer ToolKind::Axe => stat
| ToolKind::Axe .skill_set
| ToolKind::Bow .skills
| ToolKind::Unique(UniqueKind::QuadMedQuick) .contains_key(&Skill::Axe(skills::AxeSkill::LUnlockLeap)),
| ToolKind::Unique(UniqueKind::QuadLowBreathe) ToolKind::Hammer => stat
) .skill_set
.skills
.contains_key(&Skill::Hammer(skills::HammerSkill::LUnlockLeap)),
ToolKind::Bow => stat
.skill_set
.skills
.contains_key(&Skill::Bow(skills::BowSkill::UnlockRepeater)),
ToolKind::Staff => stat
.skill_set
.skills
.contains_key(&Skill::Staff(skills::StaffSkill::UnlockShockwave)),
ToolKind::Debug
| ToolKind::Unique(UniqueKind::QuadMedQuick)
| ToolKind::Unique(UniqueKind::QuadLowBreathe) => true,
_ => false,
}
} else { } else {
false false
} }

View File

@ -147,6 +147,8 @@ image_ids! {
group_icon_hover: "voxygen.element.buttons.group_hover", group_icon_hover: "voxygen.element.buttons.group_hover",
group_icon_press: "voxygen.element.buttons.group_press", group_icon_press: "voxygen.element.buttons.group_press",
sp_indicator_arrow: "voxygen.element.buttons.arrow_down_gold",
// Skill Icons // Skill Icons
twohsword_m1: "voxygen.element.icons.2hsword_m1", twohsword_m1: "voxygen.element.icons.2hsword_m1",
twohsword_m2: "voxygen.element.icons.2hsword_m2", twohsword_m2: "voxygen.element.icons.2hsword_m2",

View File

@ -1895,6 +1895,7 @@ impl Hud {
tooltip_manager, tooltip_manager,
i18n, i18n,
&player_stats, &player_stats,
self.pulse,
) )
.set(self.ids.buttons, ui_widgets) .set(self.ids.buttons, ui_widgets)
{ {

View File

@ -37,6 +37,8 @@ use iced::{
}; };
use vek::Rgba; use vek::Rgba;
use inline_tweak::*;
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0); pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2); pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
pub const TOOLTIP_BACK_COLOR: Rgba<u8> = Rgba::new(20, 18, 10, 255); pub const TOOLTIP_BACK_COLOR: Rgba<u8> = Rgba::new(20, 18, 10, 255);
@ -444,7 +446,7 @@ impl Controls {
select_button, select_button,
Column::with_children(vec![ Column::with_children(vec![
Text::new(&character.character.alias) Text::new(&character.character.alias)
.size(fonts.cyri.scale(30)) .size(fonts.cyri.scale(tweak!(26)))
.into(), .into(),
// TODO: only construct string once when characters // TODO: only construct string once when characters
// are // are