transform character state

This commit is contained in:
crabman 2024-02-11 13:46:32 +01:00
parent b9a3fa1edc
commit 036e79284e
No known key found for this signature in database
9 changed files with 227 additions and 4 deletions

View File

@ -930,6 +930,7 @@
secondary: Simple(None, "common.abilities.debug.upboost"), secondary: Simple(None, "common.abilities.debug.upboost"),
abilities: [ abilities: [
Simple(None, "common.abilities.debug.possess"), Simple(None, "common.abilities.debug.possess"),
Simple(None, "common.abilities.debug.evolve"),
], ],
), ),
Tool(Farming): ( Tool(Farming): (

View File

@ -0,0 +1,6 @@
Transform(
buildup_duration: 2.0,
recover_duration: 0.5,
target: "common.entity.wild.peaceful.crab",
specifier: Some(Evolve),
)

View File

@ -1,5 +1,7 @@
common-abilities-debug-possess = Possessing Arrow common-abilities-debug-possess = Possessing Arrow
.desc = Shoots a poisonous arrow. Lets you control your target. .desc = Shoots a poisonous arrow. Lets you control your target.
common-abilities-debug-evolve = Evolve
.desc = You become your better self.
common-abilities-hammer-leap = Smash of Doom common-abilities-hammer-leap = Smash of Doom
.desc = An AOE attack with knockback. Leaps to position of cursor. .desc = An AOE attack with knockback. Leaps to position of cursor.
common-abilities-bow-shotgun = Burst common-abilities-bow-shotgun = Burst

View File

@ -646,6 +646,7 @@ impl From<&CharacterState> for CharacterAbilityType {
| CharacterState::UseItem(_) | CharacterState::UseItem(_)
| CharacterState::SpriteInteract(_) | CharacterState::SpriteInteract(_)
| CharacterState::Skate(_) | CharacterState::Skate(_)
| CharacterState::Transform(_)
| CharacterState::Wallrun(_) => Self::Other, | CharacterState::Wallrun(_) => Self::Other,
} }
} }
@ -997,6 +998,15 @@ pub enum CharacterAbility {
#[serde(default)] #[serde(default)]
meta: AbilityMeta, meta: AbilityMeta,
}, },
Transform {
buildup_duration: f32,
recover_duration: f32,
target: String,
#[serde(default)]
specifier: Option<transform::FrontendSpecifier>,
#[serde(default)]
meta: AbilityMeta,
},
} }
impl Default for CharacterAbility { impl Default for CharacterAbility {
@ -1115,7 +1125,8 @@ impl CharacterAbility {
| CharacterAbility::Blink { .. } | CharacterAbility::Blink { .. }
| CharacterAbility::Music { .. } | CharacterAbility::Music { .. }
| CharacterAbility::BasicSummon { .. } | CharacterAbility::BasicSummon { .. }
| CharacterAbility::SpriteSummon { .. } => true, | CharacterAbility::SpriteSummon { .. }
| CharacterAbility::Transform { .. } => true,
} }
} }
@ -1662,6 +1673,16 @@ impl CharacterAbility {
*energy_cost /= stats.energy_efficiency; *energy_cost /= stats.energy_efficiency;
*melee_constructor = melee_constructor.adjusted_by_stats(stats); *melee_constructor = melee_constructor.adjusted_by_stats(stats);
}, },
Transform {
ref mut buildup_duration,
ref mut recover_duration,
target: _,
specifier: _,
meta: _,
} => {
*buildup_duration /= stats.speed;
*recover_duration /= stats.speed;
},
} }
self self
} }
@ -1702,7 +1723,8 @@ impl CharacterAbility {
| Blink { .. } | Blink { .. }
| Music { .. } | Music { .. }
| BasicSummon { .. } | BasicSummon { .. }
| SpriteSummon { .. } => 0.0, | SpriteSummon { .. }
| Transform { .. } => 0.0,
} }
} }
@ -1750,7 +1772,8 @@ impl CharacterAbility {
| Blink { .. } | Blink { .. }
| Music { .. } | Music { .. }
| BasicSummon { .. } | BasicSummon { .. }
| SpriteSummon { .. } => 0, | SpriteSummon { .. }
| Transform { .. } => 0,
} }
} }
@ -1782,7 +1805,8 @@ impl CharacterAbility {
| Music { meta, .. } | Music { meta, .. }
| DiveMelee { meta, .. } | DiveMelee { meta, .. }
| RiposteMelee { meta, .. } | RiposteMelee { meta, .. }
| RapidMelee { meta, .. } => *meta, | RapidMelee { meta, .. }
| Transform { meta, .. } => *meta,
} }
} }
@ -2935,6 +2959,23 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,
exhausted: false, exhausted: false,
}), }),
CharacterAbility::Transform {
buildup_duration,
recover_duration,
target,
specifier,
meta: _,
} => CharacterState::Transform(transform::Data {
static_data: transform::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
specifier: *specifier,
target: target.to_owned(),
ability_info,
},
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
} }
} }
} }

View File

@ -51,6 +51,7 @@ event_emitters! {
energy_change: event::EnergyChangeEvent, energy_change: event::EnergyChangeEvent,
knockback: event::KnockbackEvent, knockback: event::KnockbackEvent,
sprite_light: event::ToggleSpriteLightEvent, sprite_light: event::ToggleSpriteLightEvent,
transform: event::TransformEvent,
} }
} }
@ -172,6 +173,8 @@ pub enum CharacterState {
/// A series of consecutive, identical attacks that only go through buildup /// A series of consecutive, identical attacks that only go through buildup
/// and recover once for the entire state /// and recover once for the entire state
RapidMelee(rapid_melee::Data), RapidMelee(rapid_melee::Data),
/// Transforms an entity into another
Transform(transform::Data),
} }
impl CharacterState { impl CharacterState {
@ -518,6 +521,7 @@ impl CharacterState {
CharacterState::DiveMelee(data) => data.behavior(j, output_events), CharacterState::DiveMelee(data) => data.behavior(j, output_events),
CharacterState::RiposteMelee(data) => data.behavior(j, output_events), CharacterState::RiposteMelee(data) => data.behavior(j, output_events),
CharacterState::RapidMelee(data) => data.behavior(j, output_events), CharacterState::RapidMelee(data) => data.behavior(j, output_events),
CharacterState::Transform(data) => data.behavior(j, output_events),
} }
} }
@ -573,6 +577,7 @@ impl CharacterState {
CharacterState::DiveMelee(data) => data.handle_event(j, output_events, action), CharacterState::DiveMelee(data) => data.handle_event(j, output_events, action),
CharacterState::RiposteMelee(data) => data.handle_event(j, output_events, action), CharacterState::RiposteMelee(data) => data.handle_event(j, output_events, action),
CharacterState::RapidMelee(data) => data.handle_event(j, output_events, action), CharacterState::RapidMelee(data) => data.handle_event(j, output_events, action),
CharacterState::Transform(data) => data.handle_event(j, output_events, action),
} }
} }
@ -625,6 +630,7 @@ impl CharacterState {
CharacterState::DiveMelee(data) => Some(data.static_data.ability_info), CharacterState::DiveMelee(data) => Some(data.static_data.ability_info),
CharacterState::RiposteMelee(data) => Some(data.static_data.ability_info), CharacterState::RiposteMelee(data) => Some(data.static_data.ability_info),
CharacterState::RapidMelee(data) => Some(data.static_data.ability_info), CharacterState::RapidMelee(data) => Some(data.static_data.ability_info),
CharacterState::Transform(data) => Some(data.static_data.ability_info),
} }
} }
@ -669,6 +675,7 @@ impl CharacterState {
CharacterState::DiveMelee(data) => Some(data.stage_section), CharacterState::DiveMelee(data) => Some(data.stage_section),
CharacterState::RiposteMelee(data) => Some(data.stage_section), CharacterState::RiposteMelee(data) => Some(data.stage_section),
CharacterState::RapidMelee(data) => Some(data.stage_section), CharacterState::RapidMelee(data) => Some(data.stage_section),
CharacterState::Transform(data) => Some(data.stage_section),
} }
} }
@ -857,6 +864,11 @@ impl CharacterState {
recover: Some(data.static_data.recover_duration), recover: Some(data.static_data.recover_duration),
..Default::default() ..Default::default()
}), }),
CharacterState::Transform(data) => Some(DurationsInfo {
buildup: Some(data.static_data.buildup_duration),
recover: Some(data.static_data.recover_duration),
..Default::default()
}),
} }
} }
@ -901,6 +913,7 @@ impl CharacterState {
CharacterState::DiveMelee(data) => Some(data.timer), CharacterState::DiveMelee(data) => Some(data.timer),
CharacterState::RiposteMelee(data) => Some(data.timer), CharacterState::RiposteMelee(data) => Some(data.timer),
CharacterState::RapidMelee(data) => Some(data.timer), CharacterState::RapidMelee(data) => Some(data.timer),
CharacterState::Transform(data) => Some(data.timer),
} }
} }
@ -960,6 +973,7 @@ impl CharacterState {
CharacterState::DiveMelee(_) => Some(AttackSource::Melee), CharacterState::DiveMelee(_) => Some(AttackSource::Melee),
CharacterState::RiposteMelee(_) => Some(AttackSource::Melee), CharacterState::RiposteMelee(_) => Some(AttackSource::Melee),
CharacterState::RapidMelee(_) => Some(AttackSource::Melee), CharacterState::RapidMelee(_) => Some(AttackSource::Melee),
CharacterState::Transform(_) => None,
} }
} }
} }

View File

@ -35,6 +35,7 @@ pub mod sprite_interact;
pub mod sprite_summon; pub mod sprite_summon;
pub mod stunned; pub mod stunned;
pub mod talk; pub mod talk;
pub mod transform;
pub mod use_item; pub mod use_item;
pub mod utils; pub mod utils;
pub mod wallrun; pub mod wallrun;

View File

@ -0,0 +1,125 @@
use std::time::Duration;
use common_assets::AssetExt;
use rand::thread_rng;
use serde::{Deserialize, Serialize};
use vek::Vec3;
use crate::{
comp::{item::Reagent, CharacterState, StateUpdate},
event::TransformEvent,
generation::{EntityConfig, EntityInfo},
states::utils::{end_ability, tick_attack_or_default},
};
use super::{
behavior::CharacterBehavior,
utils::{AbilityInfo, StageSection},
};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum FrontendSpecifier {
Evolve,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
/// How long until state has until transformation
pub buildup_duration: Duration,
/// How long the state has until exiting
pub recover_duration: Duration,
/// The entity configuration you will be transformed into
pub target: String,
pub ability_info: AbilityInfo,
/// Used to specify the transformation to the frontend
pub specifier: Option<FrontendSpecifier>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// Struct containing data that does not change over the course of the
/// character state
pub static_data: StaticData,
/// Timer for each stage
pub timer: Duration,
/// What section the character stage is in
pub stage_section: StageSection,
}
impl CharacterBehavior for Data {
fn behavior(
&self,
data: &super::behavior::JoinData,
output_events: &mut crate::comp::character_state::OutputEvents,
) -> crate::comp::StateUpdate {
let mut update = StateUpdate::from(data);
match self.stage_section {
StageSection::Buildup => {
// Tick the timer as long as buildup hasn't finihsed
if self.timer < self.static_data.buildup_duration {
update.character = CharacterState::Transform(Data {
static_data: self.static_data.clone(),
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
// Buildup finished, start transformation
} else {
let Ok(entity_config) = EntityConfig::load(&self.static_data.target) else {
end_ability(data, &mut update);
return update;
};
let entity_info = EntityInfo::at(Vec3::zero()).with_entity_config(
entity_config.read().clone(),
Some(&self.static_data.target),
&mut thread_rng(),
None,
);
// Handle frontend events
if let Some(specifier) = self.static_data.specifier {
match specifier {
FrontendSpecifier::Evolve => {
output_events.emit_local(crate::event::LocalEvent::CreateOutcome(
crate::outcome::Outcome::Explosion {
pos: data.pos.0,
power: 5.0,
radius: 2.0,
is_attack: false,
reagent: Some(Reagent::White),
},
))
},
}
}
output_events.emit_server(TransformEvent(*data.uid, entity_info));
update.character = CharacterState::Transform(Data {
static_data: self.static_data.clone(),
timer: Duration::default(),
stage_section: StageSection::Recover,
});
}
},
StageSection::Recover => {
// Wait for recovery period to finish
if self.timer < self.static_data.recover_duration {
update.character = CharacterState::Transform(Data {
static_data: self.static_data.clone(),
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
} else {
// End the ability after recovery is done
end_ability(data, &mut update);
}
},
_ => {
// If we somehow ended up in an incorrect character state, end the ability
end_ability(data, &mut update);
},
}
update
}
}

View File

@ -209,6 +209,7 @@ impl<'a> System<'a> for Sys {
| CharacterState::Stunned(_) | CharacterState::Stunned(_)
| CharacterState::BasicBlock(_) | CharacterState::BasicBlock(_)
| CharacterState::UseItem(_) | CharacterState::UseItem(_)
| CharacterState::Transform(_)
| CharacterState::SpriteInteract(_) => {}, | CharacterState::SpriteInteract(_) => {},
} }
}); });

View File

@ -1384,6 +1384,38 @@ impl ParticleMgr {
}); });
} }
}, },
CharacterState::Transform(data) => {
if matches!(data.stage_section, StageSection::Buildup)
&& let Some(specifier) = data.static_data.specifier
{
match specifier {
states::transform::FrontendSpecifier::Evolve => {
self.particles.resize_with(
self.particles.len()
+ usize::from(
self.scheduler.heartbeats(Duration::from_millis(10)),
),
|| {
let start_pos = interpolated.pos
+ (Vec2::unit_y()
* rng.gen::<f32>()
* body.max_radius())
.rotated_z(rng.gen_range(0.0..(PI * 2.0)))
.with_z(body.height() * rng.gen::<f32>());
Particle::new_directed(
Duration::from_millis(100),
time,
ParticleMode::BarrelOrgan,
start_pos,
start_pos + Vec3::unit_z() * 2.0,
)
},
)
},
}
}
},
_ => {}, _ => {},
} }
} }