Added stance component that persists even after sheathing weapon (does not yet work with M1 replacement).

This commit is contained in:
Sam
2022-10-30 00:58:16 -04:00
parent d0a46ed82b
commit 9875008efa
56 changed files with 198 additions and 266 deletions

View File

@ -15,34 +15,34 @@
Simple(Some(Sword(ReachingCombo)), "common.abilities.sword.reaching_combo"), Simple(Some(Sword(ReachingCombo)), "common.abilities.sword.reaching_combo"),
// Damagey ones // Damagey ones
Contextualized({ Contextualized({
Sword(Balanced): (Some(Sword(BalancedFinisher)), "common.abilities.sword.balanced_finisher"), Stance(None): (Some(Sword(BalancedFinisher)), "common.abilities.sword.balanced_finisher"),
Sword(Offensive): (Some(Sword(OffensiveFinisher)), "common.abilities.sword.offensive_finisher"), Stance(Sword(Offensive)): (Some(Sword(OffensiveFinisher)), "common.abilities.sword.offensive_finisher"),
Sword(Crippling): (Some(Sword(CripplingFinisher)), "common.abilities.sword.crippling_finisher"), Stance(Sword(Crippling)): (Some(Sword(CripplingFinisher)), "common.abilities.sword.crippling_finisher"),
Sword(Cleaving): (Some(Sword(CleavingFinisher)), "common.abilities.sword.cleaving_finisher"), Stance(Sword(Cleaving)): (Some(Sword(CleavingFinisher)), "common.abilities.sword.cleaving_finisher"),
Sword(Parrying): (Some(Sword(ParryingCounter)), "common.abilities.sword.parrying_counter"), Stance(Sword(Parrying)): (Some(Sword(ParryingCounter)), "common.abilities.sword.parrying_counter"),
Sword(Heavy): (Some(Sword(HeavyFinisher)), "common.abilities.sword.heavy_finisher"), Stance(Sword(Heavy)): (Some(Sword(HeavyFinisher)), "common.abilities.sword.heavy_finisher"),
Sword(Reaching): (Some(Sword(ReachingFlurry)), "common.abilities.sword.reaching_flurry"), Stance(Sword(Reaching)): (Some(Sword(ReachingFlurry)), "common.abilities.sword.reaching_flurry"),
}), }),
// Movementy ones // Movementy ones
Contextualized({ Contextualized({
Sword(Offensive): (Some(Sword(OffensiveAdvance)), "common.abilities.sword.offensive_advance"), Stance(Sword(Offensive)): (Some(Sword(OffensiveAdvance)), "common.abilities.sword.offensive_advance"),
Sword(Crippling): (Some(Sword(CripplingStrike)), "common.abilities.sword.crippling_strike"), Stance(Sword(Crippling)): (Some(Sword(CripplingStrike)), "common.abilities.sword.crippling_strike"),
Sword(Cleaving): (Some(Sword(CleavingDive)), "common.abilities.sword.cleaving_dive"), Stance(Sword(Cleaving)): (Some(Sword(CleavingDive)), "common.abilities.sword.cleaving_dive"),
Sword(Defensive): (Some(Sword(DefensiveRetreat)), "common.abilities.sword.defensive_retreat"), Stance(Sword(Defensive)): (Some(Sword(DefensiveRetreat)), "common.abilities.sword.defensive_retreat"),
Sword(Parrying): (Some(Sword(ParryingRiposte)), "common.abilities.sword.parrying_riposte"), Stance(Sword(Parrying)): (Some(Sword(ParryingRiposte)), "common.abilities.sword.parrying_riposte"),
Sword(Heavy): (Some(Sword(HeavyFortitude)), "common.abilities.sword.heavy_fortitude"), Stance(Sword(Heavy)): (Some(Sword(HeavyFortitude)), "common.abilities.sword.heavy_fortitude"),
Sword(Mobility): (Some(Sword(MobilityFeint)), "common.abilities.sword.mobility_feint"), Stance(Sword(Mobility)): (Some(Sword(MobilityFeint)), "common.abilities.sword.mobility_feint"),
Sword(Reaching): (Some(Sword(ReachingCharge)), "common.abilities.sword.reaching_charge"), Stance(Sword(Reaching)): (Some(Sword(ReachingCharge)), "common.abilities.sword.reaching_charge"),
}), }),
// Utilityy ones // Utilityy ones
Contextualized({ Contextualized({
Sword(Crippling): (Some(Sword(CripplingGouge)), "common.abilities.sword.crippling_gouge"), Stance(Sword(Crippling)): (Some(Sword(CripplingGouge)), "common.abilities.sword.crippling_gouge"),
Sword(Cleaving): (Some(Sword(CleavingSpin)), "common.abilities.sword.cleaving_spin"), Stance(Sword(Cleaving)): (Some(Sword(CleavingSpin)), "common.abilities.sword.cleaving_spin"),
Sword(Defensive): (Some(Sword(DefensiveBulwark)), "common.abilities.sword.defensive_bulwark"), Stance(Sword(Defensive)): (Some(Sword(DefensiveBulwark)), "common.abilities.sword.defensive_bulwark"),
Sword(Parrying): (Some(Sword(ParryingParry)), "common.abilities.sword.parrying_parry"), Stance(Sword(Parrying)): (Some(Sword(ParryingParry)), "common.abilities.sword.parrying_parry"),
Sword(Heavy): (Some(Sword(HeavyPommelStrike)), "common.abilities.sword.heavy_pommelstrike"), Stance(Sword(Heavy)): (Some(Sword(HeavyPommelStrike)), "common.abilities.sword.heavy_pommelstrike"),
Sword(Mobility): (Some(Sword(MobilityAgility)), "common.abilities.sword.mobility_agility"), Stance(Sword(Mobility)): (Some(Sword(MobilityAgility)), "common.abilities.sword.mobility_agility"),
Sword(Reaching): (Some(Sword(ReachingSkewer)), "common.abilities.sword.reaching_skewer"), Stance(Sword(Reaching)): (Some(Sword(ReachingSkewer)), "common.abilities.sword.reaching_skewer"),
}), }),
], ],
), ),

View File

@ -37,7 +37,4 @@ ComboMelee2(
], ],
is_stance: true, is_stance: true,
energy_cost_per_strike: 0, energy_cost_per_strike: 0,
meta: (
kind: Some(Sword(Balanced)),
),
) )

View File

@ -14,7 +14,4 @@ FinisherMelee(
angle: 15.0, angle: 15.0,
), ),
minimum_combo: 10, minimum_combo: 10,
meta: (
kind: Some(Sword(Balanced)),
),
) )

View File

@ -21,7 +21,4 @@ ChargedMelee(
swing_duration: 0.1, swing_duration: 0.1,
hit_timing: 0.2, hit_timing: 0.2,
recover_duration: 0.2, recover_duration: 0.2,
meta: (
kind: Some(Sword(Balanced)),
),
) )

View File

@ -48,8 +48,6 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Cleaving)),
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: (
kind: Some(Sword(Cleaving)),
),
) )

View File

@ -21,7 +21,4 @@ DiveMelee(
angle: 15.0, angle: 15.0,
multi_target: Some(Normal), multi_target: Some(Normal),
), ),
meta: (
kind: Some(Sword(Cleaving)),
),
) )

View File

@ -15,7 +15,4 @@ FinisherMelee(
multi_target: Some(Scaling(0.5)), multi_target: Some(Scaling(0.5)),
), ),
minimum_combo: 10, minimum_combo: 10,
meta: (
kind: Some(Sword(Cleaving)),
),
) )

View File

@ -21,7 +21,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 20, energy_cost_per_strike: 20,
meta: (
kind: Some(Sword(Cleaving)),
),
) )

View File

@ -48,8 +48,6 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Crippling)),
energy_cost_per_strike: 4, energy_cost_per_strike: 4,
meta: (
kind: Some(Sword(Crippling)),
),
) )

View File

@ -24,7 +24,4 @@ FinisherMelee(
kind: Sqrt, kind: Sqrt,
)), )),
minimum_combo: 10, minimum_combo: 10,
meta: (
kind: Some(Sword(Crippling)),
),
) )

View File

@ -26,7 +26,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 25, energy_cost_per_strike: 25,
meta: (
kind: Some(Sword(Crippling)),
),
) )

View File

@ -26,7 +26,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 25, energy_cost_per_strike: 25,
meta: (
kind: Some(Sword(Crippling)),
),
) )

View File

@ -6,7 +6,4 @@ SelfBuff(
buff_strength: 0.4, buff_strength: 0.4,
buff_duration: Some(30.0), buff_duration: Some(30.0),
energy_cost: 40, energy_cost: 40,
meta: (
kind: Some(Sword(Defensive)),
),
) )

View File

@ -36,9 +36,9 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Defensive)),
energy_cost_per_strike: 2, energy_cost_per_strike: 2,
meta: ( meta: (
kind: Some(Sword(Defensive)),
capabilities: ( capabilities: (
// Blocking can interrupt attack // Blocking can interrupt attack
bits: 0b00000010, bits: 0b00000010,

View File

@ -25,7 +25,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 10, energy_cost_per_strike: 10,
meta: (
kind: Some(Sword(Defensive)),
),
) )

View File

@ -36,9 +36,9 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Heavy)),
energy_cost_per_strike: 4, energy_cost_per_strike: 4,
meta: ( meta: (
kind: Some(Sword(Heavy)),
capabilities: ( capabilities: (
// Poise and knockback resistant during attack // Poise and knockback resistant during attack
bits: 0b00011000, bits: 0b00011000,

View File

@ -24,7 +24,4 @@ FinisherMelee(
kind: Linear, kind: Linear,
)), )),
minimum_combo: 10, minimum_combo: 10,
meta: (
kind: Some(Sword(Heavy)),
),
) )

View File

@ -6,7 +6,4 @@ SelfBuff(
buff_strength: 0.5, buff_strength: 0.5,
buff_duration: Some(30.0), buff_duration: Some(30.0),
energy_cost: 40, energy_cost: 40,
meta: (
kind: Some(Sword(Heavy)),
),
) )

View File

@ -20,7 +20,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 15, energy_cost_per_strike: 15,
meta: (
kind: Some(Sword(Heavy)),
),
) )

View File

@ -6,7 +6,4 @@ SelfBuff(
buff_strength: 0.2, buff_strength: 0.2,
buff_duration: Some(20.0), buff_duration: Some(20.0),
energy_cost: 40, energy_cost: 40,
meta: (
kind: Some(Sword(Mobility)),
),
) )

View File

@ -70,9 +70,9 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Mobility)),
energy_cost_per_strike: 2, energy_cost_per_strike: 2,
meta: ( meta: (
kind: Some(Sword(Mobility)),
capabilities: ( capabilities: (
// Rolling can interrupt attack // Rolling can interrupt attack
bits: 0b00000001, bits: 0b00000001,

View File

@ -25,7 +25,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 10, energy_cost_per_strike: 10,
meta: (
kind: Some(Sword(Mobility)),
),
) )

View File

@ -25,7 +25,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 10, energy_cost_per_strike: 10,
meta: (
kind: Some(Sword(Offensive)),
),
) )

View File

@ -58,8 +58,6 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Offensive)),
energy_cost_per_strike: 3, energy_cost_per_strike: 3,
meta: (
kind: Some(Sword(Offensive)),
),
) )

View File

@ -24,7 +24,4 @@ FinisherMelee(
kind: Sqrt, kind: Sqrt,
)), )),
minimum_combo: 10, minimum_combo: 10,
meta: (
kind: Some(Sword(Offensive)),
),
) )

View File

@ -36,9 +36,9 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Parrying)),
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
kind: Some(Sword(Parrying)),
capabilities: ( capabilities: (
// Buildup auto parries melee attacks // Buildup auto parries melee attacks
bits: 0b00000100, bits: 0b00000100,

View File

@ -26,7 +26,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 15, energy_cost_per_strike: 15,
meta: (
kind: Some(Sword(Parrying)),
),
) )

View File

@ -9,7 +9,4 @@ BasicBlock(
), ),
energy_cost: 15, energy_cost: 15,
can_hold: false, can_hold: false,
meta: (
kind: Some(Sword(Parrying)),
),
) )

View File

@ -13,7 +13,4 @@ RiposteMelee(
range: 4.0, range: 4.0,
angle: 20.0, angle: 20.0,
), ),
meta: (
kind: Some(Sword(Parrying)),
)
) )

View File

@ -24,7 +24,4 @@ DashMelee(
recover_duration: 0.5, recover_duration: 0.5,
ori_modifier: 0.1, ori_modifier: 0.1,
charge_through: false, charge_through: false,
meta: (
kind: Some(Sword(Reaching)),
),
) )

View File

@ -36,8 +36,6 @@ ComboMelee2(
), ),
], ],
is_stance: true, is_stance: true,
stance: Some(Sword(Reaching)),
energy_cost_per_strike: 4, energy_cost_per_strike: 4,
meta: (
kind: Some(Sword(Reaching)),
),
) )

View File

@ -14,7 +14,4 @@ RapidMelee(
), ),
energy_cost: 8, energy_cost: 8,
max_strikes: 6, max_strikes: 6,
meta: (
kind: Some(Sword(Reaching)),
),
) )

View File

@ -26,7 +26,4 @@ ComboMelee2(
], ],
is_stance: false, is_stance: false,
energy_cost_per_strike: 15, energy_cost_per_strike: 15,
meta: (
kind: Some(Sword(Reaching)),
),
) )

View File

@ -43,6 +43,7 @@ macro_rules! synced_components {
shockwave: Shockwave, shockwave: Shockwave,
beam_segment: BeamSegment, beam_segment: BeamSegment,
alignment: Alignment, alignment: Alignment,
stance: Stance,
// TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum // TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum
// from other entities (e.g. just keys needed to show appearance // from other entities (e.g. just keys needed to show appearance
// based on their loadout). Also, it looks like this actually has // based on their loadout). Also, it looks like this actually has
@ -220,6 +221,10 @@ impl NetSync for SkillSet {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Stance {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
// These are synced only from the client's own entity. // These are synced only from the client's own entity.
impl NetSync for Admin { impl NetSync for Admin {

View File

@ -566,6 +566,7 @@ pub enum CharacterAbility {
ComboMelee2 { ComboMelee2 {
strikes: Vec<combo_melee2::Strike<f32>>, strikes: Vec<combo_melee2::Strike<f32>>,
is_stance: bool, is_stance: bool,
stance: Option<Stance>,
energy_cost_per_strike: f32, energy_cost_per_strike: f32,
#[serde(default)] #[serde(default)]
meta: AbilityMeta, meta: AbilityMeta,
@ -1085,6 +1086,7 @@ impl CharacterAbility {
is_stance: _, is_stance: _,
ref mut energy_cost_per_strike, ref mut energy_cost_per_strike,
meta: _, meta: _,
stance: _,
} => { } => {
*energy_cost_per_strike /= stats.energy_efficiency; *energy_cost_per_strike /= stats.energy_efficiency;
*strikes = strikes *strikes = strikes
@ -2273,11 +2275,13 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
strikes, strikes,
energy_cost_per_strike, energy_cost_per_strike,
is_stance, is_stance,
stance,
meta: _, meta: _,
} => CharacterState::ComboMelee2(combo_melee2::Data { } => CharacterState::ComboMelee2(combo_melee2::Data {
static_data: combo_melee2::StaticData { static_data: combo_melee2::StaticData {
strikes: strikes.iter().map(|s| s.to_duration()).collect(), strikes: strikes.iter().map(|s| s.to_duration()).collect(),
is_stance: *is_stance, is_stance: *is_stance,
stance: *stance,
energy_cost_per_strike: *energy_cost_per_strike, energy_cost_per_strike: *energy_cost_per_strike,
ability_info, ability_info,
}, },
@ -2809,21 +2813,12 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct AbilityMeta { pub struct AbilityMeta {
pub kind: Option<AbilityKind>,
#[serde(default)] #[serde(default)]
pub capabilities: Capability, pub capabilities: Capability,
} }
// Only extend this if it is needed to control certain functionality of
// abilities
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AbilityKind {
Sword(SwordStance),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum SwordStance { pub enum SwordStance {
Balanced,
Offensive, Offensive,
Crippling, Crippling,
Cleaving, Cleaving,
@ -2849,3 +2844,17 @@ bitflags::bitflags! {
const KNOCKBACK_RESISTANT = 0b00010000; const KNOCKBACK_RESISTANT = 0b00010000;
} }
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum Stance {
None,
Sword(SwordStance),
}
impl Default for Stance {
fn default() -> Self { Self::None }
}
impl Component for Stance {
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
}

View File

@ -3,11 +3,7 @@
use crate::{ use crate::{
assets::{self, Asset, AssetExt, AssetHandle}, assets::{self, Asset, AssetExt, AssetHandle},
comp::{ comp::{ability::Stance, skills::Skill, CharacterAbility},
ability::{AbilityKind, SwordStance},
skills::Skill,
CharacterAbility, CharacterState,
},
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -333,20 +329,12 @@ impl<T> AuxiliaryAbilityKind<T> {
#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash)]
pub enum AbilityContext { pub enum AbilityContext {
Sword(SwordStance), Stance(Stance),
} }
impl AbilityContext { impl AbilityContext {
pub fn try_from(char_state: Option<&CharacterState>) -> Option<Self> { pub fn try_from(stance: Option<&Stance>) -> Option<Self> {
if let Some(AbilityKind::Sword(stance)) = char_state stance.map(|stance| Self::Stance(*stance))
.and_then(|cs| cs.ability_info())
.and_then(|info| info.ability_meta)
.and_then(|meta| meta.kind)
{
Some(Self::Sword(stance))
} else {
None
}
} }
} }

View File

@ -51,7 +51,7 @@ pub mod visual;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use self::{ pub use self::{
ability::{ ability::{
Ability, AbilityInput, ActiveAbilities, CharacterAbility, CharacterAbilityType, Ability, AbilityInput, ActiveAbilities, CharacterAbility, CharacterAbilityType, Stance,
MAX_ABILITIES, MAX_ABILITIES,
}, },
admin::{Admin, AdminRole}, admin::{Admin, AdminRole},

View File

@ -238,6 +238,10 @@ pub enum ServerEvent {
requesting_player_uuid: String, requesting_player_uuid: String,
character_id: CharacterId, character_id: CharacterId,
}, },
ChangeStance {
entity: EcsEntity,
stance: comp::Stance,
},
} }
pub struct EventBus<E> { pub struct EventBus<E> {

View File

@ -5,7 +5,8 @@ use crate::{
item::{tool::AbilityMap, MaterialStatManifest}, item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller, ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller,
ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, StateUpdate, Stats, Vel, InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, Stance, StateUpdate, Stats,
Vel,
}, },
link::Is, link::Is,
mounting::Rider, mounting::Rider,
@ -144,6 +145,7 @@ pub struct JoinData<'a> {
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid, pub terrain: &'a TerrainGrid,
pub mount_data: Option<&'a Is<Rider>>, pub mount_data: Option<&'a Is<Rider>>,
pub stance: Option<&'a Stance>,
} }
pub struct JoinStruct<'a> { pub struct JoinStruct<'a> {
@ -170,6 +172,7 @@ pub struct JoinStruct<'a> {
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid, pub terrain: &'a TerrainGrid,
pub mount_data: Option<&'a Is<Rider>>, pub mount_data: Option<&'a Is<Rider>>,
pub stance: Option<&'a Stance>,
} }
impl<'a> JoinData<'a> { impl<'a> JoinData<'a> {
@ -210,6 +213,7 @@ impl<'a> JoinData<'a> {
terrain: j.terrain, terrain: j.terrain,
active_abilities: j.active_abilities, active_abilities: j.active_abilities,
mount_data: j.mount_data, mount_data: j.mount_data,
stance: j.stance,
} }
} }
} }

View File

@ -3,8 +3,10 @@ use crate::{
character_state::OutputEvents, character_state::OutputEvents,
slot::{EquipSlot, Slot}, slot::{EquipSlot, Slot},
tool::Stats, tool::Stats,
CharacterState, InputAttr, InputKind, InventoryAction, MeleeConstructor, StateUpdate, CharacterState, InputAttr, InputKind, InventoryAction, MeleeConstructor, Stance,
StateUpdate,
}, },
event::ServerEvent,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
idle, idle,
@ -83,6 +85,9 @@ pub struct StaticData {
/// Whether or not combo melee should function as a stance (where it remains /// Whether or not combo melee should function as a stance (where it remains
/// in the character state after a strike has finished) /// in the character state after a strike has finished)
pub is_stance: bool, pub is_stance: bool,
/// If a stance is added, character state will attempt to enter that stance
/// if not already in it
pub stance: Option<Stance>,
/// The amount of energy consumed with each swing /// The amount of energy consumed with each swing
pub energy_cost_per_strike: f32, pub energy_cost_per_strike: f32,
/// What key is used to press ability /// What key is used to press ability
@ -116,6 +121,15 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate { fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
if let Some(stance) = self.static_data.stance {
if data.stance != Some(&stance) {
output_events.emit_server(ServerEvent::ChangeStance {
entity: data.entity,
stance,
});
}
}
// If is a stance, use M1 to control strikes, otherwise use the input that // If is a stance, use M1 to control strikes, otherwise use the input that
// activated the ability // activated the ability
let ability_input = if self.static_data.is_stance { let ability_input = if self.static_data.is_stance {

View File

@ -1034,7 +1034,7 @@ pub fn handle_jump(
} }
fn handle_ability(data: &JoinData<'_>, update: &mut StateUpdate, input: InputKind) -> bool { fn handle_ability(data: &JoinData<'_>, update: &mut StateUpdate, input: InputKind) -> bool {
let context = AbilityContext::try_from(Some(data.character)); let context = AbilityContext::try_from(data.stance);
if let Some(ability_input) = input.into() { if let Some(ability_input) = input.into() {
if let Some((ability, from_offhand)) = data if let Some((ability, from_offhand)) = data
.active_abilities .active_abilities
@ -1279,7 +1279,8 @@ pub fn get_buff_strength(data: &JoinData<'_>, ai: AbilityInfo) -> f32 {
} }
pub fn input_is_pressed(data: &JoinData<'_>, input: InputKind) -> bool { pub fn input_is_pressed(data: &JoinData<'_>, input: InputKind) -> bool {
data.controller.queued_inputs.contains_key(&input) || data.controller.held_inputs.contains_key(&input) data.controller.queued_inputs.contains_key(&input)
|| data.controller.held_inputs.contains_key(&input)
} }
/// Checked `Duration` addition. Computes `timer` + `dt`, applying relevant stat /// Checked `Duration` addition. Computes `timer` + `dt`, applying relevant stat

View File

@ -209,6 +209,7 @@ impl State {
ecs.register::<comp::Alignment>(); ecs.register::<comp::Alignment>();
ecs.register::<comp::LootOwner>(); ecs.register::<comp::LootOwner>();
ecs.register::<comp::Admin>(); ecs.register::<comp::Admin>();
ecs.register::<comp::Stance>();
// Register components send from clients -> server // Register components send from clients -> server
ecs.register::<comp::Controller>(); ecs.register::<comp::Controller>();

View File

@ -9,7 +9,7 @@ use common::{
character_state::OutputEvents, character_state::OutputEvents,
inventory::item::{tool::AbilityMap, MaterialStatManifest}, inventory::item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet, Stance,
StateUpdate, Stats, Vel, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
@ -51,6 +51,7 @@ pub struct ReadData<'a> {
alignments: ReadStorage<'a, comp::Alignment>, alignments: ReadStorage<'a, comp::Alignment>,
terrain: ReadExpect<'a, TerrainGrid>, terrain: ReadExpect<'a, TerrainGrid>,
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
stances: ReadStorage<'a, Stance>,
} }
/// ## Character Behavior System /// ## Character Behavior System
@ -200,6 +201,7 @@ impl<'a> System<'a> for Sys {
alignment: read_data.alignments.get(entity), alignment: read_data.alignments.get(entity),
terrain: &read_data.terrain, terrain: &read_data.terrain,
mount_data: read_data.is_riders.get(entity), mount_data: read_data.is_riders.get(entity),
stance: read_data.stances.get(entity),
}; };
for action in actions { for action in actions {

View File

@ -1,12 +1,12 @@
use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight}; use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight};
use common::{ use common::{
comp::{ comp::{
ability::{self, Ability, AbilityKind, ActiveAbilities, AuxiliaryAbility, Capability}, ability::{self, Ability, ActiveAbilities, AuxiliaryAbility, Capability},
buff::BuffKind, buff::BuffKind,
item::tool::AbilityContext, item::tool::AbilityContext,
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill}, skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction, ControlEvent, AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction, ControlEvent,
Controller, InputKind, Controller, InputKind, Stance,
}, },
path::TraversalConfig, path::TraversalConfig,
states::{self_buff, sprite_summon, utils::StageSection}, states::{self_buff, sprite_summon, utils::StageSection},
@ -476,15 +476,15 @@ impl<'a> AgentData<'a> {
const INT_COUNTER_STANCE: usize = 0; const INT_COUNTER_STANCE: usize = 0;
use ability::SwordStance; use ability::SwordStance;
let stance = |stance| match stance { let stance = |stance| match stance {
1 => SwordStance::Offensive, 1 => Stance::Sword(SwordStance::Offensive),
2 => SwordStance::Defensive, 2 => Stance::Sword(SwordStance::Defensive),
3 => SwordStance::Mobility, 3 => Stance::Sword(SwordStance::Mobility),
4 => SwordStance::Crippling, 4 => Stance::Sword(SwordStance::Crippling),
5 => SwordStance::Cleaving, 5 => Stance::Sword(SwordStance::Cleaving),
6 => SwordStance::Parrying, 6 => Stance::Sword(SwordStance::Parrying),
7 => SwordStance::Heavy, 7 => Stance::Sword(SwordStance::Heavy),
8 => SwordStance::Reaching, 8 => Stance::Sword(SwordStance::Reaching),
_ => SwordStance::Balanced, _ => Stance::None,
}; };
if !agent.action_state.initialized { if !agent.action_state.initialized {
// TODO: Don't always assume that if they have skill checked for, they have // TODO: Don't always assume that if they have skill checked for, they have
@ -519,11 +519,11 @@ impl<'a> AgentData<'a> {
}) })
}; };
match stance(agent.action_state.int_counters[INT_COUNTER_STANCE]) { match stance(agent.action_state.int_counters[INT_COUNTER_STANCE]) {
SwordStance::Balanced => { Stance::None => {
// Balanced finisher // Balanced finisher
set_sword_ability(0, 8); set_sword_ability(0, 8);
}, },
SwordStance::Offensive => { Stance::Sword(SwordStance::Offensive) => {
// Offensive combo // Offensive combo
set_sword_ability(0, 0); set_sword_ability(0, 0);
// Offensive advance // Offensive advance
@ -531,7 +531,7 @@ impl<'a> AgentData<'a> {
// Offensive finisher // Offensive finisher
set_sword_ability(2, 8); set_sword_ability(2, 8);
}, },
SwordStance::Defensive => { Stance::Sword(SwordStance::Defensive) => {
// Defensive combo // Defensive combo
set_sword_ability(0, 3); set_sword_ability(0, 3);
// Defensive retreat // Defensive retreat
@ -539,7 +539,7 @@ impl<'a> AgentData<'a> {
// Defensive bulwark // Defensive bulwark
set_sword_ability(2, 10); set_sword_ability(2, 10);
}, },
SwordStance::Mobility => { Stance::Sword(SwordStance::Mobility) => {
// Mobility combo // Mobility combo
set_sword_ability(0, 6); set_sword_ability(0, 6);
// Mobility feint // Mobility feint
@ -547,7 +547,7 @@ impl<'a> AgentData<'a> {
// Mobility agility // Mobility agility
set_sword_ability(2, 10); set_sword_ability(2, 10);
}, },
SwordStance::Crippling => { Stance::Sword(SwordStance::Crippling) => {
// Crippling combo // Crippling combo
set_sword_ability(0, 1); set_sword_ability(0, 1);
// Crippling finisher // Crippling finisher
@ -557,7 +557,7 @@ impl<'a> AgentData<'a> {
// Crippling gouge // Crippling gouge
set_sword_ability(3, 10); set_sword_ability(3, 10);
}, },
SwordStance::Cleaving => { Stance::Sword(SwordStance::Cleaving) => {
// Cleaving combo // Cleaving combo
set_sword_ability(0, 2); set_sword_ability(0, 2);
// Cleaving finisher // Cleaving finisher
@ -567,7 +567,7 @@ impl<'a> AgentData<'a> {
// Cleaving dive // Cleaving dive
set_sword_ability(3, 9); set_sword_ability(3, 9);
}, },
SwordStance::Parrying => { Stance::Sword(SwordStance::Parrying) => {
// Parrying combo // Parrying combo
set_sword_ability(0, 4); set_sword_ability(0, 4);
// Parrying parry // Parrying parry
@ -577,7 +577,7 @@ impl<'a> AgentData<'a> {
// Parrying counter // Parrying counter
set_sword_ability(3, 8); set_sword_ability(3, 8);
}, },
SwordStance::Heavy => { Stance::Sword(SwordStance::Heavy) => {
// Heavy combo // Heavy combo
set_sword_ability(0, 5); set_sword_ability(0, 5);
// Heavy finisher // Heavy finisher
@ -587,7 +587,7 @@ impl<'a> AgentData<'a> {
// Heavy fortitude // Heavy fortitude
set_sword_ability(3, 9); set_sword_ability(3, 9);
}, },
SwordStance::Reaching => { Stance::Sword(SwordStance::Reaching) => {
// Reaching combo // Reaching combo
set_sword_ability(0, 7); set_sword_ability(0, 7);
// Reaching charge // Reaching charge
@ -680,20 +680,15 @@ impl<'a> AgentData<'a> {
}; };
let in_stance = |stance| { let in_stance = |stance| {
if let CharacterState::ComboMelee2(c) = self.char_state { if let Some(Stance::Sword(sword_stance)) = self.stance {
c.static_data.is_stance stance == *sword_stance
&& c.static_data
.ability_info
.ability_meta
.and_then(|meta| meta.kind)
.map_or(false, |kind| AbilityKind::Sword(stance) == kind)
} else { } else {
false false
} }
}; };
match stance(agent.action_state.int_counters[INT_COUNTER_STANCE]) { match stance(agent.action_state.int_counters[INT_COUNTER_STANCE]) {
SwordStance::Balanced => { Stance::None => {
const BALANCED_FINISHER: FinisherMeleeData = FinisherMeleeData { const BALANCED_FINISHER: FinisherMeleeData = FinisherMeleeData {
range: 2.5, range: 2.5,
angle: 12.5, angle: 12.5,
@ -717,7 +712,7 @@ impl<'a> AgentData<'a> {
fallback_tactics(agent, controller); fallback_tactics(agent, controller);
} }
}, },
SwordStance::Offensive => { Stance::Sword(SwordStance::Offensive) => {
const OFFENSIVE_COMBO: ComboMeleeData = ComboMeleeData { const OFFENSIVE_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0, min_range: 0.0,
max_range: 2.0, max_range: 2.0,
@ -786,7 +781,7 @@ impl<'a> AgentData<'a> {
} }
} }
}, },
SwordStance::Defensive => { Stance::Sword(SwordStance::Defensive) => {
const DEFENSIVE_COMBO: ComboMeleeData = ComboMeleeData { const DEFENSIVE_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0, min_range: 0.0,
max_range: 2.5, max_range: 2.5,
@ -898,7 +893,7 @@ impl<'a> AgentData<'a> {
); );
} }
}, },
SwordStance::Mobility => { Stance::Sword(SwordStance::Mobility) => {
const MOBILITY_COMBO: ComboMeleeData = ComboMeleeData { const MOBILITY_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0, min_range: 0.0,
max_range: 2.5, max_range: 2.5,
@ -998,7 +993,7 @@ impl<'a> AgentData<'a> {
}; };
controller.inputs.move_dir.rotated_z(PI / 4.0 * dir); controller.inputs.move_dir.rotated_z(PI / 4.0 * dir);
}, },
SwordStance::Crippling => { Stance::Sword(SwordStance::Crippling) => {
const CRIPPLING_COMBO: ComboMeleeData = ComboMeleeData { const CRIPPLING_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0, min_range: 0.0,
max_range: 2.5, max_range: 2.5,
@ -1076,7 +1071,7 @@ impl<'a> AgentData<'a> {
); );
} }
}, },
SwordStance::Cleaving => { Stance::Sword(SwordStance::Cleaving) => {
// TODO: Rewrite cleaving stance tactics when agents can consider multiple // TODO: Rewrite cleaving stance tactics when agents can consider multiple
// targets at once. Remove hack to make cleaving AI appear less frequently above // targets at once. Remove hack to make cleaving AI appear less frequently above
// when doing so. // when doing so.
@ -1188,7 +1183,7 @@ impl<'a> AgentData<'a> {
); );
} }
}, },
SwordStance::Parrying => { Stance::Sword(SwordStance::Parrying) => {
const PARRYING_COMBO: ComboMeleeData = ComboMeleeData { const PARRYING_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0, min_range: 0.0,
max_range: 2.5, max_range: 2.5,
@ -1294,7 +1289,7 @@ impl<'a> AgentData<'a> {
); );
} }
}, },
SwordStance::Heavy => { Stance::Sword(SwordStance::Heavy) => {
const HEAVY_COMBO: ComboMeleeData = ComboMeleeData { const HEAVY_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0, min_range: 0.0,
max_range: 2.5, max_range: 2.5,
@ -1371,7 +1366,7 @@ impl<'a> AgentData<'a> {
advance(agent, controller, HEAVY_COMBO.max_range, HEAVY_COMBO.angle); advance(agent, controller, HEAVY_COMBO.max_range, HEAVY_COMBO.angle);
} }
}, },
SwordStance::Reaching => { Stance::Sword(SwordStance::Reaching) => {
const REACHING_COMBO: ComboMeleeData = ComboMeleeData { const REACHING_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0, min_range: 0.0,
max_range: 4.5, max_range: 4.5,
@ -1622,7 +1617,7 @@ impl<'a> AgentData<'a> {
enum ActionStateConditions { enum ActionStateConditions {
ConditionStaffCanShockwave = 0, ConditionStaffCanShockwave = 0,
} }
let context = AbilityContext::try_from(Some(self.char_state)); let context = AbilityContext::try_from(self.stance);
let extract_ability = |input: AbilityInput| { let extract_ability = |input: AbilityInput| {
self.active_abilities self.active_abilities
.activate_ability( .activate_ability(

View File

@ -4,7 +4,8 @@ use common::{
group, group,
item::MaterialStatManifest, item::MaterialStatManifest,
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory, ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stats, Vel, LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance, Stats,
Vel,
}, },
link::Is, link::Is,
mounting::Mount, mounting::Mount,
@ -47,6 +48,7 @@ pub struct AgentData<'a> {
pub buffs: Option<&'a Buffs>, pub buffs: Option<&'a Buffs>,
pub stats: Option<&'a Stats>, pub stats: Option<&'a Stats>,
pub poise: Option<&'a Poise>, pub poise: Option<&'a Poise>,
pub stance: Option<&'a Stance>,
pub cached_spatial_grid: &'a common::CachedSpatialGrid, pub cached_spatial_grid: &'a common::CachedSpatialGrid,
pub msm: &'a MaterialStatManifest, pub msm: &'a MaterialStatManifest,
} }
@ -182,6 +184,7 @@ pub struct ReadData<'a> {
pub loot_owners: ReadStorage<'a, LootOwner>, pub loot_owners: ReadStorage<'a, LootOwner>,
pub msm: ReadExpect<'a, MaterialStatManifest>, pub msm: ReadExpect<'a, MaterialStatManifest>,
pub poises: ReadStorage<'a, Poise>, pub poises: ReadStorage<'a, Poise>,
pub stances: ReadStorage<'a, Stance>,
} }
pub enum Path { pub enum Path {

View File

@ -1480,3 +1480,14 @@ pub fn handle_make_admin(server: &mut Server, entity: EcsEntity, admin: comp::Ad
.write_component_ignore_entity_dead(entity, admin); .write_component_ignore_entity_dead(entity, admin);
} }
} }
pub fn handle_stance_change(server: &mut Server, entity: EcsEntity, new_stance: comp::Stance) {
if let Some(mut stance) = server
.state
.ecs_mut()
.write_storage::<comp::Stance>()
.get_mut(entity)
{
*stance = new_stance;
}
}

View File

@ -13,8 +13,8 @@ use entity_manipulation::{
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change, handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook, handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook,
handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground,
handle_make_admin, handle_parry_hook, handle_poise, handle_respawn, handle_teleport_to, handle_make_admin, handle_parry_hook, handle_poise, handle_respawn, handle_stance_change,
handle_update_map_marker, handle_teleport_to, handle_update_map_marker,
}; };
use group_manip::handle_group; use group_manip::handle_group;
use information::handle_site_info; use information::handle_site_info;
@ -300,6 +300,9 @@ impl Server {
admin, admin,
uuid, uuid,
} => handle_make_admin(self, entity, admin, uuid), } => handle_make_admin(self, entity, admin, uuid),
ServerEvent::ChangeStance { entity, stance } => {
handle_stance_change(self, entity, stance)
},
} }
} }

View File

@ -288,6 +288,7 @@ impl StateExt for State {
.with(comp::Buffs::default()) .with(comp::Buffs::default())
.with(comp::Combo::default()) .with(comp::Combo::default())
.with(comp::Auras::default()) .with(comp::Auras::default())
.with(comp::Stance::default())
} }
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder { fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {
@ -560,6 +561,7 @@ impl StateExt for State {
self.write_component_ignore_entity_dead(entity, comp::Buffs::default()); self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
self.write_component_ignore_entity_dead(entity, comp::Auras::default()); self.write_component_ignore_entity_dead(entity, comp::Auras::default());
self.write_component_ignore_entity_dead(entity, comp::Combo::default()); self.write_component_ignore_entity_dead(entity, comp::Combo::default());
self.write_component_ignore_entity_dead(entity, comp::Stance::default());
// Make sure physics components are updated // Make sure physics components are updated
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced()); self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());

View File

@ -203,6 +203,7 @@ impl<'a> System<'a> for Sys {
cached_spatial_grid: &read_data.cached_spatial_grid, cached_spatial_grid: &read_data.cached_spatial_grid,
msm: &read_data.msm, msm: &read_data.msm,
poise: read_data.poises.get(entity), poise: read_data.poises.get(entity),
stance: read_data.stances.get(entity),
}; };
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////

View File

@ -10,7 +10,7 @@ use crate::{
use i18n::Localization; use i18n::Localization;
use common::{ use common::{
comp::{BuffKind, Buffs, CharacterState, Energy, Health}, comp::{BuffKind, Buffs, Energy, Health, Stance},
resources::Time, resources::Time,
}; };
use conrod_core::{ use conrod_core::{
@ -47,7 +47,7 @@ pub struct BuffsBar<'a> {
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
buffs: &'a Buffs, buffs: &'a Buffs,
char_state: &'a CharacterState, stance: Option<&'a Stance>,
pulse: f32, pulse: f32,
global_state: &'a GlobalState, global_state: &'a GlobalState,
health: &'a Health, health: &'a Health,
@ -63,7 +63,7 @@ impl<'a> BuffsBar<'a> {
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
buffs: &'a Buffs, buffs: &'a Buffs,
char_state: &'a CharacterState, stance: Option<&'a Stance>,
pulse: f32, pulse: f32,
global_state: &'a GlobalState, global_state: &'a GlobalState,
health: &'a Health, health: &'a Health,
@ -78,7 +78,7 @@ impl<'a> BuffsBar<'a> {
tooltip_manager, tooltip_manager,
localized_strings, localized_strings,
buffs, buffs,
char_state, stance,
pulse, pulse,
global_state, global_state,
health, health,
@ -138,7 +138,7 @@ impl<'a> Widget for BuffsBar<'a> {
.desc_font_size(self.fonts.cyri.scale(12)) .desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR); .desc_text_color(TEXT_COLOR);
let buff_icons = BuffIcon::icons_vec(self.buffs, self.char_state); let buff_icons = BuffIcon::icons_vec(self.buffs, self.stance);
if let BuffPosition::Bar = buff_position { if let BuffPosition::Bar = buff_position {
let decayed_health = 1.0 - self.health.maximum() / self.health.base_max(); let decayed_health = 1.0 - self.health.maximum() / self.health.base_max();
let show_health = self.global_state.settings.interface.always_show_bars let show_health = self.global_state.settings.interface.always_show_bars

View File

@ -359,9 +359,7 @@ impl<'a> Widget for Group<'a> {
let uid_allocator = client_state.ecs().read_resource::<UidAllocator>(); let uid_allocator = client_state.ecs().read_resource::<UidAllocator>();
let bodies = client_state.ecs().read_storage::<common::comp::Body>(); let bodies = client_state.ecs().read_storage::<common::comp::Body>();
let poises = client_state.ecs().read_storage::<common::comp::Poise>(); let poises = client_state.ecs().read_storage::<common::comp::Poise>();
let char_states = client_state let stances = client_state.ecs().read_storage::<common::comp::Stance>();
.ecs()
.read_storage::<common::comp::CharacterState>();
// Keep track of the total number of widget ids we are using for buffs // Keep track of the total number of widget ids we are using for buffs
let mut total_buff_count = 0; let mut total_buff_count = 0;
@ -377,7 +375,7 @@ impl<'a> Widget for Group<'a> {
let is_leader = uid == leader; let is_leader = uid == leader;
let body = entity.and_then(|entity| bodies.get(entity)); let body = entity.and_then(|entity| bodies.get(entity));
let poise = entity.and_then(|entity| poises.get(entity)); let poise = entity.and_then(|entity| poises.get(entity));
let char_state = entity.and_then(|entity| char_states.get(entity)); let stance = entity.and_then(|entity| stances.get(entity));
if let ( if let (
Some(stats), Some(stats),
@ -387,10 +385,8 @@ impl<'a> Widget for Group<'a> {
Some(energy), Some(energy),
Some(body), Some(body),
Some(poise), Some(poise),
Some(char_state), ) = (stats, skill_set, inventory, health, energy, body, poise)
) = ( {
stats, skill_set, inventory, health, energy, body, poise, char_state,
) {
let combat_rating = combat::combat_rating( let combat_rating = combat::combat_rating(
inventory, health, energy, poise, skill_set, *body, self.msm, inventory, health, energy, poise, skill_set, *body, self.msm,
); );
@ -509,7 +505,7 @@ impl<'a> Widget for Group<'a> {
.top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0) .top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0)
.set(state.ids.member_energy[i], ui); .set(state.ids.member_energy[i], ui);
if let Some(buffs) = buffs { if let Some(buffs) = buffs {
let buff_icons = BuffIcon::icons_vec(buffs, char_state); let buff_icons = BuffIcon::icons_vec(buffs, stance);
// Limit displayed buffs to 11 // Limit displayed buffs to 11
let buff_count = buff_icons.len().min(11); let buff_count = buff_icons.len().min(11);
total_buff_count += buff_count; total_buff_count += buff_count;

View File

@ -580,29 +580,34 @@ impl<'a> BuffIcon<'a> {
} }
} }
pub fn icons_vec(buffs: &comp::Buffs, char_state: &comp::CharacterState) -> Vec<Self> { pub fn icons_vec(buffs: &comp::Buffs, stance: Option<&comp::Stance>) -> Vec<Self> {
buffs buffs
.iter_active() .iter_active()
.filter_map(BuffIcon::from_buffs) .filter_map(BuffIcon::from_buffs)
.chain(BuffIcon::from_char_state(char_state).into_iter()) .chain(stance.and_then(BuffIcon::from_stance).into_iter())
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
fn from_char_state(char_state: &comp::CharacterState) -> Option<Self> { fn from_stance(stance: &comp::Stance) -> Option<Self> {
if let Some(ability_kind) = char_state use comp::ability::{Stance, SwordStance};
.ability_info() let id = match stance {
.and_then(|info| info.ability_meta) Stance::Sword(SwordStance::Offensive) => "common.abilities.sword.offensive_combo",
.and_then(|meta| meta.kind) Stance::Sword(SwordStance::Crippling) => "common.abilities.sword.crippling_combo",
{ Stance::Sword(SwordStance::Cleaving) => "common.abilities.sword.cleaving_combo",
let id = util::representative_ability_id(ability_kind); Stance::Sword(SwordStance::Defensive) => "common.abilities.sword.defensive_combo",
Stance::Sword(SwordStance::Parrying) => "common.abilities.sword.parrying_combo",
Stance::Sword(SwordStance::Heavy) => "common.abilities.sword.heavy_combo",
Stance::Sword(SwordStance::Mobility) => "common.abilities.sword.mobility_combo",
Stance::Sword(SwordStance::Reaching) => "common.abilities.sword.reaching_combo",
Stance::None => {
return None;
},
};
Some(BuffIcon { Some(BuffIcon {
kind: BuffIconKind::Ability { ability_id: id }, kind: BuffIconKind::Ability { ability_id: id },
is_buff: true, is_buff: true,
end_time: None, end_time: None,
}) })
} else {
None
}
} }
fn from_buffs<'b, I: Iterator<Item = &'b comp::Buff>>(buffs: I) -> Option<Self> { fn from_buffs<'b, I: Iterator<Item = &'b comp::Buff>>(buffs: I) -> Option<Self> {
@ -1489,7 +1494,7 @@ impl Hud {
let poises = ecs.read_storage::<comp::Poise>(); let poises = ecs.read_storage::<comp::Poise>();
let alignments = ecs.read_storage::<comp::Alignment>(); let alignments = ecs.read_storage::<comp::Alignment>();
let is_mount = ecs.read_storage::<Is<Mount>>(); let is_mount = ecs.read_storage::<Is<Mount>>();
let char_states = ecs.read_storage::<comp::CharacterState>(); let stances = ecs.read_storage::<comp::Stance>();
let time = ecs.read_resource::<Time>(); let time = ecs.read_resource::<Time>();
// Check if there was a persistence load error of the skillset, and if so // Check if there was a persistence load error of the skillset, and if so
@ -2230,7 +2235,7 @@ impl Hud {
&uids, &uids,
&inventories, &inventories,
poises.maybe(), poises.maybe(),
(alignments.maybe(), is_mount.maybe(), &char_states), (alignments.maybe(), is_mount.maybe(), stances.maybe()),
) )
.join() .join()
.filter(|t| { .filter(|t| {
@ -2253,7 +2258,7 @@ impl Hud {
uid, uid,
inventory, inventory,
poise, poise,
(alignment, is_mount, char_state), (alignment, is_mount, stance),
)| { )| {
// Use interpolated position if available // Use interpolated position if available
let pos = interpolated.map_or(pos.0, |i| i.pos); let pos = interpolated.map_or(pos.0, |i| i.pos);
@ -2304,7 +2309,7 @@ impl Hud {
} else { } else {
0.0 0.0
}, },
char_state, stance,
}); });
// Only render bubble if nearby or if its me and setting is on // Only render bubble if nearby or if its me and setting is on
let bubble = if (dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) && !is_me) let bubble = if (dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) && !is_me)
@ -2849,7 +2854,6 @@ impl Hud {
let stats = ecs.read_storage::<comp::Stats>(); let stats = ecs.read_storage::<comp::Stats>();
let skill_sets = ecs.read_storage::<comp::SkillSet>(); let skill_sets = ecs.read_storage::<comp::SkillSet>();
let buffs = ecs.read_storage::<comp::Buffs>(); let buffs = ecs.read_storage::<comp::Buffs>();
let char_states = ecs.read_storage::<comp::CharacterState>();
let msm = ecs.read_resource::<MaterialStatManifest>(); let msm = ecs.read_resource::<MaterialStatManifest>();
let time = ecs.read_resource::<Time>(); let time = ecs.read_resource::<Time>();
@ -2968,6 +2972,7 @@ impl Hud {
let poises = ecs.read_storage::<comp::Poise>(); let poises = ecs.read_storage::<comp::Poise>();
let combos = ecs.read_storage::<comp::Combo>(); let combos = ecs.read_storage::<comp::Combo>();
let time = ecs.read_resource::<Time>(); let time = ecs.read_resource::<Time>();
let stances = ecs.read_storage::<comp::Stance>();
// Combo floater stuffs // Combo floater stuffs
self.floaters.combo_floater = self.floaters.combo_floater.map(|mut f| { self.floaters.combo_floater = self.floaters.combo_floater.map(|mut f| {
f.timer -= dt.as_secs_f64(); f.timer -= dt.as_secs_f64();
@ -2990,7 +2995,7 @@ impl Hud {
skillsets.get(entity), skillsets.get(entity),
bodies.get(entity), bodies.get(entity),
) { ) {
let context = AbilityContext::try_from(char_states.get(entity)); let context = AbilityContext::try_from(stances.get(entity));
match Skillbar::new( match Skillbar::new(
client, client,
&info, &info,
@ -3018,7 +3023,6 @@ impl Hud {
self.floaters.combo_floater, self.floaters.combo_floater,
context, context,
combos.get(entity), combos.get(entity),
char_states.get(entity),
) )
.set(self.ids.skillbar, ui_widgets) .set(self.ids.skillbar, ui_widgets)
{ {
@ -3138,11 +3142,10 @@ impl Hud {
} }
// Buffs // Buffs
if let (Some(player_buffs), Some(health), Some(energy), Some(char_state)) = ( if let (Some(player_buffs), Some(health), Some(energy)) = (
buffs.get(info.viewpoint_entity), buffs.get(info.viewpoint_entity),
healths.get(entity), healths.get(entity),
energies.get(entity), energies.get(entity),
char_states.get(entity),
) { ) {
for event in BuffsBar::new( for event in BuffsBar::new(
&self.imgs, &self.imgs,
@ -3151,7 +3154,7 @@ impl Hud {
tooltip_manager, tooltip_manager,
i18n, i18n,
player_buffs, player_buffs,
char_state, stances.get(entity),
self.pulse, self.pulse,
global_state, global_state,
health, health,
@ -3463,7 +3466,7 @@ impl Hud {
bodies.get(entity), bodies.get(entity),
poises.get(entity), poises.get(entity),
) { ) {
let context = AbilityContext::try_from(char_states.get(entity)); let context = AbilityContext::try_from(stances.get(entity));
for event in Diary::new( for event in Diary::new(
&self.show, &self.show,
client, client,

View File

@ -10,7 +10,7 @@ use crate::{
ui::{fonts::Fonts, Ingameable}, ui::{fonts::Fonts, Ingameable},
}; };
use common::{ use common::{
comp::{Buffs, CharacterState, Energy, Health, SpeechBubble, SpeechBubbleType}, comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType, Stance},
resources::Time, resources::Time,
}; };
use conrod_core::{ use conrod_core::{
@ -72,7 +72,7 @@ pub struct Info<'a> {
pub buffs: &'a Buffs, pub buffs: &'a Buffs,
pub energy: Option<&'a Energy>, pub energy: Option<&'a Energy>,
pub combat_rating: f32, pub combat_rating: f32,
pub char_state: &'a CharacterState, pub stance: Option<&'a Stance>,
} }
/// Determines whether to show the healthbar /// Determines whether to show the healthbar
@ -168,9 +168,7 @@ impl<'a> Ingameable for Overhead<'a> {
self.info.map_or(0, |info| { self.info.map_or(0, |info| {
2 + 1 2 + 1
+ if self.bubble.is_none() { + if self.bubble.is_none() {
2 * BuffIcon::icons_vec(info.buffs, info.char_state) 2 * BuffIcon::icons_vec(info.buffs, info.stance).len().min(11)
.len()
.min(11)
} else { } else {
0 0
} }
@ -209,7 +207,7 @@ impl<'a> Widget for Overhead<'a> {
buffs, buffs,
energy, energy,
combat_rating, combat_rating,
char_state, stance,
}) = self.info }) = self.info
{ {
// Used to set healthbar colours based on hp_percentage // Used to set healthbar colours based on hp_percentage
@ -239,7 +237,7 @@ impl<'a> Widget for Overhead<'a> {
}; };
// Buffs // Buffs
// Alignment // Alignment
let buff_icons = BuffIcon::icons_vec(buffs, char_state); let buff_icons = BuffIcon::icons_vec(buffs, stance);
let buff_count = buff_icons.len().min(11); let buff_count = buff_icons.len().min(11);
Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT) Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT)
.x_y(-1.0, name_y + 60.0) .x_y(-1.0, name_y + 60.0)

View File

@ -30,8 +30,7 @@ use common::comp::{
ItemDesc, MaterialStatManifest, ItemDesc, MaterialStatManifest,
}, },
skillset::SkillGroupKind, skillset::SkillGroupKind,
Ability, ActiveAbilities, Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Ability, ActiveAbilities, Body, Combo, Energy, Health, Inventory, Poise, PoiseState, SkillSet,
PoiseState, SkillSet,
}; };
use conrod_core::{ use conrod_core::{
color, color,
@ -313,7 +312,6 @@ pub struct Skillbar<'a> {
combo_floater: Option<ComboFloater>, combo_floater: Option<ComboFloater>,
context: Option<AbilityContext>, context: Option<AbilityContext>,
combo: Option<&'a Combo>, combo: Option<&'a Combo>,
char_state: Option<&'a CharacterState>,
} }
impl<'a> Skillbar<'a> { impl<'a> Skillbar<'a> {
@ -345,7 +343,6 @@ impl<'a> Skillbar<'a> {
combo_floater: Option<ComboFloater>, combo_floater: Option<ComboFloater>,
context: Option<AbilityContext>, context: Option<AbilityContext>,
combo: Option<&'a Combo>, combo: Option<&'a Combo>,
char_state: Option<&'a CharacterState>,
) -> Self { ) -> Self {
Self { Self {
client, client,
@ -375,7 +372,6 @@ impl<'a> Skillbar<'a> {
combo_floater, combo_floater,
context, context,
combo, combo,
char_state,
} }
} }
@ -1088,18 +1084,6 @@ impl<'a> Skillbar<'a> {
.active_abilities .active_abilities
.and_then(|a| Ability::from(a.primary).ability_id(Some(self.inventory), self.context)); .and_then(|a| Ability::from(a.primary).ability_id(Some(self.inventory), self.context));
let primary_ability_id = if let Some(override_id) = self
.char_state
.and_then(|cs| cs.ability_info())
.and_then(|info| info.ability_meta)
.and_then(|meta| meta.kind)
.map(util::representative_ability_id)
{
Some(override_id)
} else {
primary_ability_id
};
let (primary_ability_title, primary_ability_desc) = let (primary_ability_title, primary_ability_desc) =
util::ability_description(primary_ability_id.unwrap_or(""), self.localized_strings); util::ability_description(primary_ability_id.unwrap_or(""), self.localized_strings);

View File

@ -1,7 +1,6 @@
use super::img_ids; use super::img_ids;
use common::{ use common::{
comp::{ comp::{
ability::{AbilityKind, SwordStance},
inventory::trade_pricing::TradePricing, inventory::trade_pricing::TradePricing,
item::{ item::{
armor::{Armor, ArmorKind, Protection}, armor::{Armor, ArmorKind, Protection},
@ -431,17 +430,3 @@ pub fn ability_description<'a>(
(loc.get_msg(&ability), loc.get_attr(&ability, "desc")) (loc.get_msg(&ability), loc.get_attr(&ability, "desc"))
} }
pub fn representative_ability_id(ability_kind: AbilityKind) -> &'static str {
match ability_kind {
AbilityKind::Sword(SwordStance::Balanced) => "common.abilities.sword.balanced_combo",
AbilityKind::Sword(SwordStance::Offensive) => "common.abilities.sword.offensive_combo",
AbilityKind::Sword(SwordStance::Crippling) => "common.abilities.sword.crippling_combo",
AbilityKind::Sword(SwordStance::Cleaving) => "common.abilities.sword.cleaving_combo",
AbilityKind::Sword(SwordStance::Defensive) => "common.abilities.sword.defensive_combo",
AbilityKind::Sword(SwordStance::Parrying) => "common.abilities.sword.parrying_combo",
AbilityKind::Sword(SwordStance::Heavy) => "common.abilities.sword.heavy_combo",
AbilityKind::Sword(SwordStance::Mobility) => "common.abilities.sword.mobility_combo",
AbilityKind::Sword(SwordStance::Reaching) => "common.abilities.sword.reaching_combo",
}
}

View File

@ -34,7 +34,7 @@ use common::{
inventory::slot::EquipSlot, inventory::slot::EquipSlot,
item::{tool::AbilityContext, Hands, ItemKind, ToolKind}, item::{tool::AbilityContext, Hands, ItemKind, ToolKind},
Body, CharacterState, Collider, Controller, Health, Inventory, Item, ItemKey, Last, Body, CharacterState, Collider, Controller, Health, Inventory, Item, ItemKey, Last,
LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, Vel, LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, Stance, Vel,
}, },
link::Is, link::Is,
mounting::Rider, mounting::Rider,
@ -747,7 +747,7 @@ impl FigureMgr {
item, item,
light_emitter, light_emitter,
is_rider, is_rider,
collider, (collider, stance),
), ),
) in ( ) in (
&ecs.entities(), &ecs.entities(),
@ -765,7 +765,10 @@ impl FigureMgr {
ecs.read_storage::<Item>().maybe(), ecs.read_storage::<Item>().maybe(),
ecs.read_storage::<LightEmitter>().maybe(), ecs.read_storage::<LightEmitter>().maybe(),
ecs.read_storage::<Is<Rider>>().maybe(), ecs.read_storage::<Is<Rider>>().maybe(),
(
ecs.read_storage::<Collider>().maybe(), ecs.read_storage::<Collider>().maybe(),
ecs.read_storage::<Stance>().maybe(),
),
) )
.join() .join()
.enumerate() .enumerate()
@ -917,7 +920,7 @@ impl FigureMgr {
let second_tool_spec = second_tool_spec.as_deref(); let second_tool_spec = second_tool_spec.as_deref();
let hands = (active_tool_hand, second_tool_hand); let hands = (active_tool_hand, second_tool_hand);
let context = AbilityContext::try_from(character); let context = AbilityContext::try_from(stance);
let ability_id = character.and_then(|c| { let ability_id = character.and_then(|c| {
c.ability_info() c.ability_info()