diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb7ab61d6..63c9428937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Piercing damage now ignores an amount of protection equal to damage value - Slashing damage now reduces target's energy by an amount equal to damage dealt to target post-mitigation - Crushing damage now does poise damage to a target equal to the amount mitigated by armor +- UI to select abilities and assign to hotbar +- Position of abilities on hotbar is now persisted through the server ### Changed diff --git a/Cargo.lock b/Cargo.lock index adc7000c1e..098459832b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2651,6 +2651,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inline_tweak" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7033e97b20277cc0d043226d1940fa7719ff08d2305d1fc7421e53066d00eb4b" +dependencies = [ + "lazy_static", +] + [[package]] name = "inotify" version = "0.7.1" @@ -6521,6 +6530,7 @@ dependencies = [ "iced_native", "iced_winit", "image", + "inline_tweak", "itertools", "keyboard-keynames", "lazy_static", diff --git a/assets/voxygen/element/ui/diary/abilitiy_desc_frame.png b/assets/voxygen/element/ui/diary/abilitiy_desc_frame.png new file mode 100644 index 0000000000..2e78223a3a --- /dev/null +++ b/assets/voxygen/element/ui/diary/abilitiy_desc_frame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57ce5b8dd65aa9e3c65ee78e8f6d683a628bb9e2a0244c5d0912b60e39f23409 +size 2900 diff --git a/assets/voxygen/element/ui/diary/abilitiy_desc_frame_dual.png b/assets/voxygen/element/ui/diary/abilitiy_desc_frame_dual.png new file mode 100644 index 0000000000..95014ee231 --- /dev/null +++ b/assets/voxygen/element/ui/diary/abilitiy_desc_frame_dual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5e2b2f24fdbd828d126a06d5d67962294ebca83a23d1814c97e64d57f6fd01e +size 2905 diff --git a/assets/voxygen/element/ui/diary/buttons/arrow_l.png b/assets/voxygen/element/ui/diary/buttons/arrow_l.png new file mode 100644 index 0000000000..54080e5cda --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/arrow_l.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd5a83f36705708ab713d572fb7f0043db47649ac44d6abb08eca2d89740df62 +size 2918 diff --git a/assets/voxygen/element/ui/diary/buttons/arrow_l_click.png b/assets/voxygen/element/ui/diary/buttons/arrow_l_click.png new file mode 100644 index 0000000000..ca8e8bcb7c --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/arrow_l_click.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c13a5906e19d2fab7986b7cb26cc9d97437e7c786ffde7f76c874205c60d5189 +size 2908 diff --git a/assets/voxygen/element/ui/diary/buttons/arrow_l_inactive.png b/assets/voxygen/element/ui/diary/buttons/arrow_l_inactive.png new file mode 100644 index 0000000000..e8c1810076 --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/arrow_l_inactive.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:881e973bbb250a3f4d4a0289a4dd80e53aff26b700e221c7702a8bf19abc33db +size 2924 diff --git a/assets/voxygen/element/ui/diary/buttons/arrow_r.png b/assets/voxygen/element/ui/diary/buttons/arrow_r.png new file mode 100644 index 0000000000..5796b02348 --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/arrow_r.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f368f51e07ad113e3d3dcb5427198e9aecccbbd81bedc465db6cd35452e5f6e2 +size 2924 diff --git a/assets/voxygen/element/ui/diary/buttons/arrow_r_click.png b/assets/voxygen/element/ui/diary/buttons/arrow_r_click.png new file mode 100644 index 0000000000..66f43291ab --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/arrow_r_click.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c37fdf59d778c542b05768f1a516e96256eda1f06b736f9745a63169f1f1e9ec +size 2914 diff --git a/assets/voxygen/element/ui/diary/buttons/arrow_r_inactive.png b/assets/voxygen/element/ui/diary/buttons/arrow_r_inactive.png new file mode 100644 index 0000000000..5a6a6dc668 --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/arrow_r_inactive.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44d735b7ad33c756e0742809ad7b18ceb6031a2b117637ae81d0c38411c9397a +size 2927 diff --git a/assets/voxygen/element/ui/diary/buttons/skilltree.png b/assets/voxygen/element/ui/diary/buttons/skilltree.png new file mode 100644 index 0000000000..69039b2866 --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/skilltree.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98cf2d5e0db3a932ba0a8ac0dbf676b33302ecba8e59985b3a631cf2e151ca62 +size 3018 diff --git a/assets/voxygen/element/ui/diary/buttons/spellbook.png b/assets/voxygen/element/ui/diary/buttons/spellbook.png new file mode 100644 index 0000000000..9c32e68615 --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/spellbook.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abb462c94fac62a1bb1a61bf34f6df9897c0c139a8b87863036fc1e95b24c744 +size 3410 diff --git a/assets/voxygen/element/ui/diary/buttons/stats.png b/assets/voxygen/element/ui/diary/buttons/stats.png new file mode 100644 index 0000000000..e5f8ffb25c --- /dev/null +++ b/assets/voxygen/element/ui/diary/buttons/stats.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5d67d7e4ad27bfd6c82f92e38c570eb6f05a298d018f6895219ef0460c5ae23 +size 4203 diff --git a/assets/voxygen/element/ui/diary/diary_skills_bg.png b/assets/voxygen/element/ui/diary/diary_skills_bg.png new file mode 100644 index 0000000000..3e1dd16091 --- /dev/null +++ b/assets/voxygen/element/ui/diary/diary_skills_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7486150f2f17b968bd4dc268e4920a6c0c666e38f03f3d8cdcc989a95244530 +size 3486 diff --git a/assets/voxygen/element/ui/diary/spellbook_bg.png b/assets/voxygen/element/ui/diary/spellbook_bg.png new file mode 100644 index 0000000000..f9dcb70d3b --- /dev/null +++ b/assets/voxygen/element/ui/diary/spellbook_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6f859adc37e5999541621f7f39531d2ba550026a47c032a7976ac97c0f26cae +size 62015 diff --git a/client/src/lib.rs b/client/src/lib.rs index 41f364b938..e6b6181f8f 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -27,7 +27,7 @@ use common::{ group, invite::{InviteKind, InviteResponse}, skills::Skill, - slot::{InvSlotId, Slot}, + slot::{EquipSlot, InvSlotId, Slot}, CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent, UtteranceKind, @@ -1404,8 +1404,26 @@ impl Client { } pub fn change_ability(&mut self, slot: usize, new_ability: comp::ability::AuxiliaryAbility) { + let auxiliary_key = self + .inventories() + .get(self.entity()) + .map_or((None, None), |inv| { + let tool_kind = |slot| { + inv.equipped(slot).and_then(|item| match item.kind() { + comp::item::ItemKind::Tool(tool) => Some(tool.kind), + _ => None, + }) + }; + + ( + tool_kind(EquipSlot::ActiveMainhand), + tool_kind(EquipSlot::ActiveOffhand), + ) + }); + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::ChangeAbility { slot, + auxiliary_key, new_ability, })) } diff --git a/common/base/src/lib.rs b/common/base/src/lib.rs index 800869cf01..d97aa4a003 100644 --- a/common/base/src/lib.rs +++ b/common/base/src/lib.rs @@ -31,7 +31,7 @@ macro_rules! dev_panic { if cfg!(any(debug_assertions, test)) { panic!("{}", $msg); } else { - tracing::warn!("{}", $msg); + tracing::error!("{}", $msg); } }; diff --git a/common/src/combat.rs b/common/src/combat.rs index 5545668770..4e933ca4f0 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -761,46 +761,29 @@ impl Damage { inventory: Option<&Inventory>, stats: Option<&Stats>, ) -> f32 { - let inventory_dr = if let Some(inventory) = inventory { - let protection = inventory - .equipped_items() - .filter_map(|item| { - if let ItemKind::Armor(armor) = &item.kind() { - Some(armor.protection()) - } else { - None - } - }) - .map(|protection| match protection { - Some(Protection::Normal(protection)) => Some(protection), - Some(Protection::Invincible) => None, - None => Some(0.0), - }) - .sum::>(); + let protection = compute_protection(inventory); - let penetration = if let Some(damage) = damage { - if let DamageKind::Piercing = damage.kind { - (damage.value * PIERCING_PENETRATION_FRACTION) - .min(protection.unwrap_or(0.0)) - .max(0.0) - } else { - 0.0 - } + let penetration = if let Some(damage) = damage { + if let DamageKind::Piercing = damage.kind { + (damage.value * PIERCING_PENETRATION_FRACTION) + .min(protection.unwrap_or(0.0)) + .max(0.0) } else { 0.0 - }; - - let protection = protection.map(|p| p - penetration); - - const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; - - match protection { - Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()), - None => 1.0, } } else { 0.0 }; + + let protection = protection.map(|p| p - penetration); + + const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; + + let inventory_dr = match protection { + Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()), + None => 1.0, + }; + let stats_dr = if let Some(stats) = stats { stats.damage_reduction } else { @@ -1172,3 +1155,25 @@ pub fn compute_stealth_coefficient(inventory: Option<&Inventory>) -> f32 { .fold(2.0, |a, b| a + b.max(0.0)) }) } + +/// Computes the total protection provided from armor. Is used to determine the +/// damage reduction applied to damage received by an entity None indicates that +/// the armor equipped makes the entity invulnerable +#[cfg(not(target_arch = "wasm32"))] +pub fn compute_protection(inventory: Option<&Inventory>) -> Option { + inventory.map_or(Some(0.0), |inv| { + inv.equipped_items() + .filter_map(|item| { + if let ItemKind::Armor(armor) = &item.kind() { + armor.protection() + } else { + None + } + }) + .map(|protection| match protection { + Protection::Normal(protection) => Some(protection), + Protection::Invincible => None, + }) + .sum::>() + }) +} diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 1c5daf23d9..9b2bcc3762 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -25,12 +25,14 @@ use crate::{ }, terrain::SpriteKind, }; +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; use specs_idvs::IdvStorage; use std::{convert::TryFrom, time::Duration}; pub const MAX_ABILITIES: usize = 5; +pub type AuxiliaryKey = (Option, Option); // TODO: Potentially look into storing previous ability sets for weapon // combinations and automatically reverting back to them on switching to that @@ -41,7 +43,7 @@ pub struct ActiveAbilities { pub primary: PrimaryAbility, pub secondary: SecondaryAbility, pub movement: MovementAbility, - pub abilities: [AuxiliaryAbility; MAX_ABILITIES], + pub auxiliary_sets: HashMap, } impl Component for ActiveAbilities { @@ -54,31 +56,72 @@ impl Default for ActiveAbilities { primary: PrimaryAbility::Tool, secondary: SecondaryAbility::Tool, movement: MovementAbility::Species, - abilities: [AuxiliaryAbility::Empty; MAX_ABILITIES], + auxiliary_sets: HashMap::new(), } } } impl ActiveAbilities { - pub fn new(inv: Option<&Inventory>, skill_set: Option<&SkillSet>) -> Self { - let mut pool = Self::default(); - pool.auto_update(inv, skill_set); - pool + pub fn new(auxiliary_sets: HashMap) -> Self { + ActiveAbilities { + auxiliary_sets, + ..Self::default() + } } - pub fn change_ability(&mut self, slot: usize, new_ability: AuxiliaryAbility) { - if let Some(ability) = self.abilities.get_mut(slot) { + pub fn change_ability( + &mut self, + slot: usize, + auxiliary_key: AuxiliaryKey, + new_ability: AuxiliaryAbility, + inventory: Option<&Inventory>, + skill_set: Option<&SkillSet>, + ) { + let auxiliary_set = self + .auxiliary_sets + .entry(auxiliary_key) + .or_insert(Self::default_ability_set(inventory, skill_set)); + if let Some(ability) = auxiliary_set.get_mut(slot) { *ability = new_ability; } } - pub fn get_ability(&self, input: AbilityInput) -> Ability { + pub fn auxiliary_set( + &self, + inv: Option<&Inventory>, + skill_set: Option<&SkillSet>, + ) -> [AuxiliaryAbility; MAX_ABILITIES] { + let tool_kind = |slot| { + inv.and_then(|inv| inv.equipped(slot)) + .and_then(|item| match item.kind() { + ItemKind::Tool(tool) => Some(tool.kind), + _ => None, + }) + }; + + let aux_key = ( + tool_kind(EquipSlot::ActiveMainhand), + tool_kind(EquipSlot::ActiveOffhand), + ); + + self.auxiliary_sets + .get(&aux_key) + .copied() + .unwrap_or_else(|| Self::default_ability_set(inv, skill_set)) + } + + pub fn get_ability( + &self, + input: AbilityInput, + inventory: Option<&Inventory>, + skill_set: Option<&SkillSet>, + ) -> Ability { match input { AbilityInput::Primary => self.primary.into(), AbilityInput::Secondary => self.secondary.into(), AbilityInput::Movement => self.movement.into(), AbilityInput::Auxiliary(index) => self - .abilities + .auxiliary_set(inventory, skill_set) .get(index) .copied() .map(|a| a.into()) @@ -96,7 +139,7 @@ impl ActiveAbilities { body: Option<&Body>, // bool is from_offhand ) -> Option<(CharacterAbility, bool)> { - let ability = self.get_ability(input); + let ability = self.get_ability(input, inv, Some(skill_set)); let ability_set = |equip_slot| { inv.and_then(|inv| inv.equipped(equip_slot)) @@ -150,35 +193,35 @@ impl ActiveAbilities { } } - // TODO: Potentially remove after there is an actual UI - pub fn auto_update(&mut self, inv: Option<&Inventory>, skill_set: Option<&SkillSet>) { - fn iter_unlocked_abilities<'a>( - inv: Option<&'a Inventory>, - skill_set: Option<&'a SkillSet>, - equip_slot: EquipSlot, - ) -> impl Iterator + 'a { - inv.and_then(|inv| inv.equipped(equip_slot)) - .into_iter() - .flat_map(|i| &i.item_config_expect().abilities.abilities) - .enumerate() - .filter_map(move |(i, (skill, _))| { - skill - .map_or(true, |s| skill_set.map_or(false, |ss| ss.has_skill(s))) - .then_some(i) - }) - } - - let main_abilities = iter_unlocked_abilities(inv, skill_set, EquipSlot::ActiveMainhand) - .map(AuxiliaryAbility::MainWeapon); - let off_abilities = iter_unlocked_abilities(inv, skill_set, EquipSlot::ActiveOffhand) - .map(AuxiliaryAbility::OffWeapon); - - (0..MAX_ABILITIES) - .zip(main_abilities.chain(off_abilities)) - .for_each(|(i, ability)| { - self.change_ability(i, ability); + pub fn iter_unlocked_abilities<'a>( + inv: Option<&'a Inventory>, + skill_set: Option<&'a SkillSet>, + equip_slot: EquipSlot, + ) -> impl Iterator + 'a { + inv.and_then(|inv| inv.equipped(equip_slot)) + .into_iter() + .flat_map(|i| &i.item_config_expect().abilities.abilities) + .enumerate() + .filter_map(move |(i, (skill, _))| { + skill + .map_or(true, |s| skill_set.map_or(false, |ss| ss.has_skill(s))) + .then_some(i) }) } + + fn default_ability_set<'a>( + inv: Option<&'a Inventory>, + skill_set: Option<&'a SkillSet>, + ) -> [AuxiliaryAbility; MAX_ABILITIES] { + let mut iter = Self::iter_unlocked_abilities(inv, skill_set, EquipSlot::ActiveMainhand) + .map(AuxiliaryAbility::MainWeapon) + .chain( + Self::iter_unlocked_abilities(inv, skill_set, EquipSlot::ActiveOffhand) + .map(AuxiliaryAbility::OffWeapon), + ); + + [(); MAX_ABILITIES].map(|()| iter.next().unwrap_or(AuxiliaryAbility::Empty)) + } } pub enum AbilityInput { diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 7c06b42e1c..fe803208ea 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -138,6 +138,7 @@ pub enum ControlEvent { Utterance(UtteranceKind), ChangeAbility { slot: usize, + auxiliary_key: ability::AuxiliaryKey, new_ability: ability::AuxiliaryAbility, }, } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 9476eb598f..b33dfbe589 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -48,7 +48,10 @@ pub mod visual; // Reexports #[cfg(not(target_arch = "wasm32"))] pub use self::{ - ability::{Ability, AbilityInput, ActiveAbilities, CharacterAbility, CharacterAbilityType}, + ability::{ + Ability, AbilityInput, ActiveAbilities, CharacterAbility, CharacterAbilityType, + MAX_ABILITIES, + }, admin::{Admin, AdminRole}, agent::{Agent, Alignment, Behavior, BehaviorCapability, BehaviorState, PidController}, anchor::Anchor, diff --git a/common/src/comp/poise.rs b/common/src/comp/poise.rs index 916844e44e..3cd52e052a 100644 --- a/common/src/comp/poise.rs +++ b/common/src/comp/poise.rs @@ -231,15 +231,14 @@ impl Poise { .equipped_items() .filter_map(|item| { if let ItemKind::Armor(armor) = &item.kind() { - Some(armor.poise_resilience()) + armor.poise_resilience() } else { None } }) .map(|protection| match protection { - Some(Protection::Normal(protection)) => Some(protection), - Some(Protection::Invincible) => None, - None => Some(0.0), + Protection::Normal(protection) => Some(protection), + Protection::Invincible => None, }) .sum::>(); match protection { diff --git a/common/src/event.rs b/common/src/event.rs index d1e8215a11..f5dd7d6e4e 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -115,6 +115,7 @@ pub enum ServerEvent { comp::Inventory, Option, Vec<(comp::Pet, comp::Body, comp::Stats)>, + comp::ActiveAbilities, ), }, ExitIngame { @@ -203,6 +204,12 @@ pub enum ServerEvent { EntityAttackedHook { entity: EcsEntity, }, + ChangeAbility { + entity: EcsEntity, + slot: usize, + auxiliary_key: comp::ability::AuxiliaryKey, + new_ability: comp::ability::AuxiliaryAbility, + }, } pub struct EventBus { diff --git a/common/systems/src/controller.rs b/common/systems/src/controller.rs index dcc403e15f..baefa30fd9 100644 --- a/common/systems/src/controller.rs +++ b/common/systems/src/controller.rs @@ -1,7 +1,7 @@ use common::{ comp::{ agent::{Sound, SoundKind}, - ActiveAbilities, Body, BuffChange, ControlEvent, Controller, Pos, + Body, BuffChange, ControlEvent, Controller, Pos, }, event::{EventBus, ServerEvent}, uid::UidAllocator, @@ -27,20 +27,13 @@ pub struct ReadData<'a> { pub struct Sys; impl<'a> System<'a> for Sys { - type SystemData = ( - ReadData<'a>, - WriteStorage<'a, Controller>, - WriteStorage<'a, ActiveAbilities>, - ); + type SystemData = (ReadData<'a>, WriteStorage<'a, Controller>); const NAME: &'static str = "controller"; const ORIGIN: Origin = Origin::Common; const PHASE: Phase = Phase::Create; - fn run( - _job: &mut Job, - (read_data, mut controllers, mut active_abilities): Self::SystemData, - ) { + fn run(_job: &mut Job, (read_data, mut controllers): Self::SystemData) { let mut server_emitter = read_data.server_bus.emitter(); for (entity, controller) in (&read_data.entities, &mut controllers).join() { @@ -110,10 +103,17 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Sound { sound }); } }, - ControlEvent::ChangeAbility { slot, new_ability } => { - if let Some(mut active_abilities) = active_abilities.get_mut(entity) { - active_abilities.change_ability(slot, new_ability); - } + ControlEvent::ChangeAbility { + slot, + auxiliary_key, + new_ability, + } => { + server_emitter.emit(ServerEvent::ChangeAbility { + entity, + slot, + auxiliary_key, + new_ability, + }); }, } } diff --git a/server/src/character_creator.rs b/server/src/character_creator.rs index 54a27cb808..a93c7a3ea2 100644 --- a/server/src/character_creator.rs +++ b/server/src/character_creator.rs @@ -1,4 +1,4 @@ -use crate::persistence::character_updater::CharacterUpdater; +use crate::persistence::{character_updater::CharacterUpdater, PersistedComponents}; use common::{ character::CharacterId, comp::{inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, SkillSet, Stats}, @@ -65,12 +65,15 @@ pub fn create_character( let waypoint = None; - character_updater.create_character( - entity, - player_uuid, - character_alias, - (body, stats, skill_set, inventory, waypoint, Vec::new()), - ); + character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents { + body, + stats, + skill_set, + inventory, + waypoint, + pets: Vec::new(), + active_abilities: Default::default(), + }); Ok(()) } diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index d449977484..3402009ac3 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -1,4 +1,4 @@ -use crate::{client::Client, sys, Server, StateExt}; +use crate::{client::Client, persistence::PersistedComponents, sys, Server, StateExt}; use common::{ character::CharacterId, comp::{ @@ -35,14 +35,7 @@ pub fn handle_initialize_character( pub fn handle_loaded_character_data( server: &mut Server, entity: EcsEntity, - loaded_components: ( - comp::Body, - comp::Stats, - comp::SkillSet, - comp::Inventory, - Option, - Vec<(comp::Pet, comp::Body, comp::Stats)>, - ), + loaded_components: PersistedComponents, ) { server .state diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 2123983133..40ba9eebba 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1,6 +1,7 @@ use crate::{ client::Client, comp::{ + ability, agent::{Agent, AgentEvent, Sound, SoundKind}, skillset::SkillGroupKind, BuffKind, BuffSource, PhysicsState, @@ -1260,3 +1261,26 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) { buff_change: buff::BuffChange::RemoveByKind(buff::BuffKind::Saturation), }); } + +pub fn handle_change_ability( + server: &Server, + entity: EcsEntity, + slot: usize, + auxiliary_key: ability::AuxiliaryKey, + new_ability: ability::AuxiliaryAbility, +) { + let ecs = &server.state.ecs(); + let inventories = ecs.read_storage::(); + let skill_sets = ecs.read_storage::(); + + if let Some(mut active_abilities) = ecs.write_storage::().get_mut(entity) + { + active_abilities.change_ability( + slot, + auxiliary_key, + new_ability, + inventories.get(entity), + skill_sets.get(entity), + ); + } +} diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 8208121a01..34896df0d9 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -767,19 +767,6 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv // manipulating the inventory mutated the trade, so reset the accept flags trades.implicit_mutation_occurred(&uid); } - - // After any inventory manipulation, update the ability - // TODO: Make less hacky, probably remove entirely but needs UI - if let Some(mut active_abilities) = state - .ecs() - .write_storage::() - .get_mut(entity) - { - active_abilities.auto_update( - state.ecs().read_storage::().get(entity), - state.ecs().read_storage::().get(entity), - ); - } } fn within_pickup_range>( diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 4fee80df93..82c733389e 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -1,4 +1,7 @@ -use crate::{events::interaction::handle_tame_pet, state_ext::StateExt, Server}; +use crate::{ + events::interaction::handle_tame_pet, persistence::PersistedComponents, state_ext::StateExt, + Server, +}; use common::event::{EventBus, ServerEvent}; use common_base::span; use entity_creation::{ @@ -6,10 +9,10 @@ use entity_creation::{ handle_initialize_character, handle_loaded_character_data, handle_shockwave, handle_shoot, }; use entity_manipulation::{ - handle_aura, handle_bonk, handle_buff, handle_combo_change, handle_delete, handle_destroy, - handle_energy_change, handle_entity_attacked_hook, handle_explosion, handle_health_change, - handle_knockback, handle_land_on_ground, handle_parry, handle_poise, handle_respawn, - handle_teleport_to, + handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change, + handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook, + handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, handle_parry, + handle_poise, handle_respawn, handle_teleport_to, }; use group_manip::handle_group; use information::handle_site_info; @@ -129,6 +132,17 @@ impl Server { character_id, } => handle_initialize_character(self, entity, character_id), ServerEvent::UpdateCharacterData { entity, components } => { + let (body, stats, skill_set, inventory, waypoint, pets, active_abilities) = + components; + let components = PersistedComponents { + body, + stats, + skill_set, + inventory, + waypoint, + pets, + active_abilities, + }; handle_loaded_character_data(self, entity, components); }, ServerEvent::ExitIngame { entity } => { @@ -233,6 +247,12 @@ impl Server { ServerEvent::EntityAttackedHook { entity } => { handle_entity_attacked_hook(self, entity) }, + ServerEvent::ChangeAbility { + entity, + slot, + auxiliary_key, + new_ability, + } => handle_change_ability(self, entity, slot, auxiliary_key, new_ability), } } diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 83112b3568..55113c75a4 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -202,6 +202,7 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { Some(presence), Some(skill_set), Some(inventory), + Some(active_abilities), Some(player_uid), Some(player_info), mut character_updater, @@ -210,6 +211,9 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { state.read_storage::().get(entity), state.read_storage::().get(entity), state.read_storage::().get(entity), + state + .read_storage::() + .get(entity), state.read_storage::().get(entity), state.read_storage::().get(entity), state.ecs().fetch_mut::(), @@ -251,7 +255,13 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { character_updater.add_pending_logout_update( char_id, - (skill_set.clone(), inventory.clone(), pets, waypoint), + ( + skill_set.clone(), + inventory.clone(), + pets, + waypoint, + active_abilities.clone(), + ), ); }, PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ }, diff --git a/server/src/lib.rs b/server/src/lib.rs index 371a28db3b..fa19904156 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -55,6 +55,7 @@ use crate::{ connection_handler::ConnectionHandler, data_dir::DataDir, login_provider::LoginProvider, + persistence::PersistedComponents, presence::{Presence, RegionSubscription, RepositionOnChunkLoad}, rtsim::RtSim, state_ext::StateExt, @@ -844,9 +845,29 @@ impl Server { }, CharacterLoaderResponseKind::CharacterData(result) => { let message = match *result { - Ok(character_data) => ServerEvent::UpdateCharacterData { - entity: query_result.entity, - components: character_data, + Ok(character_data) => { + let PersistedComponents { + body, + stats, + skill_set, + inventory, + waypoint, + pets, + active_abilities, + } = character_data; + let character_data = ( + body, + stats, + skill_set, + inventory, + waypoint, + pets, + active_abilities, + ); + ServerEvent::UpdateCharacterData { + entity: query_result.entity, + components: character_data, + } }, Err(error) => { // We failed to load data for the character from the DB. Notify the diff --git a/server/src/migrations/V47__ability_sets.sql b/server/src/migrations/V47__ability_sets.sql new file mode 100644 index 0000000000..1132fb43ba --- /dev/null +++ b/server/src/migrations/V47__ability_sets.sql @@ -0,0 +1,12 @@ +-- Creates new ability_set table +CREATE TABLE "ability_set" ( + "entity_id" INT NOT NULL, + "ability_sets" TEXT NOT NULL, + PRIMARY KEY("entity_id"), + FOREIGN KEY("entity_id") REFERENCES "character"("character_id") +); + +-- Inserts starting ability sets for everyone +INSERT INTO ability_set +SELECT c.character_id, '[]' +FROM character c diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index 5668adcec5..6517d49d5b 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -8,10 +8,10 @@ extern crate rusqlite; use super::{error::PersistenceError, models::*}; use crate::{ - comp, - comp::Inventory, + comp::{self, Inventory}, persistence::{ character::conversions::{ + convert_active_abilities_from_database, convert_active_abilities_to_database, convert_body_from_database, convert_body_to_database_json, convert_character_from_database, convert_inventory_from_database_items, convert_items_to_database_items, convert_loadout_from_database_items, @@ -234,19 +234,34 @@ pub fn load_character_data( }) .collect::>(); - Ok(( - convert_body_from_database(&body_data.variant, &body_data.body_data)?, - convert_stats_from_database(character_data.alias), - convert_skill_set_from_database(&skill_group_data), - convert_inventory_from_database_items( + let mut stmt = connection.prepare_cached( + " + SELECT ability_sets + FROM ability_set + WHERE entity_id = ?1", + )?; + + let ability_set_data = stmt.query_row(&[char_id], |row| { + Ok(AbilitySets { + entity_id: char_id, + ability_sets: row.get(0)?, + }) + })?; + + Ok(PersistedComponents { + body: convert_body_from_database(&body_data.variant, &body_data.body_data)?, + stats: convert_stats_from_database(character_data.alias), + skill_set: convert_skill_set_from_database(&skill_group_data), + inventory: convert_inventory_from_database_items( character_containers.inventory_container_id, &inventory_items, character_containers.loadout_container_id, &loadout_items, )?, - char_waypoint, + waypoint: char_waypoint, pets, - )) + active_abilities: convert_active_abilities_from_database(&ability_set_data), + }) } /// Loads a list of characters belonging to the player. This data is a small @@ -327,14 +342,22 @@ pub fn create_character( uuid: &str, character_alias: &str, persisted_components: PersistedComponents, - transactionn: &mut Transaction, + transaction: &mut Transaction, ) -> CharacterCreationResult { - check_character_limit(uuid, transactionn)?; + check_character_limit(uuid, transaction)?; - let (body, _stats, skill_set, inventory, waypoint, _) = persisted_components; + let PersistedComponents { + body, + stats: _, + skill_set, + inventory, + waypoint, + pets: _, + active_abilities, + } = persisted_components; // Fetch new entity IDs for character, inventory and loadout - let mut new_entity_ids = get_new_entity_ids(transactionn, |next_id| next_id + 3)?; + let mut new_entity_ids = get_new_entity_ids(transaction, |next_id| next_id + 3)?; // Create pseudo-container items for character let character_id = new_entity_ids.next().unwrap(); @@ -365,7 +388,7 @@ pub fn create_character( }, ]; - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO item (item_id, parent_container_item_id, @@ -386,7 +409,7 @@ pub fn create_character( } drop(stmt); - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO body (body_id, variant, @@ -402,7 +425,7 @@ pub fn create_character( ])?; drop(stmt); - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO character (character_id, player_uuid, @@ -421,7 +444,7 @@ pub fn create_character( let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups()); - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO skill_group (entity_id, skill_group_kind, @@ -444,10 +467,25 @@ pub fn create_character( } drop(stmt); + let ability_sets = convert_active_abilities_to_database(character_id, &active_abilities); + + let mut stmt = transaction.prepare_cached( + " + INSERT INTO ability_set (entity_id, + ability_sets) + VALUES (?1, ?2)", + )?; + + stmt.execute(&[ + &character_id as &dyn ToSql, + &ability_sets.ability_sets as &dyn ToSql, + ])?; + drop(stmt); + // Insert default inventory and loadout item records let mut inserts = Vec::new(); - get_new_entity_ids(transactionn, |mut next_id| { + get_new_entity_ids(transaction, |mut next_id| { let inserts_ = convert_items_to_database_items( loadout_container_id, &inventory, @@ -458,7 +496,7 @@ pub fn create_character( next_id })?; - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO item (item_id, parent_container_item_id, @@ -479,7 +517,7 @@ pub fn create_character( } drop(stmt); - load_character_list(uuid, transactionn).map(|list| (character_id, list)) + load_character_list(uuid, transaction).map(|list| (character_id, list)) } pub fn edit_character( @@ -579,6 +617,17 @@ pub fn delete_character( delete_pets(transaction, char_id, Rc::new(pet_ids))?; } + // Delete ability sets + let mut stmt = transaction.prepare_cached( + " + DELETE + FROM ability_set + WHERE entity_id = ?1", + )?; + + stmt.execute(&[&char_id])?; + drop(stmt); + // Delete character let mut stmt = transaction.prepare_cached( " @@ -892,6 +941,7 @@ pub fn update( inventory: comp::Inventory, pets: Vec, char_waypoint: Option, + active_abilities: comp::ability::ActiveAbilities, transaction: &mut Transaction, ) -> Result<(), PersistenceError> { // Run pet persistence @@ -1035,5 +1085,27 @@ pub fn update( ))); } + let ability_sets = convert_active_abilities_to_database(char_id, &active_abilities); + + let mut stmt = transaction.prepare_cached( + " + UPDATE ability_set + SET ability_sets = ?1 + WHERE entity_id = ?2 + ", + )?; + + let ability_sets_count = stmt.execute(&[ + &ability_sets.ability_sets as &dyn ToSql, + &char_id as &dyn ToSql, + ])?; + + if ability_sets_count != 1 { + return Err(PersistenceError::OtherError(format!( + "Error updating ability_set table for char_id {}", + char_id + ))); + } + Ok(()) } diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index a962ea3ce8..0b73a2a046 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -1,11 +1,11 @@ use crate::persistence::{ character::EntityId, - models::{Character, Item, SkillGroup}, + models::{AbilitySets, Character, Item, SkillGroup}, }; use crate::persistence::{ error::PersistenceError, - json_models::{self, CharacterPosition, GenericBody, HumanoidBody}, + json_models::{self, CharacterPosition, DatabaseAbilitySet, GenericBody, HumanoidBody}, }; use common::{ character::CharacterId, @@ -602,3 +602,28 @@ pub fn convert_skill_groups_to_database<'a, I: Iterator AbilitySets { + let ability_sets = json_models::active_abilities_to_db_model(active_abilities); + AbilitySets { + entity_id, + ability_sets: serde_json::to_string(&ability_sets).unwrap_or_default(), + } +} + +pub fn convert_active_abilities_from_database( + ability_sets: &AbilitySets, +) -> ability::ActiveAbilities { + let ability_sets = serde_json::from_str::>(&ability_sets.ability_sets) + .unwrap_or_else(|err| { + common_base::dev_panic!(format!( + "Failed to parse ability sets. Error: {:#?}\nAbility sets:\n{:#?}", + err, ability_sets.ability_sets + )); + Vec::new() + }); + json_models::active_abilities_from_db_model(ability_sets) +} diff --git a/server/src/persistence/character_updater.rs b/server/src/persistence/character_updater.rs index f5d590de87..349ac82ce8 100644 --- a/server/src/persistence/character_updater.rs +++ b/server/src/persistence/character_updater.rs @@ -24,6 +24,7 @@ pub type CharacterUpdateData = ( comp::Inventory, Vec, Option, + comp::ability::ActiveAbilities, ); pub type PetPersistenceData = (comp::Pet, comp::Body, comp::Stats); @@ -330,21 +331,25 @@ impl CharacterUpdater { &'a comp::Inventory, Vec, Option<&'a comp::Waypoint>, + &'a comp::ability::ActiveAbilities, ), >, ) { let updates = updates - .map(|(character_id, skill_set, inventory, pets, waypoint)| { - ( - character_id, + .map( + |(character_id, skill_set, inventory, pets, waypoint, active_abilities)| { ( - skill_set.clone(), - inventory.clone(), - pets, - waypoint.cloned(), - ), - ) - }) + character_id, + ( + skill_set.clone(), + inventory.clone(), + pets, + waypoint.cloned(), + active_abilities.clone(), + ), + ) + }, + ) .chain(self.pending_logout_updates.drain()) .collect::>(); @@ -382,18 +387,19 @@ fn execute_batch_update( let mut transaction = connection.connection.transaction()?; transaction.set_drop_behavior(DropBehavior::Rollback); trace!("Transaction started for character batch update"); - updates - .into_iter() - .try_for_each(|(character_id, (stats, inventory, pets, waypoint))| { + updates.into_iter().try_for_each( + |(character_id, (stats, inventory, pets, waypoint, active_abilities))| { super::character::update( character_id, stats, inventory, pets, waypoint, + active_abilities, &mut transaction, ) - })?; + }, + )?; transaction.commit()?; trace!("Commit for character batch update completed"); diff --git a/server/src/persistence/json_models.rs b/server/src/persistence/json_models.rs index 41e0be70f9..9522fb2d9c 100644 --- a/server/src/persistence/json_models.rs +++ b/server/src/persistence/json_models.rs @@ -1,4 +1,6 @@ use common::comp; +use common_base::dev_panic; +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::string::ToString; use vek::Vec3; @@ -106,3 +108,176 @@ pub fn db_string_to_skill_group(skill_group_string: &str) -> comp::skillset::Ski ), } } + +#[derive(Serialize, Deserialize)] +pub struct DatabaseAbilitySet { + mainhand: String, + offhand: String, + abilities: Vec, +} + +fn aux_ability_to_string(ability: common::comp::ability::AuxiliaryAbility) -> String { + use common::comp::ability::AuxiliaryAbility; + match ability { + AuxiliaryAbility::MainWeapon(index) => format!("Main Weapon:index:{}", index), + AuxiliaryAbility::OffWeapon(index) => format!("Off Weapon:index:{}", index), + AuxiliaryAbility::Empty => String::from("Empty"), + } +} + +fn aux_ability_from_string(ability: &str) -> common::comp::ability::AuxiliaryAbility { + use common::comp::ability::AuxiliaryAbility; + let mut parts = ability.split(":index:"); + match parts.next() { + Some("Main Weapon") => match parts + .next() + .map(|index| index.parse::().map_err(|_| index)) + { + Some(Ok(index)) => AuxiliaryAbility::MainWeapon(index), + Some(Err(error)) => { + dev_panic!(format!( + "Conversion from database to ability set failed. Unable to parse index for \ + mainhand abilities: {}", + error + )); + AuxiliaryAbility::Empty + }, + None => { + dev_panic!(String::from( + "Conversion from database to ability set failed. Unable to find an index for \ + mainhand abilities" + )); + AuxiliaryAbility::Empty + }, + }, + Some("Off Weapon") => match parts + .next() + .map(|index| index.parse::().map_err(|_| index)) + { + Some(Ok(index)) => AuxiliaryAbility::OffWeapon(index), + Some(Err(error)) => { + dev_panic!(format!( + "Conversion from database to ability set failed. Unable to parse index for \ + offhand abilities: {}", + error + )); + AuxiliaryAbility::Empty + }, + None => { + dev_panic!(String::from( + "Conversion from database to ability set failed. Unable to find an index for \ + offhand abilities" + )); + AuxiliaryAbility::Empty + }, + }, + Some("Empty") => AuxiliaryAbility::Empty, + unknown => { + dev_panic!(format!( + "Conversion from database to ability set failed. Unknown auxiliary ability: {:#?}", + unknown + )); + AuxiliaryAbility::Empty + }, + } +} + +fn tool_kind_to_string(tool: Option) -> String { + use common::comp::item::tool::ToolKind::*; + String::from(match tool { + Some(Sword) => "Sword", + Some(Axe) => "Axe", + Some(Hammer) => "Hammer", + Some(Bow) => "Bow", + Some(Staff) => "Staff", + Some(Sceptre) => "Sceptre", + Some(Dagger) => "Dagger", + Some(Shield) => "Shield", + Some(Spear) => "Spear", + Some(Blowgun) => "Blowgun", + Some(Pick) => "Pick", + + // Toolkinds that are not anticipated to have many active aiblities (if any at all) + Some(Farming) => "Farming", + Some(Debug) => "Debug", + Some(Natural) => "Natural", + Some(Empty) => "Empty", + None => "None", + }) +} + +fn tool_kind_from_string(tool: String) -> Option { + use common::comp::item::tool::ToolKind::*; + match tool.as_str() { + "Sword" => Some(Sword), + "Axe" => Some(Axe), + "Hammer" => Some(Hammer), + "Bow" => Some(Bow), + "Staff" => Some(Staff), + "Sceptre" => Some(Sceptre), + "Dagger" => Some(Dagger), + "Shield" => Some(Shield), + "Spear" => Some(Spear), + "Blowgun" => Some(Blowgun), + "Pick" => Some(Pick), + + "Farming" => Some(Farming), + "Debug" => Some(Debug), + "Natural" => Some(Natural), + "Empty" => Some(Empty), + "None" => None, + unknown => { + dev_panic!(format!( + "Conversion from database to ability set failed. Unknown toolkind: {:#?}", + unknown + )); + None + }, + } +} + +pub fn active_abilities_to_db_model( + active_abilities: &comp::ability::ActiveAbilities, +) -> Vec { + active_abilities + .auxiliary_sets + .iter() + .map(|((mainhand, offhand), abilities)| DatabaseAbilitySet { + mainhand: tool_kind_to_string(*mainhand), + offhand: tool_kind_to_string(*offhand), + abilities: abilities + .iter() + .map(|ability| aux_ability_to_string(*ability)) + .collect(), + }) + .collect::>() +} + +pub fn active_abilities_from_db_model( + ability_sets: Vec, +) -> comp::ability::ActiveAbilities { + let ability_sets = ability_sets + .into_iter() + .map( + |DatabaseAbilitySet { + mainhand, + offhand, + abilities, + }| { + let mut auxiliary_abilities = + [comp::ability::AuxiliaryAbility::Empty; comp::ability::MAX_ABILITIES]; + for (empty, ability) in auxiliary_abilities.iter_mut().zip(abilities.into_iter()) { + *empty = aux_ability_from_string(&ability); + } + ( + ( + tool_kind_from_string(mainhand), + tool_kind_from_string(offhand), + ), + auxiliary_abilities, + ) + }, + ) + .collect::>(); + comp::ability::ActiveAbilities::new(ability_sets) +} diff --git a/server/src/persistence/mod.rs b/server/src/persistence/mod.rs index 8964be8f53..782dcbbca1 100644 --- a/server/src/persistence/mod.rs +++ b/server/src/persistence/mod.rs @@ -21,15 +21,17 @@ use std::{ }; use tracing::info; -/// A tuple of the components that are persisted to the DB for each character -pub type PersistedComponents = ( - comp::Body, - comp::Stats, - comp::SkillSet, - comp::Inventory, - Option, - Vec, -); +/// A struct of the components that are persisted to the DB for each character +#[derive(Debug)] +pub struct PersistedComponents { + pub body: comp::Body, + pub stats: comp::Stats, + pub skill_set: comp::SkillSet, + pub inventory: comp::Inventory, + pub waypoint: Option, + pub pets: Vec, + pub active_abilities: comp::ActiveAbilities, +} pub type EditableComponents = (comp::Body,); diff --git a/server/src/persistence/models.rs b/server/src/persistence/models.rs index 42d6468af7..1f339316fb 100644 --- a/server/src/persistence/models.rs +++ b/server/src/persistence/models.rs @@ -35,3 +35,8 @@ pub struct Pet { pub body_variant: String, pub body_data: String, } + +pub struct AbilitySets { + pub entity_id: i64, + pub ability_sets: String, +} diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 50c2c8ac65..3aa1aa030e 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -238,10 +238,7 @@ impl StateExt for State { .unwrap_or(0), )) .with(stats) - .with(comp::ActiveAbilities::new( - Some(&inventory), - Some(&skill_set), - )) + .with(comp::ActiveAbilities::default()) .with(skill_set) .maybe_with(health) .with(poise) @@ -500,7 +497,15 @@ impl StateExt for State { } fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) { - let (body, stats, skill_set, inventory, waypoint, pets) = components; + let PersistedComponents { + body, + stats, + skill_set, + inventory, + waypoint, + pets, + active_abilities, + } = components; if let Some(player_uid) = self.read_component_copied::(entity) { // Notify clients of a player list update @@ -530,10 +535,7 @@ impl StateExt for State { self.write_component_ignore_entity_dead(entity, comp::Energy::new(body, energy_level)); self.write_component_ignore_entity_dead(entity, comp::Poise::new(body)); self.write_component_ignore_entity_dead(entity, stats); - self.write_component_ignore_entity_dead( - entity, - comp::ActiveAbilities::new(Some(&inventory), Some(&skill_set)), - ); + self.write_component_ignore_entity_dead(entity, active_abilities); self.write_component_ignore_entity_dead(entity, skill_set); self.write_component_ignore_entity_dead(entity, inventory); self.write_component_ignore_entity_dead( diff --git a/server/src/sys/persistence.rs b/server/src/sys/persistence.rs index addbc00cf3..f483f7be00 100644 --- a/server/src/sys/persistence.rs +++ b/server/src/sys/persistence.rs @@ -2,7 +2,7 @@ use crate::{persistence::character_updater, presence::Presence, sys::SysSchedule use common::{ comp::{ pet::{is_tameable, Pet}, - Alignment, Body, Inventory, SkillSet, Stats, Waypoint, + ActiveAbilities, Alignment, Body, Inventory, SkillSet, Stats, Waypoint, }, uid::Uid, }; @@ -25,6 +25,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Waypoint>, ReadStorage<'a, Pet>, ReadStorage<'a, Stats>, + ReadStorage<'a, ActiveAbilities>, WriteExpect<'a, character_updater::CharacterUpdater>, Write<'a, SysScheduler>, ); @@ -45,6 +46,7 @@ impl<'a> System<'a> for Sys { player_waypoints, pets, stats, + active_abilities, mut updater, mut scheduler, ): Self::SystemData, @@ -57,11 +59,18 @@ impl<'a> System<'a> for Sys { &player_inventories, &uids, player_waypoints.maybe(), + &active_abilities, ) .join() .filter_map( - |(presence, skill_set, inventory, player_uid, waypoint)| match presence.kind - { + |( + presence, + skill_set, + inventory, + player_uid, + waypoint, + active_abilities, + )| match presence.kind { PresenceKind::Character(id) => { let pets = (&alignments, &bodies, &stats, &pets) .join() @@ -78,7 +87,7 @@ impl<'a> System<'a> for Sys { }) .collect(); - Some((id, skill_set, inventory, pets, waypoint)) + Some((id, skill_set, inventory, pets, waypoint, active_abilities)) }, PresenceKind::Spectator => None, }, diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index d32f38d4fa..84ac7ceee4 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -122,7 +122,7 @@ treeculler = "0.2" tokio = { version = "1.14", default-features = false, features = ["rt-multi-thread"] } num_cpus = "1.0" # vec_map = { version = "0.8.2" } -# inline_tweak = "1.0.2" +inline_tweak = "1.0.2" itertools = "0.10.0" # Tracy diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index a9b94ad4fd..eea5901611 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -1,12 +1,22 @@ use super::{ img_ids::{Imgs, ImgsRot}, item_imgs::{animate_by_pulse, ItemImgs, ItemKey::Tool}, - Position, PositionSpecifier, Show, CRITICAL_HP_COLOR, HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, - UI_MAIN, XP_COLOR, + Position, PositionSpecifier, Show, BLACK, CRITICAL_HP_COLOR, HP_COLOR, TEXT_COLOR, + UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; use crate::{ - hud, - ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, + game_input::GameInput, + hud::{ + self, + slots::{AbilitySlot, SlotManager}, + util, + }, + ui::{ + fonts::Fonts, + slot::{ContentSize, SlotMaker}, + ImageFrame, Tooltip, TooltipManager, Tooltipable, + }, + GlobalState, }; use conrod_core::{ color, image, @@ -14,16 +24,27 @@ use conrod_core::{ widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, }; use i18n::Localization; +use vek::*; use client::{self, Client}; use common::{ + combat, comp::{ - item::tool::ToolKind, + self, + ability::{Ability, ActiveAbilities, AuxiliaryAbility, MAX_ABILITIES}, + inventory::{ + item::{ + tool::{MaterialStatManifest, ToolKind}, + ItemKind, + }, + slot::EquipSlot, + }, skills::{ self, AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill, SceptreSkill, Skill, StaffSkill, SwimSkill, SwordSkill, SKILL_MODIFIERS, }, skillset::{SkillGroupKind, SkillSet}, + Body, Energy, Health, Inventory, Poise, }, consts::{ENERGY_PER_LEVEL, HP_PER_LEVEL}, }; @@ -39,6 +60,9 @@ widget_ids! { close, title, content_align, + section_imgs[], + section_btns[], + // Skill tree stuffs exp_bar_bg, exp_bar_frame, exp_bar_content_align, @@ -184,6 +208,29 @@ widget_ids! { skill_general_climb_2, skill_general_swim_0, skill_general_swim_1, + // Ability selection + spellbook_art, + sb_page_left_align, + sb_page_right_align, + spellbook_skills_bg, + spellbook_btn, + spellbook_btn_bg, + ability_select_title, + ability_page_left, + ability_page_right, + active_abilities[], + active_abilities_keys[], + main_weap_select, + off_weap_select, + abilities[], + ability_frames[], + abilities_dual[], + ability_titles[], + ability_descs[], + dragged_ability, + // Stats + stat_names[], + stat_values[], } } @@ -191,13 +238,22 @@ widget_ids! { pub struct Diary<'a> { show: &'a Show, _client: &'a Client, + global_state: &'a GlobalState, skill_set: &'a SkillSet, + active_abilities: &'a ActiveAbilities, + inventory: &'a Inventory, + health: &'a Health, + energy: &'a Energy, + poise: &'a Poise, + body: &'a Body, + msm: &'a MaterialStatManifest, imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a Fonts, localized_strings: &'a Localization, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut SlotManager, pulse: f32, #[conrod(common_builder)] @@ -208,29 +264,62 @@ pub struct Diary<'a> { created_btns_bot_r: usize, } +pub struct DiaryShow { + pub skilltreetab: SelectedSkillTree, + pub section: DiarySection, +} + +impl Default for DiaryShow { + fn default() -> Self { + Self { + skilltreetab: SelectedSkillTree::General, + section: DiarySection::SkillTrees, + } + } +} + +#[allow(clippy::too_many_arguments)] impl<'a> Diary<'a> { pub fn new( show: &'a Show, _client: &'a Client, + global_state: &'a GlobalState, skill_set: &'a SkillSet, + active_abilities: &'a ActiveAbilities, + inventory: &'a Inventory, + health: &'a Health, + energy: &'a Energy, + poise: &'a Poise, + body: &'a Body, + msm: &'a MaterialStatManifest, imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a Fonts, localized_strings: &'a Localization, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut SlotManager, pulse: f32, ) -> Self { Self { show, _client, + global_state, skill_set, + active_abilities, + inventory, + health, + energy, + poise, + body, + msm, imgs, item_imgs, fonts, localized_strings, rot_imgs, tooltip_manager, + slot_manager, pulse, common: widget::CommonBuilder::default(), created_btns_top_l: 0, @@ -255,18 +344,40 @@ const TREES: [&str; 8] = [ "Mining", ]; +// Possible future sections: Bestiary ("Pokedex" of fought enemies), Weapon and +// armour catalogue, Achievements... +const SECTIONS: [&str; 3] = ["Skill-Trees", "Abilities", "Stats"]; + pub enum Event { Close, ChangeSkillTree(SelectedSkillTree), UnlockSkill(Skill), + ChangeSection(DiarySection), +} + +#[derive(PartialEq)] +pub enum DiarySection { + SkillTrees, + AbilitySelection, + Stats, +} + +pub struct DiaryState { + ids: Ids, + ability_page: usize, } impl<'a> Widget for Diary<'a> { type Event = Vec; - type State = Ids; + type State = DiaryState; type Style = (); - fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { Ids::new(id_gen) } + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + DiaryState { + ids: Ids::new(id_gen), + ability_page: 0, + } + } fn style(&self) -> Self::Style {} @@ -294,7 +405,6 @@ impl<'a> Widget for Diary<'a> { .font_id(self.fonts.cyri.conrod_id) .desc_text_color(TEXT_COLOR); - let sel_tab = &self.show.skilltreetab; //Animation timer Frame let frame_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; @@ -302,27 +412,27 @@ impl<'a> Widget for Diary<'a> { .w_h(1202.0, 886.0) .mid_top_with_margin_on(ui.window, 5.0) .color(Some(UI_MAIN)) - .set(state.bg, ui); + .set(state.ids.bg, ui); Image::new(self.imgs.diary_frame) .w_h(1202.0, 886.0) - .middle_of(state.bg) + .middle_of(state.ids.bg) .color(Some(UI_HIGHLIGHT_0)) - .set(state.frame, ui); + .set(state.ids.frame, ui); // Icon Image::new(self.imgs.spellbook_button) .w_h(30.0, 27.0) - .top_left_with_margins_on(state.frame, 8.0, 8.0) - .set(state.icon, ui); + .top_left_with_margins_on(state.ids.frame, 8.0, 8.0) + .set(state.ids.icon, ui); // X-Button if Button::image(self.imgs.close_button) .w_h(24.0, 25.0) .hover_image(self.imgs.close_btn_hover) .press_image(self.imgs.close_btn_press) - .top_right_with_margins_on(state.frame, 0.0, 0.0) - .set(state.close, ui) + .top_right_with_margins_on(state.ids.frame, 0.0, 0.0) + .set(state.ids.close, ui) .was_clicked() { events.push(Event::Close); @@ -330,252 +440,843 @@ impl<'a> Widget for Diary<'a> { // Title Text::new(self.localized_strings.get("hud.diary")) - .mid_top_with_margin_on(state.frame, 3.0) + .mid_top_with_margin_on(state.ids.frame, 3.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(29)) .color(TEXT_COLOR) - .set(state.title, ui); + .set(state.ids.title, ui); // Content Alignment Rectangle::fill_with([599.0 * 2.0, 419.0 * 2.0], color::TRANSPARENT) - .mid_top_with_margin_on(state.frame, 46.0) - .set(state.content_align, ui); + .mid_top_with_margin_on(state.ids.frame, 46.0) + .set(state.ids.content_align, ui); // Contents + // Section buttons + let sel_section = &self.show.diary_fields.section; - // Skill Trees - - // Skill Tree Selection + // Update len state.update(|s| { - s.weapon_btns - .resize(TREES.len(), &mut ui.widget_id_generator()) + s.ids + .section_imgs + .resize(SECTIONS.len(), &mut ui.widget_id_generator()) }); state.update(|s| { - s.weapon_imgs - .resize(TREES.len(), &mut ui.widget_id_generator()) + s.ids + .section_btns + .resize(SECTIONS.len(), &mut ui.widget_id_generator()) }); - state.update(|s| { - s.lock_imgs - .resize(TREES.len(), &mut ui.widget_id_generator()) - }); - - // Draw skillgroup tab's icons - for (i, skilltree_name) in TREES.iter().copied().enumerate() { - let skill_group = match skill_tree_from_str(skilltree_name) { + for (i, section_name) in SECTIONS.iter().copied().enumerate() { + let section = match section_from_str(section_name) { Some(st) => st, None => { - tracing::warn!("unexpected tree name: {}", skilltree_name); + tracing::warn!("unexpected section name: {}", section_name); continue; }, }; - - // Check if we have this skill tree unlocked - let locked = !self.skill_set.skill_group_accessible(skill_group); - - // Weapon button image + // Section Icons + let section_desc = match section_name { + "Abilities" => "List of your currently available abilities.", + "Skill-Trees" => "", + "Stats" => "", + _ => "", + }; let btn_img = { - let img = match skilltree_name { - "General Combat" => self.imgs.swords_crossed, - "Sword" => self.imgs.sword, - "Hammer" => self.imgs.hammer, - "Axe" => self.imgs.axe, - "Sceptre" => self.imgs.sceptre, - "Bow" => self.imgs.bow, - "Fire Staff" => self.imgs.staff, - "Mining" => self.imgs.mining, + let img = match section_name { + "Abilities" => self.imgs.spellbook_ico, + "Skill-Trees" => self.imgs.skilltree_ico, + "Stats" => self.imgs.stats_ico, _ => self.imgs.nothing, }; - if i == 0 { - Image::new(img).top_left_with_margins_on(state.content_align, 10.0, 5.0) + Image::new(img).top_left_with_margins_on(state.ids.content_align, 0.0, -50.0) } else { - Image::new(img).down_from(state.weapon_btns[i - 1], 5.0) + Image::new(img).down_from(state.ids.section_btns[i - 1], 5.0) } }; - btn_img.w_h(50.0, 50.0).set(state.weapon_imgs[i], ui); - - // Lock Image - if locked { - Image::new(self.imgs.lock) - .w_h(50.0, 50.0) - .middle_of(state.weapon_imgs[i]) - .graphics_for(state.weapon_imgs[i]) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8))) - .set(state.lock_imgs[i], ui); - } - - // Weapon icons - let have_points = { - let available = self.skill_set.available_sp(skill_group); - let earned = self.skill_set.earned_sp(skill_group); - let total_cost = skill_group.total_skill_point_cost(); - - available > 0 && (earned - available) < total_cost - }; - - let border_image = if skill_group == *sel_tab || have_points { + btn_img.w_h(50.0, 50.0).set(state.ids.section_imgs[i], ui); + // Section Buttons + let border_image = if section == *sel_section { self.imgs.wpn_icon_border_pressed } else { self.imgs.wpn_icon_border }; - let hover_image = if skill_group == *sel_tab { + let hover_image = if section == *sel_section { self.imgs.wpn_icon_border_pressed } else { self.imgs.wpn_icon_border_mo }; - let press_image = if skill_group == *sel_tab { + let press_image = if section == *sel_section { self.imgs.wpn_icon_border_pressed } else { self.imgs.wpn_icon_border_press }; - - let color = if skill_group != *sel_tab && have_points { - Color::Rgba(0.92, 0.76, 0.0, frame_ani) - } else { - TEXT_COLOR - }; - - let tooltip_txt = if locked { - self.localized_strings.get("hud.skill.not_unlocked") - } else { - "" - }; - - let wpn_button = Button::image(border_image) + let section_buttons = Button::image(border_image) .w_h(50.0, 50.0) .hover_image(hover_image) .press_image(press_image) - .middle_of(state.weapon_imgs[i]) - .image_color(color) + .middle_of(state.ids.section_imgs[i]) .with_tooltip( self.tooltip_manager, - skilltree_name, - tooltip_txt, + section_name, + section_desc, &diary_tooltip, TEXT_COLOR, ) - .set(state.weapon_btns[i], ui); - if wpn_button.was_clicked() { - events.push(Event::ChangeSkillTree(skill_group)) + .set(state.ids.section_btns[i], ui); + if section_buttons.was_clicked() { + events.push(Event::ChangeSection(section)) } } + match self.show.diary_fields.section { + DiarySection::SkillTrees => { + // Skill Trees + let sel_tab = &self.show.diary_fields.skilltreetab; - // Exp Bars and Rank Display - let current_exp = self.skill_set.available_experience(*sel_tab) as f64; - let max_exp = self.skill_set.skill_point_cost(*sel_tab) as f64; - let exp_percentage = current_exp / max_exp; - let rank = self.skill_set.earned_sp(*sel_tab); - let rank_txt = format!("{}", rank); - let exp_txt = format!("{}/{}", current_exp, max_exp); - let available_pts = self.skill_set.available_sp(*sel_tab); - let available_pts_txt = format!("{}", available_pts); - Image::new(self.imgs.diary_exp_bg) - .w_h(480.0, 76.0) - .mid_bottom_with_margin_on(state.content_align, 10.0) - .set(state.exp_bar_bg, ui); - Rectangle::fill_with([400.0, 40.0], color::TRANSPARENT) - .top_left_with_margins_on(state.exp_bar_bg, 32.0, 40.0) - .set(state.exp_bar_content_align, ui); - Image::new(self.imgs.bar_content) - .w_h(400.0 * exp_percentage, 40.0) - .top_left_with_margins_on(state.exp_bar_content_align, 0.0, 0.0) - .color(Some(XP_COLOR)) - .set(state.exp_bar_content, ui); - Image::new(self.imgs.diary_exp_frame) - .w_h(480.0, 76.0) - .color(Some(UI_HIGHLIGHT_0)) - .middle_of(state.exp_bar_bg) - .set(state.exp_bar_frame, ui); - // Show EXP bar text on hover - if ui - .widget_input(state.exp_bar_frame) - .mouse() - .map_or(false, |m| m.is_over()) - { - Text::new(&exp_txt) - .mid_top_with_margin_on(state.exp_bar_frame, 47.0) + // Skill Tree Selection + state.update(|s| { + s.ids + .weapon_btns + .resize(TREES.len(), &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .weapon_imgs + .resize(TREES.len(), &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .lock_imgs + .resize(TREES.len(), &mut ui.widget_id_generator()) + }); + + // Draw skillgroup tab's icons + for (i, skilltree_name) in TREES.iter().copied().enumerate() { + let skill_group = match skill_tree_from_str(skilltree_name) { + Some(st) => st, + None => { + tracing::warn!("unexpected tree name: {}", skilltree_name); + continue; + }, + }; + + // Check if we have this skill tree unlocked + let locked = !self.skill_set.skill_group_accessible(skill_group); + + // Weapon button image + let btn_img = { + let img = match skilltree_name { + "General Combat" => self.imgs.swords_crossed, + "Sword" => self.imgs.sword, + "Hammer" => self.imgs.hammer, + "Axe" => self.imgs.axe, + "Sceptre" => self.imgs.sceptre, + "Bow" => self.imgs.bow, + "Fire Staff" => self.imgs.staff, + "Mining" => self.imgs.mining, + _ => self.imgs.nothing, + }; + + if i == 0 { + Image::new(img).top_left_with_margins_on( + state.ids.content_align, + 10.0, + 5.0, + ) + } else { + Image::new(img).down_from(state.ids.weapon_btns[i - 1], 5.0) + } + }; + btn_img.w_h(50.0, 50.0).set(state.ids.weapon_imgs[i], ui); + + // Lock Image + if locked { + Image::new(self.imgs.lock) + .w_h(50.0, 50.0) + .middle_of(state.ids.weapon_imgs[i]) + .graphics_for(state.ids.weapon_imgs[i]) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8))) + .set(state.ids.lock_imgs[i], ui); + } + + // Weapon icons + let have_points = { + let available = self.skill_set.available_sp(skill_group); + let earned = self.skill_set.earned_sp(skill_group); + let total_cost = skill_group.total_skill_point_cost(); + + available > 0 && (earned - available) < total_cost + }; + + let border_image = if skill_group == *sel_tab || have_points { + self.imgs.wpn_icon_border_pressed + } else { + self.imgs.wpn_icon_border + }; + + let hover_image = if skill_group == *sel_tab { + self.imgs.wpn_icon_border_pressed + } else { + self.imgs.wpn_icon_border_mo + }; + + let press_image = if skill_group == *sel_tab { + self.imgs.wpn_icon_border_pressed + } else { + self.imgs.wpn_icon_border_press + }; + + let color = if skill_group != *sel_tab && have_points { + Color::Rgba(0.92, 0.76, 0.0, frame_ani) + } else { + TEXT_COLOR + }; + + let tooltip_txt = if locked { + self.localized_strings.get("hud.skill.not_unlocked") + } else { + "" + }; + + let wpn_button = Button::image(border_image) + .w_h(50.0, 50.0) + .hover_image(hover_image) + .press_image(press_image) + .middle_of(state.ids.weapon_imgs[i]) + .image_color(color) + .with_tooltip( + self.tooltip_manager, + skilltree_name, + tooltip_txt, + &diary_tooltip, + TEXT_COLOR, + ) + .set(state.ids.weapon_btns[i], ui); + if wpn_button.was_clicked() { + events.push(Event::ChangeSkillTree(skill_group)) + } + } + + // Exp Bars and Rank Display + let current_exp = self.skill_set.available_experience(*sel_tab) as f64; + let max_exp = self.skill_set.skill_point_cost(*sel_tab) as f64; + let exp_percentage = current_exp / max_exp; + let rank = self.skill_set.earned_sp(*sel_tab); + let rank_txt = format!("{}", rank); + let exp_txt = format!("{}/{}", current_exp, max_exp); + let available_pts = self.skill_set.available_sp(*sel_tab); + let available_pts_txt = format!("{}", available_pts); + Image::new(self.imgs.diary_exp_bg) + .w_h(480.0, 76.0) + .mid_bottom_with_margin_on(state.ids.content_align, 10.0) + .set(state.ids.exp_bar_bg, ui); + Rectangle::fill_with([400.0, 40.0], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.exp_bar_bg, 32.0, 40.0) + .set(state.ids.exp_bar_content_align, ui); + Image::new(self.imgs.bar_content) + .w_h(400.0 * exp_percentage, 40.0) + .top_left_with_margins_on(state.ids.exp_bar_content_align, 0.0, 0.0) + .color(Some(XP_COLOR)) + .set(state.ids.exp_bar_content, ui); + Image::new(self.imgs.diary_exp_frame) + .w_h(480.0, 76.0) + .color(Some(UI_HIGHLIGHT_0)) + .middle_of(state.ids.exp_bar_bg) + .set(state.ids.exp_bar_frame, ui); + // Show EXP bar text on hover + if ui + .widget_input(state.ids.exp_bar_frame) + .mouse() + .map_or(false, |m| m.is_over()) + { + Text::new(&exp_txt) + .mid_top_with_margin_on(state.ids.exp_bar_frame, 47.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(TEXT_COLOR) + .graphics_for(state.ids.exp_bar_frame) + .set(state.ids.exp_bar_txt, ui); + } + Text::new(&rank_txt) + .mid_top_with_margin_on(state.ids.exp_bar_frame, match rank { + 0..=99 => 5.0, + 100..=999 => 8.0, + _ => 10.0, + }) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(match rank { + 0..=99 => 28, + 100..=999 => 21, + _ => 15, + })) + .color(TEXT_COLOR) + .set(state.ids.exp_bar_rank, ui); + + Text::new( + &self + .localized_strings + .get("hud.skill.sp_available") + .replace("{number}", &available_pts_txt), + ) + .mid_top_with_margin_on(state.ids.content_align, 700.0) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(14)) - .color(TEXT_COLOR) - .graphics_for(state.exp_bar_frame) - .set(state.exp_bar_txt, ui); - } - Text::new(&rank_txt) - .mid_top_with_margin_on(state.exp_bar_frame, match rank { - 0..=99 => 5.0, - 100..=999 => 8.0, - _ => 10.0, - }) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(match rank { - 0..=99 => 28, - 100..=999 => 21, - _ => 15, - })) - .color(TEXT_COLOR) - .set(state.exp_bar_rank, ui); + .font_size(self.fonts.cyri.scale(28)) + .color(if available_pts > 0 { + Color::Rgba(0.92, 0.76, 0.0, frame_ani) + } else { + TEXT_COLOR + }) + .set(state.ids.available_pts_txt, ui); + // Skill Trees + // Alignment Placing + let x = 200.0; + let y = 100.0; + // Alignment rectangles for skills + Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.content_align, y, x) + .set(state.ids.skills_top_l_align, ui); + Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) + .top_right_with_margins_on(state.ids.content_align, y, x) + .set(state.ids.skills_top_r_align, ui); + Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) + .bottom_left_with_margins_on(state.ids.content_align, y, x) + .set(state.ids.skills_bot_l_align, ui); + Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) + .bottom_right_with_margins_on(state.ids.content_align, y, x) + .set(state.ids.skills_bot_r_align, ui); - Text::new( - &self - .localized_strings - .get("hud.skill.sp_available") - .replace("{number}", &available_pts_txt), - ) - .mid_top_with_margin_on(state.content_align, 700.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(28)) - .color(if available_pts > 0 { - Color::Rgba(0.92, 0.76, 0.0, frame_ani) - } else { - TEXT_COLOR - }) - .set(state.available_pts_txt, ui); - // Skill Trees - // Alignment Placing - let x = 200.0; - let y = 100.0; - // Alignment rectangles for skills - Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) - .top_left_with_margins_on(state.content_align, y, x) - .set(state.skills_top_l_align, ui); - Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) - .top_right_with_margins_on(state.content_align, y, x) - .set(state.skills_top_r_align, ui); - Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) - .bottom_left_with_margins_on(state.content_align, y, x) - .set(state.skills_bot_l_align, ui); - Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) - .bottom_right_with_margins_on(state.content_align, y, x) - .set(state.skills_bot_r_align, ui); + match sel_tab { + SelectedSkillTree::General => { + self.handle_general_skills_window(&diary_tooltip, state, ui, events) + }, + SelectedSkillTree::Weapon(ToolKind::Sword) => { + self.handle_sword_skills_window(&diary_tooltip, state, ui, events) + }, + SelectedSkillTree::Weapon(ToolKind::Hammer) => { + self.handle_hammer_skills_window(&diary_tooltip, state, ui, events) + }, + SelectedSkillTree::Weapon(ToolKind::Axe) => { + self.handle_axe_skills_window(&diary_tooltip, state, ui, events) + }, + SelectedSkillTree::Weapon(ToolKind::Sceptre) => { + self.handle_sceptre_skills_window(&diary_tooltip, state, ui, events) + }, + SelectedSkillTree::Weapon(ToolKind::Bow) => { + self.handle_bow_skills_window(&diary_tooltip, state, ui, events) + }, + SelectedSkillTree::Weapon(ToolKind::Staff) => { + self.handle_staff_skills_window(&diary_tooltip, state, ui, events) + }, + SelectedSkillTree::Weapon(ToolKind::Pick) => { + self.handle_mining_skills_window(&diary_tooltip, state, ui, events) + }, + _ => events, + } + }, + DiarySection::AbilitySelection => { + use comp::ability::AbilityInput; - match sel_tab { - SelectedSkillTree::General => { - self.handle_general_skills_window(&diary_tooltip, state, ui, events) + // Background Art + Image::new(self.imgs.book_bg) + .w_h(299.0 * 4.0, 184.0 * 4.0) + .mid_top_with_margin_on(state.ids.content_align, 4.0) + //.graphics_for(state.ids.content_align) + .set(state.ids.spellbook_art, ui); + Image::new(self.imgs.skills_bg) + .w_h(240.0 * 2.0, 40.0 * 2.0) + .mid_bottom_with_margin_on(state.ids.content_align, 8.0) + .set(state.ids.spellbook_skills_bg, ui); + + Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.spellbook_art, 0.0, 0.0) + .set(state.ids.sb_page_left_align, ui); + Rectangle::fill_with([299.0 * 2.0, 184.0 * 4.0], color::TRANSPARENT) + .top_right_with_margins_on(state.ids.spellbook_art, 0.0, 0.0) + .set(state.ids.sb_page_right_align, ui); + + // Display all active abilities on bottom of window + state.update(|s| { + s.ids + .active_abilities + .resize(MAX_ABILITIES, &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .active_abilities_keys + .resize(MAX_ABILITIES, &mut ui.widget_id_generator()) + }); + + let mut slot_maker = SlotMaker { + empty_slot: self.imgs.inv_slot, + filled_slot: self.imgs.inv_slot, + selected_slot: self.imgs.inv_slot_sel, + background_color: Some(UI_MAIN), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: 0.9, + }, + selected_content_scale: 1.067, + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(-4.0, 0.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: &(self.active_abilities, self.inventory, self.skill_set), + image_source: self.imgs, + slot_manager: Some(self.slot_manager), + pulse: 0.0, + }; + + for i in 0..MAX_ABILITIES { + let ability_id = self + .active_abilities + .get_ability( + AbilityInput::Auxiliary(i), + Some(self.inventory), + Some(self.skill_set), + ) + .ability_id(Some(self.inventory)); + let (ability_title, ability_desc) = if let Some(ability_id) = ability_id { + util::ability_description(ability_id) + } else { + ("Drag an ability here to use it.", "") + }; + + let image_size = 80.0; + let image_offsets = 92.0 * i as f64; + + let slot = AbilitySlot::Slot(i); + let mut ability_slot = slot_maker.fabricate(slot, [image_size; 2]); + + if i == 0 { + ability_slot = ability_slot.top_left_with_margins_on( + state.ids.spellbook_skills_bg, + 0.0, + 32.0 + image_offsets, + ); + } else { + ability_slot = + ability_slot.right_from(state.ids.active_abilities[i - 1], 4.0) + } + ability_slot + .with_tooltip( + self.tooltip_manager, + ability_title, + ability_desc, + &diary_tooltip, + TEXT_COLOR, + ) + .set(state.ids.active_abilities[i], ui); + + // Display Slot Keybinding + let keys = &self.global_state.settings.controls; + let key_layout = &self.global_state.window.key_layout; + let ability_key = [ + GameInput::Slot1, + GameInput::Slot2, + GameInput::Slot3, + GameInput::Slot4, + GameInput::Slot5, + ] + .get(i) + .and_then(|input| keys.get_binding(*input)) + .map(|key| key.display_string(key_layout)) + .unwrap_or_default(); + + Text::new(&ability_key) + .top_left_with_margins_on(state.ids.active_abilities[i], 0.0, 4.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(20)) + .color(TEXT_COLOR) + .graphics_for(state.ids.active_abilities[i]) + .set(state.ids.active_abilities_keys[i], ui); + } + + let same_weap_kinds = self + .inventory + .equipped(EquipSlot::ActiveMainhand) + .zip(self.inventory.equipped(EquipSlot::ActiveOffhand)) + .map_or(false, |(a, b)| { + if let (ItemKind::Tool(tool_a), ItemKind::Tool(tool_b)) = (&a.kind, &b.kind) + { + (a.ability_spec(), tool_a.kind) == (b.ability_spec(), tool_b.kind) + } else { + false + } + }); + + let main_weap_abilities = ActiveAbilities::iter_unlocked_abilities( + Some(self.inventory), + Some(self.skill_set), + EquipSlot::ActiveMainhand, + ) + .map(AuxiliaryAbility::MainWeapon) + .map(|a| (Ability::from(a).ability_id(Some(self.inventory)), a)); + let off_weap_abilities = ActiveAbilities::iter_unlocked_abilities( + Some(self.inventory), + Some(self.skill_set), + EquipSlot::ActiveOffhand, + ) + .map(AuxiliaryAbility::OffWeapon) + .map(|a| (Ability::from(a).ability_id(Some(self.inventory)), a)); + + let abilities: Vec<_> = if same_weap_kinds { + // When the weapons have the same ability kind take only the main weapons, + main_weap_abilities.collect() + } else { + main_weap_abilities.chain(off_weap_abilities).collect() + }; + + const ABILITIES_PER_PAGE: usize = 12; + + let page_indices = (abilities.len().saturating_sub(1)) / ABILITIES_PER_PAGE; + + if state.ability_page > page_indices { + state.update(|s| s.ability_page = 0); + } + + state.update(|s| { + s.ids + .abilities + .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .abilities_dual + .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .ability_titles + .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .ability_frames + .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .ability_descs + .resize(ABILITIES_PER_PAGE, &mut ui.widget_id_generator()) + }); + + // Page button + // Left Arrow + let left_arrow = Button::image(if state.ability_page > 0 { + self.imgs.arrow_l + } else { + self.imgs.arrow_l_inactive + }) + .bottom_left_with_margins_on(state.ids.spellbook_art, -83.0, 10.0) + .w_h(48.0, 55.0); + // Grey out arrows when inactive + if state.ability_page > 0 { + if left_arrow + .hover_image(self.imgs.arrow_l_click) + .press_image(self.imgs.arrow_l) + .set(state.ids.ability_page_left, ui) + .was_clicked() + { + state.update(|s| s.ability_page -= 1); + } + } else { + left_arrow.set(state.ids.ability_page_left, ui); + } + // Right Arrow + let right_arrow = Button::image(if state.ability_page < page_indices { + self.imgs.arrow_r + } else { + self.imgs.arrow_r_inactive + }) + .bottom_right_with_margins_on(state.ids.spellbook_art, -83.0, 10.0) + .w_h(48.0, 55.0); + if state.ability_page < page_indices { + // Only show right button if not on last page + if right_arrow + .hover_image(self.imgs.arrow_r_click) + .press_image(self.imgs.arrow_r) + .set(state.ids.ability_page_right, ui) + .was_clicked() + { + state.update(|s| s.ability_page += 1); + }; + } else { + right_arrow.set(state.ids.ability_page_right, ui); + } + + let ability_start = state.ability_page * ABILITIES_PER_PAGE; + + let mut slot_maker = SlotMaker { + empty_slot: self.imgs.inv_slot, + filled_slot: self.imgs.inv_slot, + selected_slot: self.imgs.inv_slot_sel, + background_color: Some(UI_MAIN), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: 1.0, + }, + selected_content_scale: 1.067, + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(-4.0, 0.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: &(self.active_abilities, self.inventory, self.skill_set), + image_source: self.imgs, + slot_manager: Some(self.slot_manager), + pulse: 0.0, + }; + + for (id_index, (ability_id, ability)) in abilities + .iter() + .skip(ability_start) + .take(ABILITIES_PER_PAGE) + .enumerate() + { + let (ability_title, ability_desc) = + util::ability_description(ability_id.unwrap_or("")); + + let (align_state, image_offsets) = if id_index < 6 { + (state.ids.sb_page_left_align, 120.0 * id_index as f64) + } else { + (state.ids.sb_page_right_align, 120.0 * (id_index - 6) as f64) + }; + + Image::new(if same_weap_kinds { + self.imgs.ability_frame_dual + } else { + self.imgs.ability_frame + }) + .w_h(566.0, 108.0) + .top_left_with_margins_on(align_state, 16.0 + image_offsets, 16.0) + .color(Some(UI_HIGHLIGHT_0)) + .set(state.ids.ability_frames[id_index], ui); + + let slot = AbilitySlot::Ability(*ability); + slot_maker + .fabricate(slot, [100.0; 2]) + .top_left_with_margins_on(align_state, 20.0 + image_offsets, 20.0) + .set(state.ids.abilities[id_index], ui); + + if same_weap_kinds { + if let AuxiliaryAbility::MainWeapon(slot) = ability { + let ability = AuxiliaryAbility::OffWeapon(*slot); + + let slot = AbilitySlot::Ability(ability); + slot_maker + .fabricate(slot, [100.0; 2]) + .top_right_with_margins_on(align_state, 20.0 + image_offsets, 20.0) + .set(state.ids.abilities_dual[id_index], ui); + } + } + // The page width... + let text_width = 299.0 * 2.0 + - if same_weap_kinds && matches!(ability, AuxiliaryAbility::MainWeapon(_)) { + // with double the width of an ability image and some padding subtracted + // if dual wielding two of the same weapon kind + (20.0 + 100.0 + 10.0) * 2.0 + } else { + // or the width of an ability image and some padding subtracted + // otherwise + 20.0 * 2.0 + 100.0 + }; + Text::new(ability_title) + .top_left_with_margins_on(state.ids.abilities[id_index], 5.0, 110.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(28)) + .color(TEXT_COLOR) + .w(text_width) + .graphics_for(state.ids.abilities[id_index]) + .set(state.ids.ability_titles[id_index], ui); + Text::new(ability_desc) + .top_left_with_margins_on(state.ids.abilities[id_index], 40.0, 110.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)) + .color(TEXT_COLOR) + .w(text_width) + .graphics_for(state.ids.abilities[id_index]) + .set(state.ids.ability_descs[id_index], ui); + } + + events }, - SelectedSkillTree::Weapon(ToolKind::Sword) => { - self.handle_sword_skills_window(&diary_tooltip, state, ui, events) + DiarySection::Stats => { + const STATS: [&str; 13] = [ + "Hitpoints", + "Energy", + "Poise", + "Combat-Rating", + "Protection", + "Stun-Resistance", + "Crit-Power", + "Energy Reward", + "Stealth", + "Weapon Power", + "Weapon Speed", + "Weapon Poise", + "Weapon Crit-Chance", + ]; + + // Background Art + Image::new(self.imgs.book_bg) + .w_h(299.0 * 4.0, 184.0 * 4.0) + .mid_top_with_margin_on(state.ids.content_align, 4.0) + .set(state.ids.spellbook_art, ui); + + state.update(|s| { + s.ids + .stat_names + .resize(STATS.len(), &mut ui.widget_id_generator()) + }); + state.update(|s| { + s.ids + .stat_values + .resize(STATS.len(), &mut ui.widget_id_generator()) + }); + for (i, stat) in STATS.iter().copied().enumerate() { + // Stat names + let mut txt = Text::new(stat) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(29)) + .color(BLACK); + + if i == 0 { + txt = txt.top_left_with_margins_on(state.ids.spellbook_art, 20.0, 20.0); + } else { + txt = txt.down_from(state.ids.stat_names[i - 1], 10.0); + }; + txt.set(state.ids.stat_names[i], ui); + + let main_weap_stats = self + .inventory + .equipped(EquipSlot::ActiveMainhand) + .and_then(|item| match &item.kind { + ItemKind::Tool(tool) => { + Some(tool.stats.resolve_stats(self.msm, item.components())) + }, + _ => None, + }); + + let off_weap_stats = self + .inventory + .equipped(EquipSlot::ActiveOffhand) + .and_then(|item| match &item.kind { + ItemKind::Tool(tool) => { + Some(tool.stats.resolve_stats(self.msm, item.components())) + }, + _ => None, + }); + + // Stat values + let value = match stat { + "Hitpoints" => format!("{}", self.health.base_max() as u32), + "Energy" => format!("{}", self.energy.base_max() as u32), + "Poise" => format!("{}", self.poise.base_max() as u32), + "Combat-Rating" => { + let cr = combat::combat_rating( + self.inventory, + self.health, + self.energy, + self.poise, + self.skill_set, + *self.body, + self.msm, + ); + format!("{:.2}", cr * 10.0) + }, + "Protection" => { + let protection = combat::compute_protection(Some(self.inventory)); + match protection { + Some(prot) => format!("{}", prot), + None => String::from("Invincible"), + } + }, + "Stun-Resistance" => { + let stun_res = Poise::compute_poise_damage_reduction(self.inventory); + format!("{:.2}%", stun_res * 100.0) + }, + "Crit-Power" => { + let critpwr = combat::compute_crit_mult(Some(self.inventory)); + format!("x{:.2}", critpwr) + }, + "Energy Reward" => { + let energy_rew = + combat::compute_energy_reward_mod(Some(self.inventory)); + format!("{:+.0}%", (energy_rew - 1.0) * 100.0) + }, + "Stealth" => { + let stealth = combat::compute_stealth_coefficient(Some(self.inventory)); + format!("{:.2}", stealth) + }, + "Weapon Power" => match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => { + format!("{} {}", m_stats.power * 10.0, o_stats.power * 10.0) + }, + (Some(stats), None) | (None, Some(stats)) => { + format!("{}", stats.power * 10.0) + }, + (None, None) => String::new(), + }, + "Weapon Speed" => { + let spd_fmt = |sp| (sp - 1.0) * 100.0; + match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => format!( + "{:+.0}% {:+.0}%", + spd_fmt(m_stats.speed), + spd_fmt(o_stats.speed) + ), + (Some(stats), None) | (None, Some(stats)) => { + format!("{:+.0}%", spd_fmt(stats.speed)) + }, + _ => String::new(), + } + }, + "Weapon Poise" => match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => { + format!( + "{} {}", + m_stats.effect_power * 10.0, + o_stats.effect_power * 10.0 + ) + }, + (Some(stats), None) | (None, Some(stats)) => { + format!("{}", stats.effect_power * 10.0) + }, + (None, None) => String::new(), + }, + "Weapon Crit-Chance" => { + let crit_fmt = |cc| cc * 100.0; + match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => format!( + "{:.1}% {:.1}%", + crit_fmt(m_stats.crit_chance), + crit_fmt(o_stats.crit_chance) + ), + (Some(stats), None) | (None, Some(stats)) => { + format!("{:.1}%", crit_fmt(stats.crit_chance)) + }, + (None, None) => String::new(), + } + }, + unknown => unreachable!(unknown), + }; + + let mut number = Text::new(&value) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(29)) + .color(BLACK); + + if i == 0 { + number = number.right_from(state.ids.stat_names[i], 265.0); + } else { + number = number.down_from(state.ids.stat_values[i - 1], 10.0); + }; + number.set(state.ids.stat_values[i], ui); + } + + events }, - SelectedSkillTree::Weapon(ToolKind::Hammer) => { - self.handle_hammer_skills_window(&diary_tooltip, state, ui, events) - }, - SelectedSkillTree::Weapon(ToolKind::Axe) => { - self.handle_axe_skills_window(&diary_tooltip, state, ui, events) - }, - SelectedSkillTree::Weapon(ToolKind::Sceptre) => { - self.handle_sceptre_skills_window(&diary_tooltip, state, ui, events) - }, - SelectedSkillTree::Weapon(ToolKind::Bow) => { - self.handle_bow_skills_window(&diary_tooltip, state, ui, events) - }, - SelectedSkillTree::Weapon(ToolKind::Staff) => { - self.handle_staff_skills_window(&diary_tooltip, state, ui, events) - }, - SelectedSkillTree::Weapon(ToolKind::Pick) => { - self.handle_mining_skills_window(&diary_tooltip, state, ui, events) - }, - _ => events, } } } @@ -594,6 +1295,15 @@ fn skill_tree_from_str(string: &str) -> Option { } } +fn section_from_str(string: &str) -> Option { + match string { + "Abilities" => Some(DiarySection::AbilitySelection), + "Skill-Trees" => Some(DiarySection::SkillTrees), + "Stats" => Some(DiarySection::Stats), + _ => None, + } +} + enum SkillIcon<'a> { Unlockable { skill: Skill, @@ -614,17 +1324,17 @@ impl<'a> Diary<'a> { fn handle_general_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { let tree_title = self.localized_strings.get("common.weapons.general"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -652,9 +1362,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) + .middle_of(state.ids.content_align) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.general_combat_render_0, ui); + .set(state.ids.general_combat_render_0, ui); Image::new(animate_by_pulse( &self @@ -663,9 +1373,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.general_combat_render_0) + .middle_of(state.ids.general_combat_render_0) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.general_combat_render_1, ui); + .set(state.ids.general_combat_render_1, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ @@ -676,110 +1386,110 @@ impl<'a> Diary<'a> { SkillIcon::Unlockable { skill: Skill::General(HealthIncrease), image: self.imgs.health_plus_skill, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_general_stat_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_general_stat_0, }, SkillIcon::Unlockable { skill: Skill::General(EnergyIncrease), image: self.imgs.energy_plus_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_general_stat_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_general_stat_1, }, // Top right skills SkillIcon::Unlockable { skill: Skill::UnlockGroup(Weapon(Sword)), image: self.imgs.unlock_sword_skill, - position: MidTopWithMarginOn(state.skills_top_r[0], 3.0), - id: state.skill_general_tree_0, + position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0), + id: state.ids.skill_general_tree_0, }, SkillIcon::Unlockable { skill: Skill::UnlockGroup(Weapon(Axe)), image: self.imgs.unlock_axe_skill, - position: MidTopWithMarginOn(state.skills_top_r[1], 3.0), - id: state.skill_general_tree_1, + position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0), + id: state.ids.skill_general_tree_1, }, SkillIcon::Unlockable { skill: Skill::UnlockGroup(Weapon(Hammer)), image: self.imgs.unlock_hammer_skill, - position: MidTopWithMarginOn(state.skills_top_r[2], 3.0), - id: state.skill_general_tree_2, + position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0), + id: state.ids.skill_general_tree_2, }, SkillIcon::Unlockable { skill: Skill::UnlockGroup(Weapon(Bow)), image: self.imgs.unlock_bow_skill, - position: MidTopWithMarginOn(state.skills_top_r[3], 3.0), - id: state.skill_general_tree_3, + position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0), + id: state.ids.skill_general_tree_3, }, SkillIcon::Unlockable { skill: Skill::UnlockGroup(Weapon(Staff)), image: self.imgs.unlock_staff_skill0, - position: MidTopWithMarginOn(state.skills_top_r[4], 3.0), - id: state.skill_general_tree_4, + position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0), + id: state.ids.skill_general_tree_4, }, SkillIcon::Unlockable { skill: Skill::UnlockGroup(Weapon(Sceptre)), image: self.imgs.unlock_sceptre_skill, - position: MidTopWithMarginOn(state.skills_top_r[5], 3.0), - id: state.skill_general_tree_5, + position: MidTopWithMarginOn(state.ids.skills_top_r[5], 3.0), + id: state.ids.skill_general_tree_5, }, // Bottom left skills SkillIcon::Descriptive { title: "hud.skill.dodge_title", desc: "hud.skill.dodge", image: self.imgs.skill_dodge_skill, - position: MidTopWithMarginOn(state.skills_bot_l[0], 3.0), - id: state.skill_general_roll_0, + position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0), + id: state.ids.skill_general_roll_0, }, SkillIcon::Unlockable { skill: Skill::Roll(RollSkill::Cost), image: self.imgs.utility_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_l[1], 3.0), - id: state.skill_general_roll_1, + position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0), + id: state.ids.skill_general_roll_1, }, SkillIcon::Unlockable { skill: Skill::Roll(Strength), image: self.imgs.utility_speed_skill, - position: MidTopWithMarginOn(state.skills_bot_l[2], 3.0), - id: state.skill_general_roll_2, + position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0), + id: state.ids.skill_general_roll_2, }, SkillIcon::Unlockable { skill: Skill::Roll(Duration), image: self.imgs.utility_duration_skill, - position: MidTopWithMarginOn(state.skills_bot_l[3], 3.0), - id: state.skill_general_roll_3, + position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0), + id: state.ids.skill_general_roll_3, }, // Bottom right skills SkillIcon::Descriptive { title: "hud.skill.climbing_title", desc: "hud.skill.climbing", image: self.imgs.skill_climbing_skill, - position: MidTopWithMarginOn(state.skills_bot_r[0], 3.0), - id: state.skill_general_climb_0, + position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0), + id: state.ids.skill_general_climb_0, }, SkillIcon::Unlockable { skill: Skill::Climb(ClimbSkill::Cost), image: self.imgs.utility_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_r[1], 3.0), - id: state.skill_general_climb_1, + position: MidTopWithMarginOn(state.ids.skills_bot_r[1], 3.0), + id: state.ids.skill_general_climb_1, }, SkillIcon::Unlockable { skill: Skill::Climb(ClimbSkill::Speed), image: self.imgs.utility_speed_skill, - position: MidTopWithMarginOn(state.skills_bot_r[2], 3.0), - id: state.skill_general_climb_2, + position: MidTopWithMarginOn(state.ids.skills_bot_r[2], 3.0), + id: state.ids.skill_general_climb_2, }, SkillIcon::Descriptive { title: "hud.skill.swim_title", desc: "hud.skill.swim", image: self.imgs.skill_swim_skill, - position: MidTopWithMarginOn(state.skills_bot_r[3], 3.0), - id: state.skill_general_swim_0, + position: MidTopWithMarginOn(state.ids.skills_bot_r[3], 3.0), + id: state.ids.skill_general_swim_0, }, SkillIcon::Unlockable { skill: Skill::Swim(SwimSkill::Speed), image: self.imgs.utility_speed_skill, - position: MidTopWithMarginOn(state.skills_bot_r[4], 3.0), - id: state.skill_general_swim_1, + position: MidTopWithMarginOn(state.ids.skills_bot_r[4], 3.0), + id: state.ids.skill_general_swim_1, }, ]; @@ -790,7 +1500,7 @@ impl<'a> Diary<'a> { fn handle_sword_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { @@ -798,11 +1508,11 @@ impl<'a> Diary<'a> { let tree_title = self.localized_strings.get("common.weapons.sword"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -830,9 +1540,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) + .middle_of(state.ids.content_align) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.sword_render, ui); + .set(state.ids.sword_render, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ // Top Left skills @@ -843,114 +1553,114 @@ impl<'a> Diary<'a> { title: "hud.skill.sw_trip_str_title", desc: "hud.skill.sw_trip_str", image: self.imgs.twohsword_m1, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_sword_combo_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_sword_combo_0, }, SkillIcon::Unlockable { skill: Skill::Sword(TsCombo), image: self.imgs.physical_combo_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_sword_combo_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_sword_combo_1, }, SkillIcon::Unlockable { skill: Skill::Sword(TsDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_l[2], 3.0), - id: state.skill_sword_combo_2, + position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0), + id: state.ids.skill_sword_combo_2, }, SkillIcon::Unlockable { skill: Skill::Sword(TsSpeed), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_l[3], 3.0), - id: state.skill_sword_combo_3, + position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0), + id: state.ids.skill_sword_combo_3, }, SkillIcon::Unlockable { skill: Skill::Sword(TsRegen), image: self.imgs.physical_energy_regen_skill, - position: MidTopWithMarginOn(state.skills_top_l[4], 3.0), - id: state.skill_sword_combo_4, + position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0), + id: state.ids.skill_sword_combo_4, }, // Top right skills SkillIcon::Descriptive { title: "hud.skill.sw_dash_title", desc: "hud.skill.sw_dash", image: self.imgs.twohsword_m2, - position: MidTopWithMarginOn(state.skills_top_r[0], 3.0), - id: state.skill_sword_dash_0, + position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0), + id: state.ids.skill_sword_dash_0, }, SkillIcon::Unlockable { skill: Skill::Sword(DDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_r[1], 3.0), - id: state.skill_sword_dash_1, + position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0), + id: state.ids.skill_sword_dash_1, }, SkillIcon::Unlockable { skill: Skill::Sword(DDrain), image: self.imgs.physical_energy_drain_skill, - position: MidTopWithMarginOn(state.skills_top_r[2], 3.0), - id: state.skill_sword_dash_2, + position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0), + id: state.ids.skill_sword_dash_2, }, SkillIcon::Unlockable { skill: Skill::Sword(DCost), image: self.imgs.physical_cost_skill, - position: MidTopWithMarginOn(state.skills_top_r[3], 3.0), - id: state.skill_sword_dash_3, + position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0), + id: state.ids.skill_sword_dash_3, }, SkillIcon::Unlockable { skill: Skill::Sword(DSpeed), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_r[4], 3.0), - id: state.skill_sword_dash_4, + position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0), + id: state.ids.skill_sword_dash_4, }, SkillIcon::Unlockable { skill: Skill::Sword(DChargeThrough), image: self.imgs.physical_distance_skill, - position: MidTopWithMarginOn(state.skills_top_r[5], 3.0), - id: state.skill_sword_dash_5, + position: MidTopWithMarginOn(state.ids.skills_top_r[5], 3.0), + id: state.ids.skill_sword_dash_5, }, SkillIcon::Unlockable { skill: Skill::Sword(DScaling), image: self.imgs.physical_amount_skill, - position: MidTopWithMarginOn(state.skills_top_r[6], 3.0), - id: state.skill_sword_dash_6, + position: MidTopWithMarginOn(state.ids.skills_top_r[6], 3.0), + id: state.ids.skill_sword_dash_6, }, // Bottom left skills SkillIcon::Unlockable { skill: Skill::Sword(UnlockSpin), image: self.imgs.sword_whirlwind, - position: MidTopWithMarginOn(state.skills_bot_l[0], 3.0), - id: state.skill_sword_spin_0, + position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0), + id: state.ids.skill_sword_spin_0, }, SkillIcon::Unlockable { skill: Skill::Sword(SDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_l[1], 3.0), - id: state.skill_sword_spin_1, + position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0), + id: state.ids.skill_sword_spin_1, }, SkillIcon::Unlockable { skill: Skill::Sword(SSpeed), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_l[2], 3.0), - id: state.skill_sword_spin_2, + position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0), + id: state.ids.skill_sword_spin_2, }, SkillIcon::Unlockable { skill: Skill::Sword(SCost), image: self.imgs.physical_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_l[3], 3.0), - id: state.skill_sword_spin_3, + position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0), + id: state.ids.skill_sword_spin_3, }, SkillIcon::Unlockable { skill: Skill::Sword(SSpins), image: self.imgs.physical_amount_skill, - position: MidTopWithMarginOn(state.skills_bot_l[4], 3.0), - id: state.skill_sword_spin_4, + position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0), + id: state.ids.skill_sword_spin_4, }, // Bottom right skills SkillIcon::Unlockable { skill: Skill::Sword(InterruptingAttacks), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_r[0], 3.0), - id: state.skill_sword_passive_0, + position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0), + id: state.ids.skill_sword_passive_0, }, ]; @@ -961,7 +1671,7 @@ impl<'a> Diary<'a> { fn handle_hammer_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { @@ -969,11 +1679,11 @@ impl<'a> Diary<'a> { let tree_title = self.localized_strings.get("common.weapons.hammer"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -1001,9 +1711,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) + .middle_of(state.ids.content_align) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.hammer_render, ui); + .set(state.ids.hammer_render, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ // Top Left skills @@ -1014,101 +1724,101 @@ impl<'a> Diary<'a> { title: "hud.skill.hmr_single_strike_title", desc: "hud.skill.hmr_single_strike", image: self.imgs.twohhammer_m1, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_hammer_combo_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_hammer_combo_0, }, SkillIcon::Unlockable { skill: Skill::Hammer(SsKnockback), image: self.imgs.physical_knockback_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_hammer_combo_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_hammer_combo_1, }, SkillIcon::Unlockable { skill: Skill::Hammer(SsDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_l[2], 3.0), - id: state.skill_hammer_combo_2, + position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0), + id: state.ids.skill_hammer_combo_2, }, SkillIcon::Unlockable { skill: Skill::Hammer(SsSpeed), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_l[3], 3.0), - id: state.skill_hammer_combo_3, + position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0), + id: state.ids.skill_hammer_combo_3, }, SkillIcon::Unlockable { skill: Skill::Hammer(SsRegen), image: self.imgs.physical_energy_regen_skill, - position: MidTopWithMarginOn(state.skills_top_l[4], 3.0), - id: state.skill_hammer_combo_4, + position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0), + id: state.ids.skill_hammer_combo_4, }, // Top right skills SkillIcon::Descriptive { title: "hud.skill.hmr_charged_melee_title", desc: "hud.skill.hmr_charged_melee", image: self.imgs.hammergolf, - position: MidTopWithMarginOn(state.skills_top_r[0], 3.0), - id: state.skill_hammer_charged_0, + position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0), + id: state.ids.skill_hammer_charged_0, }, SkillIcon::Unlockable { skill: Skill::Hammer(CKnockback), image: self.imgs.physical_knockback_skill, - position: MidTopWithMarginOn(state.skills_top_r[1], 3.0), - id: state.skill_hammer_charged_1, + position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0), + id: state.ids.skill_hammer_charged_1, }, SkillIcon::Unlockable { skill: Skill::Hammer(CDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_r[2], 3.0), - id: state.skill_hammer_charged_2, + position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0), + id: state.ids.skill_hammer_charged_2, }, SkillIcon::Unlockable { skill: Skill::Hammer(CDrain), image: self.imgs.physical_energy_drain_skill, - position: MidTopWithMarginOn(state.skills_top_r[3], 3.0), - id: state.skill_hammer_charged_3, + position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0), + id: state.ids.skill_hammer_charged_3, }, SkillIcon::Unlockable { skill: Skill::Hammer(CSpeed), image: self.imgs.physical_amount_skill, - position: MidTopWithMarginOn(state.skills_top_r[4], 3.0), - id: state.skill_hammer_charged_4, + position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0), + id: state.ids.skill_hammer_charged_4, }, // Bottom left skills SkillIcon::Unlockable { skill: Skill::Hammer(UnlockLeap), image: self.imgs.hammerleap, - position: MidTopWithMarginOn(state.skills_bot_l[0], 3.0), - id: state.skill_hammer_leap_0, + position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0), + id: state.ids.skill_hammer_leap_0, }, SkillIcon::Unlockable { skill: Skill::Hammer(LDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_l[1], 3.0), - id: state.skill_hammer_leap_1, + position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0), + id: state.ids.skill_hammer_leap_1, }, SkillIcon::Unlockable { skill: Skill::Hammer(LKnockback), image: self.imgs.physical_knockback_skill, - position: MidTopWithMarginOn(state.skills_bot_l[2], 3.0), - id: state.skill_hammer_leap_2, + position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0), + id: state.ids.skill_hammer_leap_2, }, SkillIcon::Unlockable { skill: Skill::Hammer(LCost), image: self.imgs.physical_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_l[3], 3.0), - id: state.skill_hammer_leap_3, + position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0), + id: state.ids.skill_hammer_leap_3, }, SkillIcon::Unlockable { skill: Skill::Hammer(LDistance), image: self.imgs.physical_distance_skill, - position: MidTopWithMarginOn(state.skills_bot_l[4], 3.0), - id: state.skill_hammer_leap_4, + position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0), + id: state.ids.skill_hammer_leap_4, }, SkillIcon::Unlockable { skill: Skill::Hammer(LRange), image: self.imgs.physical_radius_skill, - position: MidTopWithMarginOn(state.skills_bot_l[5], 3.0), - id: state.skill_hammer_leap_5, + position: MidTopWithMarginOn(state.ids.skills_bot_l[5], 3.0), + id: state.ids.skill_hammer_leap_5, }, ]; @@ -1119,7 +1829,7 @@ impl<'a> Diary<'a> { fn handle_axe_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { @@ -1127,11 +1837,11 @@ impl<'a> Diary<'a> { let tree_title = self.localized_strings.get("common.weapons.axe"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -1159,9 +1869,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) + .middle_of(state.ids.content_align) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.axe_render, ui); + .set(state.ids.axe_render, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ // Top Left skills @@ -1172,101 +1882,101 @@ impl<'a> Diary<'a> { title: "hud.skill.axe_double_strike_title", desc: "hud.skill.axe_double_strike", image: self.imgs.twohaxe_m1, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_axe_combo_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_axe_combo_0, }, SkillIcon::Unlockable { skill: Skill::Axe(DsCombo), image: self.imgs.physical_combo_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_axe_combo_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_axe_combo_1, }, SkillIcon::Unlockable { skill: Skill::Axe(DsDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_l[2], 3.0), - id: state.skill_axe_combo_2, + position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0), + id: state.ids.skill_axe_combo_2, }, SkillIcon::Unlockable { skill: Skill::Axe(DsSpeed), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_l[3], 3.0), - id: state.skill_axe_combo_3, + position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0), + id: state.ids.skill_axe_combo_3, }, SkillIcon::Unlockable { skill: Skill::Axe(DsRegen), image: self.imgs.physical_energy_regen_skill, - position: MidTopWithMarginOn(state.skills_top_l[4], 3.0), - id: state.skill_axe_combo_4, + position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0), + id: state.ids.skill_axe_combo_4, }, // Top right skills SkillIcon::Descriptive { title: "hud.skill.axe_spin_title", desc: "hud.skill.axe_spin", image: self.imgs.axespin, - position: MidTopWithMarginOn(state.skills_top_r[0], 3.0), - id: state.skill_axe_spin_0, + position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0), + id: state.ids.skill_axe_spin_0, }, SkillIcon::Unlockable { skill: Skill::Axe(SInfinite), image: self.imgs.physical_infinite_skill, - position: MidTopWithMarginOn(state.skills_top_r[1], 3.0), - id: state.skill_axe_spin_1, + position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0), + id: state.ids.skill_axe_spin_1, }, SkillIcon::Unlockable { skill: Skill::Axe(SDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_r[2], 3.0), - id: state.skill_axe_spin_2, + position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0), + id: state.ids.skill_axe_spin_2, }, SkillIcon::Unlockable { skill: Skill::Axe(SHelicopter), image: self.imgs.physical_helicopter_skill, - position: MidTopWithMarginOn(state.skills_top_r[3], 3.0), - id: state.skill_axe_spin_3, + position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0), + id: state.ids.skill_axe_spin_3, }, SkillIcon::Unlockable { skill: Skill::Axe(SSpeed), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_r[4], 3.0), - id: state.skill_axe_spin_4, + position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0), + id: state.ids.skill_axe_spin_4, }, SkillIcon::Unlockable { skill: Skill::Axe(SCost), image: self.imgs.physical_cost_skill, - position: MidTopWithMarginOn(state.skills_top_r[5], 3.0), - id: state.skill_axe_spin_5, + position: MidTopWithMarginOn(state.ids.skills_top_r[5], 3.0), + id: state.ids.skill_axe_spin_5, }, // Bottom left skills SkillIcon::Unlockable { skill: Skill::Axe(UnlockLeap), image: self.imgs.skill_axe_leap_slash, - position: MidTopWithMarginOn(state.skills_bot_l[0], 3.0), - id: state.skill_axe_leap_0, + position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0), + id: state.ids.skill_axe_leap_0, }, SkillIcon::Unlockable { skill: Skill::Axe(LDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_l[1], 3.0), - id: state.skill_axe_leap_1, + position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0), + id: state.ids.skill_axe_leap_1, }, SkillIcon::Unlockable { skill: Skill::Axe(LKnockback), image: self.imgs.physical_knockback_skill, - position: MidTopWithMarginOn(state.skills_bot_l[2], 3.0), - id: state.skill_axe_leap_2, + position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0), + id: state.ids.skill_axe_leap_2, }, SkillIcon::Unlockable { skill: Skill::Axe(LCost), image: self.imgs.physical_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_l[3], 3.0), - id: state.skill_axe_leap_3, + position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0), + id: state.ids.skill_axe_leap_3, }, SkillIcon::Unlockable { skill: Skill::Axe(LDistance), image: self.imgs.physical_distance_skill, - position: MidTopWithMarginOn(state.skills_bot_l[4], 3.0), - id: state.skill_axe_leap_4, + position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0), + id: state.ids.skill_axe_leap_4, }, ]; @@ -1277,7 +1987,7 @@ impl<'a> Diary<'a> { fn handle_sceptre_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { @@ -1285,11 +1995,11 @@ impl<'a> Diary<'a> { let tree_title = self.localized_strings.get("common.weapons.sceptre"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -1317,9 +2027,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) + .middle_of(state.ids.content_align) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.sceptre_render, ui); + .set(state.ids.sceptre_render, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ // Top Left skills @@ -1330,95 +2040,95 @@ impl<'a> Diary<'a> { title: "hud.skill.sc_lifesteal_title", desc: "hud.skill.sc_lifesteal", image: self.imgs.skill_sceptre_lifesteal, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_sceptre_lifesteal_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_sceptre_lifesteal_0, }, SkillIcon::Unlockable { skill: Skill::Sceptre(LDamage), image: self.imgs.magic_damage_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_sceptre_lifesteal_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_sceptre_lifesteal_1, }, SkillIcon::Unlockable { skill: Skill::Sceptre(LRange), image: self.imgs.magic_distance_skill, - position: MidTopWithMarginOn(state.skills_top_l[2], 3.0), - id: state.skill_sceptre_lifesteal_2, + position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0), + id: state.ids.skill_sceptre_lifesteal_2, }, SkillIcon::Unlockable { skill: Skill::Sceptre(LLifesteal), image: self.imgs.magic_lifesteal_skill, - position: MidTopWithMarginOn(state.skills_top_l[3], 3.0), - id: state.skill_sceptre_lifesteal_3, + position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0), + id: state.ids.skill_sceptre_lifesteal_3, }, SkillIcon::Unlockable { skill: Skill::Sceptre(LRegen), image: self.imgs.magic_energy_regen_skill, - position: MidTopWithMarginOn(state.skills_top_l[4], 3.0), - id: state.skill_sceptre_lifesteal_4, + position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0), + id: state.ids.skill_sceptre_lifesteal_4, }, // Top right skills SkillIcon::Descriptive { title: "hud.skill.sc_heal_title", desc: "hud.skill.sc_heal", image: self.imgs.skill_sceptre_heal, - position: MidTopWithMarginOn(state.skills_top_r[0], 3.0), - id: state.skill_sceptre_heal_0, + position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0), + id: state.ids.skill_sceptre_heal_0, }, SkillIcon::Unlockable { skill: Skill::Sceptre(HHeal), image: self.imgs.heal_heal_skill, - position: MidTopWithMarginOn(state.skills_top_r[1], 3.0), - id: state.skill_sceptre_heal_1, + position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0), + id: state.ids.skill_sceptre_heal_1, }, SkillIcon::Unlockable { skill: Skill::Sceptre(HDuration), image: self.imgs.heal_duration_skill, - position: MidTopWithMarginOn(state.skills_top_r[2], 3.0), - id: state.skill_sceptre_heal_2, + position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0), + id: state.ids.skill_sceptre_heal_2, }, SkillIcon::Unlockable { skill: Skill::Sceptre(HRange), image: self.imgs.heal_radius_skill, - position: MidTopWithMarginOn(state.skills_top_r[3], 3.0), - id: state.skill_sceptre_heal_3, + position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0), + id: state.ids.skill_sceptre_heal_3, }, SkillIcon::Unlockable { skill: Skill::Sceptre(HCost), image: self.imgs.heal_cost_skill, - position: MidTopWithMarginOn(state.skills_top_r[4], 3.0), - id: state.skill_sceptre_heal_4, + position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0), + id: state.ids.skill_sceptre_heal_4, }, // Bottom left skills SkillIcon::Unlockable { skill: Skill::Sceptre(UnlockAura), image: self.imgs.skill_sceptre_aura, - position: MidTopWithMarginOn(state.skills_bot_l[0], 3.0), - id: state.skill_sceptre_aura_0, + position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0), + id: state.ids.skill_sceptre_aura_0, }, SkillIcon::Unlockable { skill: Skill::Sceptre(AStrength), image: self.imgs.buff_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_l[1], 3.0), - id: state.skill_sceptre_aura_1, + position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0), + id: state.ids.skill_sceptre_aura_1, }, SkillIcon::Unlockable { skill: Skill::Sceptre(ADuration), image: self.imgs.buff_duration_skill, - position: MidTopWithMarginOn(state.skills_bot_l[2], 3.0), - id: state.skill_sceptre_aura_2, + position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0), + id: state.ids.skill_sceptre_aura_2, }, SkillIcon::Unlockable { skill: Skill::Sceptre(ARange), image: self.imgs.buff_radius_skill, - position: MidTopWithMarginOn(state.skills_bot_l[3], 3.0), - id: state.skill_sceptre_aura_3, + position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0), + id: state.ids.skill_sceptre_aura_3, }, SkillIcon::Unlockable { skill: Skill::Sceptre(ACost), image: self.imgs.buff_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_l[4], 3.0), - id: state.skill_sceptre_aura_4, + position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0), + id: state.ids.skill_sceptre_aura_4, }, ]; @@ -1429,7 +2139,7 @@ impl<'a> Diary<'a> { fn handle_bow_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { @@ -1437,11 +2147,11 @@ impl<'a> Diary<'a> { let tree_title = self.localized_strings.get("common.weapons.bow"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -1469,8 +2179,8 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) - .set(state.bow_render, ui); + .middle_of(state.ids.content_align) + .set(state.ids.bow_render, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ // Top Left skills @@ -1481,102 +2191,102 @@ impl<'a> Diary<'a> { title: "hud.skill.bow_charged_title", desc: "hud.skill.bow_charged", image: self.imgs.bow_m1, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_bow_charged_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_bow_charged_0, }, SkillIcon::Unlockable { skill: Skill::Bow(CDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_bow_charged_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_bow_charged_1, }, SkillIcon::Unlockable { skill: Skill::Bow(CRegen), image: self.imgs.physical_energy_regen_skill, - position: MidTopWithMarginOn(state.skills_top_l[2], 3.0), - id: state.skill_bow_charged_2, + position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0), + id: state.ids.skill_bow_charged_2, }, SkillIcon::Unlockable { skill: Skill::Bow(CKnockback), image: self.imgs.physical_knockback_skill, - position: MidTopWithMarginOn(state.skills_top_l[3], 3.0), - id: state.skill_bow_charged_3, + position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0), + id: state.ids.skill_bow_charged_3, }, SkillIcon::Unlockable { skill: Skill::Bow(CSpeed), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_l[4], 3.0), - id: state.skill_bow_charged_4, + position: MidTopWithMarginOn(state.ids.skills_top_l[4], 3.0), + id: state.ids.skill_bow_charged_4, }, SkillIcon::Unlockable { skill: Skill::Bow(CMove), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_l[5], 3.0), - id: state.skill_bow_charged_5, + position: MidTopWithMarginOn(state.ids.skills_top_l[5], 3.0), + id: state.ids.skill_bow_charged_5, }, // Top right skills SkillIcon::Descriptive { title: "hud.skill.bow_repeater_title", desc: "hud.skill.bow_repeater", image: self.imgs.bow_m2, - position: MidTopWithMarginOn(state.skills_top_r[0], 3.0), - id: state.skill_bow_repeater_0, + position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0), + id: state.ids.skill_bow_repeater_0, }, SkillIcon::Unlockable { skill: Skill::Bow(RDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_top_r[1], 3.0), - id: state.skill_bow_repeater_1, + position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0), + id: state.ids.skill_bow_repeater_1, }, SkillIcon::Unlockable { skill: Skill::Bow(RCost), image: self.imgs.physical_cost_skill, - position: MidTopWithMarginOn(state.skills_top_r[2], 3.0), - id: state.skill_bow_repeater_2, + position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0), + id: state.ids.skill_bow_repeater_2, }, SkillIcon::Unlockable { skill: Skill::Bow(RSpeed), image: self.imgs.physical_speed_skill, - position: MidTopWithMarginOn(state.skills_top_r[3], 3.0), - id: state.skill_bow_repeater_3, + position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0), + id: state.ids.skill_bow_repeater_3, }, // Bottom left skills SkillIcon::Unlockable { skill: Skill::Bow(UnlockShotgun), image: self.imgs.skill_bow_jump_burst, - position: MidTopWithMarginOn(state.skills_bot_l[0], 3.0), - id: state.skill_bow_shotgun_0, + position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0), + id: state.ids.skill_bow_shotgun_0, }, SkillIcon::Unlockable { skill: Skill::Bow(SDamage), image: self.imgs.physical_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_l[1], 3.0), - id: state.skill_bow_shotgun_1, + position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0), + id: state.ids.skill_bow_shotgun_1, }, SkillIcon::Unlockable { skill: Skill::Bow(SCost), image: self.imgs.physical_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_l[2], 3.0), - id: state.skill_bow_shotgun_2, + position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0), + id: state.ids.skill_bow_shotgun_2, }, SkillIcon::Unlockable { skill: Skill::Bow(SArrows), image: self.imgs.physical_amount_skill, - position: MidTopWithMarginOn(state.skills_bot_l[3], 3.0), - id: state.skill_bow_shotgun_3, + position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0), + id: state.ids.skill_bow_shotgun_3, }, SkillIcon::Unlockable { skill: Skill::Bow(SSpread), image: self.imgs.physical_explosion_skill, - position: MidTopWithMarginOn(state.skills_bot_l[4], 3.0), - id: state.skill_bow_shotgun_4, + position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0), + id: state.ids.skill_bow_shotgun_4, }, // Bottom right skills SkillIcon::Unlockable { skill: Skill::Bow(ProjSpeed), image: self.imgs.physical_projectile_speed_skill, - position: MidTopWithMarginOn(state.skills_bot_r[0], 3.0), - id: state.skill_bow_passive_0, + position: MidTopWithMarginOn(state.ids.skills_bot_r[0], 3.0), + id: state.ids.skill_bow_passive_0, }, ]; @@ -1587,7 +2297,7 @@ impl<'a> Diary<'a> { fn handle_staff_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { @@ -1595,11 +2305,11 @@ impl<'a> Diary<'a> { let tree_title = self.localized_strings.get("common.weapons.staff"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -1627,9 +2337,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) + .middle_of(state.ids.content_align) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.staff_render, ui); + .set(state.ids.staff_render, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ @@ -1641,89 +2351,89 @@ impl<'a> Diary<'a> { title: "hud.skill.st_fireball_title", desc: "hud.skill.st_fireball", image: self.imgs.fireball, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_staff_basic_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_staff_basic_0, }, SkillIcon::Unlockable { skill: Skill::Staff(BDamage), image: self.imgs.magic_damage_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_staff_basic_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_staff_basic_1, }, SkillIcon::Unlockable { skill: Skill::Staff(BRegen), image: self.imgs.magic_energy_regen_skill, - position: MidTopWithMarginOn(state.skills_top_l[2], 3.0), - id: state.skill_staff_basic_2, + position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0), + id: state.ids.skill_staff_basic_2, }, SkillIcon::Unlockable { skill: Skill::Staff(BRadius), image: self.imgs.magic_radius_skill, - position: MidTopWithMarginOn(state.skills_top_l[3], 3.0), - id: state.skill_staff_basic_3, + position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0), + id: state.ids.skill_staff_basic_3, }, // Top right skills SkillIcon::Descriptive { title: "hud.skill.st_flamethrower_title", desc: "hud.skill.st_flamethrower", image: self.imgs.flamethrower, - position: MidTopWithMarginOn(state.skills_top_r[0], 3.0), - id: state.skill_staff_beam_0, + position: MidTopWithMarginOn(state.ids.skills_top_r[0], 3.0), + id: state.ids.skill_staff_beam_0, }, SkillIcon::Unlockable { skill: Skill::Staff(FDamage), image: self.imgs.magic_damage_skill, - position: MidTopWithMarginOn(state.skills_top_r[1], 3.0), - id: state.skill_staff_beam_1, + position: MidTopWithMarginOn(state.ids.skills_top_r[1], 3.0), + id: state.ids.skill_staff_beam_1, }, SkillIcon::Unlockable { skill: Skill::Staff(FDrain), image: self.imgs.magic_energy_drain_skill, - position: MidTopWithMarginOn(state.skills_top_r[2], 3.0), - id: state.skill_staff_beam_2, + position: MidTopWithMarginOn(state.ids.skills_top_r[2], 3.0), + id: state.ids.skill_staff_beam_2, }, SkillIcon::Unlockable { skill: Skill::Staff(FRange), image: self.imgs.magic_radius_skill, - position: MidTopWithMarginOn(state.skills_top_r[3], 3.0), - id: state.skill_staff_beam_3, + position: MidTopWithMarginOn(state.ids.skills_top_r[3], 3.0), + id: state.ids.skill_staff_beam_3, }, SkillIcon::Unlockable { skill: Skill::Staff(FVelocity), image: self.imgs.magic_projectile_speed_skill, - position: MidTopWithMarginOn(state.skills_top_r[4], 3.0), - id: state.skill_staff_beam_4, + position: MidTopWithMarginOn(state.ids.skills_top_r[4], 3.0), + id: state.ids.skill_staff_beam_4, }, // Bottom left skills SkillIcon::Unlockable { skill: Skill::Staff(UnlockShockwave), image: self.imgs.fire_aoe, - position: MidTopWithMarginOn(state.skills_bot_l[0], 3.0), - id: state.skill_staff_shockwave_0, + position: MidTopWithMarginOn(state.ids.skills_bot_l[0], 3.0), + id: state.ids.skill_staff_shockwave_0, }, SkillIcon::Unlockable { skill: Skill::Staff(SDamage), image: self.imgs.magic_damage_skill, - position: MidTopWithMarginOn(state.skills_bot_l[1], 3.0), - id: state.skill_staff_shockwave_1, + position: MidTopWithMarginOn(state.ids.skills_bot_l[1], 3.0), + id: state.ids.skill_staff_shockwave_1, }, SkillIcon::Unlockable { skill: Skill::Staff(SKnockback), image: self.imgs.magic_knockback_skill, - position: MidTopWithMarginOn(state.skills_bot_l[2], 3.0), - id: state.skill_staff_shockwave_2, + position: MidTopWithMarginOn(state.ids.skills_bot_l[2], 3.0), + id: state.ids.skill_staff_shockwave_2, }, SkillIcon::Unlockable { skill: Skill::Staff(SCost), image: self.imgs.magic_cost_skill, - position: MidTopWithMarginOn(state.skills_bot_l[3], 3.0), - id: state.skill_staff_shockwave_3, + position: MidTopWithMarginOn(state.ids.skills_bot_l[3], 3.0), + id: state.ids.skill_staff_shockwave_3, }, SkillIcon::Unlockable { skill: Skill::Staff(SRange), image: self.imgs.magic_radius_skill, - position: MidTopWithMarginOn(state.skills_bot_l[4], 3.0), - id: state.skill_staff_shockwave_4, + position: MidTopWithMarginOn(state.ids.skills_bot_l[4], 3.0), + id: state.ids.skill_staff_shockwave_4, }, ]; @@ -1734,7 +2444,7 @@ impl<'a> Diary<'a> { fn handle_mining_skills_window( &mut self, diary_tooltip: &Tooltip, - state: &mut State, + state: &mut State, ui: &mut UiCell, mut events: Vec, ) -> Vec { @@ -1742,11 +2452,11 @@ impl<'a> Diary<'a> { let tree_title = self.localized_strings.get("common.tool.mining"); Text::new(tree_title) - .mid_top_with_margin_on(state.content_align, 2.0) + .mid_top_with_margin_on(state.ids.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) - .set(state.tree_title_txt, ui); + .set(state.ids.tree_title_txt, ui); // Number of skills per rectangle per weapon, start counting at 0 // Maximum of 9 skills/8 indices @@ -1774,9 +2484,9 @@ impl<'a> Diary<'a> { self.pulse, )) .wh(ART_SIZE) - .middle_of(state.content_align) + .middle_of(state.ids.content_align) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .set(state.pick_render, ui); + .set(state.ids.pick_render, ui); use PositionSpecifier::MidTopWithMarginOn; let skill_buttons = &[ @@ -1788,26 +2498,26 @@ impl<'a> Diary<'a> { title: "hud.skill.pick_strike_title", desc: "hud.skill.pick_strike", image: self.imgs.pickaxe, - position: MidTopWithMarginOn(state.skills_top_l[0], 3.0), - id: state.skill_pick_m1, + position: MidTopWithMarginOn(state.ids.skills_top_l[0], 3.0), + id: state.ids.skill_pick_m1, }, SkillIcon::Unlockable { skill: Skill::Pick(Speed), image: self.imgs.pickaxe_speed_skill, - position: MidTopWithMarginOn(state.skills_top_l[1], 3.0), - id: state.skill_pick_m1_0, + position: MidTopWithMarginOn(state.ids.skills_top_l[1], 3.0), + id: state.ids.skill_pick_m1_0, }, SkillIcon::Unlockable { skill: Skill::Pick(OreGain), image: self.imgs.pickaxe_oregain_skill, - position: MidTopWithMarginOn(state.skills_top_l[2], 3.0), - id: state.skill_pick_m1_1, + position: MidTopWithMarginOn(state.ids.skills_top_l[2], 3.0), + id: state.ids.skill_pick_m1_1, }, SkillIcon::Unlockable { skill: Skill::Pick(GemGain), image: self.imgs.pickaxe_gemgain_skill, - position: MidTopWithMarginOn(state.skills_top_l[3], 3.0), - id: state.skill_pick_m1_2, + position: MidTopWithMarginOn(state.ids.skills_top_l[3], 3.0), + id: state.ids.skill_pick_m1_2, }, ]; @@ -1864,7 +2574,7 @@ impl<'a> Diary<'a> { fn setup_state_for_skill_icons( &mut self, - state: &mut State, + state: &mut State, ui: &mut UiCell, skills_top_l: usize, skills_top_r: usize, @@ -1873,19 +2583,23 @@ impl<'a> Diary<'a> { ) { // Update widget id array len state.update(|s| { - s.skills_top_l + s.ids + .skills_top_l .resize(skills_top_l, &mut ui.widget_id_generator()) }); state.update(|s| { - s.skills_top_r + s.ids + .skills_top_r .resize(skills_top_r, &mut ui.widget_id_generator()) }); state.update(|s| { - s.skills_bot_l + s.ids + .skills_bot_l .resize(skills_bot_l, &mut ui.widget_id_generator()) }); state.update(|s| { - s.skills_bot_r + s.ids + .skills_bot_r .resize(skills_bot_r, &mut ui.widget_id_generator()) }); @@ -1935,52 +2649,52 @@ impl<'a> Diary<'a> { while self.created_btns_top_l < skills_top_l { let pos = skill_pos( self.created_btns_top_l, - state.skills_top_l_align, - state.skills_top_l[0], + state.ids.skills_top_l_align, + state.ids.skills_top_l[0], ); Button::image(self.imgs.wpn_icon_border_skills) .w_h(80.0, 100.0) .position(pos) - .set(state.skills_top_l[self.created_btns_top_l], ui); + .set(state.ids.skills_top_l[self.created_btns_top_l], ui); self.created_btns_top_l += 1; } // TOP-RIGHT Skills while self.created_btns_top_r < skills_top_r { let pos = skill_pos( self.created_btns_top_r, - state.skills_top_r_align, - state.skills_top_r[0], + state.ids.skills_top_r_align, + state.ids.skills_top_r[0], ); Button::image(self.imgs.wpn_icon_border_skills) .w_h(80.0, 100.0) .position(pos) - .set(state.skills_top_r[self.created_btns_top_r], ui); + .set(state.ids.skills_top_r[self.created_btns_top_r], ui); self.created_btns_top_r += 1; } // BOTTOM-LEFT Skills while self.created_btns_bot_l < skills_bot_l { let pos = skill_pos( self.created_btns_bot_l, - state.skills_bot_l_align, - state.skills_bot_l[0], + state.ids.skills_bot_l_align, + state.ids.skills_bot_l[0], ); Button::image(self.imgs.wpn_icon_border_skills) .w_h(80.0, 100.0) .position(pos) - .set(state.skills_bot_l[self.created_btns_bot_l], ui); + .set(state.ids.skills_bot_l[self.created_btns_bot_l], ui); self.created_btns_bot_l += 1; } // BOTTOM-RIGHT Skills while self.created_btns_bot_r < skills_bot_r { let pos = skill_pos( self.created_btns_bot_r, - state.skills_bot_r_align, - state.skills_bot_r[0], + state.ids.skills_bot_r_align, + state.ids.skills_bot_r[0], ); Button::image(self.imgs.wpn_icon_border_skills) .w_h(80.0, 100.0) .position(pos) - .set(state.skills_bot_r[self.created_btns_bot_r], ui); + .set(state.ids.skills_bot_r[self.created_btns_bot_r], ui); self.created_btns_bot_r += 1; } } diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs index 6007aa812a..f0a94ace5c 100644 --- a/voxygen/src/hud/hotbar.rs +++ b/voxygen/src/hud/hotbar.rs @@ -1,5 +1,5 @@ use crate::hud::item_imgs::ItemKey; -use common::comp::inventory::item::Item; +use common::comp::{self, inventory::item::Item}; use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, PartialEq)] @@ -70,7 +70,13 @@ impl State { { use common::comp::ability::AuxiliaryAbility; for ((i, ability), hotbar_slot) in active_abilities - .abilities + .auxiliary_set( + client.inventories().get(client.entity()), + client + .state() + .read_storage::() + .get(client.entity()), + ) .iter() .enumerate() .zip(self.slots.iter_mut()) diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 72dec72177..0d795e563f 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -68,8 +68,18 @@ image_ids! { diary_exp_bg: "voxygen.element.ui.diary.diary_exp_bg", diary_exp_frame: "voxygen.element.ui.diary.diary_exp_frame", pixel: "voxygen.element.ui.diary.pixel", + arrow_l: "voxygen.element.ui.diary.buttons.arrow_l", + arrow_l_click: "voxygen.element.ui.diary.buttons.arrow_l_click", + arrow_l_inactive: "voxygen.element.ui.diary.buttons.arrow_l_inactive", + arrow_r: "voxygen.element.ui.diary.buttons.arrow_r", + arrow_r_click: "voxygen.element.ui.diary.buttons.arrow_r_click", + arrow_r_inactive: "voxygen.element.ui.diary.buttons.arrow_r_inactive", + ability_frame: "voxygen.element.ui.diary.abilitiy_desc_frame", + ability_frame_dual: "voxygen.element.ui.diary.abilitiy_desc_frame_dual", // Skill Trees + book_bg: "voxygen.element.ui.diary.spellbook_bg", + skills_bg: "voxygen.element.ui.diary.diary_skills_bg", slot_skills: "voxygen.element.ui.diary.buttons.slot_skilltree", swords_crossed: "voxygen.element.weapons.swords_crossed", sceptre: "voxygen.element.weapons.sceptre", @@ -81,6 +91,9 @@ image_ids! { mining: "voxygen.element.weapons.mining", pickaxe: "voxygen.element.skills.pickaxe", pickaxe_ico: "voxygen.element.weapons.pickaxe", + skilltree_ico: "voxygen.element.ui.diary.buttons.skilltree", + spellbook_ico: "voxygen.element.ui.diary.buttons.spellbook", + stats_ico: "voxygen.element.ui.diary.buttons.stats", lock: "voxygen.element.ui.diary.buttons.lock", wpn_icon_border_skills: "voxygen.element.ui.diary.buttons.border_skills", wpn_icon_border: "voxygen.element.ui.generic.buttons.border", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 3bea1bdc01..2b03fd478e 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -74,7 +74,9 @@ use client::Client; use common::{ combat, comp::{ - self, fluid_dynamics, + self, + ability::AuxiliaryAbility, + fluid_dynamics, inventory::{slot::InvSlotId, trade_pricing::TradePricing}, item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality}, skillset::{skills::Skill, SkillGroupKind}, @@ -535,8 +537,7 @@ pub enum Event { RemoveBuff(BuffKind), UnlockSkill(Skill), RequestSiteInfo(SiteId), - // TODO: This variant currently unused. UI is needed for it to be properly used. - ChangeAbility(usize, comp::ability::AuxiliaryAbility), + ChangeAbility(usize, AuxiliaryAbility), SettingsChange(SettingsChange), AcknowledgePersistenceLoadError, @@ -651,7 +652,7 @@ pub struct Show { ingame: bool, chat_tab_settings_index: Option, settings_tab: SettingsTab, - skilltreetab: SelectedSkillTree, + diary_fields: diary::DiaryShow, crafting_tab: CraftingTab, crafting_search_key: Option, craft_sprite: Option<(Vec3, SpriteKind)>, @@ -746,6 +747,7 @@ impl Show { self.salvage = false; self.bag = false; self.map = false; + self.diary_fields = diary::DiaryShow::default(); self.diary = open; self.want_grab = !open; } @@ -851,7 +853,7 @@ impl Show { } fn open_skill_tree(&mut self, tree_sel: SelectedSkillTree) { - self.skilltreetab = tree_sel; + self.diary_fields.skilltreetab = tree_sel; self.social = false; } @@ -1045,7 +1047,7 @@ impl Hud { group_menu: false, chat_tab_settings_index: None, settings_tab: SettingsTab::Interface, - skilltreetab: SelectedSkillTree::General, + diary_fields: diary::DiaryShow::default(), crafting_tab: CraftingTab::All, crafting_search_key: None, craft_sprite: None, @@ -3097,17 +3099,42 @@ impl Hud { if self.show.diary { let entity = client.entity(); let skill_sets = ecs.read_storage::(); - if let Some(skill_set) = skill_sets.get(entity) { + if let ( + Some(skill_set), + Some(active_abilities), + Some(inventory), + Some(health), + Some(energy), + Some(body), + Some(poise), + ) = ( + skill_sets.get(entity), + active_abilities.get(entity), + inventories.get(entity), + healths.get(entity), + energies.get(entity), + bodies.get(entity), + poises.get(entity), + ) { for event in Diary::new( &self.show, client, + global_state, skill_set, + active_abilities, + inventory, + health, + energy, + poise, + body, + &msm, &self.imgs, &self.item_imgs, &self.fonts, i18n, &self.rot_imgs, tooltip_manager, + &mut self.slot_manager, self.pulse, ) .set(self.ids.diary, ui_widgets) @@ -3122,6 +3149,9 @@ impl Hud { self.show.open_skill_tree(tree_sel) }, diary::Event::UnlockSkill(skill) => events.push(Event::UnlockSkill(skill)), + diary::Event::ChangeSection(section) => { + self.show.diary_fields.section = section; + }, } } } @@ -3278,7 +3308,7 @@ impl Hud { // Maintain slot manager 'slot_events: for event in self.slot_manager.maintain(ui_widgets) { use comp::slot::Slot; - use slots::{InventorySlot, SlotKind::*}; + use slots::{AbilitySlot, InventorySlot, SlotKind::*}; let to_slot = |slot_kind| match slot_kind { Inventory(InventorySlot { slot, ours: true, .. @@ -3287,6 +3317,7 @@ impl Hud { Equip(e) => Some(Slot::Equip(e)), Hotbar(_) => None, Trade(_) => None, + Ability(_) => None, }; match event { slot::Event::Dragged(a, b) => { @@ -3336,6 +3367,33 @@ impl Hud { } } } + } else if let (Ability(a), Ability(b)) = (a, b) { + match (a, b) { + (AbilitySlot::Ability(ability), AbilitySlot::Slot(index)) => { + events.push(Event::ChangeAbility(index, ability)); + }, + (AbilitySlot::Slot(a), AbilitySlot::Slot(b)) => { + let me = client.entity(); + if let Some(active_abilities) = active_abilities.get(me) { + let ability_a = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(a) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + let ability_b = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(b) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + events.push(Event::ChangeAbility(a, ability_b)); + events.push(Event::ChangeAbility(b, ability_a)); + } + }, + (AbilitySlot::Slot(index), _) => { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); + }, + (AbilitySlot::Ability(_), AbilitySlot::Ability(_)) => {}, + } } }, slot::Event::Dropped(from) => { @@ -3355,6 +3413,8 @@ impl Hud { })); } } + } else if let Ability(AbilitySlot::Slot(index)) = from { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); } }, slot::Event::SplitDropped(from) => { @@ -3364,6 +3424,8 @@ impl Hud { } else if let Hotbar(h) = from { self.hotbar.clear_slot(h); events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned()))); + } else if let Ability(AbilitySlot::Slot(index)) = from { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); } }, slot::Event::SplitDragged(a, b) => { @@ -3407,6 +3469,33 @@ impl Hud { } } } + } else if let (Ability(a), Ability(b)) = (a, b) { + match (a, b) { + (AbilitySlot::Ability(ability), AbilitySlot::Slot(index)) => { + events.push(Event::ChangeAbility(index, ability)); + }, + (AbilitySlot::Slot(a), AbilitySlot::Slot(b)) => { + let me = client.entity(); + if let Some(active_abilities) = active_abilities.get(me) { + let ability_a = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(a) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + let ability_b = active_abilities + .auxiliary_set(inventories.get(me), skill_sets.get(me)) + .get(b) + .copied() + .unwrap_or(AuxiliaryAbility::Empty); + events.push(Event::ChangeAbility(a, ability_b)); + events.push(Event::ChangeAbility(b, ability_a)); + } + }, + (AbilitySlot::Slot(index), _) => { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); + }, + (AbilitySlot::Ability(_), AbilitySlot::Ability(_)) => {}, + } } }, slot::Event::Used(from) => { @@ -3442,6 +3531,8 @@ impl Hud { }, hotbar::SlotContents::Ability(_) => {}, }); + } else if let Ability(AbilitySlot::Slot(index)) = from { + events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty)); } }, slot::Event::Request { diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 76e4510799..e4f5248f65 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -601,13 +601,13 @@ impl<'a> Skillbar<'a> { // Helper let tooltip_text = |slot| { - let (hotbar, inventory, _, _, active_abilities, _) = content_source; + let (hotbar, inventory, _, skill_set, active_abilities, _) = content_source; hotbar.get(slot).and_then(|content| match content { hotbar::SlotContents::Inventory(i, _) => inventory .get_by_hash(i) .map(|item| (item.name(), item.description())), hotbar::SlotContents::Ability(i) => active_abilities - .abilities + .auxiliary_set(Some(inventory), Some(skill_set)) .get(i) .and_then(|a| Ability::from(*a).ability_id(Some(inventory))) .map(util::ability_description), diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 7256c698a7..ec2fd4fb7c 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -6,8 +6,9 @@ use super::{ }; use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{ - ability::AbilityInput, slot::InvSlotId, Ability, ActiveAbilities, Body, Energy, Inventory, - SkillSet, + ability::{Ability, AbilityInput, AuxiliaryAbility}, + slot::InvSlotId, + ActiveAbilities, Body, Energy, Inventory, SkillSet, }; use conrod_core::{image, Color}; use specs::Entity as EcsEntity; @@ -20,6 +21,7 @@ pub enum SlotKind { Equip(EquipSlot), Hotbar(HotbarSlot), Trade(TradeSlot), + Ability(AbilitySlot), /* Spellbook(SpellbookSlot), TODO */ } @@ -141,7 +143,7 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { }, hotbar::SlotContents::Ability(i) => { let ability_id = active_abilities - .abilities + .auxiliary_set(Some(inventory), Some(skillset)) .get(i) .and_then(|a| Ability::from(*a).ability_id(Some(inventory))); @@ -192,6 +194,42 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { } } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AbilitySlot { + Slot(usize), + Ability(AuxiliaryAbility), +} + +type AbilitiesSource<'a> = (&'a ActiveAbilities, &'a Inventory, &'a SkillSet); + +impl<'a> SlotKey, img_ids::Imgs> for AbilitySlot { + type ImageKey = String; + + fn image_key( + &self, + (active_abilities, inventory, skillset): &AbilitiesSource<'a>, + ) -> Option<(Self::ImageKey, Option)> { + let ability_id = match self { + Self::Slot(index) => active_abilities + .get_ability( + AbilityInput::Auxiliary(*index), + Some(inventory), + Some(skillset), + ) + .ability_id(Some(inventory)), + Self::Ability(ability) => Ability::from(*ability).ability_id(Some(inventory)), + }; + + ability_id.map(|id| (String::from(id), None)) + } + + fn amount(&self, _source: &AbilitiesSource) -> Option { None } + + fn image_ids(ability_id: &Self::ImageKey, imgs: &img_ids::Imgs) -> Vec { + vec![util::ability_image(imgs, ability_id)] + } +} + impl From for SlotKind { fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } } @@ -207,4 +245,15 @@ impl From for SlotKind { fn from(trade: TradeSlot) -> Self { Self::Trade(trade) } } -impl SumSlot for SlotKind {} +impl From for SlotKind { + fn from(ability: AbilitySlot) -> Self { Self::Ability(ability) } +} + +impl SumSlot for SlotKind { + fn drag_size(&self) -> Option<[f64; 2]> { + Some(match self { + Self::Ability(_) => [80.0; 2], + _ => return None, + }) + } +} diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index 2f200652bf..aa38d674c3 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -365,52 +365,41 @@ pub fn ability_description(ability_id: &str) -> (&str, &str) { // Debug stick "common.abilities.debug.possess" => ( "Possessing Arrow", - "\n\ - Shoots a poisonous arrow.\n\ - Lets you control your target.", + "Shoots a poisonous arrow. Lets you control your target.", ), // Sword "common.abilities.sword.spin" => ( "Whirlwind", - "\n\ - Move forward while spinning with your sword.", + "Move forward while spinning with your sword.", ), // Axe "common.abilities.axe.leap" => ( "Axe Jump", - "\n\ - A jump with the slashing leap to position of cursor.", + "A jump with the slashing leap to position of cursor.", ), // Hammer "common.abilities.hammer.leap" => ( "Smash of Doom", - "\n\ - An AOE attack with knockback.\n\ - Leaps to position of cursor.", + "An AOE attack with knockback. Leaps to position of cursor.", ), // Bow "common.abilities.bow.shotgun" => ( "Burst", - "\n\ - Launches a burst of arrows", + "Launches a burst of arrows", ), // Staff "common.abilities.staff.fireshockwave" => ( "Ring of Fire", - "\n\ - Ignites the ground with fiery shockwave.", + "Ignites the ground with fiery shockwave.", ), // Sceptre "common.abilities.sceptre.wardingaura" => ( "Thorn Bulwark", - "\n\ - Protects you and your group with thorns\n\ - for a short amount of time.", + "Protects you and your group with thorns for a short amount of time.", ), _ => ( "Ability has no title", - "\n\ - Ability has no description." + "Ability has no description." ), } } diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index d6ce8933b7..a8a2898663 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -19,7 +19,9 @@ pub trait SlotKey: Copy { fn image_ids(key: &Self::ImageKey, source: &I) -> Vec; } -pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static {} +pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static { + fn drag_size(&self) -> Option<[f64; 2]>; +} pub struct ContentSize { // Width divided by height @@ -217,6 +219,12 @@ where let content_img = *content_img; let drag_amount = *drag_amount; + let dragged_size = if let Some(dragged_size) = slot.drag_size() { + dragged_size + } else { + self.drag_img_size.map(|e| e as f64).into_array() + }; + // If we are dragging and we right click, drop half the stack // on the ground or into the slot under the cursor. This only // works with open slots or slots containing the same kind of @@ -260,9 +268,8 @@ where // Draw image of contents being dragged let [mouse_x, mouse_y] = input.mouse.xy; - let size = self.drag_img_size.map(|e| e as f64).into_array(); super::ghost_image::GhostImage::new(content_img) - .wh(size) + .wh(dragged_size) .xy([mouse_x, mouse_y]) .set(self.drag_id, ui);