Ability pool mostly functional.

This commit is contained in:
Sam 2021-11-09 12:56:07 -05:00
parent 7438252560
commit a8bec0280c
8 changed files with 206 additions and 67 deletions

View File

@ -15,6 +15,7 @@ sum_type! {
CanBuild(comp::CanBuild), CanBuild(comp::CanBuild),
Stats(comp::Stats), Stats(comp::Stats),
SkillSet(comp::SkillSet), SkillSet(comp::SkillSet),
AbilityPool(comp::AbilityPool),
Buffs(comp::Buffs), Buffs(comp::Buffs),
Auras(comp::Auras), Auras(comp::Auras),
Energy(comp::Energy), Energy(comp::Energy),
@ -50,6 +51,7 @@ sum_type! {
CanBuild(PhantomData<comp::CanBuild>), CanBuild(PhantomData<comp::CanBuild>),
Stats(PhantomData<comp::Stats>), Stats(PhantomData<comp::Stats>),
SkillSet(PhantomData<comp::SkillSet>), SkillSet(PhantomData<comp::SkillSet>),
AbilityPool(PhantomData<comp::AbilityPool>),
Buffs(PhantomData<comp::Buffs>), Buffs(PhantomData<comp::Buffs>),
Auras(PhantomData<comp::Auras>), Auras(PhantomData<comp::Auras>),
Energy(PhantomData<comp::Energy>), Energy(PhantomData<comp::Energy>),
@ -85,6 +87,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Stats(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::SkillSet(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::AbilityPool(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Buffs(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::Auras(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Energy(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::CanBuild(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Stats(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::SkillSet(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::AbilityPool(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Buffs(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::Auras(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Energy(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::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world), EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
EcsCompPhantom::SkillSet(_) => sync::handle_remove::<comp::SkillSet>(entity, world), EcsCompPhantom::SkillSet(_) => sync::handle_remove::<comp::SkillSet>(entity, world),
EcsCompPhantom::AbilityPool(_) => {
sync::handle_remove::<comp::AbilityPool>(entity, world)
},
EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world), EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world),
EcsCompPhantom::Auras(_) => sync::handle_remove::<comp::Auras>(entity, world), EcsCompPhantom::Auras(_) => sync::handle_remove::<comp::Auras>(entity, world),
EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world), EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world),

View File

@ -3,9 +3,17 @@ use crate::{
combat::{self, CombatEffect, DamageKind, Knockback}, combat::{self, CombatEffect, DamageKind, Knockback},
comp::{ comp::{
self, aura, beam, buff, self, aura, beam, buff,
inventory::item::tool::{Stats, ToolKind}, controller::InputKind,
inventory::{
item::{
tool::{Stats, ToolKind},
ItemKind,
},
slot::EquipSlot,
Inventory,
},
projectile::ProjectileConstructor, projectile::ProjectileConstructor,
skills::{self, SKILL_MODIFIERS}, skills::{self, Skill, SkillSet, SKILL_MODIFIERS},
Body, CharacterState, LightEmitter, StateUpdate, Body, CharacterState, LightEmitter, StateUpdate,
}, },
states::{ states::{
@ -16,8 +24,162 @@ use crate::{
terrain::SpriteKind, terrain::SpriteKind,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::{convert::TryFrom, time::Duration}; use std::{convert::TryFrom, time::Duration};
pub const MAX_ABILITIES: usize = 5;
// TODO: Should primary, secondary, and dodge be moved into here? Would
// essentially require custom enum that are only used for those (except maybe
// dodge if we make movement and have potentially differ based off of armor) but
// would also allow logic to be a bit more centralized
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AbilityPool {
primary: Ability,
secondary: Ability,
movement: Ability,
abilities: [Ability; MAX_ABILITIES],
}
impl Component for AbilityPool {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
}
impl Default for AbilityPool {
fn default() -> Self {
Self {
primary: Ability::ToolPrimary,
secondary: Ability::ToolSecondary,
movement: Ability::SpeciesMovement,
abilities: [Ability::Empty; MAX_ABILITIES],
}
}
}
impl AbilityPool {
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: Ability) {
if let Some(ability) = self.abilities.get_mut(slot) {
*ability = new_ability;
}
}
pub fn activate_ability(
&self,
input: InputKind,
inv: Option<&Inventory>,
skill_set: &SkillSet,
body: &Body,
// bool is from_offhand
) -> Option<(CharacterAbility, bool)> {
let ability = match input {
InputKind::Primary => Some(self.primary),
InputKind::Secondary => Some(self.secondary),
InputKind::Roll => Some(self.movement),
InputKind::Ability(index) => self.abilities.get(index).copied(),
_ => None,
}
.unwrap_or(Ability::Empty);
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 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.clone())
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false)),
Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand)
.map(|abilities| abilities.secondary.clone())
.map(|ability| (scale_ability(ability, EquipSlot::ActiveOffhand), true))
.or({
ability_set(EquipSlot::ActiveMainhand)
.map(|abilities| abilities.secondary.clone())
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false))
}),
Ability::SpeciesMovement => matches!(body, Body::Humanoid(_))
.then_some(CharacterAbility::default_roll())
.map(|ability| (ability.adjusted_by_skills(skill_set, None), false)),
Ability::MainWeaponAbility(index) => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| abilities.abilities.get(index).cloned())
.and_then(unlocked)
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false)),
Ability::OffWeaponAbility(index) => ability_set(EquipSlot::ActiveOffhand)
.and_then(|abilities| abilities.abilities.get(index).cloned())
.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(
inv: Option<&Inventory>,
skill_set: Option<&SkillSet>,
equip_slot: EquipSlot,
) -> Vec<Ability> {
let ability_from_slot = move |i| match equip_slot {
EquipSlot::ActiveMainhand => Ability::MainWeaponAbility(i),
EquipSlot::ActiveOffhand => Ability::OffWeaponAbility(i),
_ => Ability::Empty,
};
inv
.and_then(|inv| inv.equipped(equip_slot))
.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(ability_from_slot(i)))
// TODO: Let someone smarter than borrow checker remove collect
.collect()
}
let main_abilities = iter_unlocked_abilities(inv, skill_set, EquipSlot::ActiveMainhand);
let off_abilities = iter_unlocked_abilities(inv, skill_set, EquipSlot::ActiveOffhand);
for (i, ability) in
(0..MAX_ABILITIES).zip(main_abilities.iter().chain(off_abilities.iter()))
{
self.change_ability(i, *ability);
}
}
}
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub enum Ability {
ToolPrimary,
ToolSecondary,
SpeciesMovement,
MainWeaponAbility(usize),
OffWeaponAbility(usize),
Empty,
/* For future use
* ArmorAbility(usize), */
}
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum CharacterAbilityType { pub enum CharacterAbilityType {
BasicMelee, BasicMelee,
@ -899,7 +1061,7 @@ impl CharacterAbility {
#[warn(clippy::pedantic)] #[warn(clippy::pedantic)]
fn adjusted_by_mining_skills(&mut self, skillset: &skills::SkillSet) { fn adjusted_by_mining_skills(&mut self, skillset: &skills::SkillSet) {
use skills::{MiningSkill::Speed, Skill}; use skills::MiningSkill::Speed;
if let CharacterAbility::BasicMelee { if let CharacterAbility::BasicMelee {
ref mut buildup_duration, ref mut buildup_duration,
@ -921,8 +1083,6 @@ impl CharacterAbility {
#[warn(clippy::pedantic)] #[warn(clippy::pedantic)]
fn adjusted_by_general_skills(&mut self, skillset: &skills::SkillSet) { fn adjusted_by_general_skills(&mut self, skillset: &skills::SkillSet) {
use skills::Skill;
if let CharacterAbility::Roll { if let CharacterAbility::Roll {
ref mut energy_cost, ref mut energy_cost,
ref mut roll_strength, ref mut roll_strength,

View File

@ -47,7 +47,7 @@ pub mod visual;
// Reexports // Reexports
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use self::{ pub use self::{
ability::{CharacterAbility, CharacterAbilityType}, ability::{Ability, AbilityPool, CharacterAbility, CharacterAbilityType},
admin::{Admin, AdminRole}, admin::{Admin, AdminRole},
agent::{Agent, Alignment, Behavior, BehaviorCapability, BehaviorState, PidController}, agent::{Agent, Alignment, Behavior, BehaviorCapability, BehaviorState, PidController},
anchor::Anchor, anchor::Anchor,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
comp::{ comp::{
self, character_state::OutputEvents, item::MaterialStatManifest, Beam, Body, self, character_state::OutputEvents, item::MaterialStatManifest, AbilityPool, Beam, Body,
CharacterState, Combo, ControlAction, Controller, ControllerInputs, Density, Energy, CharacterState, Combo, ControlAction, Controller, ControllerInputs, Density, Energy,
Health, InputAttr, InputKind, Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState, Health, InputAttr, InputKind, Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState,
Pos, SkillSet, StateUpdate, Stats, Vel, Pos, SkillSet, StateUpdate, Stats, Vel,
@ -124,6 +124,7 @@ pub struct JoinData<'a> {
pub updater: &'a LazyUpdate, pub updater: &'a LazyUpdate,
pub stats: &'a Stats, pub stats: &'a Stats,
pub skill_set: &'a SkillSet, pub skill_set: &'a SkillSet,
pub ability_pool: &'a AbilityPool,
pub msm: &'a MaterialStatManifest, pub msm: &'a MaterialStatManifest,
pub combo: &'a Combo, pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
@ -149,6 +150,7 @@ pub struct JoinStruct<'a> {
pub beam: Option<&'a Beam>, pub beam: Option<&'a Beam>,
pub stat: &'a Stats, pub stat: &'a Stats,
pub skill_set: &'a SkillSet, pub skill_set: &'a SkillSet,
pub ability_pool: &'a AbilityPool,
pub combo: &'a Combo, pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid, pub terrain: &'a TerrainGrid,
@ -186,6 +188,7 @@ impl<'a> JoinData<'a> {
combo: j.combo, combo: j.combo,
alignment: j.alignment, alignment: j.alignment,
terrain: j.terrain, terrain: j.terrain,
ability_pool: j.ability_pool,
} }
} }
} }

View File

@ -821,61 +821,16 @@ pub fn handle_jump(
} }
fn handle_ability(data: &JoinData<'_>, update: &mut StateUpdate, input: InputKind) { fn handle_ability(data: &JoinData<'_>, update: &mut StateUpdate, input: InputKind) {
let hands = get_hands(data); if let Some((ability, from_offhand)) = data
.ability_pool
// Mouse1 and Skill1 always use the MainHand slot .activate_ability(input, data.inventory, data.skill_set, data.body)
let always_main_hand = matches!(input, InputKind::Primary | InputKind::Ability(0)); .filter(|(ability, _)| ability.requirements_paid(data, update))
let no_main_hand = hands.0.is_none(); {
// skill_index used to select ability for the AbilityKey::Skill2 input update.character = CharacterState::from((
let (equip_slot, skill_index) = if no_main_hand { &ability,
(Some(EquipSlot::ActiveOffhand), 1) AbilityInfo::from_input(data, from_offhand, input),
} else if always_main_hand { data,
(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))
{
update.character = CharacterState::from((
&ability,
AbilityInfo::from_input(
data,
matches!(equip_slot, EquipSlot::ActiveOffhand),
input,
),
data,
));
}
} }
} }

View File

@ -129,6 +129,7 @@ impl State {
ecs.register::<comp::Player>(); ecs.register::<comp::Player>();
ecs.register::<comp::Stats>(); ecs.register::<comp::Stats>();
ecs.register::<comp::SkillSet>(); ecs.register::<comp::SkillSet>();
ecs.register::<comp::AbilityPool>();
ecs.register::<comp::Buffs>(); ecs.register::<comp::Buffs>();
ecs.register::<comp::Auras>(); ecs.register::<comp::Auras>();
ecs.register::<comp::Energy>(); ecs.register::<comp::Energy>();

View File

@ -5,9 +5,10 @@ use specs::{
use common::{ use common::{
comp::{ comp::{
self, character_state::OutputEvents, inventory::item::MaterialStatManifest, Beam, Body, self, character_state::OutputEvents, inventory::item::MaterialStatManifest, AbilityPool,
CharacterState, Combo, Controller, Density, Energy, Health, Inventory, InventoryManip, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, Inventory,
Mass, Melee, Mounting, Ori, PhysicsState, Poise, Pos, SkillSet, StateUpdate, Stats, Vel, InventoryManip, Mass, Melee, Mounting, Ori, PhysicsState, Poise, Pos, SkillSet,
StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
outcome::Outcome, outcome::Outcome,
@ -38,6 +39,7 @@ pub struct ReadData<'a> {
mountings: ReadStorage<'a, Mounting>, mountings: ReadStorage<'a, Mounting>,
stats: ReadStorage<'a, Stats>, stats: ReadStorage<'a, Stats>,
skill_sets: ReadStorage<'a, SkillSet>, skill_sets: ReadStorage<'a, SkillSet>,
ability_pools: ReadStorage<'a, AbilityPool>,
msm: Read<'a, MaterialStatManifest>, msm: Read<'a, MaterialStatManifest>,
combos: ReadStorage<'a, Combo>, combos: ReadStorage<'a, Combo>,
alignments: ReadStorage<'a, comp::Alignment>, alignments: ReadStorage<'a, comp::Alignment>,
@ -107,7 +109,7 @@ impl<'a> System<'a> for Sys {
health, health,
body, body,
physics, physics,
(stat, skill_set), (stat, skill_set, ability_pool),
combo, combo,
) in ( ) in (
&read_data.entities, &read_data.entities,
@ -124,7 +126,11 @@ impl<'a> System<'a> for Sys {
read_data.healths.maybe(), read_data.healths.maybe(),
&read_data.bodies, &read_data.bodies,
&read_data.physics_states, &read_data.physics_states,
(&read_data.stats, &read_data.skill_sets), (
&read_data.stats,
&read_data.skill_sets,
&read_data.ability_pools,
),
&read_data.combos, &read_data.combos,
) )
.join() .join()
@ -181,6 +187,7 @@ impl<'a> System<'a> for Sys {
beam: read_data.beams.get(entity), beam: read_data.beams.get(entity),
stat, stat,
skill_set, skill_set,
ability_pool,
combo, combo,
alignment: read_data.alignments.get(entity), alignment: read_data.alignments.get(entity),
terrain: &read_data.terrain, terrain: &read_data.terrain,

View File

@ -215,6 +215,7 @@ impl StateExt for State {
.unwrap_or(0), .unwrap_or(0),
)) ))
.with(stats) .with(stats)
.with(comp::AbilityPool::new(Some(&inventory), Some(&skill_set)))
.with(skill_set) .with(skill_set)
.maybe_with(health) .maybe_with(health)
.with(poise) .with(poise)
@ -267,6 +268,7 @@ impl StateExt for State {
.with(comp::Energy::new(ship.into(), 0)) .with(comp::Energy::new(ship.into(), 0))
.with(comp::Stats::new("Airship".to_string())) .with(comp::Stats::new("Airship".to_string()))
.with(comp::SkillSet::default()) .with(comp::SkillSet::default())
.with(comp::AbilityPool::default())
.with(comp::Combo::default()); .with(comp::Combo::default());
if mountable { if mountable {
@ -504,6 +506,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::Energy::new(body, energy_level));
self.write_component_ignore_entity_dead(entity, comp::Poise::new(body)); 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, stats);
self.write_component_ignore_entity_dead(
entity,
comp::AbilityPool::new(Some(&inventory), Some(&skill_set)),
);
self.write_component_ignore_entity_dead(entity, 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(entity, inventory);
self.write_component_ignore_entity_dead( self.write_component_ignore_entity_dead(