mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/ability_selection' into 'master'
Ability Selection UI Closes #419, #421, and #1431 See merge request veloren/veloren!3031
This commit is contained in:
commit
dd612a9473
@ -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
|
||||
|
||||
|
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -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",
|
||||
|
BIN
assets/voxygen/element/ui/diary/abilitiy_desc_frame.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/abilitiy_desc_frame.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/abilitiy_desc_frame_dual.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/abilitiy_desc_frame_dual.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/arrow_l.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/arrow_l.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/arrow_l_click.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/arrow_l_click.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/arrow_l_inactive.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/arrow_l_inactive.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/arrow_r.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/arrow_r.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/arrow_r_click.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/arrow_r_click.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/arrow_r_inactive.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/arrow_r_inactive.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/skilltree.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/skilltree.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/spellbook.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/spellbook.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/buttons/stats.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/buttons/stats.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/diary_skills_bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/diary_skills_bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/diary/spellbook_bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/diary/spellbook_bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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,
|
||||
}))
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ macro_rules! dev_panic {
|
||||
if cfg!(any(debug_assertions, test)) {
|
||||
panic!("{}", $msg);
|
||||
} else {
|
||||
tracing::warn!("{}", $msg);
|
||||
tracing::error!("{}", $msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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::<Option<f32>>();
|
||||
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<f32> {
|
||||
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::<Option<f32>>()
|
||||
})
|
||||
}
|
||||
|
@ -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<ToolKind>, Option<ToolKind>);
|
||||
|
||||
// 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<AuxiliaryKey, [AuxiliaryAbility; MAX_ABILITIES]>,
|
||||
}
|
||||
|
||||
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<AuxiliaryKey, [AuxiliaryAbility; MAX_ABILITIES]>) -> 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<Item = usize> + '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<Item = usize> + '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 {
|
||||
|
@ -138,6 +138,7 @@ pub enum ControlEvent {
|
||||
Utterance(UtteranceKind),
|
||||
ChangeAbility {
|
||||
slot: usize,
|
||||
auxiliary_key: ability::AuxiliaryKey,
|
||||
new_ability: ability::AuxiliaryAbility,
|
||||
},
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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::<Option<f32>>();
|
||||
match protection {
|
||||
|
@ -115,6 +115,7 @@ pub enum ServerEvent {
|
||||
comp::Inventory,
|
||||
Option<comp::Waypoint>,
|
||||
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<E> {
|
||||
|
@ -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<Self>,
|
||||
(read_data, mut controllers, mut active_abilities): Self::SystemData,
|
||||
) {
|
||||
fn run(_job: &mut Job<Self>, (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,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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<comp::Waypoint>,
|
||||
Vec<(comp::Pet, comp::Body, comp::Stats)>,
|
||||
),
|
||||
loaded_components: PersistedComponents,
|
||||
) {
|
||||
server
|
||||
.state
|
||||
|
@ -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::<comp::Inventory>();
|
||||
let skill_sets = ecs.read_storage::<comp::SkillSet>();
|
||||
|
||||
if let Some(mut active_abilities) = ecs.write_storage::<comp::ActiveAbilities>().get_mut(entity)
|
||||
{
|
||||
active_abilities.change_ability(
|
||||
slot,
|
||||
auxiliary_key,
|
||||
new_ability,
|
||||
inventories.get(entity),
|
||||
skill_sets.get(entity),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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::<comp::ActiveAbilities>()
|
||||
.get_mut(entity)
|
||||
{
|
||||
active_abilities.auto_update(
|
||||
state.ecs().read_storage::<comp::Inventory>().get(entity),
|
||||
state.ecs().read_storage::<comp::SkillSet>().get(entity),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn within_pickup_range<S: FindDist<find_dist::Cylinder>>(
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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::<Presence>().get(entity),
|
||||
state.read_storage::<comp::SkillSet>().get(entity),
|
||||
state.read_storage::<comp::Inventory>().get(entity),
|
||||
state
|
||||
.read_storage::<comp::ability::ActiveAbilities>()
|
||||
.get(entity),
|
||||
state.read_storage::<Uid>().get(entity),
|
||||
state.read_storage::<comp::Player>().get(entity),
|
||||
state.ecs().fetch_mut::<CharacterUpdater>(),
|
||||
@ -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 */ },
|
||||
|
@ -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
|
||||
|
12
server/src/migrations/V47__ability_sets.sql
Normal file
12
server/src/migrations/V47__ability_sets.sql
Normal file
@ -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
|
@ -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::<Vec<(comp::Pet, comp::Body, comp::Stats)>>();
|
||||
|
||||
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<PetPersistenceData>,
|
||||
char_waypoint: Option<comp::Waypoint>,
|
||||
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(())
|
||||
}
|
||||
|
@ -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<Item = &'a skillset::Ski
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn convert_active_abilities_to_database(
|
||||
entity_id: CharacterId,
|
||||
active_abilities: &ability::ActiveAbilities,
|
||||
) -> 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::<Vec<DatabaseAbilitySet>>(&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)
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ pub type CharacterUpdateData = (
|
||||
comp::Inventory,
|
||||
Vec<PetPersistenceData>,
|
||||
Option<comp::Waypoint>,
|
||||
comp::ability::ActiveAbilities,
|
||||
);
|
||||
|
||||
pub type PetPersistenceData = (comp::Pet, comp::Body, comp::Stats);
|
||||
@ -330,21 +331,25 @@ impl CharacterUpdater {
|
||||
&'a comp::Inventory,
|
||||
Vec<PetPersistenceData>,
|
||||
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::<Vec<_>>();
|
||||
|
||||
@ -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");
|
||||
|
@ -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<String>,
|
||||
}
|
||||
|
||||
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::<usize>().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::<usize>().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<common::comp::item::tool::ToolKind>) -> 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<common::comp::item::tool::ToolKind> {
|
||||
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<DatabaseAbilitySet> {
|
||||
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::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn active_abilities_from_db_model(
|
||||
ability_sets: Vec<DatabaseAbilitySet>,
|
||||
) -> 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::<HashMap<_, _>>();
|
||||
comp::ability::ActiveAbilities::new(ability_sets)
|
||||
}
|
||||
|
@ -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<comp::Waypoint>,
|
||||
Vec<PetPersistenceData>,
|
||||
);
|
||||
/// 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<comp::Waypoint>,
|
||||
pub pets: Vec<PetPersistenceData>,
|
||||
pub active_abilities: comp::ActiveAbilities,
|
||||
}
|
||||
|
||||
pub type EditableComponents = (comp::Body,);
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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::<Uid>(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(
|
||||
|
@ -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<Self>>,
|
||||
);
|
||||
@ -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,
|
||||
},
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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::<comp::SkillSet>()
|
||||
.get(client.entity()),
|
||||
)
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(self.slots.iter_mut())
|
||||
|
@ -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",
|
||||
|
@ -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<usize>,
|
||||
settings_tab: SettingsTab,
|
||||
skilltreetab: SelectedSkillTree,
|
||||
diary_fields: diary::DiaryShow,
|
||||
crafting_tab: CraftingTab,
|
||||
crafting_search_key: Option<String>,
|
||||
craft_sprite: Option<(Vec3<i32>, 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::<comp::SkillSet>();
|
||||
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 {
|
||||
|
@ -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),
|
||||
|
@ -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<HotbarSource<'a>, 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<HotbarSource<'a>, 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<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
|
||||
type ImageKey = String;
|
||||
|
||||
fn image_key(
|
||||
&self,
|
||||
(active_abilities, inventory, skillset): &AbilitiesSource<'a>,
|
||||
) -> Option<(Self::ImageKey, Option<Color>)> {
|
||||
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<u32> { None }
|
||||
|
||||
fn image_ids(ability_id: &Self::ImageKey, imgs: &img_ids::Imgs) -> Vec<image::Id> {
|
||||
vec![util::ability_image(imgs, ability_id)]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InventorySlot> for SlotKind {
|
||||
fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) }
|
||||
}
|
||||
@ -207,4 +245,15 @@ impl From<TradeSlot> for SlotKind {
|
||||
fn from(trade: TradeSlot) -> Self { Self::Trade(trade) }
|
||||
}
|
||||
|
||||
impl SumSlot for SlotKind {}
|
||||
impl From<AbilitySlot> 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,9 @@ pub trait SlotKey<C, I>: Copy {
|
||||
fn image_ids(key: &Self::ImageKey, source: &I) -> Vec<image::Id>;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user