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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,6 +43,7 @@ macro_rules! synced_components {
shockwave: Shockwave,
beam_segment: BeamSegment,
alignment: Alignment,
stance: Stance,
// TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum
// from other entities (e.g. just keys needed to show appearance
// 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;
}
impl NetSync for Stance {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
// These are synced only from the client's own entity.
impl NetSync for Admin {

View File

@ -566,6 +566,7 @@ pub enum CharacterAbility {
ComboMelee2 {
strikes: Vec<combo_melee2::Strike<f32>>,
is_stance: bool,
stance: Option<Stance>,
energy_cost_per_strike: f32,
#[serde(default)]
meta: AbilityMeta,
@ -1085,6 +1086,7 @@ impl CharacterAbility {
is_stance: _,
ref mut energy_cost_per_strike,
meta: _,
stance: _,
} => {
*energy_cost_per_strike /= stats.energy_efficiency;
*strikes = strikes
@ -2273,11 +2275,13 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
strikes,
energy_cost_per_strike,
is_stance,
stance,
meta: _,
} => CharacterState::ComboMelee2(combo_melee2::Data {
static_data: combo_melee2::StaticData {
strikes: strikes.iter().map(|s| s.to_duration()).collect(),
is_stance: *is_stance,
stance: *stance,
energy_cost_per_strike: *energy_cost_per_strike,
ability_info,
},
@ -2809,21 +2813,12 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct AbilityMeta {
pub kind: Option<AbilityKind>,
#[serde(default)]
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)]
pub enum SwordStance {
Balanced,
Offensive,
Crippling,
Cleaving,
@ -2849,3 +2844,17 @@ bitflags::bitflags! {
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::{
assets::{self, Asset, AssetExt, AssetHandle},
comp::{
ability::{AbilityKind, SwordStance},
skills::Skill,
CharacterAbility, CharacterState,
},
comp::{ability::Stance, skills::Skill, CharacterAbility},
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
@ -333,20 +329,12 @@ impl<T> AuxiliaryAbilityKind<T> {
#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash)]
pub enum AbilityContext {
Sword(SwordStance),
Stance(Stance),
}
impl AbilityContext {
pub fn try_from(char_state: Option<&CharacterState>) -> Option<Self> {
if let Some(AbilityKind::Sword(stance)) = char_state
.and_then(|cs| cs.ability_info())
.and_then(|info| info.ability_meta)
.and_then(|meta| meta.kind)
{
Some(Self::Sword(stance))
} else {
None
}
pub fn try_from(stance: Option<&Stance>) -> Option<Self> {
stance.map(|stance| Self::Stance(*stance))
}
}

View File

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

View File

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

View File

@ -5,7 +5,8 @@ use crate::{
item::{tool::AbilityMap, 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,
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, Stance, StateUpdate, Stats,
Vel,
},
link::Is,
mounting::Rider,
@ -144,6 +145,7 @@ pub struct JoinData<'a> {
pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid,
pub mount_data: Option<&'a Is<Rider>>,
pub stance: Option<&'a Stance>,
}
pub struct JoinStruct<'a> {
@ -170,6 +172,7 @@ pub struct JoinStruct<'a> {
pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid,
pub mount_data: Option<&'a Is<Rider>>,
pub stance: Option<&'a Stance>,
}
impl<'a> JoinData<'a> {
@ -210,6 +213,7 @@ impl<'a> JoinData<'a> {
terrain: j.terrain,
active_abilities: j.active_abilities,
mount_data: j.mount_data,
stance: j.stance,
}
}
}

View File

@ -3,8 +3,10 @@ use crate::{
character_state::OutputEvents,
slot::{EquipSlot, Slot},
tool::Stats,
CharacterState, InputAttr, InputKind, InventoryAction, MeleeConstructor, StateUpdate,
CharacterState, InputAttr, InputKind, InventoryAction, MeleeConstructor, Stance,
StateUpdate,
},
event::ServerEvent,
states::{
behavior::{CharacterBehavior, JoinData},
idle,
@ -83,6 +85,9 @@ pub struct StaticData {
/// Whether or not combo melee should function as a stance (where it remains
/// in the character state after a strike has finished)
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
pub energy_cost_per_strike: f32,
/// 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 {
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
// activated the ability
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 {
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, from_offhand)) = data
.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 {
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

View File

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

View File

@ -9,7 +9,7 @@ use common::{
character_state::OutputEvents,
inventory::item::{tool::AbilityMap, MaterialStatManifest},
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,
},
event::{EventBus, LocalEvent, ServerEvent},
@ -51,6 +51,7 @@ pub struct ReadData<'a> {
alignments: ReadStorage<'a, comp::Alignment>,
terrain: ReadExpect<'a, TerrainGrid>,
inventories: ReadStorage<'a, Inventory>,
stances: ReadStorage<'a, Stance>,
}
/// ## Character Behavior System
@ -200,6 +201,7 @@ impl<'a> System<'a> for Sys {
alignment: read_data.alignments.get(entity),
terrain: &read_data.terrain,
mount_data: read_data.is_riders.get(entity),
stance: read_data.stances.get(entity),
};
for action in actions {

View File

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

View File

@ -4,7 +4,8 @@ use common::{
group,
item::MaterialStatManifest,
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,
mounting::Mount,
@ -47,6 +48,7 @@ pub struct AgentData<'a> {
pub buffs: Option<&'a Buffs>,
pub stats: Option<&'a Stats>,
pub poise: Option<&'a Poise>,
pub stance: Option<&'a Stance>,
pub cached_spatial_grid: &'a common::CachedSpatialGrid,
pub msm: &'a MaterialStatManifest,
}
@ -182,6 +184,7 @@ pub struct ReadData<'a> {
pub loot_owners: ReadStorage<'a, LootOwner>,
pub msm: ReadExpect<'a, MaterialStatManifest>,
pub poises: ReadStorage<'a, Poise>,
pub stances: ReadStorage<'a, Stance>,
}
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);
}
}
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_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook,
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_update_map_marker,
handle_make_admin, handle_parry_hook, handle_poise, handle_respawn, handle_stance_change,
handle_teleport_to, handle_update_map_marker,
};
use group_manip::handle_group;
use information::handle_site_info;
@ -300,6 +300,9 @@ impl Server {
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::Combo::default())
.with(comp::Auras::default())
.with(comp::Stance::default())
}
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::Auras::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
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,
msm: &read_data.msm,
poise: read_data.poises.get(entity),
stance: read_data.stances.get(entity),
};
///////////////////////////////////////////////////////////

View File

@ -10,7 +10,7 @@ use crate::{
use i18n::Localization;
use common::{
comp::{BuffKind, Buffs, CharacterState, Energy, Health},
comp::{BuffKind, Buffs, Energy, Health, Stance},
resources::Time,
};
use conrod_core::{
@ -47,7 +47,7 @@ pub struct BuffsBar<'a> {
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization,
buffs: &'a Buffs,
char_state: &'a CharacterState,
stance: Option<&'a Stance>,
pulse: f32,
global_state: &'a GlobalState,
health: &'a Health,
@ -63,7 +63,7 @@ impl<'a> BuffsBar<'a> {
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization,
buffs: &'a Buffs,
char_state: &'a CharacterState,
stance: Option<&'a Stance>,
pulse: f32,
global_state: &'a GlobalState,
health: &'a Health,
@ -78,7 +78,7 @@ impl<'a> BuffsBar<'a> {
tooltip_manager,
localized_strings,
buffs,
char_state,
stance,
pulse,
global_state,
health,
@ -138,7 +138,7 @@ impl<'a> Widget for BuffsBar<'a> {
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.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 {
let decayed_health = 1.0 - self.health.maximum() / self.health.base_max();
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 bodies = client_state.ecs().read_storage::<common::comp::Body>();
let poises = client_state.ecs().read_storage::<common::comp::Poise>();
let char_states = client_state
.ecs()
.read_storage::<common::comp::CharacterState>();
let stances = client_state.ecs().read_storage::<common::comp::Stance>();
// Keep track of the total number of widget ids we are using for buffs
let mut total_buff_count = 0;
@ -377,7 +375,7 @@ impl<'a> Widget for Group<'a> {
let is_leader = uid == leader;
let body = entity.and_then(|entity| bodies.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 (
Some(stats),
@ -387,10 +385,8 @@ impl<'a> Widget for Group<'a> {
Some(energy),
Some(body),
Some(poise),
Some(char_state),
) = (
stats, skill_set, inventory, health, energy, body, poise, char_state,
) {
) = (stats, skill_set, inventory, health, energy, body, poise)
{
let combat_rating = combat::combat_rating(
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)
.set(state.ids.member_energy[i], ui);
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
let buff_count = buff_icons.len().min(11);
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
.iter_active()
.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<_>>()
}
fn from_char_state(char_state: &comp::CharacterState) -> Option<Self> {
if let Some(ability_kind) = char_state
.ability_info()
.and_then(|info| info.ability_meta)
.and_then(|meta| meta.kind)
{
let id = util::representative_ability_id(ability_kind);
Some(BuffIcon {
kind: BuffIconKind::Ability { ability_id: id },
is_buff: true,
end_time: None,
})
} else {
None
}
fn from_stance(stance: &comp::Stance) -> Option<Self> {
use comp::ability::{Stance, SwordStance};
let id = match stance {
Stance::Sword(SwordStance::Offensive) => "common.abilities.sword.offensive_combo",
Stance::Sword(SwordStance::Crippling) => "common.abilities.sword.crippling_combo",
Stance::Sword(SwordStance::Cleaving) => "common.abilities.sword.cleaving_combo",
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 {
kind: BuffIconKind::Ability { ability_id: id },
is_buff: true,
end_time: None,
})
}
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 alignments = ecs.read_storage::<comp::Alignment>();
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>();
// Check if there was a persistence load error of the skillset, and if so
@ -2230,7 +2235,7 @@ impl Hud {
&uids,
&inventories,
poises.maybe(),
(alignments.maybe(), is_mount.maybe(), &char_states),
(alignments.maybe(), is_mount.maybe(), stances.maybe()),
)
.join()
.filter(|t| {
@ -2253,7 +2258,7 @@ impl Hud {
uid,
inventory,
poise,
(alignment, is_mount, char_state),
(alignment, is_mount, stance),
)| {
// Use interpolated position if available
let pos = interpolated.map_or(pos.0, |i| i.pos);
@ -2304,7 +2309,7 @@ impl Hud {
} else {
0.0
},
char_state,
stance,
});
// 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)
@ -2849,7 +2854,6 @@ impl Hud {
let stats = ecs.read_storage::<comp::Stats>();
let skill_sets = ecs.read_storage::<comp::SkillSet>();
let buffs = ecs.read_storage::<comp::Buffs>();
let char_states = ecs.read_storage::<comp::CharacterState>();
let msm = ecs.read_resource::<MaterialStatManifest>();
let time = ecs.read_resource::<Time>();
@ -2968,6 +2972,7 @@ impl Hud {
let poises = ecs.read_storage::<comp::Poise>();
let combos = ecs.read_storage::<comp::Combo>();
let time = ecs.read_resource::<Time>();
let stances = ecs.read_storage::<comp::Stance>();
// Combo floater stuffs
self.floaters.combo_floater = self.floaters.combo_floater.map(|mut f| {
f.timer -= dt.as_secs_f64();
@ -2990,7 +2995,7 @@ impl Hud {
skillsets.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(
client,
&info,
@ -3018,7 +3023,6 @@ impl Hud {
self.floaters.combo_floater,
context,
combos.get(entity),
char_states.get(entity),
)
.set(self.ids.skillbar, ui_widgets)
{
@ -3138,11 +3142,10 @@ impl Hud {
}
// 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),
healths.get(entity),
energies.get(entity),
char_states.get(entity),
) {
for event in BuffsBar::new(
&self.imgs,
@ -3151,7 +3154,7 @@ impl Hud {
tooltip_manager,
i18n,
player_buffs,
char_state,
stances.get(entity),
self.pulse,
global_state,
health,
@ -3463,7 +3466,7 @@ impl Hud {
bodies.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(
&self.show,
client,

View File

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

View File

@ -30,8 +30,7 @@ use common::comp::{
ItemDesc, MaterialStatManifest,
},
skillset::SkillGroupKind,
Ability, ActiveAbilities, Body, CharacterState, Combo, Energy, Health, Inventory, Poise,
PoiseState, SkillSet,
Ability, ActiveAbilities, Body, Combo, Energy, Health, Inventory, Poise, PoiseState, SkillSet,
};
use conrod_core::{
color,
@ -313,7 +312,6 @@ pub struct Skillbar<'a> {
combo_floater: Option<ComboFloater>,
context: Option<AbilityContext>,
combo: Option<&'a Combo>,
char_state: Option<&'a CharacterState>,
}
impl<'a> Skillbar<'a> {
@ -345,7 +343,6 @@ impl<'a> Skillbar<'a> {
combo_floater: Option<ComboFloater>,
context: Option<AbilityContext>,
combo: Option<&'a Combo>,
char_state: Option<&'a CharacterState>,
) -> Self {
Self {
client,
@ -375,7 +372,6 @@ impl<'a> Skillbar<'a> {
combo_floater,
context,
combo,
char_state,
}
}
@ -1088,18 +1084,6 @@ impl<'a> Skillbar<'a> {
.active_abilities
.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) =
util::ability_description(primary_ability_id.unwrap_or(""), self.localized_strings);

View File

@ -1,7 +1,6 @@
use super::img_ids;
use common::{
comp::{
ability::{AbilityKind, SwordStance},
inventory::trade_pricing::TradePricing,
item::{
armor::{Armor, ArmorKind, Protection},
@ -431,17 +430,3 @@ pub fn ability_description<'a>(
(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,
item::{tool::AbilityContext, Hands, ItemKind, ToolKind},
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,
mounting::Rider,
@ -747,7 +747,7 @@ impl FigureMgr {
item,
light_emitter,
is_rider,
collider,
(collider, stance),
),
) in (
&ecs.entities(),
@ -765,7 +765,10 @@ impl FigureMgr {
ecs.read_storage::<Item>().maybe(),
ecs.read_storage::<LightEmitter>().maybe(),
ecs.read_storage::<Is<Rider>>().maybe(),
ecs.read_storage::<Collider>().maybe(),
(
ecs.read_storage::<Collider>().maybe(),
ecs.read_storage::<Stance>().maybe(),
),
)
.join()
.enumerate()
@ -917,7 +920,7 @@ impl FigureMgr {
let second_tool_spec = second_tool_spec.as_deref();
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| {
c.ability_info()