mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/ability-pool-and-hotbar' into 'master'
Ability pool See merge request veloren/veloren!2996
This commit is contained in:
commit
6e57380735
@ -29,6 +29,20 @@
|
||||
(Some(Bow(UnlockShotgun)), "common.abilities.bow.shotgun"),
|
||||
],
|
||||
),
|
||||
Tool(Staff): (
|
||||
primary: "common.abilities.staff.firebomb",
|
||||
secondary: "common.abilities.staff.flamethrower",
|
||||
abilities: [
|
||||
(Some(Staff(UnlockShockwave)), "common.abilities.staff.fireshockwave"),
|
||||
],
|
||||
),
|
||||
Tool(Sceptre): (
|
||||
primary: "common.abilities.sceptre.lifestealbeam",
|
||||
secondary: "common.abilities.sceptre.healingaura",
|
||||
abilities: [
|
||||
(Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"),
|
||||
],
|
||||
),
|
||||
Custom("Husk"): (
|
||||
primary: "common.abilities.custom.husk.singlestrike",
|
||||
secondary: "common.abilities.custom.husk.triplestrike",
|
||||
@ -70,20 +84,6 @@
|
||||
abilities: [
|
||||
],
|
||||
),
|
||||
Tool(Staff): (
|
||||
primary: "common.abilities.staff.firebomb",
|
||||
secondary: "common.abilities.staff.flamethrower",
|
||||
abilities: [
|
||||
(Some(Staff(UnlockShockwave)), "common.abilities.staff.fireshockwave"),
|
||||
],
|
||||
),
|
||||
Tool(Sceptre): (
|
||||
primary: "common.abilities.sceptre.lifestealbeam",
|
||||
secondary: "common.abilities.sceptre.healingaura",
|
||||
abilities: [
|
||||
(Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"),
|
||||
],
|
||||
),
|
||||
Tool(Dagger): (
|
||||
primary: "common.abilities.dagger.tempbasic",
|
||||
secondary: "common.abilities.dagger.tempbasic",
|
||||
|
@ -1396,6 +1396,13 @@ impl Client {
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn change_ability(&mut self, slot: usize, new_ability: comp::ability::AuxiliaryAbility) {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::ChangeAbility {
|
||||
slot,
|
||||
new_ability,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Execute a single client tick, handle input and update the game state by
|
||||
/// the given duration.
|
||||
pub fn tick(
|
||||
|
@ -15,6 +15,7 @@ sum_type! {
|
||||
CanBuild(comp::CanBuild),
|
||||
Stats(comp::Stats),
|
||||
SkillSet(comp::SkillSet),
|
||||
ActiveAbilities(comp::ActiveAbilities),
|
||||
Buffs(comp::Buffs),
|
||||
Auras(comp::Auras),
|
||||
Energy(comp::Energy),
|
||||
@ -50,6 +51,7 @@ sum_type! {
|
||||
CanBuild(PhantomData<comp::CanBuild>),
|
||||
Stats(PhantomData<comp::Stats>),
|
||||
SkillSet(PhantomData<comp::SkillSet>),
|
||||
ActiveAbilities(PhantomData<comp::ActiveAbilities>),
|
||||
Buffs(PhantomData<comp::Buffs>),
|
||||
Auras(PhantomData<comp::Auras>),
|
||||
Energy(PhantomData<comp::Energy>),
|
||||
@ -85,6 +87,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::SkillSet(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::ActiveAbilities(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Auras(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world),
|
||||
@ -124,6 +127,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::SkillSet(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::ActiveAbilities(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Auras(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world),
|
||||
@ -163,6 +167,9 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),
|
||||
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
|
||||
EcsCompPhantom::SkillSet(_) => sync::handle_remove::<comp::SkillSet>(entity, world),
|
||||
EcsCompPhantom::ActiveAbilities(_) => {
|
||||
sync::handle_remove::<comp::ActiveAbilities>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world),
|
||||
EcsCompPhantom::Auras(_) => sync::handle_remove::<comp::Auras>(entity, world),
|
||||
EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world),
|
||||
|
@ -3,9 +3,16 @@ use crate::{
|
||||
combat::{self, CombatEffect, DamageKind, Knockback},
|
||||
comp::{
|
||||
self, aura, beam, buff,
|
||||
inventory::item::tool::{Stats, ToolKind},
|
||||
inventory::{
|
||||
item::{
|
||||
tool::{AbilityItem, Stats, ToolKind},
|
||||
ItemKind,
|
||||
},
|
||||
slot::EquipSlot,
|
||||
Inventory,
|
||||
},
|
||||
projectile::ProjectileConstructor,
|
||||
skills::{self, SKILL_MODIFIERS},
|
||||
skills::{self, Skill, SkillSet, SKILL_MODIFIERS},
|
||||
Body, CharacterState, LightEmitter, StateUpdate,
|
||||
},
|
||||
states::{
|
||||
@ -16,8 +23,280 @@ use crate::{
|
||||
terrain::SpriteKind,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{convert::TryFrom, time::Duration};
|
||||
|
||||
pub const MAX_ABILITIES: usize = 5;
|
||||
|
||||
// TODO: Potentially look into storing previous ability sets for weapon
|
||||
// combinations and automatically reverting back to them on switching to that
|
||||
// set of weapons. Consider after UI is set up and people weigh in on memory
|
||||
// considerations.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ActiveAbilities {
|
||||
pub primary: PrimaryAbility,
|
||||
pub secondary: SecondaryAbility,
|
||||
pub movement: MovementAbility,
|
||||
pub abilities: [AuxiliaryAbility; MAX_ABILITIES],
|
||||
}
|
||||
|
||||
impl Component for ActiveAbilities {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
impl Default for ActiveAbilities {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
primary: PrimaryAbility::Tool,
|
||||
secondary: SecondaryAbility::Tool,
|
||||
movement: MovementAbility::Species,
|
||||
abilities: [AuxiliaryAbility::Empty; MAX_ABILITIES],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 change_ability(&mut self, slot: usize, new_ability: AuxiliaryAbility) {
|
||||
if let Some(ability) = self.abilities.get_mut(slot) {
|
||||
*ability = new_ability;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ability(&self, input: AbilityInput) -> Ability {
|
||||
match input {
|
||||
AbilityInput::Primary => self.primary.into(),
|
||||
AbilityInput::Secondary => self.secondary.into(),
|
||||
AbilityInput::Movement => self.movement.into(),
|
||||
AbilityInput::Auxiliary(index) => self
|
||||
.abilities
|
||||
.get(index)
|
||||
.copied()
|
||||
.map(|a| a.into())
|
||||
.unwrap_or(Ability::Empty),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the CharacterAbility from an ability input, and also whether the
|
||||
/// ability was from a weapon wielded in the offhand
|
||||
pub fn activate_ability(
|
||||
&self,
|
||||
input: AbilityInput,
|
||||
inv: Option<&Inventory>,
|
||||
skill_set: &SkillSet,
|
||||
body: Option<&Body>,
|
||||
// bool is from_offhand
|
||||
) -> Option<(CharacterAbility, bool)> {
|
||||
let ability = self.get_ability(input);
|
||||
|
||||
let ability_set = |equip_slot| {
|
||||
inv.and_then(|inv| inv.equipped(equip_slot))
|
||||
.map(|i| &i.item_config_expect().abilities)
|
||||
};
|
||||
|
||||
let scale_ability = |ability: CharacterAbility, equip_slot| {
|
||||
let tool_kind =
|
||||
inv.and_then(|inv| inv.equipped(equip_slot))
|
||||
.and_then(|item| match &item.kind {
|
||||
ItemKind::Tool(tool) => Some(tool.kind),
|
||||
_ => None,
|
||||
});
|
||||
ability.adjusted_by_skills(skill_set, tool_kind)
|
||||
};
|
||||
|
||||
let unwrap_ability = |(skill_req, ability): &(Option<Skill>, AbilityItem)| {
|
||||
(*skill_req, ability.ability.clone())
|
||||
};
|
||||
|
||||
let unlocked = |(s, a): (Option<Skill>, CharacterAbility)| {
|
||||
// If there is a skill requirement and the skillset does not contain the
|
||||
// required skill, return None
|
||||
s.map_or(true, |s| skill_set.has_skill(s)).then_some(a)
|
||||
};
|
||||
|
||||
match ability {
|
||||
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand)
|
||||
.map(|abilities| abilities.primary.ability.clone())
|
||||
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false)),
|
||||
Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand)
|
||||
.map(|abilities| abilities.secondary.ability.clone())
|
||||
.map(|ability| (scale_ability(ability, EquipSlot::ActiveOffhand), true))
|
||||
.or_else(|| {
|
||||
ability_set(EquipSlot::ActiveMainhand)
|
||||
.map(|abilities| abilities.secondary.ability.clone())
|
||||
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false))
|
||||
}),
|
||||
Ability::SpeciesMovement => matches!(body, Some(Body::Humanoid(_)))
|
||||
.then(CharacterAbility::default_roll)
|
||||
.map(|ability| (ability.adjusted_by_skills(skill_set, None), false)),
|
||||
Ability::MainWeaponAux(index) => ability_set(EquipSlot::ActiveMainhand)
|
||||
.and_then(|abilities| abilities.abilities.get(index).map(unwrap_ability))
|
||||
.and_then(unlocked)
|
||||
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false)),
|
||||
Ability::OffWeaponAux(index) => ability_set(EquipSlot::ActiveOffhand)
|
||||
.and_then(|abilities| abilities.abilities.get(index).map(unwrap_ability))
|
||||
.and_then(unlocked)
|
||||
.map(|ability| (scale_ability(ability, EquipSlot::ActiveOffhand), true)),
|
||||
Ability::Empty => None,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 enum AbilityInput {
|
||||
Primary,
|
||||
Secondary,
|
||||
Movement,
|
||||
Auxiliary(usize),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum Ability {
|
||||
ToolPrimary,
|
||||
ToolSecondary,
|
||||
SpeciesMovement,
|
||||
MainWeaponAux(usize),
|
||||
OffWeaponAux(usize),
|
||||
Empty,
|
||||
/* For future use
|
||||
* ArmorAbility(usize), */
|
||||
}
|
||||
|
||||
impl Ability {
|
||||
pub fn ability_id(self, inv: Option<&Inventory>) -> Option<&str> {
|
||||
let ability_set = |equip_slot| {
|
||||
inv.and_then(|inv| inv.equipped(equip_slot))
|
||||
.map(|i| &i.item_config_expect().abilities)
|
||||
};
|
||||
|
||||
match self {
|
||||
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand)
|
||||
.map(|abilities| abilities.primary.id.as_str()),
|
||||
Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand)
|
||||
.map(|abilities| abilities.secondary.id.as_str())
|
||||
.or_else(|| {
|
||||
ability_set(EquipSlot::ActiveMainhand)
|
||||
.map(|abilities| abilities.secondary.id.as_str())
|
||||
}),
|
||||
Ability::SpeciesMovement => None, // TODO: Make not None
|
||||
Ability::MainWeaponAux(index) => {
|
||||
ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| {
|
||||
abilities
|
||||
.abilities
|
||||
.get(index)
|
||||
.map(|(_, ability)| ability.id.as_str())
|
||||
})
|
||||
},
|
||||
Ability::OffWeaponAux(index) => {
|
||||
ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| {
|
||||
abilities
|
||||
.abilities
|
||||
.get(index)
|
||||
.map(|(_, ability)| ability.id.as_str())
|
||||
})
|
||||
},
|
||||
Ability::Empty => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum PrimaryAbility {
|
||||
Tool,
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl From<PrimaryAbility> for Ability {
|
||||
fn from(primary: PrimaryAbility) -> Self {
|
||||
match primary {
|
||||
PrimaryAbility::Tool => Ability::ToolPrimary,
|
||||
PrimaryAbility::Empty => Ability::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum SecondaryAbility {
|
||||
Tool,
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl From<SecondaryAbility> for Ability {
|
||||
fn from(primary: SecondaryAbility) -> Self {
|
||||
match primary {
|
||||
SecondaryAbility::Tool => Ability::ToolSecondary,
|
||||
SecondaryAbility::Empty => Ability::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum MovementAbility {
|
||||
Species,
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl From<MovementAbility> for Ability {
|
||||
fn from(primary: MovementAbility) -> Self {
|
||||
match primary {
|
||||
MovementAbility::Species => Ability::SpeciesMovement,
|
||||
MovementAbility::Empty => Ability::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum AuxiliaryAbility {
|
||||
MainWeapon(usize),
|
||||
OffWeapon(usize),
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl From<AuxiliaryAbility> for Ability {
|
||||
fn from(primary: AuxiliaryAbility) -> Self {
|
||||
match primary {
|
||||
AuxiliaryAbility::MainWeapon(i) => Ability::MainWeaponAux(i),
|
||||
AuxiliaryAbility::OffWeapon(i) => Ability::OffWeaponAux(i),
|
||||
AuxiliaryAbility::Empty => Ability::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum CharacterAbilityType {
|
||||
BasicMelee,
|
||||
@ -899,7 +1178,7 @@ impl CharacterAbility {
|
||||
|
||||
#[warn(clippy::pedantic)]
|
||||
fn adjusted_by_mining_skills(&mut self, skillset: &skills::SkillSet) {
|
||||
use skills::{MiningSkill::Speed, Skill};
|
||||
use skills::MiningSkill::Speed;
|
||||
|
||||
if let CharacterAbility::BasicMelee {
|
||||
ref mut buildup_duration,
|
||||
@ -921,8 +1200,6 @@ impl CharacterAbility {
|
||||
|
||||
#[warn(clippy::pedantic)]
|
||||
fn adjusted_by_general_skills(&mut self, skillset: &skills::SkillSet) {
|
||||
use skills::Skill;
|
||||
|
||||
if let CharacterAbility::Roll {
|
||||
ref mut energy_cost,
|
||||
ref mut roll_strength,
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
ability,
|
||||
inventory::slot::{EquipSlot, InvSlotId, Slot},
|
||||
invite::{InviteKind, InviteResponse},
|
||||
BuffKind,
|
||||
@ -135,6 +136,10 @@ pub enum ControlEvent {
|
||||
RemoveBuff(BuffKind),
|
||||
Respawn,
|
||||
Utterance(UtteranceKind),
|
||||
ChangeAbility {
|
||||
slot: usize,
|
||||
new_ability: ability::AuxiliaryAbility,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -189,6 +194,19 @@ impl InputKind {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InputKind> for Option<ability::AbilityInput> {
|
||||
fn from(input: InputKind) -> Option<ability::AbilityInput> {
|
||||
use ability::AbilityInput;
|
||||
match input {
|
||||
InputKind::Primary => Some(AbilityInput::Primary),
|
||||
InputKind::Secondary => Some(AbilityInput::Secondary),
|
||||
InputKind::Roll => Some(AbilityInput::Movement),
|
||||
InputKind::Ability(index) => Some(AbilityInput::Auxiliary(index)),
|
||||
InputKind::Jump | InputKind::Fly | InputKind::Block => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct InputAttr {
|
||||
pub select_pos: Option<Vec3<f32>>,
|
||||
|
@ -8,10 +8,7 @@ pub use tool::{AbilitySet, AbilitySpec, Hands, MaterialStatManifest, Tool, ToolK
|
||||
|
||||
use crate::{
|
||||
assets::{self, AssetExt, Error},
|
||||
comp::{
|
||||
inventory::{item::tool::AbilityMap, InvSlot},
|
||||
CharacterAbility,
|
||||
},
|
||||
comp::inventory::{item::tool::AbilityMap, InvSlot},
|
||||
effect::Effect,
|
||||
recipe::RecipeInput,
|
||||
terrain::Block,
|
||||
@ -433,7 +430,7 @@ impl PartialEq for ItemDef {
|
||||
// TODO: Look into removing ItemConfig and just using AbilitySet
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ItemConfig {
|
||||
pub abilities: AbilitySet<CharacterAbility>,
|
||||
pub abilities: AbilitySet<tool::AbilityItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -449,9 +446,10 @@ impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
|
||||
) -> Result<Self, Self::Error> {
|
||||
if let ItemKind::Tool(tool) = &item.kind {
|
||||
// If no custom ability set is specified, fall back to abilityset of tool kind.
|
||||
let tool_default = ability_map
|
||||
.get_ability_set(&AbilitySpec::Tool(tool.kind))
|
||||
.cloned();
|
||||
let tool_default = |tool_kind| {
|
||||
let key = &AbilitySpec::Tool(tool_kind);
|
||||
ability_map.get_ability_set(key)
|
||||
};
|
||||
let abilities = if let Some(set_key) = item.ability_spec() {
|
||||
if let Some(set) = ability_map.get_ability_set(set_key) {
|
||||
set.clone().modified_by_tool(tool, msm, &item.components)
|
||||
@ -461,10 +459,10 @@ impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
|
||||
default ability set.",
|
||||
set_key
|
||||
);
|
||||
tool_default.unwrap_or_default()
|
||||
tool_default(tool.kind).cloned().unwrap_or_default()
|
||||
}
|
||||
} else if let Some(set) = tool_default {
|
||||
set.modified_by_tool(tool, msm, &item.components)
|
||||
} else if let Some(set) = tool_default(tool.kind) {
|
||||
set.clone().modified_by_tool(tool, msm, &item.components)
|
||||
} else {
|
||||
error!(
|
||||
"No ability set defined for tool: {:?}, falling back to default ability set.",
|
||||
|
@ -345,7 +345,7 @@ pub struct AbilitySet<T> {
|
||||
pub abilities: Vec<(Option<Skill>, T)>,
|
||||
}
|
||||
|
||||
impl AbilitySet<CharacterAbility> {
|
||||
impl AbilitySet<AbilityItem> {
|
||||
pub fn modified_by_tool(
|
||||
self,
|
||||
tool: &Tool,
|
||||
@ -353,7 +353,10 @@ impl AbilitySet<CharacterAbility> {
|
||||
components: &[Item],
|
||||
) -> Self {
|
||||
let stats = Stats::from((msm, components, tool));
|
||||
self.map(|a| a.adjusted_by_stats(stats))
|
||||
self.map(|a| AbilityItem {
|
||||
id: a.id,
|
||||
ability: a.ability.adjusted_by_stats(stats),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,12 +379,18 @@ impl<T> AbilitySet<T> {
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for AbilitySet<CharacterAbility> {
|
||||
impl Default for AbilitySet<AbilityItem> {
|
||||
fn default() -> Self {
|
||||
AbilitySet {
|
||||
primary: CharacterAbility::default(),
|
||||
secondary: CharacterAbility::default(),
|
||||
abilities: vec![],
|
||||
primary: AbilityItem {
|
||||
id: String::new(),
|
||||
ability: CharacterAbility::default(),
|
||||
},
|
||||
secondary: AbilityItem {
|
||||
id: String::new(),
|
||||
ability: CharacterAbility::default(),
|
||||
},
|
||||
abilities: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -393,7 +402,13 @@ pub enum AbilitySpec {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AbilityMap<T = CharacterAbility>(HashMap<AbilitySpec, AbilitySet<T>>);
|
||||
pub struct AbilityItem {
|
||||
pub id: String,
|
||||
pub ability: CharacterAbility,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AbilityMap<T = AbilityItem>(HashMap<AbilitySpec, AbilitySet<T>>);
|
||||
|
||||
impl Default for AbilityMap {
|
||||
fn default() -> Self {
|
||||
@ -434,7 +449,10 @@ impl assets::Compound for AbilityMap {
|
||||
kind.clone(),
|
||||
// expect cannot fail because CharacterAbility always
|
||||
// provides a default value in case of failure
|
||||
set.map_ref(|s| cache.load_expect(s).cloned()),
|
||||
set.map_ref(|s| AbilityItem {
|
||||
id: s.clone(),
|
||||
ability: cache.load_expect(s).cloned(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
|
@ -1,4 +1,5 @@
|
||||
#[cfg(not(target_arch = "wasm32"))] mod ability;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod ability;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod admin;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod agent;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -47,7 +48,7 @@ pub mod visual;
|
||||
// Reexports
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use self::{
|
||||
ability::{CharacterAbility, CharacterAbilityType},
|
||||
ability::{Ability, AbilityInput, ActiveAbilities, CharacterAbility, CharacterAbilityType},
|
||||
admin::{Admin, AdminRole},
|
||||
agent::{Agent, Alignment, Behavior, BehaviorCapability, BehaviorState, PidController},
|
||||
anchor::Anchor,
|
||||
@ -77,7 +78,11 @@ pub use self::{
|
||||
group::Group,
|
||||
inputs::CanBuild,
|
||||
inventory::{
|
||||
item::{self, tool, Item, ItemConfig, ItemDrop},
|
||||
item::{
|
||||
self,
|
||||
tool::{self, AbilityItem},
|
||||
Item, ItemConfig, ItemDrop,
|
||||
},
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||
},
|
||||
last::Last,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
self, character_state::OutputEvents, item::MaterialStatManifest, Beam, Body,
|
||||
CharacterState, Combo, ControlAction, Controller, ControllerInputs, Density, Energy,
|
||||
self, character_state::OutputEvents, item::MaterialStatManifest, ActiveAbilities, Beam,
|
||||
Body, CharacterState, Combo, ControlAction, Controller, ControllerInputs, Density, Energy,
|
||||
Health, InputAttr, InputKind, Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState,
|
||||
Pos, SkillSet, StateUpdate, Stats, Vel,
|
||||
},
|
||||
@ -124,6 +124,7 @@ pub struct JoinData<'a> {
|
||||
pub updater: &'a LazyUpdate,
|
||||
pub stats: &'a Stats,
|
||||
pub skill_set: &'a SkillSet,
|
||||
pub active_abilities: &'a ActiveAbilities,
|
||||
pub msm: &'a MaterialStatManifest,
|
||||
pub combo: &'a Combo,
|
||||
pub alignment: Option<&'a comp::Alignment>,
|
||||
@ -149,6 +150,7 @@ pub struct JoinStruct<'a> {
|
||||
pub beam: Option<&'a Beam>,
|
||||
pub stat: &'a Stats,
|
||||
pub skill_set: &'a SkillSet,
|
||||
pub active_abilities: &'a ActiveAbilities,
|
||||
pub combo: &'a Combo,
|
||||
pub alignment: Option<&'a comp::Alignment>,
|
||||
pub terrain: &'a TerrainGrid,
|
||||
@ -186,6 +188,7 @@ impl<'a> JoinData<'a> {
|
||||
combo: j.combo,
|
||||
alignment: j.alignment,
|
||||
terrain: j.terrain,
|
||||
active_abilities: j.active_abilities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -821,58 +821,20 @@ pub fn handle_jump(
|
||||
}
|
||||
|
||||
fn handle_ability(data: &JoinData<'_>, update: &mut StateUpdate, input: InputKind) {
|
||||
let hands = get_hands(data);
|
||||
|
||||
// Mouse1 and Skill1 always use the MainHand slot
|
||||
let always_main_hand = matches!(input, InputKind::Primary | InputKind::Ability(0));
|
||||
let no_main_hand = hands.0.is_none();
|
||||
// skill_index used to select ability for the AbilityKey::Skill2 input
|
||||
let (equip_slot, skill_index) = if no_main_hand {
|
||||
(Some(EquipSlot::ActiveOffhand), 1)
|
||||
} else if always_main_hand {
|
||||
(Some(EquipSlot::ActiveMainhand), 0)
|
||||
} else {
|
||||
match hands {
|
||||
(Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
|
||||
(_, Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
|
||||
(Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
|
||||
(_, _) => (None, 0),
|
||||
}
|
||||
};
|
||||
|
||||
let unlocked = |(s, a): (Option<Skill>, CharacterAbility)| {
|
||||
s.map_or(true, |s| data.skill_set.has_skill(s)).then_some(a)
|
||||
};
|
||||
|
||||
if let Some(equip_slot) = equip_slot {
|
||||
if let Some(ability) = data
|
||||
.inventory
|
||||
.and_then(|inv| inv.equipped(equip_slot))
|
||||
.map(|i| &i.item_config_expect().abilities)
|
||||
.and_then(|abilities| match input {
|
||||
InputKind::Primary => Some(abilities.primary.clone()),
|
||||
InputKind::Secondary => Some(abilities.secondary.clone()),
|
||||
InputKind::Ability(0) => abilities.abilities.get(0).cloned().and_then(unlocked),
|
||||
InputKind::Ability(i) => abilities
|
||||
.abilities
|
||||
.get(if i < 2 { skill_index } else { i })
|
||||
.cloned()
|
||||
.and_then(unlocked),
|
||||
InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None,
|
||||
})
|
||||
.map(|a| {
|
||||
let tool = unwrap_tool_data(data, equip_slot).map(|t| t.kind);
|
||||
a.adjusted_by_skills(data.skill_set, tool)
|
||||
})
|
||||
.filter(|ability| ability.requirements_paid(data, update))
|
||||
if let Some(ability_input) = input.into() {
|
||||
if let Some((ability, from_offhand)) = data
|
||||
.active_abilities
|
||||
.activate_ability(
|
||||
ability_input,
|
||||
data.inventory,
|
||||
data.skill_set,
|
||||
Some(data.body),
|
||||
)
|
||||
.filter(|(ability, _)| ability.requirements_paid(data, update))
|
||||
{
|
||||
update.character = CharacterState::from((
|
||||
&ability,
|
||||
AbilityInfo::from_input(
|
||||
data,
|
||||
matches!(equip_slot, EquipSlot::ActiveOffhand),
|
||||
input,
|
||||
),
|
||||
AbilityInfo::from_input(data, from_offhand, input),
|
||||
data,
|
||||
));
|
||||
}
|
||||
|
@ -129,6 +129,7 @@ impl State {
|
||||
ecs.register::<comp::Player>();
|
||||
ecs.register::<comp::Stats>();
|
||||
ecs.register::<comp::SkillSet>();
|
||||
ecs.register::<comp::ActiveAbilities>();
|
||||
ecs.register::<comp::Buffs>();
|
||||
ecs.register::<comp::Auras>();
|
||||
ecs.register::<comp::Energy>();
|
||||
|
@ -5,9 +5,10 @@ use specs::{
|
||||
|
||||
use common::{
|
||||
comp::{
|
||||
self, character_state::OutputEvents, inventory::item::MaterialStatManifest, Beam, Body,
|
||||
CharacterState, Combo, Controller, Density, Energy, Health, Inventory, InventoryManip,
|
||||
Mass, Melee, Mounting, Ori, PhysicsState, Poise, Pos, SkillSet, StateUpdate, Stats, Vel,
|
||||
self, character_state::OutputEvents, inventory::item::MaterialStatManifest,
|
||||
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
|
||||
Inventory, InventoryManip, Mass, Melee, Mounting, Ori, PhysicsState, Poise, Pos, SkillSet,
|
||||
StateUpdate, Stats, Vel,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
outcome::Outcome,
|
||||
@ -38,6 +39,7 @@ pub struct ReadData<'a> {
|
||||
mountings: ReadStorage<'a, Mounting>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
skill_sets: ReadStorage<'a, SkillSet>,
|
||||
active_abilities: ReadStorage<'a, ActiveAbilities>,
|
||||
msm: Read<'a, MaterialStatManifest>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
alignments: ReadStorage<'a, comp::Alignment>,
|
||||
@ -107,7 +109,7 @@ impl<'a> System<'a> for Sys {
|
||||
health,
|
||||
body,
|
||||
physics,
|
||||
(stat, skill_set),
|
||||
(stat, skill_set, active_abilities),
|
||||
combo,
|
||||
) in (
|
||||
&read_data.entities,
|
||||
@ -124,7 +126,11 @@ impl<'a> System<'a> for Sys {
|
||||
read_data.healths.maybe(),
|
||||
&read_data.bodies,
|
||||
&read_data.physics_states,
|
||||
(&read_data.stats, &read_data.skill_sets),
|
||||
(
|
||||
&read_data.stats,
|
||||
&read_data.skill_sets,
|
||||
&read_data.active_abilities,
|
||||
),
|
||||
&read_data.combos,
|
||||
)
|
||||
.join()
|
||||
@ -181,6 +187,7 @@ impl<'a> System<'a> for Sys {
|
||||
beam: read_data.beams.get(entity),
|
||||
stat,
|
||||
skill_set,
|
||||
active_abilities,
|
||||
combo,
|
||||
alignment: read_data.alignments.get(entity),
|
||||
terrain: &read_data.terrain,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use common::{
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
Body, BuffChange, ControlEvent, Controller, Pos,
|
||||
ActiveAbilities, Body, BuffChange, ControlEvent, Controller, Pos,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
uid::UidAllocator,
|
||||
@ -27,13 +27,20 @@ pub struct ReadData<'a> {
|
||||
pub struct Sys;
|
||||
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (ReadData<'a>, WriteStorage<'a, Controller>);
|
||||
type SystemData = (
|
||||
ReadData<'a>,
|
||||
WriteStorage<'a, Controller>,
|
||||
WriteStorage<'a, ActiveAbilities>,
|
||||
);
|
||||
|
||||
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): Self::SystemData) {
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(read_data, mut controllers, mut active_abilities): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = read_data.server_bus.emitter();
|
||||
|
||||
for (entity, controller) in (&read_data.entities, &mut controllers).join() {
|
||||
@ -103,6 +110,11 @@ 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);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -767,6 +767,19 @@ 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>>(
|
||||
|
@ -66,8 +66,7 @@ use common::{
|
||||
assets::AssetExt,
|
||||
character::CharacterId,
|
||||
cmd::ChatCommand,
|
||||
comp,
|
||||
comp::{item::MaterialStatManifest, CharacterAbility},
|
||||
comp::{self, item::MaterialStatManifest},
|
||||
event::{EventBus, ServerEvent},
|
||||
recipe::default_recipe_book,
|
||||
resources::{BattleMode, Time, TimeOfDay},
|
||||
@ -287,7 +286,7 @@ impl Server {
|
||||
Arc::<RwLock<DatabaseSettings>>::clone(&database_settings),
|
||||
)?);
|
||||
|
||||
let ability_map = comp::item::tool::AbilityMap::<CharacterAbility>::load_expect_cloned(
|
||||
let ability_map = comp::item::tool::AbilityMap::<comp::AbilityItem>::load_expect_cloned(
|
||||
"common.abilities.ability_set_manifest",
|
||||
);
|
||||
state.ecs_mut().insert(ability_map);
|
||||
|
@ -215,6 +215,10 @@ impl StateExt for State {
|
||||
.unwrap_or(0),
|
||||
))
|
||||
.with(stats)
|
||||
.with(comp::ActiveAbilities::new(
|
||||
Some(&inventory),
|
||||
Some(&skill_set),
|
||||
))
|
||||
.with(skill_set)
|
||||
.maybe_with(health)
|
||||
.with(poise)
|
||||
@ -267,6 +271,7 @@ impl StateExt for State {
|
||||
.with(comp::Energy::new(ship.into(), 0))
|
||||
.with(comp::Stats::new("Airship".to_string()))
|
||||
.with(comp::SkillSet::default())
|
||||
.with(comp::ActiveAbilities::default())
|
||||
.with(comp::Combo::default());
|
||||
|
||||
if mountable {
|
||||
@ -504,6 +509,10 @@ 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, skill_set);
|
||||
self.write_component_ignore_entity_dead(entity, inventory);
|
||||
self.write_component_ignore_entity_dead(
|
||||
|
@ -19,9 +19,9 @@ use common::{
|
||||
},
|
||||
projectile::ProjectileConstructor,
|
||||
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
||||
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility,
|
||||
CharacterState, Combo, ControlAction, ControlEvent, Controller, Energy, Health,
|
||||
HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori,
|
||||
AbilityInput, ActiveAbilities, Agent, Alignment, BehaviorCapability, BehaviorState, Body,
|
||||
CharacterAbility, CharacterState, Combo, ControlAction, ControlEvent, Controller, Energy,
|
||||
Health, HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori,
|
||||
PhysicsState, Pos, Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel,
|
||||
},
|
||||
consts::GRAVITY,
|
||||
@ -74,6 +74,7 @@ struct AgentData<'a> {
|
||||
is_gliding: bool,
|
||||
health: Option<&'a Health>,
|
||||
char_state: &'a CharacterState,
|
||||
active_abilities: &'a ActiveAbilities,
|
||||
cached_spatial_grid: &'a common::CachedSpatialGrid,
|
||||
}
|
||||
|
||||
@ -160,6 +161,7 @@ pub struct ReadData<'a> {
|
||||
rtsim_entities: ReadStorage<'a, RtSimEntity>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
active_abilities: ReadStorage<'a, ActiveAbilities>,
|
||||
}
|
||||
|
||||
const DAMAGE_MEMORY_DURATION: f64 = 0.25;
|
||||
@ -212,7 +214,11 @@ impl<'a> System<'a> for Sys {
|
||||
),
|
||||
read_data.bodies.maybe(),
|
||||
&read_data.inventories,
|
||||
&read_data.skill_set,
|
||||
(
|
||||
&read_data.char_states,
|
||||
&read_data.skill_set,
|
||||
&read_data.active_abilities,
|
||||
),
|
||||
&read_data.physics_states,
|
||||
&read_data.uids,
|
||||
&mut agents,
|
||||
@ -220,10 +226,9 @@ impl<'a> System<'a> for Sys {
|
||||
read_data.light_emitter.maybe(),
|
||||
read_data.groups.maybe(),
|
||||
read_data.mount_states.maybe(),
|
||||
&read_data.char_states,
|
||||
)
|
||||
.par_join()
|
||||
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state, _)| {
|
||||
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state)| {
|
||||
// Skip mounted entities
|
||||
mount_state
|
||||
.map(|ms| *ms == MountState::Unmounted)
|
||||
@ -241,7 +246,7 @@ impl<'a> System<'a> for Sys {
|
||||
(pos, vel, ori),
|
||||
body,
|
||||
inventory,
|
||||
skill_set,
|
||||
(char_state, skill_set, active_abilities),
|
||||
physics_state,
|
||||
uid,
|
||||
agent,
|
||||
@ -249,7 +254,6 @@ impl<'a> System<'a> for Sys {
|
||||
light_emitter,
|
||||
group,
|
||||
_,
|
||||
char_state,
|
||||
)| {
|
||||
// Hack, replace with better system when groups are more sophisticated
|
||||
// Override alignment if in a group unless entity is owned already
|
||||
@ -359,6 +363,7 @@ impl<'a> System<'a> for Sys {
|
||||
is_gliding,
|
||||
health: read_data.healths.get(entity),
|
||||
char_state,
|
||||
active_abilities,
|
||||
cached_spatial_grid: &read_data.cached_spatial_grid,
|
||||
};
|
||||
///////////////////////////////////////////////////////////
|
||||
@ -2502,30 +2507,16 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
let extract_ability = |ability: &CharacterAbility| {
|
||||
ability
|
||||
.clone()
|
||||
.adjusted_by_skills(self.skill_set, Some(ToolKind::Staff))
|
||||
let extract_ability = |input: AbilityInput| {
|
||||
self.active_abilities
|
||||
.activate_ability(input, Some(self.inventory), self.skill_set, self.body)
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
};
|
||||
let (flamethrower, shockwave) = self
|
||||
.inventory
|
||||
.equipped(EquipSlot::ActiveMainhand)
|
||||
.map(|i| &i.item_config_expect().abilities)
|
||||
.map(|a| {
|
||||
(
|
||||
Some(a.secondary.clone()),
|
||||
a.abilities.get(0).map(|(_, s)| s),
|
||||
)
|
||||
})
|
||||
.map_or(
|
||||
(CharacterAbility::default(), CharacterAbility::default()),
|
||||
|(s, a)| {
|
||||
(
|
||||
extract_ability(&s.unwrap_or_default()),
|
||||
extract_ability(a.unwrap_or(&CharacterAbility::default())),
|
||||
)
|
||||
},
|
||||
);
|
||||
let (flamethrower, shockwave) = (
|
||||
extract_ability(AbilityInput::Secondary),
|
||||
extract_ability(AbilityInput::Auxiliary(0)),
|
||||
);
|
||||
let flamethrower_range = match flamethrower {
|
||||
CharacterAbility::BasicBeam { range, .. } => range,
|
||||
_ => 20.0_f32,
|
||||
|
@ -281,7 +281,15 @@ impl Sys {
|
||||
} => {
|
||||
presence.lossy_terrain_compression = lossy_terrain_compression;
|
||||
},
|
||||
_ => tracing::error!("not a client_in_game msg"),
|
||||
ClientGeneral::RequestCharacterList
|
||||
| ClientGeneral::CreateCharacter { .. }
|
||||
| ClientGeneral::DeleteCharacter(_)
|
||||
| ClientGeneral::Character(_)
|
||||
| ClientGeneral::Spectate
|
||||
| ClientGeneral::TerrainChunkRequest { .. }
|
||||
| ClientGeneral::ChatMsg(_)
|
||||
| ClientGeneral::Command(..)
|
||||
| ClientGeneral::Terminate => tracing::error!("not a client_in_game msg"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use common::{
|
||||
comp::{
|
||||
item::{tool::AbilityMap, MaterialStatManifest},
|
||||
Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Combo, Density,
|
||||
Energy, Group, Health, Inventory, Item, LightEmitter, Mass, MountState, Mounting, Ori,
|
||||
Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
|
||||
ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider,
|
||||
Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, MountState,
|
||||
Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
|
||||
},
|
||||
uid::Uid,
|
||||
};
|
||||
@ -44,6 +44,7 @@ pub struct TrackedComps<'a> {
|
||||
pub player: ReadStorage<'a, Player>,
|
||||
pub stats: ReadStorage<'a, Stats>,
|
||||
pub skill_set: ReadStorage<'a, SkillSet>,
|
||||
pub active_abilities: ReadStorage<'a, ActiveAbilities>,
|
||||
pub buffs: ReadStorage<'a, Buffs>,
|
||||
pub auras: ReadStorage<'a, Auras>,
|
||||
pub energy: ReadStorage<'a, Energy>,
|
||||
@ -91,6 +92,10 @@ impl<'a> TrackedComps<'a> {
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.active_abilities
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.buffs
|
||||
.get(entity)
|
||||
.cloned()
|
||||
@ -187,6 +192,7 @@ pub struct ReadTrackers<'a> {
|
||||
pub player: ReadExpect<'a, UpdateTracker<Player>>,
|
||||
pub stats: ReadExpect<'a, UpdateTracker<Stats>>,
|
||||
pub skill_set: ReadExpect<'a, UpdateTracker<SkillSet>>,
|
||||
pub active_abilities: ReadExpect<'a, UpdateTracker<ActiveAbilities>>,
|
||||
pub buffs: ReadExpect<'a, UpdateTracker<Buffs>>,
|
||||
pub auras: ReadExpect<'a, UpdateTracker<Auras>>,
|
||||
pub energy: ReadExpect<'a, UpdateTracker<Energy>>,
|
||||
@ -223,6 +229,12 @@ impl<'a> ReadTrackers<'a> {
|
||||
.with_component(&comps.uid, &*self.player, &comps.player, filter)
|
||||
.with_component(&comps.uid, &*self.stats, &comps.stats, filter)
|
||||
.with_component(&comps.uid, &*self.skill_set, &comps.skill_set, filter)
|
||||
.with_component(
|
||||
&comps.uid,
|
||||
&*self.active_abilities,
|
||||
&comps.active_abilities,
|
||||
filter,
|
||||
)
|
||||
.with_component(&comps.uid, &*self.buffs, &comps.buffs, filter)
|
||||
.with_component(&comps.uid, &*self.auras, &comps.auras, filter)
|
||||
.with_component(&comps.uid, &*self.energy, &comps.energy, filter)
|
||||
@ -266,6 +278,7 @@ pub struct WriteTrackers<'a> {
|
||||
player: WriteExpect<'a, UpdateTracker<Player>>,
|
||||
stats: WriteExpect<'a, UpdateTracker<Stats>>,
|
||||
skill_set: WriteExpect<'a, UpdateTracker<SkillSet>>,
|
||||
active_abilities: WriteExpect<'a, UpdateTracker<ActiveAbilities>>,
|
||||
buffs: WriteExpect<'a, UpdateTracker<Buffs>>,
|
||||
auras: WriteExpect<'a, UpdateTracker<Auras>>,
|
||||
energy: WriteExpect<'a, UpdateTracker<Energy>>,
|
||||
@ -296,6 +309,9 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
trackers.player.record_changes(&comps.player);
|
||||
trackers.stats.record_changes(&comps.stats);
|
||||
trackers.skill_set.record_changes(&comps.skill_set);
|
||||
trackers
|
||||
.active_abilities
|
||||
.record_changes(&comps.active_abilities);
|
||||
trackers.buffs.record_changes(&comps.buffs);
|
||||
trackers.auras.record_changes(&comps.auras);
|
||||
trackers.energy.record_changes(&comps.energy);
|
||||
@ -341,6 +357,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
log_counts!(player, "Players");
|
||||
log_counts!(stats, "Stats");
|
||||
log_counts!(skill_set, "SkillSet");
|
||||
log_counts!(active_abilities, "ActiveAbilities");
|
||||
log_counts!(energy, "Energies");
|
||||
log_counts!(combo, "Combos");
|
||||
log_vounts!(health, "Healths");
|
||||
@ -367,6 +384,7 @@ pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<Player>();
|
||||
world.register_tracker::<Stats>();
|
||||
world.register_tracker::<SkillSet>();
|
||||
world.register_tracker::<ActiveAbilities>();
|
||||
world.register_tracker::<Buffs>();
|
||||
world.register_tracker::<Auras>();
|
||||
world.register_tracker::<Energy>();
|
||||
|
@ -1,9 +1,4 @@
|
||||
use crate::hud::slots::EquipSlot;
|
||||
use common::comp::{
|
||||
item::{tool::Hands, ItemKind},
|
||||
slot::InvSlotId,
|
||||
Inventory,
|
||||
};
|
||||
use common::comp::slot::InvSlotId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@ -23,8 +18,7 @@ pub enum Slot {
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum SlotContents {
|
||||
Inventory(InvSlotId),
|
||||
Ability3,
|
||||
Ability4,
|
||||
Ability(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
@ -59,120 +53,39 @@ impl State {
|
||||
self.slots[slot as usize] = Some(SlotContents::Inventory(inventory_pos));
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
// Adds ability3 slot if it is missing and should be present
|
||||
// Removes if it is there and shouldn't be present
|
||||
pub fn maintain_ability3(&mut self, client: &client::Client) {
|
||||
// TODO: remove pending UI
|
||||
// Adds ability slots if missing and should be present
|
||||
// Removes ability slots if not there and shouldn't be present
|
||||
pub fn maintain_abilities(&mut self, client: &client::Client) {
|
||||
use specs::WorldExt;
|
||||
let inventories = client.state().ecs().read_storage::<Inventory>();
|
||||
let inventory = inventories.get(client.entity());
|
||||
let skill_sets = client
|
||||
if let Some(active_abilities) = client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<common::comp::SkillSet>();
|
||||
let skill_set = skill_sets.get(client.entity());
|
||||
|
||||
let hands =
|
||||
|equip_slot| match inventory.and_then(|i| i.equipped(equip_slot).map(|i| i.kind())) {
|
||||
Some(ItemKind::Tool(tool)) => Some(tool.hands),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let equip_slot = match (
|
||||
hands(EquipSlot::ActiveMainhand),
|
||||
hands(EquipSlot::ActiveOffhand),
|
||||
) {
|
||||
(Some(_), _) => Some(EquipSlot::ActiveMainhand),
|
||||
(_, Some(_)) => Some(EquipSlot::ActiveOffhand),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let should_be_present = if let (Some(inventory), Some(skill_set), Some(equip_slot)) =
|
||||
(inventory, skill_set, equip_slot)
|
||||
.read_storage::<common::comp::ActiveAbilities>()
|
||||
.get(client.entity())
|
||||
{
|
||||
inventory.equipped(equip_slot).map_or(false, |i| {
|
||||
i.item_config_expect()
|
||||
.abilities
|
||||
.abilities
|
||||
.get(0)
|
||||
.as_ref()
|
||||
.map_or(false, |(s, _)| s.map_or(true, |s| skill_set.has_skill(s)))
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if should_be_present {
|
||||
if !self
|
||||
.slots
|
||||
use common::comp::ability::AuxiliaryAbility;
|
||||
for ((i, ability), hotbar_slot) in active_abilities
|
||||
.abilities
|
||||
.iter()
|
||||
.any(|s| matches!(s, Some(SlotContents::Ability3)))
|
||||
.enumerate()
|
||||
.zip(self.slots.iter_mut())
|
||||
{
|
||||
self.slots[0] = Some(SlotContents::Ability3);
|
||||
if matches!(ability, AuxiliaryAbility::Empty) {
|
||||
if matches!(hotbar_slot, Some(SlotContents::Ability(_))) {
|
||||
// If ability is empty but hotbar shows an ability, clear it
|
||||
*hotbar_slot = None;
|
||||
}
|
||||
} else {
|
||||
// If an ability is not empty show it on the hotbar
|
||||
*hotbar_slot = Some(SlotContents::Ability(i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.slots
|
||||
.iter_mut()
|
||||
.filter(|s| matches!(s, Some(SlotContents::Ability3)))
|
||||
.for_each(|s| *s = None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maintain_ability4(&mut self, client: &client::Client) {
|
||||
use specs::WorldExt;
|
||||
let inventories = client.state().ecs().read_storage::<Inventory>();
|
||||
let inventory = inventories.get(client.entity());
|
||||
let skill_sets = client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<common::comp::SkillSet>();
|
||||
let skill_set = skill_sets.get(client.entity());
|
||||
let should_be_present = if let (Some(inventory), Some(skill_set)) = (inventory, skill_set) {
|
||||
let hands = |equip_slot| match inventory.equipped(equip_slot).map(|i| i.kind()) {
|
||||
Some(ItemKind::Tool(tool)) => Some(tool.hands),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
|
||||
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
|
||||
|
||||
let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) {
|
||||
(Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
|
||||
(Some(_), Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
|
||||
(Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
|
||||
(None, Some(_)) => (Some(EquipSlot::ActiveOffhand), 1),
|
||||
(_, _) => (None, 0),
|
||||
};
|
||||
|
||||
if let Some(equip_slot) = equip_slot {
|
||||
inventory.equipped(equip_slot).map_or(false, |i| {
|
||||
i.item_config_expect()
|
||||
.abilities
|
||||
.abilities
|
||||
.get(skill_index)
|
||||
.as_ref()
|
||||
.map_or(false, |(s, _)| s.map_or(true, |s| skill_set.has_skill(s)))
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if should_be_present {
|
||||
if !self
|
||||
.slots
|
||||
.iter()
|
||||
.any(|s| matches!(s, Some(SlotContents::Ability4)))
|
||||
{
|
||||
self.slots[1] = Some(SlotContents::Ability4);
|
||||
}
|
||||
} else {
|
||||
self.slots
|
||||
.iter_mut()
|
||||
.filter(|s| matches!(s, Some(SlotContents::Ability4)))
|
||||
.for_each(|s| *s = None)
|
||||
.filter(|slot| matches!(slot, Some(SlotContents::Ability(_))))
|
||||
.for_each(|slot| *slot = None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -512,8 +512,7 @@ pub enum Event {
|
||||
SortInventory,
|
||||
ChangeHotbarState(Box<HotbarState>),
|
||||
TradeAction(TradeAction),
|
||||
Ability3(bool),
|
||||
Ability4(bool),
|
||||
Ability(usize, bool),
|
||||
Logout,
|
||||
Quit,
|
||||
|
||||
@ -534,6 +533,8 @@ 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),
|
||||
|
||||
SettingsChange(SettingsChange),
|
||||
}
|
||||
@ -2639,6 +2640,7 @@ impl Hud {
|
||||
let inventories = ecs.read_storage::<comp::Inventory>();
|
||||
let energies = ecs.read_storage::<comp::Energy>();
|
||||
let skillsets = ecs.read_storage::<comp::SkillSet>();
|
||||
let active_abilities = ecs.read_storage::<comp::ActiveAbilities>();
|
||||
let character_states = ecs.read_storage::<comp::CharacterState>();
|
||||
let controllers = ecs.read_storage::<comp::Controller>();
|
||||
let bodies = ecs.read_storage::<comp::Body>();
|
||||
@ -2664,6 +2666,8 @@ impl Hud {
|
||||
Some(inventory),
|
||||
Some(energy),
|
||||
Some(skillset),
|
||||
Some(active_abilities),
|
||||
Some(body),
|
||||
Some(_character_state),
|
||||
Some(_controller),
|
||||
) = (
|
||||
@ -2671,6 +2675,8 @@ impl Hud {
|
||||
inventories.get(entity),
|
||||
energies.get(entity),
|
||||
skillsets.get(entity),
|
||||
active_abilities.get(entity),
|
||||
bodies.get(entity),
|
||||
character_states.get(entity),
|
||||
controllers.get(entity).map(|c| &c.inputs),
|
||||
) {
|
||||
@ -2685,6 +2691,8 @@ impl Hud {
|
||||
inventory,
|
||||
energy,
|
||||
skillset,
|
||||
active_abilities,
|
||||
body,
|
||||
//&character_state,
|
||||
self.pulse,
|
||||
//&controller,
|
||||
@ -3358,18 +3366,14 @@ impl Hud {
|
||||
}
|
||||
} else if let Hotbar(h) = from {
|
||||
// Used from hotbar
|
||||
self.hotbar.get(h).map(|s| {
|
||||
match s {
|
||||
hotbar::SlotContents::Inventory(i) => {
|
||||
events.push(Event::UseSlot {
|
||||
slot: comp::slot::Slot::Inventory(i),
|
||||
bypass_dialog: false,
|
||||
});
|
||||
},
|
||||
hotbar::SlotContents::Ability3 | hotbar::SlotContents::Ability4 => {
|
||||
}, /* Event::Ability3(true),
|
||||
* sticks */
|
||||
}
|
||||
self.hotbar.get(h).map(|s| match s {
|
||||
hotbar::SlotContents::Inventory(i) => {
|
||||
events.push(Event::UseSlot {
|
||||
slot: comp::slot::Slot::Inventory(i),
|
||||
bypass_dialog: false,
|
||||
});
|
||||
},
|
||||
hotbar::SlotContents::Ability(_) => {},
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -3496,8 +3500,7 @@ impl Hud {
|
||||
},
|
||||
}
|
||||
}
|
||||
self.hotbar.maintain_ability3(client);
|
||||
self.hotbar.maintain_ability4(client);
|
||||
self.hotbar.maintain_abilities(client);
|
||||
|
||||
events
|
||||
}
|
||||
@ -3577,8 +3580,7 @@ impl Hud {
|
||||
});
|
||||
}
|
||||
},
|
||||
hotbar::SlotContents::Ability3 => events.push(Event::Ability3(state)),
|
||||
hotbar::SlotContents::Ability4 => events.push(Event::Ability4(state)),
|
||||
hotbar::SlotContents::Ability(i) => events.push(Event::Ability(i, state)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use super::{
|
||||
hotbar,
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::ItemImgs,
|
||||
slots, BarNumbers, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
|
||||
slots, util, BarNumbers, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
|
||||
QUALITY_EPIC, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
|
||||
};
|
||||
use crate::{
|
||||
@ -21,12 +21,9 @@ use i18n::Localization;
|
||||
use client::{self, Client};
|
||||
use common::comp::{
|
||||
self,
|
||||
inventory::slot::EquipSlot,
|
||||
item::{
|
||||
tool::{Tool, ToolKind},
|
||||
Hands, Item, ItemDesc, ItemKind, MaterialStatManifest,
|
||||
},
|
||||
Energy, Health, Inventory, SkillSet,
|
||||
ability::AbilityInput,
|
||||
item::{ItemDesc, MaterialStatManifest},
|
||||
Ability, ActiveAbilities, Body, Energy, Health, Inventory, SkillSet,
|
||||
};
|
||||
use conrod_core::{
|
||||
color,
|
||||
@ -253,6 +250,8 @@ pub struct Skillbar<'a> {
|
||||
inventory: &'a Inventory,
|
||||
energy: &'a Energy,
|
||||
skillset: &'a SkillSet,
|
||||
active_abilities: &'a ActiveAbilities,
|
||||
body: &'a Body,
|
||||
// character_state: &'a CharacterState,
|
||||
// controller: &'a ControllerInputs,
|
||||
hotbar: &'a hotbar::State,
|
||||
@ -280,6 +279,8 @@ impl<'a> Skillbar<'a> {
|
||||
inventory: &'a Inventory,
|
||||
energy: &'a Energy,
|
||||
skillset: &'a SkillSet,
|
||||
active_abilities: &'a ActiveAbilities,
|
||||
body: &'a Body,
|
||||
// character_state: &'a CharacterState,
|
||||
pulse: f32,
|
||||
// controller: &'a ControllerInputs,
|
||||
@ -302,6 +303,8 @@ impl<'a> Skillbar<'a> {
|
||||
inventory,
|
||||
energy,
|
||||
skillset,
|
||||
active_abilities,
|
||||
body,
|
||||
common: widget::CommonBuilder::default(),
|
||||
// character_state,
|
||||
pulse,
|
||||
@ -511,7 +514,14 @@ impl<'a> Skillbar<'a> {
|
||||
let key_layout = &self.global_state.window.key_layout;
|
||||
|
||||
// TODO: avoid this
|
||||
let content_source = (self.hotbar, self.inventory, self.energy, self.skillset);
|
||||
let content_source = (
|
||||
self.hotbar,
|
||||
self.inventory,
|
||||
self.energy,
|
||||
self.skillset,
|
||||
self.active_abilities,
|
||||
self.body,
|
||||
);
|
||||
|
||||
let image_source = (self.item_imgs, self.imgs);
|
||||
let mut slot_maker = SlotMaker {
|
||||
@ -591,46 +601,16 @@ impl<'a> Skillbar<'a> {
|
||||
|
||||
// Helper
|
||||
let tooltip_text = |slot| {
|
||||
let (hotbar, inventory, ..) = content_source;
|
||||
let (hotbar, inventory, _, _, active_abilities, _) = content_source;
|
||||
hotbar.get(slot).and_then(|content| match content {
|
||||
hotbar::SlotContents::Inventory(i) => inventory
|
||||
.get(i)
|
||||
.map(|item| (item.name(), item.description())),
|
||||
hotbar::SlotContents::Ability3 => inventory
|
||||
.equipped(EquipSlot::ActiveMainhand)
|
||||
.map(|i| i.kind())
|
||||
.and_then(|kind| match kind {
|
||||
ItemKind::Tool(Tool { kind, .. }) => ability_description(kind),
|
||||
_ => None,
|
||||
}),
|
||||
hotbar::SlotContents::Ability4 => {
|
||||
let hands = |equip_slot| match inventory.equipped(equip_slot).map(|i| i.kind())
|
||||
{
|
||||
Some(ItemKind::Tool(tool)) => Some(tool.hands),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
|
||||
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
|
||||
|
||||
let equip_slot = match (active_tool_hands, second_tool_hands) {
|
||||
(Some(Hands::Two), _) => Some(EquipSlot::ActiveMainhand),
|
||||
(Some(_), Some(Hands::One)) => Some(EquipSlot::ActiveOffhand),
|
||||
(Some(Hands::One), _) => Some(EquipSlot::ActiveMainhand),
|
||||
(None, Some(_)) => Some(EquipSlot::ActiveOffhand),
|
||||
(_, _) => None,
|
||||
};
|
||||
|
||||
equip_slot.and_then(|equip_slot| {
|
||||
inventory
|
||||
.equipped(equip_slot)
|
||||
.map(|i| i.kind())
|
||||
.and_then(|kind| match kind {
|
||||
ItemKind::Tool(Tool { kind, .. }) => ability_description(kind),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
},
|
||||
hotbar::SlotContents::Ability(i) => active_abilities
|
||||
.abilities
|
||||
.get(i)
|
||||
.and_then(|a| Ability::from(*a).ability_id(Some(inventory)))
|
||||
.map(|id| util::ability_description(id)),
|
||||
})
|
||||
};
|
||||
|
||||
@ -699,30 +679,12 @@ impl<'a> Skillbar<'a> {
|
||||
.right_from(state.ids.slot5, slot_offset)
|
||||
.set(state.ids.m1_slot_bg, ui);
|
||||
|
||||
let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
|
||||
let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
|
||||
let primary_ability_id =
|
||||
Ability::from(self.active_abilities.primary).ability_id(Some(self.inventory));
|
||||
|
||||
let tool = match (
|
||||
active_tool.map(|(_, x)| x.hands),
|
||||
second_tool.map(|(_, x)| x.hands),
|
||||
) {
|
||||
(Some(_), _) => active_tool,
|
||||
(_, Some(_)) => second_tool,
|
||||
(_, _) => None,
|
||||
};
|
||||
|
||||
Button::image(match tool.map(|(_, t)| t.kind) {
|
||||
Some(ToolKind::Sword) => self.imgs.twohsword_m1,
|
||||
Some(ToolKind::Dagger) => self.imgs.onehdagger_m1,
|
||||
Some(ToolKind::Shield) => self.imgs.onehshield_m1,
|
||||
Some(ToolKind::Hammer) => self.imgs.twohhammer_m1,
|
||||
Some(ToolKind::Axe) => self.imgs.twohaxe_m1,
|
||||
Some(ToolKind::Bow) => self.imgs.bow_m1,
|
||||
Some(ToolKind::Sceptre) => self.imgs.skill_sceptre_lifesteal,
|
||||
Some(ToolKind::Staff) => self.imgs.fireball,
|
||||
Some(ToolKind::Debug) => self.imgs.flyingrod_m1,
|
||||
_ => self.imgs.nothing,
|
||||
}) // Insert Icon here
|
||||
Button::image(
|
||||
primary_ability_id.map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)),
|
||||
)
|
||||
.w_h(36.0, 36.0)
|
||||
.middle_of(state.ids.m1_slot_bg)
|
||||
.set(state.ids.m1_content, ui);
|
||||
@ -732,64 +694,31 @@ impl<'a> Skillbar<'a> {
|
||||
.right_from(state.ids.m1_slot_bg, slot_offset)
|
||||
.set(state.ids.m2_slot_bg, ui);
|
||||
|
||||
fn get_item_and_tool(
|
||||
inventory: &Inventory,
|
||||
equip_slot: EquipSlot,
|
||||
) -> Option<(&Item, &Tool)> {
|
||||
match inventory.equipped(equip_slot).map(|i| (i, i.kind())) {
|
||||
Some((i, ItemKind::Tool(tool))) => Some((i, tool)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
let secondary_ability_id =
|
||||
Ability::from(self.active_abilities.secondary).ability_id(Some(self.inventory));
|
||||
|
||||
let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
|
||||
let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
|
||||
|
||||
let tool = match (
|
||||
active_tool.map(|(_, x)| x.hands),
|
||||
second_tool.map(|(_, x)| x.hands),
|
||||
) {
|
||||
(Some(Hands::Two), _) => active_tool,
|
||||
(Some(_), Some(Hands::One)) => second_tool,
|
||||
(Some(Hands::One), _) => active_tool,
|
||||
(None, Some(_)) => second_tool,
|
||||
(_, _) => None,
|
||||
};
|
||||
|
||||
Button::image(match tool.map(|(_, t)| t.kind) {
|
||||
Some(ToolKind::Sword) => self.imgs.twohsword_m2,
|
||||
Some(ToolKind::Dagger) => self.imgs.onehdagger_m2,
|
||||
Some(ToolKind::Shield) => self.imgs.onehshield_m2,
|
||||
Some(ToolKind::Hammer) => self.imgs.hammergolf,
|
||||
Some(ToolKind::Axe) => self.imgs.axespin,
|
||||
Some(ToolKind::Bow) => self.imgs.bow_m2,
|
||||
Some(ToolKind::Sceptre) => self.imgs.skill_sceptre_heal,
|
||||
Some(ToolKind::Staff) => self.imgs.flamethrower,
|
||||
Some(ToolKind::Debug) => self.imgs.flyingrod_m2,
|
||||
_ => self.imgs.nothing,
|
||||
})
|
||||
Button::image(
|
||||
secondary_ability_id.map_or(self.imgs.nothing, |id| util::ability_image(self.imgs, id)),
|
||||
)
|
||||
.w_h(36.0, 36.0)
|
||||
.middle_of(state.ids.m2_slot_bg)
|
||||
.image_color(if let Some((item, tool)) = tool {
|
||||
.image_color(
|
||||
if self.energy.current()
|
||||
>= item
|
||||
.item_config_expect()
|
||||
.abilities
|
||||
.secondary
|
||||
.clone()
|
||||
.adjusted_by_skills(self.skillset, Some(tool.kind))
|
||||
.get_energy_cost()
|
||||
>= self
|
||||
.active_abilities
|
||||
.activate_ability(
|
||||
AbilityInput::Secondary,
|
||||
Some(self.inventory),
|
||||
self.skillset,
|
||||
Some(self.body),
|
||||
)
|
||||
.map_or(0.0, |(a, _)| a.get_energy_cost())
|
||||
{
|
||||
Color::Rgba(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::Rgba(0.3, 0.3, 0.3, 0.8)
|
||||
}
|
||||
} else {
|
||||
match tool.map(|(_, t)| t.kind) {
|
||||
None => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
||||
_ => Color::Rgba(1.0, 1.0, 1.0, 1.0),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
.set(state.ids.m2_content, ui);
|
||||
|
||||
// M1 and M2 icons
|
||||
@ -891,48 +820,3 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn ability_description(tool: &ToolKind) -> Option<(&str, &str)> {
|
||||
match tool {
|
||||
ToolKind::Hammer => Some((
|
||||
"Smash of Doom",
|
||||
"\n\
|
||||
An AOE attack with knockback.\n\
|
||||
Leaps to position of cursor.",
|
||||
)),
|
||||
ToolKind::Axe => Some((
|
||||
"Axe Jump",
|
||||
"\n\
|
||||
A jump with the slashing leap to position of cursor.",
|
||||
)),
|
||||
ToolKind::Staff => Some((
|
||||
"Ring of Fire",
|
||||
"\n\
|
||||
Ignites the ground with fiery shockwave.",
|
||||
)),
|
||||
ToolKind::Sword => Some((
|
||||
"Whirlwind",
|
||||
"\n\
|
||||
Move forward while spinning with your sword.",
|
||||
)),
|
||||
ToolKind::Bow => Some((
|
||||
"Burst",
|
||||
"\n\
|
||||
Launches a burst of arrows",
|
||||
)),
|
||||
ToolKind::Sceptre => Some((
|
||||
"Thorn Bulwark",
|
||||
"\n\
|
||||
Protects you and your group with thorns\n\
|
||||
for a short amount of time.",
|
||||
)),
|
||||
ToolKind::Debug => Some((
|
||||
"Possessing Arrow",
|
||||
"\n\
|
||||
Shoots a poisonous arrow.\n\
|
||||
Lets you control your target.",
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,12 @@ use super::{
|
||||
hotbar::{self, Slot as HotbarSlot},
|
||||
img_ids,
|
||||
item_imgs::{ItemImgs, ItemKey},
|
||||
util,
|
||||
};
|
||||
use crate::ui::slot::{self, SlotKey, SumSlot};
|
||||
use common::comp::{
|
||||
item::{
|
||||
tool::{Hands, ToolKind},
|
||||
ItemKind,
|
||||
},
|
||||
slot::InvSlotId,
|
||||
Energy, Inventory, SkillSet,
|
||||
ability::AbilityInput, slot::InvSlotId, Ability, ActiveAbilities, Body, Energy, Inventory,
|
||||
SkillSet,
|
||||
};
|
||||
use conrod_core::{image, Color};
|
||||
use specs::Entity as EcsEntity;
|
||||
@ -113,16 +110,17 @@ impl SlotKey<Inventory, ItemImgs> for TradeSlot {
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum HotbarImage {
|
||||
Item(ItemKey),
|
||||
FireAoe,
|
||||
SnakeArrow,
|
||||
SwordWhirlwind,
|
||||
HammerLeap,
|
||||
AxeLeapSlash,
|
||||
BowJumpBurst,
|
||||
SceptreAura,
|
||||
Ability(String),
|
||||
}
|
||||
|
||||
type HotbarSource<'a> = (&'a hotbar::State, &'a Inventory, &'a Energy, &'a SkillSet);
|
||||
type HotbarSource<'a> = (
|
||||
&'a hotbar::State,
|
||||
&'a Inventory,
|
||||
&'a Energy,
|
||||
&'a SkillSet,
|
||||
&'a ActiveAbilities,
|
||||
&'a Body,
|
||||
);
|
||||
type HotbarImageSource<'a> = (&'a ItemImgs, &'a img_ids::Imgs);
|
||||
|
||||
impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
@ -130,120 +128,50 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
|
||||
fn image_key(
|
||||
&self,
|
||||
(hotbar, inventory, energy, skillset): &HotbarSource<'a>,
|
||||
(hotbar, inventory, energy, skillset, active_abilities, body): &HotbarSource<'a>,
|
||||
) -> Option<(Self::ImageKey, Option<Color>)> {
|
||||
hotbar.get(*self).and_then(|contents| match contents {
|
||||
hotbar::SlotContents::Inventory(idx) => inventory
|
||||
.get(idx)
|
||||
.map(|item| HotbarImage::Item(item.into()))
|
||||
.map(|i| (i, None)),
|
||||
hotbar::SlotContents::Ability3 => {
|
||||
let hands = |equip_slot| match inventory.equipped(equip_slot).map(|i| i.kind()) {
|
||||
Some(ItemKind::Tool(tool)) => Some(tool.hands),
|
||||
_ => None,
|
||||
};
|
||||
hotbar::SlotContents::Ability(i) => {
|
||||
let ability_id = active_abilities
|
||||
.abilities
|
||||
.get(i)
|
||||
.and_then(|a| Ability::from(*a).ability_id(Some(inventory)));
|
||||
|
||||
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
|
||||
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
|
||||
|
||||
let equip_slot = match (active_tool_hands, second_tool_hands) {
|
||||
(Some(_), _) => Some(EquipSlot::ActiveMainhand),
|
||||
(None, Some(_)) => Some(EquipSlot::ActiveOffhand),
|
||||
(_, _) => None,
|
||||
};
|
||||
|
||||
let tool =
|
||||
equip_slot.and_then(|es| match inventory.equipped(es).map(|i| (i, i.kind())) {
|
||||
Some((item, ItemKind::Tool(tool))) => Some((item, tool)),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
tool.and_then(|(item, tool)| {
|
||||
hotbar_image(tool.kind).map(|i| {
|
||||
(
|
||||
i,
|
||||
if let Some(skill) =
|
||||
item.item_config_expect().abilities.abilities.get(0)
|
||||
{
|
||||
if energy.current()
|
||||
>= skill
|
||||
.1
|
||||
.clone()
|
||||
.adjusted_by_skills(skillset, Some(tool.kind))
|
||||
.get_energy_cost()
|
||||
{
|
||||
Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
||||
} else {
|
||||
Some(Color::Rgba(0.3, 0.3, 0.3, 0.8))
|
||||
}
|
||||
} else {
|
||||
Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
||||
},
|
||||
)
|
||||
ability_id
|
||||
.map(|id| HotbarImage::Ability(id.to_string()))
|
||||
.and_then(|image| {
|
||||
active_abilities
|
||||
.activate_ability(
|
||||
AbilityInput::Auxiliary(i),
|
||||
Some(inventory),
|
||||
skillset,
|
||||
Some(body),
|
||||
)
|
||||
.map(|(ability, _)| {
|
||||
(
|
||||
image,
|
||||
if energy.current() > ability.get_energy_cost() {
|
||||
Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
||||
} else {
|
||||
Some(Color::Rgba(0.3, 0.3, 0.3, 0.8))
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
hotbar::SlotContents::Ability4 => {
|
||||
let hands = |equip_slot| match inventory.equipped(equip_slot).map(|i| i.kind()) {
|
||||
Some(ItemKind::Tool(tool)) => Some(tool.hands),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
|
||||
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
|
||||
|
||||
let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) {
|
||||
(Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
|
||||
(Some(_), Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
|
||||
(Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
|
||||
(None, Some(_)) => (Some(EquipSlot::ActiveOffhand), 1),
|
||||
(_, _) => (None, 0),
|
||||
};
|
||||
|
||||
let tool =
|
||||
match equip_slot.and_then(|es| inventory.equipped(es).map(|i| (i, i.kind()))) {
|
||||
Some((item, ItemKind::Tool(tool))) => Some((item, tool)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
tool.and_then(|(item, tool)| {
|
||||
hotbar_image(tool.kind).map(|i| {
|
||||
(
|
||||
i,
|
||||
if let Some(skill) = item
|
||||
.item_config_expect()
|
||||
.abilities
|
||||
.abilities
|
||||
.get(skill_index)
|
||||
{
|
||||
if energy.current()
|
||||
>= skill
|
||||
.1
|
||||
.clone()
|
||||
.adjusted_by_skills(skillset, Some(tool.kind))
|
||||
.get_energy_cost()
|
||||
{
|
||||
Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
||||
} else {
|
||||
Some(Color::Rgba(0.3, 0.3, 0.3, 0.8))
|
||||
}
|
||||
} else {
|
||||
Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn amount(&self, (hotbar, inventory, _, _): &HotbarSource<'a>) -> Option<u32> {
|
||||
fn amount(&self, (hotbar, inventory, _, _, _, _): &HotbarSource<'a>) -> Option<u32> {
|
||||
hotbar
|
||||
.get(*self)
|
||||
.and_then(|content| match content {
|
||||
hotbar::SlotContents::Inventory(idx) => inventory.get(idx),
|
||||
hotbar::SlotContents::Ability3 => None,
|
||||
hotbar::SlotContents::Ability4 => None,
|
||||
hotbar::SlotContents::Ability(_) => None,
|
||||
})
|
||||
.map(|item| item.amount())
|
||||
.filter(|amount| *amount > 1)
|
||||
@ -255,13 +183,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
) -> Vec<image::Id> {
|
||||
match key {
|
||||
HotbarImage::Item(key) => item_imgs.img_ids_or_not_found_img(key.clone()),
|
||||
HotbarImage::SnakeArrow => vec![imgs.snake_arrow_0],
|
||||
HotbarImage::FireAoe => vec![imgs.fire_aoe],
|
||||
HotbarImage::SwordWhirlwind => vec![imgs.sword_whirlwind],
|
||||
HotbarImage::HammerLeap => vec![imgs.hammerleap],
|
||||
HotbarImage::AxeLeapSlash => vec![imgs.skill_axe_leap_slash],
|
||||
HotbarImage::BowJumpBurst => vec![imgs.skill_bow_jump_burst],
|
||||
HotbarImage::SceptreAura => vec![imgs.skill_sceptre_aura],
|
||||
HotbarImage::Ability(ability_id) => vec![util::ability_image(imgs, ability_id)],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -282,16 +204,3 @@ impl From<TradeSlot> for SlotKind {
|
||||
}
|
||||
|
||||
impl SumSlot for SlotKind {}
|
||||
|
||||
fn hotbar_image(tool: ToolKind) -> Option<HotbarImage> {
|
||||
match tool {
|
||||
ToolKind::Staff => Some(HotbarImage::FireAoe),
|
||||
ToolKind::Hammer => Some(HotbarImage::HammerLeap),
|
||||
ToolKind::Axe => Some(HotbarImage::AxeLeapSlash),
|
||||
ToolKind::Bow => Some(HotbarImage::BowJumpBurst),
|
||||
ToolKind::Debug => Some(HotbarImage::SnakeArrow),
|
||||
ToolKind::Sword => Some(HotbarImage::SwordWhirlwind),
|
||||
ToolKind::Sceptre => Some(HotbarImage::SceptreAura),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use super::img_ids;
|
||||
use common::{
|
||||
comp::{
|
||||
inventory::trade_pricing::TradePricing,
|
||||
@ -11,6 +12,7 @@ use common::{
|
||||
effect::Effect,
|
||||
trade::{Good, SitePrices},
|
||||
};
|
||||
use conrod_core::image;
|
||||
use i18n::Localization;
|
||||
use std::{borrow::Cow, fmt::Write};
|
||||
|
||||
@ -282,3 +284,99 @@ pub fn protec2string(stat: Protection) -> String {
|
||||
Protection::Invincible => "Inf".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id {
|
||||
match ability_id {
|
||||
// Debug stick
|
||||
"common.abilities.debug.forwardboost" => imgs.flyingrod_m1,
|
||||
"common.abilities.debug.upboost" => imgs.flyingrod_m2,
|
||||
"common.abilities.debug.possess" => imgs.snake_arrow_0,
|
||||
// Sword
|
||||
"common.abilities.sword.triplestrike" => imgs.twohsword_m1,
|
||||
"common.abilities.sword.dash" => imgs.twohsword_m2,
|
||||
"common.abilities.sword.spin" => imgs.sword_whirlwind,
|
||||
// Axe
|
||||
"common.abilities.axe.doublestrike" => imgs.twohaxe_m1,
|
||||
"common.abilities.axe.spin" => imgs.axespin,
|
||||
"common.abilities.axe.leap" => imgs.skill_axe_leap_slash,
|
||||
// Hammer
|
||||
"common.abilities.hammer.singlestrike" => imgs.twohhammer_m1,
|
||||
"common.abilities.hammer.charged" => imgs.hammergolf,
|
||||
"common.abilities.hammer.leap" => imgs.hammerleap,
|
||||
// Bow
|
||||
"common.abilities.bow.charged" => imgs.bow_m1,
|
||||
"common.abilities.bow.repeater" => imgs.bow_m2,
|
||||
"common.abilities.bow.shotgun" => imgs.skill_bow_jump_burst,
|
||||
// Staff
|
||||
"common.abilities.staff.firebomb" => imgs.fireball,
|
||||
"common.abilities.staff.flamethrower" => imgs.flamethrower,
|
||||
"common.abilities.staff.fireshockwave" => imgs.fire_aoe,
|
||||
// Sceptre
|
||||
"common.abilities.sceptre.lifestealbeam" => imgs.skill_sceptre_lifesteal,
|
||||
"common.abilities.sceptre.healingaura" => imgs.skill_sceptre_heal,
|
||||
"common.abilities.sceptre.wardingaura" => imgs.skill_sceptre_aura,
|
||||
// Shield
|
||||
"common.abilities.shield.tempbasic" => imgs.onehshield_m1,
|
||||
"common.abilities.shield.block" => imgs.onehshield_m2,
|
||||
// Dagger
|
||||
"common.abilities.dagger.tempbasic" => imgs.onehdagger_m1,
|
||||
|
||||
_ => imgs.not_found,
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn ability_description(ability_id: &str) -> (&str, &str) {
|
||||
match ability_id {
|
||||
// Debug stick
|
||||
"common.abilities.debug.possess" => (
|
||||
"Possessing Arrow",
|
||||
"\n\
|
||||
Shoots a poisonous arrow.\n\
|
||||
Lets you control your target.",
|
||||
),
|
||||
// Sword
|
||||
"common.abilities.sword.spin" => (
|
||||
"Whirlwind",
|
||||
"\n\
|
||||
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.",
|
||||
),
|
||||
// Hammer
|
||||
"common.abilities.hammer.leap" => (
|
||||
"Smash of Doom",
|
||||
"\n\
|
||||
An AOE attack with knockback.\n\
|
||||
Leaps to position of cursor.",
|
||||
),
|
||||
// Bow
|
||||
"common.abilities.bow.shotgun" => (
|
||||
"Burst",
|
||||
"\n\
|
||||
Launches a burst of arrows",
|
||||
),
|
||||
// Staff
|
||||
"common.abilities.staff.fireshockwave" => (
|
||||
"Ring of Fire",
|
||||
"\n\
|
||||
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.",
|
||||
),
|
||||
_ => (
|
||||
"Ability has no title",
|
||||
"\n\
|
||||
Ability has no description."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -1370,17 +1370,9 @@ impl PlayState for SessionState {
|
||||
HudEvent::TradeAction(action) => {
|
||||
self.client.borrow_mut().perform_trade_action(action);
|
||||
},
|
||||
HudEvent::Ability3(state) => {
|
||||
HudEvent::Ability(i, state) => {
|
||||
self.client.borrow_mut().handle_input(
|
||||
InputKind::Ability(0),
|
||||
state,
|
||||
default_select_pos,
|
||||
self.target_entity,
|
||||
);
|
||||
},
|
||||
HudEvent::Ability4(state) => {
|
||||
self.client.borrow_mut().handle_input(
|
||||
InputKind::Ability(1),
|
||||
InputKind::Ability(i),
|
||||
state,
|
||||
default_select_pos,
|
||||
self.target_entity,
|
||||
@ -1433,6 +1425,9 @@ impl PlayState for SessionState {
|
||||
HudEvent::AssignLeader(uid) => {
|
||||
self.client.borrow_mut().assign_group_leader(uid);
|
||||
},
|
||||
HudEvent::ChangeAbility(slot, new_ability) => {
|
||||
self.client.borrow_mut().change_ability(slot, new_ability);
|
||||
},
|
||||
HudEvent::SettingsChange(settings_change) => {
|
||||
settings_change.process(global_state, self);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user