Make abilities depend on weapon

This commit is contained in:
timokoesters 2020-03-14 16:40:29 +01:00
parent ac1c279a9b
commit fe19698d52
12 changed files with 228 additions and 223 deletions

View File

@ -1,10 +1,12 @@
use crate::comp::CharacterState;
use specs::{Component, DenseVecStorage, FlaggedStorage, HashMapStorage};
use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub enum AbilityState {
pub enum CharacterAbility {
BasicAttack {
/// Amount of energy required to use ability
cost: i32,
buildup_duration: Duration,
recover_duration: Duration,
},
BasicBlock,
Roll,
@ -14,30 +16,43 @@ pub enum AbilityState {
cost: i32,
},
}
impl Default for AbilityState {
fn default() -> Self { Self::BasicAttack { cost: -100 } }
}
impl Component for AbilityState {
impl Component for CharacterAbility {
type Storage = DenseVecStorage<Self>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)]
pub struct AbilityPool {
pub primary: Option<AbilityState>,
pub secondary: Option<AbilityState>,
pub block: Option<AbilityState>,
pub dodge: Option<AbilityState>,
pub primary: Option<CharacterAbility>,
pub secondary: Option<CharacterAbility>,
pub block: Option<CharacterAbility>,
pub dodge: Option<CharacterAbility>,
}
impl Default for AbilityPool {
fn default() -> Self {
Self {
primary: Some(AbilityState::default()),
// primary: Some(AbilityState::TimedCombo),
secondary: Some(AbilityState::BasicBlock),
block: None,
dodge: Some(AbilityState::Roll),
impl From<CharacterAbility> for CharacterState {
fn from(ability: CharacterAbility) -> Self {
match ability {
CharacterAbility::BasicAttack {
buildup_duration,
recover_duration,
} => CharacterState::BasicAttack {
exhausted: false,
buildup_duration,
recover_duration,
},
CharacterAbility::BasicBlock { .. } => CharacterState::BasicBlock {},
CharacterAbility::Roll { .. } => CharacterState::Roll {
remaining_duration: Duration::from_millis(600),
},
CharacterAbility::ChargeAttack { .. } => CharacterState::ChargeAttack {
remaining_duration: Duration::from_millis(600),
},
CharacterAbility::TimedCombo { .. } => CharacterState::TimedCombo {
stage: 1,
stage_time_active: Duration::default(),
stage_exhausted: false,
can_transition: false,
},
}
}
}

View File

@ -19,8 +19,8 @@ pub struct StateUpdate {
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub enum CharacterState {
Idle {},
Climb {},
Idle,
Climb,
Sit {},
Equipping {
/// The weapon being equipped
@ -32,16 +32,18 @@ pub enum CharacterState {
/// The weapon being wielded
tool: ToolData,
},
Glide {},
Glide,
/// A basic attacking state
BasicAttack {
/// How long the state has until exiting
remaining_duration: Duration,
/// How long till the state deals damage
buildup_duration: Duration,
/// How long till the state remains after dealing damage
recover_duration: Duration,
/// Whether the attack can deal more damage
exhausted: bool,
},
/// A basic blocking state
BasicBlock {},
BasicBlock,
ChargeAttack {
/// How long the state has until exiting
remaining_duration: Duration,
@ -53,8 +55,6 @@ pub enum CharacterState {
/// A three-stage attack where play must click at appropriate times
/// to continue attack chain.
TimedCombo {
/// The tool this state will read to handle damage, etc.
tool: ToolData,
/// `int` denoting what stage (of 3) the attack is in.
stage: i8,
/// How long current stage has been active

View File

@ -1,6 +1,6 @@
use crate::{
assets::{self, Asset},
comp::AbilityState,
comp::CharacterAbility,
effect::Effect,
terrain::{Block, BlockKind},
};
@ -25,47 +25,55 @@ pub enum ToolKind {
Dagger,
Staff,
Shield,
Debug(Debug),
}
impl Default for ToolKind {
fn default() -> Self { Self::Axe }
Debug(DebugKind),
}
impl ToolData {
pub fn equip_time(&self) -> Duration { Duration::from_millis(self.equip_time_millis) }
pub fn attack_buildup_duration(&self) -> Duration {
Duration::from_millis(self.attack_buildup_millis)
}
pub fn attack_recover_duration(&self) -> Duration {
Duration::from_millis(self.attack_recover_millis)
}
pub fn attack_duration(&self) -> Duration {
self.attack_buildup_duration() + self.attack_recover_duration()
}
pub fn get_primary_abilities(&self) -> Vec<AbilityState> {
use AbilityState::*;
use SwordKind::*;
pub fn get_abilities(&self) -> Vec<CharacterAbility> {
use CharacterAbility::*;
use DebugKind::*;
use ToolKind::*;
let default_return = vec![AbilityState::default()];
match self.kind {
Sword(kind) => match kind {
Rapier => vec![TimedCombo { cost: -150 }],
Scimitar => default_return,
Sword(_) => vec![TimedCombo { cost: -150 }],
Axe => vec![BasicAttack {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),
}],
Hammer => vec![BasicAttack {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),
}],
Bow => vec![BasicAttack {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),
}],
Dagger => vec![BasicAttack {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),
}],
Staff => vec![BasicAttack {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),
}],
Shield => vec![BasicAttack {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),
}],
Debug(kind) => match kind {
Boost => vec![],
Possess => vec![],
},
_ => default_return,
_ => vec![],
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Debug {
pub enum DebugKind {
Boost,
Possess,
}
@ -109,7 +117,7 @@ pub enum Ingredient {
Grass,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ToolData {
pub kind: ToolKind,
equip_time_millis: u64,

View File

@ -1,7 +1,7 @@
pub mod item;
// Reexports
pub use item::{Consumable, Debug, Item, ItemKind, SwordKind, ToolData, ToolKind};
pub use item::{Consumable, DebugKind, Item, ItemKind, SwordKind, ToolData, ToolKind};
use crate::assets;
use specs::{Component, FlaggedStorage, HashMapStorage};

View File

@ -16,7 +16,7 @@ mod stats;
mod visual;
// Reexports
pub use ability::{AbilityPool, AbilityState};
pub use ability::{AbilityPool, CharacterAbility};
pub use admin::Admin;
pub use agent::{Agent, Alignment};
pub use body::{

View File

@ -21,7 +21,7 @@ sum_type! {
Mass(comp::Mass),
Gravity(comp::Gravity),
Sticky(comp::Sticky),
AbilityState(comp::AbilityState),
CharacterAbility(comp::CharacterAbility),
AbilityPool(comp::AbilityPool),
Attacking(comp::Attacking),
CharacterState(comp::CharacterState),
@ -45,7 +45,7 @@ sum_type! {
Mass(PhantomData<comp::Mass>),
Gravity(PhantomData<comp::Gravity>),
Sticky(PhantomData<comp::Sticky>),
AbilityState(PhantomData<comp::AbilityState>),
CharacterAbility(PhantomData<comp::CharacterAbility>),
AbilityPool(PhantomData<comp::AbilityPool>),
Attacking(PhantomData<comp::Attacking>),
CharacterState(PhantomData<comp::CharacterState>),
@ -69,7 +69,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::AbilityState(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CharacterAbility(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::AbilityPool(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Attacking(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world),
@ -91,7 +91,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::AbilityState(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CharacterAbility(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::AbilityPool(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Attacking(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world),
@ -115,8 +115,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
EcsCompPhantom::AbilityState(_) => {
sync::handle_remove::<comp::AbilityState>(entity, world)
EcsCompPhantom::CharacterAbility(_) => {
sync::handle_remove::<comp::CharacterAbility>(entity, world)
},
EcsCompPhantom::AbilityPool(_) => {
sync::handle_remove::<comp::AbilityPool>(entity, world)

View File

@ -107,7 +107,7 @@ impl State {
ecs.register_sync_marker();
// Register server -> all clients synced components.
ecs.register::<comp::AbilityPool>();
ecs.register::<comp::AbilityState>();
ecs.register::<comp::CharacterAbility>();
ecs.register::<comp::Projectile>();
ecs.register::<comp::Body>();
ecs.register::<comp::Player>();

View File

@ -18,51 +18,65 @@ pub fn behavior(data: &JoinData) -> StateUpdate {
if let CharacterState::BasicAttack {
exhausted,
remaining_duration,
buildup_duration,
recover_duration,
} = data.character
{
let tool_kind = data.stats.equipment.main.as_ref().map(|i| i.kind);
if let Some(Tool(tool)) = tool_kind {
handle_move(data, &mut update);
handle_move(data, &mut update);
let mut new_exhausted = *exhausted;
if !*exhausted && *remaining_duration < tool.attack_recover_duration() {
if buildup_duration != &Duration::default() {
// Start to swing
update.character = CharacterState::BasicAttack {
buildup_duration: buildup_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
recover_duration: *recover_duration,
exhausted: false,
};
} else if !*exhausted {
// Swing hits
if let Some(Tool(tool)) = tool_kind {
data.updater.insert(data.entity, Attacking {
weapon: Some(tool),
applied: false,
hit_count: 0,
});
new_exhausted = true;
}
let new_remaining_duration = remaining_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default();
if let Some(attack) = data.attacking {
if attack.applied && attack.hit_count > 0 {
data.updater.remove::<Attacking>(data.entity);
update.energy.change_by(100, EnergySource::HitEnemy);
}
}
// Tick down
update.character = CharacterState::BasicAttack {
remaining_duration: new_remaining_duration,
exhausted: new_exhausted,
buildup_duration: *buildup_duration,
recover_duration: *recover_duration,
exhausted: true,
};
// Check if attack duration has expired
if new_remaining_duration == Duration::default() {
} else if recover_duration != &Duration::default() {
// Recover from swing
update.character = CharacterState::BasicAttack {
buildup_duration: *buildup_duration,
recover_duration: recover_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
exhausted: true,
}
} else {
// Done
if let Some(Tool(tool)) = tool_kind {
update.character = CharacterState::Wielding { tool };
data.updater.remove::<Attacking>(data.entity);
} else {
update.character = CharacterState::Idle;
}
update
} else {
update
}
// More handling
if let Some(attack) = data.attacking {
if attack.applied && attack.hit_count > 0 {
data.updater.remove::<Attacking>(data.entity);
update.energy.change_by(100, EnergySource::HitEnemy);
}
}
update
} else {
update.character = CharacterState::Idle {};
update

View File

@ -17,77 +17,81 @@ pub fn behavior(data: &JoinData) -> StateUpdate {
};
if let CharacterState::TimedCombo {
tool,
stage,
stage_time_active,
stage_exhausted,
can_transition,
} = data.character
{
let mut new_can_transition = *can_transition;
let mut new_stage_exhausted = *stage_exhausted;
let new_stage_time_active = stage_time_active
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or(Duration::default());
// Sorry adam, I don't want to fix this rn, check out basic_attack to
// see how to do it
//
/*
let mut new_can_transition = *can_transition;
let mut new_stage_exhausted = *stage_exhausted;
let new_stage_time_active = stage_time_active
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or(Duration::default());
match stage {
1 => {
if new_stage_time_active > tool.attack_buildup_duration() {
if !*stage_exhausted {
// Try to deal damage
data.updater.insert(data.entity, Attacking {
weapon: Some(*tool),
applied: false,
hit_count: 0,
});
new_stage_exhausted = true;
} else {
// Make sure to remove Attacking component
data.updater.remove::<Attacking>(data.entity);
match stage {
1 => {
if new_stage_time_active > tool.attack_buildup_duration() {
if !*stage_exhausted {
// Try to deal damage
data.updater.insert(data.entity, Attacking {
weapon: Some(*tool),
applied: false,
hit_count: 0,
});
new_stage_exhausted = true;
} else {
// Make sure to remove Attacking component
data.updater.remove::<Attacking>(data.entity);
}
// Check if player has timed click right
if data.inputs.primary.is_just_pressed() {
println!("Can transition");
new_can_transition = true;
}
}
// Check if player has timed click right
if data.inputs.primary.is_just_pressed() {
println!("Can transition");
new_can_transition = true;
}
}
if new_stage_time_active > tool.attack_duration() {
if new_can_transition {
update.character = CharacterState::TimedCombo {
tool: *tool,
stage: 2,
stage_time_active: Duration::default(),
stage_exhausted: false,
can_transition: false,
if new_stage_time_active > tool.attack_duration() {
if new_can_transition {
update.character = CharacterState::TimedCombo {
tool: *tool,
stage: 2,
stage_time_active: Duration::default(),
stage_exhausted: false,
can_transition: false,
}
} else {
println!("Failed");
attempt_wield(data, &mut update);
}
} else {
println!("Failed");
attempt_wield(data, &mut update);
update.character = CharacterState::TimedCombo {
tool: *tool,
stage: 1,
stage_time_active: new_stage_time_active,
stage_exhausted: new_stage_exhausted,
can_transition: new_can_transition,
}
}
} else {
update.character = CharacterState::TimedCombo {
tool: *tool,
stage: 1,
stage_time_active: new_stage_time_active,
stage_exhausted: new_stage_exhausted,
can_transition: new_can_transition,
}
}
},
2 => {
println!("2");
attempt_wield(data, &mut update);
},
3 => {
println!("3");
attempt_wield(data, &mut update);
},
_ => {
// Should never get here.
},
}
},
2 => {
println!("2");
attempt_wield(data, &mut update);
},
3 => {
println!("3");
attempt_wield(data, &mut update);
},
_ => {
// Should never get here.
},
}
*/
}
update

View File

@ -1,5 +1,5 @@
use crate::{
comp::{AbilityState, CharacterState, EnergySource, ItemKind::Tool, StateUpdate, ToolData},
comp::{CharacterAbility, CharacterState, EnergySource, ItemKind::Tool, StateUpdate, ToolData},
event::LocalEvent,
sys::{character_behavior::JoinData, phys::GRAVITY},
};
@ -191,8 +191,8 @@ pub fn handle_primary_input(data: &JoinData, update: &mut StateUpdate) {
/// Attempts to go into `ability_pool.primary` if is `Some()` on `AbilityPool`
pub fn attempt_primary_ability(data: &JoinData, update: &mut StateUpdate) {
if let Some(ability_state) = data.ability_pool.primary {
update.character = ability_to_character_state(data, update, ability_state);
if let Some(ability) = data.ability_pool.primary {
update.character = ability.into();
}
}
@ -201,15 +201,15 @@ pub fn attempt_primary_ability(data: &JoinData, update: &mut StateUpdate) {
pub fn handle_secondary_input(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.secondary.is_pressed() {
if let CharacterState::Wielding { .. } = update.character {
attempt_seconday_ability(data, update);
attempt_secondary_ability(data, update);
}
}
}
/// Attempts to go into `ability_pool.secondary` if is `Some()` on `AbilityPool`
pub fn attempt_seconday_ability(data: &JoinData, update: &mut StateUpdate) {
if let Some(ability_state) = data.ability_pool.secondary {
update.character = ability_to_character_state(data, update, ability_state);
pub fn attempt_secondary_ability(data: &JoinData, update: &mut StateUpdate) {
if let Some(ability) = data.ability_pool.secondary {
update.character = ability.into();
}
}
@ -232,63 +232,7 @@ pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) {
}
pub fn attempt_dodge_ability(data: &JoinData, update: &mut StateUpdate) {
if let Some(ability_state) = data.ability_pool.dodge {
update.character = ability_to_character_state(data, update, ability_state);
}
}
// TODO: Might need a fn `CharacterState::new(data, update)` if
// initialization gets too lengthy.
/// Maps from `AbilityState`s to `CharacterStates`s. Also handles intializing
/// the new `CharacterState`
pub fn ability_to_character_state(
data: &JoinData,
update: &mut StateUpdate,
ability_state: AbilityState,
) -> CharacterState {
match ability_state {
AbilityState::BasicAttack { cost, .. } => {
if let Some(tool) = unwrap_tool_data(data) {
if update.energy.try_change_by(cost, EnergySource::HitEnemy).is_ok() {
return CharacterState::BasicAttack {
exhausted: false,
remaining_duration: tool.attack_duration(),
};
}
}
*data.character
},
AbilityState::BasicBlock { .. } => CharacterState::BasicBlock {},
AbilityState::Roll { .. } => CharacterState::Roll {
remaining_duration: Duration::from_millis(600),
},
AbilityState::ChargeAttack { .. } => CharacterState::ChargeAttack {
remaining_duration: Duration::from_millis(600),
},
AbilityState::TimedCombo { .. } => {
if let Some(tool) = unwrap_tool_data(data) {
CharacterState::TimedCombo {
tool,
stage: 1,
stage_time_active: Duration::default(),
stage_exhausted: false,
can_transition: false,
}
} else {
*data.character
}
},
// Do not use default match
// _ => *data.character
}
}
pub fn unwrap_tool_data(data: &JoinData) -> Option<ToolData> {
if let Some(Tool(tool)) = data.stats.equipment.main.as_ref().map(|i| i.kind) {
Some(tool)
} else {
None
if let Some(ability) = data.ability_pool.dodge {
update.character = ability.into();
}
}

View File

@ -272,7 +272,7 @@ impl Server {
let spawn_point = state.ecs().read_resource::<SpawnPoint>().0;
state.write_component(entity, body);
state.write_component(entity, comp::Stats::new(name, body, main));
state.write_component(entity, comp::Stats::new(name, body, main.clone()));
state.write_component(entity, comp::Energy::new(1000));
state.write_component(entity, comp::Controller::default());
state.write_component(entity, comp::Pos(spawn_point));
@ -286,7 +286,27 @@ impl Server {
entity,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()),
);
state.write_component(entity, comp::AbilityPool::default());
state.write_component(
entity,
if let Some(comp::Item {
kind: comp::ItemKind::Tool(tool),
..
}) = main
{
let mut abilities = tool.get_abilities();
let mut ability_drain = abilities.drain(..);
comp::AbilityPool {
primary: ability_drain.next(),
secondary: ability_drain.next(),
block: Some(comp::CharacterAbility::BasicBlock),
dodge: Some(comp::CharacterAbility::Roll),
}
} else {
comp::AbilityPool::default()
},
);
// Make sure physics are accepted.
state.write_component(entity, comp::ForceUpdate);

View File

@ -10,7 +10,7 @@ use crate::{
use common::{
assets::load_expect,
comp::{
item::{Debug, ToolData, ToolKind},
item::{DebugKind, ToolData, ToolKind},
CharacterState, ControllerInputs, Energy, ItemKind, Stats,
},
};
@ -597,7 +597,7 @@ impl<'a> Widget for Skillbar<'a> {
ToolKind::Axe => self.imgs.twohaxe_m1,
ToolKind::Bow => self.imgs.bow_m1,
ToolKind::Staff => self.imgs.staff_m1,
ToolKind::Debug(Debug::Boost) => self.imgs.flyingrod_m1,
ToolKind::Debug(DebugKind::Boost) => self.imgs.flyingrod_m1,
_ => self.imgs.twohaxe_m1,
},
_ => self.imgs.twohaxe_m1,
@ -690,7 +690,7 @@ impl<'a> Widget for Skillbar<'a> {
ToolKind::Axe => self.imgs.twohaxe_m2,
ToolKind::Bow => self.imgs.bow_m2,
ToolKind::Staff => self.imgs.staff_m2,
ToolKind::Debug(Debug::Boost) => self.imgs.flyingrod_m2,
ToolKind::Debug(DebugKind::Boost) => self.imgs.flyingrod_m2,
_ => self.imgs.twohaxe_m2,
},
_ => self.imgs.twohaxe_m2,