Stances change secondary abilities now. Contextual abilities try to fallback to no context if skill not owned for contextual ability.

This commit is contained in:
Sam 2022-11-06 21:20:44 -05:00
parent b376228d45
commit 7ec9a7677f
7 changed files with 129 additions and 65 deletions

View File

@ -13,7 +13,17 @@
Stance(Sword(Mobility)): (Some(Sword(MobilityStance)), "common.abilities.sword.mobility_combo"),
Stance(Sword(Reaching)): (Some(Sword(ReachingStance)), "common.abilities.sword.reaching_combo"),
}),
secondary: Simple(None, "common.abilities.sword.balanced_thrust"),
secondary: Contextualized({
None: (None, "common.abilities.sword.balanced_thrust"),
Stance(Sword(Offensive)): (Some(Sword(OffensiveAdvance)), "common.abilities.sword.offensive_advance"),
Stance(Sword(Defensive)): (Some(Sword(DefensiveRetreat)), "common.abilities.sword.defensive_retreat"),
Stance(Sword(Mobility)): (Some(Sword(MobilityFeint)), "common.abilities.sword.mobility_feint"),
Stance(Sword(Heavy)): (Some(Sword(HeavyPommelStrike)), "common.abilities.sword.heavy_pommelstrike"),
Stance(Sword(Parrying)): (Some(Sword(ParryingParry)), "common.abilities.sword.parrying_parry"),
Stance(Sword(Reaching)): (Some(Sword(ReachingSkewer)), "common.abilities.sword.reaching_skewer"),
Stance(Sword(Crippling)): (Some(Sword(CripplingGouge)), "common.abilities.sword.crippling_gouge"),
Stance(Sword(Cleaving)): (Some(Sword(CleavingSpin)), "common.abilities.sword.cleaving_spin"),
}),
abilities: [
Simple(Some(Sword(OffensiveStance)), "common.abilities.sword.offensive_stance"),
Simple(Some(Sword(CripplingStance)), "common.abilities.sword.crippling_stance"),
@ -35,24 +45,13 @@
}),
// Movementy ones
Contextualized({
Stance(Sword(Offensive)): (Some(Sword(OffensiveAdvance)), "common.abilities.sword.offensive_advance"),
Stance(Sword(Crippling)): (Some(Sword(CripplingStrike)), "common.abilities.sword.crippling_strike"),
Stance(Sword(Cleaving)): (Some(Sword(CleavingDive)), "common.abilities.sword.cleaving_dive"),
Stance(Sword(Defensive)): (Some(Sword(DefensiveRetreat)), "common.abilities.sword.defensive_retreat"),
Stance(Sword(Parrying)): (Some(Sword(ParryingRiposte)), "common.abilities.sword.parrying_riposte"),
Stance(Sword(Heavy)): (Some(Sword(HeavyFortitude)), "common.abilities.sword.heavy_fortitude"),
Stance(Sword(Mobility)): (Some(Sword(MobilityFeint)), "common.abilities.sword.mobility_feint"),
Stance(Sword(Reaching)): (Some(Sword(ReachingCharge)), "common.abilities.sword.reaching_charge"),
}),
// Utilityy ones
Contextualized({
Stance(Sword(Crippling)): (Some(Sword(CripplingGouge)), "common.abilities.sword.crippling_gouge"),
Stance(Sword(Cleaving)): (Some(Sword(CleavingSpin)), "common.abilities.sword.cleaving_spin"),
Stance(Sword(Defensive)): (Some(Sword(DefensiveBulwark)), "common.abilities.sword.defensive_bulwark"),
Stance(Sword(Parrying)): (Some(Sword(ParryingParry)), "common.abilities.sword.parrying_parry"),
Stance(Sword(Heavy)): (Some(Sword(HeavyPommelStrike)), "common.abilities.sword.heavy_pommelstrike"),
Stance(Sword(Mobility)): (Some(Sword(MobilityAgility)), "common.abilities.sword.mobility_agility"),
Stance(Sword(Reaching)): (Some(Sword(ReachingSkewer)), "common.abilities.sword.reaching_skewer"),
}),
],
),

View File

@ -6,7 +6,7 @@ use crate::{
character_state::AttackImmunities,
inventory::{
item::{
tool::{AbilityContext, AbilityItem, AbilityKind, Stats, ToolKind},
tool::{AbilityContext, AbilityKind, Stats, ToolKind},
ItemKind,
},
slot::EquipSlot,
@ -163,41 +163,46 @@ impl ActiveAbilities {
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)
.and_then(|abilities| abilities.primary(context).map(unwrap_ability))
.and_then(unlocked)
.and_then(|abilities| {
abilities
.primary(Some(skill_set), context)
.map(|a| a.ability.clone())
})
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false)),
Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand)
.and_then(|abilities| abilities.secondary(context).map(unwrap_ability))
.and_then(unlocked)
.and_then(|abilities| {
abilities
.secondary(Some(skill_set), context)
.map(|a| a.ability.clone())
})
.map(|ability| (scale_ability(ability, EquipSlot::ActiveOffhand), true))
.or_else(|| {
ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| abilities.secondary(context).map(unwrap_ability))
.and_then(unlocked)
.and_then(|abilities| {
abilities
.secondary(Some(skill_set), context)
.map(|a| a.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.auxiliary(index, context).map(unwrap_ability))
.and_then(unlocked)
.and_then(|abilities| {
abilities
.auxiliary(index, Some(skill_set), context)
.map(|a| a.ability.clone())
})
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), false)),
Ability::OffWeaponAux(index) => ability_set(EquipSlot::ActiveOffhand)
.and_then(|abilities| abilities.auxiliary(index, context).map(unwrap_ability))
.and_then(unlocked)
.and_then(|abilities| {
abilities
.auxiliary(index, Some(skill_set), context)
.map(|a| a.ability.clone())
})
.map(|ability| (scale_ability(ability, EquipSlot::ActiveOffhand), true)),
Ability::Empty => None,
}
@ -260,7 +265,12 @@ pub enum Ability {
}
impl Ability {
pub fn ability_id(self, inv: Option<&Inventory>, context: AbilityContext) -> Option<&str> {
pub fn ability_id<'a>(
self,
inv: Option<&'a Inventory>,
skillset: Option<&'a SkillSet>,
context: AbilityContext,
) -> Option<&'a str> {
let ability_set = |equip_slot| {
inv.and_then(|inv| inv.equipped(equip_slot))
.map(|i| &i.item_config_expect().abilities)
@ -287,20 +297,26 @@ impl Ability {
match self {
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| abilities.primary(context).map(|(_, a)| a.id.as_str())),
.and_then(|abilities| abilities.primary(skillset, context).map(|a| a.id.as_str())),
Ability::ToolSecondary => ability_set(EquipSlot::ActiveOffhand)
.and_then(|abilities| abilities.secondary(context).map(|(_, a)| a.id.as_str()))
.and_then(|abilities| {
abilities
.secondary(skillset, context)
.map(|a| a.id.as_str())
})
.or_else(|| {
ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| {
abilities.secondary(context).map(|(_, a)| a.id.as_str())
abilities
.secondary(skillset, context)
.map(|a| a.id.as_str())
})
}),
Ability::SpeciesMovement => None, // TODO: Make not None
Ability::MainWeaponAux(index) => {
ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| {
abilities
.auxiliary(index, context)
.map(|(_, ability)| ability.id.as_str())
.auxiliary(index, skillset, context)
.map(|a| a.id.as_str())
.or_else(|| {
contextual_id(abilities.abilities.get(index), EquipSlot::ActiveMainhand)
})
@ -309,8 +325,8 @@ impl Ability {
Ability::OffWeaponAux(index) => {
ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| {
abilities
.auxiliary(index, context)
.map(|(_, ability)| ability.id.as_str())
.auxiliary(index, skillset, context)
.map(|a| a.id.as_str())
.or_else(|| {
contextual_id(abilities.abilities.get(index), EquipSlot::ActiveOffhand)
})

View File

@ -3,7 +3,7 @@
use crate::{
assets::{self, Asset, AssetExt, AssetHandle},
comp::{ability::Stance, skills::Skill, CharacterAbility},
comp::{ability::Stance, skills::Skill, CharacterAbility, SkillSet},
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
@ -317,10 +317,24 @@ impl<T> AbilityKind<T> {
}
}
pub fn ability(&self, context: AbilityContext) -> Option<(Option<Skill>, &T)> {
pub fn ability(&self, skillset: Option<&SkillSet>, context: AbilityContext) -> Option<&T> {
let unlocked = |s: Option<Skill>, a| {
// If there is a skill requirement and the skillset does not contain the
// required skill, return None
s.map_or(true, |s| skillset.map_or(false, |ss| ss.has_skill(s)))
.then_some(a)
};
match self {
AbilityKind::Simple(s, a) => Some((*s, a)),
AbilityKind::Contextualized(abilities) => abilities.get(&context).map(|(s, a)| (*s, a)),
AbilityKind::Simple(s, a) => unlocked(*s, a),
AbilityKind::Contextualized(abilities) => abilities
.get(&context)
.and_then(|(s, a)| unlocked(*s, a))
.or_else(|| {
abilities
.get(&AbilityContext::None)
.and_then(|(s, a)| unlocked(*s, a))
}),
}
}
}
@ -368,16 +382,23 @@ impl<T> AbilitySet<T> {
}
}
pub fn primary(&self, context: AbilityContext) -> Option<(Option<Skill>, &T)> {
self.primary.ability(context)
pub fn primary(&self, skillset: Option<&SkillSet>, context: AbilityContext) -> Option<&T> {
self.primary.ability(skillset, context)
}
pub fn secondary(&self, context: AbilityContext) -> Option<(Option<Skill>, &T)> {
self.secondary.ability(context)
pub fn secondary(&self, skillset: Option<&SkillSet>, context: AbilityContext) -> Option<&T> {
self.secondary.ability(skillset, context)
}
pub fn auxiliary(&self, index: usize, context: AbilityContext) -> Option<(Option<Skill>, &T)> {
self.abilities.get(index).and_then(|a| a.ability(context))
pub fn auxiliary(
&self,
index: usize,
skillset: Option<&SkillSet>,
context: AbilityContext,
) -> Option<&T> {
self.abilities
.get(index)
.and_then(|a| a.ability(skillset, context))
}
}

View File

@ -869,7 +869,7 @@ impl<'a> Widget for Diary<'a> {
Some(self.inventory),
Some(self.skill_set),
)
.ability_id(Some(self.inventory), self.context);
.ability_id(Some(self.inventory), Some(self.skill_set), self.context);
let (ability_title, ability_desc) = if let Some(ability_id) = ability_id {
util::ability_description(ability_id, self.localized_strings)
} else {
@ -951,7 +951,11 @@ impl<'a> Widget for Diary<'a> {
.map(AuxiliaryAbility::MainWeapon)
.map(|a| {
(
Ability::from(a).ability_id(Some(self.inventory), self.context),
Ability::from(a).ability_id(
Some(self.inventory),
Some(self.skill_set),
self.context,
),
a,
)
});
@ -963,7 +967,11 @@ impl<'a> Widget for Diary<'a> {
.map(AuxiliaryAbility::OffWeapon)
.map(|a| {
(
Ability::from(a).ability_id(Some(self.inventory), self.context),
Ability::from(a).ability_id(
Some(self.inventory),
Some(self.skill_set),
self.context,
),
a,
)
});

View File

@ -1011,7 +1011,13 @@ impl<'a> Skillbar<'a> {
.and_then(|a| {
a.auxiliary_set(Some(inventory), Some(skill_set))
.get(i)
.and_then(|a| Ability::from(*a).ability_id(Some(inventory), context))
.and_then(|a| {
Ability::from(*a).ability_id(
Some(inventory),
Some(skill_set),
context,
)
})
})
.map(|id| util::ability_description(id, self.localized_strings)),
})
@ -1080,9 +1086,13 @@ impl<'a> Skillbar<'a> {
.right_from(state.ids.slot5, slot_offset)
.set(state.ids.m1_slot_bg, ui);
let primary_ability_id = self
.active_abilities
.and_then(|a| Ability::from(a.primary).ability_id(Some(self.inventory), self.context));
let primary_ability_id = self.active_abilities.and_then(|a| {
Ability::from(a.primary).ability_id(
Some(self.inventory),
Some(self.skillset),
self.context,
)
});
let (primary_ability_title, primary_ability_desc) =
util::ability_description(primary_ability_id.unwrap_or(""), self.localized_strings);
@ -1107,7 +1117,11 @@ impl<'a> Skillbar<'a> {
.set(state.ids.m2_slot_bg, ui);
let secondary_ability_id = self.active_abilities.and_then(|a| {
Ability::from(a.secondary).ability_id(Some(self.inventory), self.context)
Ability::from(a.secondary).ability_id(
Some(self.inventory),
Some(self.skillset),
self.context,
)
});
let (secondary_ability_title, secondary_ability_desc) =

View File

@ -153,7 +153,9 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
let ability_id = active_abilities.and_then(|a| {
a.auxiliary_set(Some(inventory), Some(skillset))
.get(i)
.and_then(|a| Ability::from(*a).ability_id(Some(inventory), *context))
.and_then(|a| {
Ability::from(*a).ability_id(Some(inventory), Some(skillset), *context)
})
});
ability_id
@ -236,8 +238,10 @@ impl<'a> SlotKey<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
Some(inventory),
Some(skillset),
)
.ability_id(Some(inventory), *context),
Self::Ability(ability) => Ability::from(*ability).ability_id(Some(inventory), *context),
.ability_id(Some(inventory), Some(skillset), *context),
Self::Ability(ability) => {
Ability::from(*ability).ability_id(Some(inventory), Some(skillset), *context)
},
};
ability_id.map(|id| (String::from(id), None))

View File

@ -34,7 +34,8 @@ use common::{
inventory::slot::EquipSlot,
item::{tool::AbilityContext, Hands, ItemKind, ToolKind},
Body, CharacterState, Collider, Controller, Health, Inventory, Item, ItemKey, Last,
LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, Stance, Vel,
LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, SkillSet, Stance,
Vel,
},
link::Is,
mounting::Rider,
@ -747,7 +748,7 @@ impl FigureMgr {
item,
light_emitter,
is_rider,
(collider, stance),
(collider, stance, skillset),
),
) in (
&ecs.entities(),
@ -768,6 +769,7 @@ impl FigureMgr {
(
ecs.read_storage::<Collider>().maybe(),
ecs.read_storage::<Stance>().maybe(),
ecs.read_storage::<SkillSet>().maybe(),
),
)
.join()
@ -925,7 +927,7 @@ impl FigureMgr {
let ability_id = character.and_then(|c| {
c.ability_info()
.and_then(|a| a.ability)
.and_then(|a| a.ability_id(inventory, context))
.and_then(|a| a.ability_id(inventory, skillset, context))
});
let move_dir = {