@@ -60,6 +60,7 @@ impl Plugin for FighterStatePlugin {
6060 . after ( FighterStateCollectSystems )
6161 . run_in_state ( GameState :: InGame )
6262 . with_system ( transition_from_idle)
63+ . with_system ( transition_from_chain)
6364 . with_system ( transition_from_flopping)
6465 . with_system ( transition_from_punching)
6566 . with_system ( transition_from_ground_slam)
@@ -75,6 +76,7 @@ impl Plugin for FighterStatePlugin {
7576 ConditionSet :: new ( )
7677 . run_in_state ( GameState :: InGame )
7778 . with_system ( idling)
79+ . with_system ( chaining)
7880 . with_system ( flopping)
7981 . with_system ( punching)
8082 . with_system ( ground_slam)
@@ -238,7 +240,7 @@ impl Flopping {
238240 pub const ANIMATION : & ' static str = "attacking" ;
239241}
240242
241- /// Component indicating the player is punching
243+ /// Component indicating the player is performing a groundslam
242244#[ derive( Component , Reflect , Default , Debug ) ]
243245#[ component( storage = "SparseSet" ) ]
244246pub struct GroundSlam {
@@ -276,6 +278,23 @@ impl Punching {
276278 pub const ANIMATION : & ' static str = "attacking" ;
277279}
278280
281+ #[ derive( Component , Default , Reflect ) ]
282+ #[ component( storage = "SparseSet" ) ]
283+ pub struct Chaining {
284+ pub has_started : bool ,
285+ pub continue_chain : bool ,
286+ pub can_extend : bool ,
287+ pub transition_to_final : bool ,
288+ pub transition_to_idle : bool ,
289+ pub link : u32 ,
290+ }
291+ impl Chaining {
292+ pub const PRIORITY : i32 = 30 ;
293+ pub const ANIMATION : & ' static str = "chaining" ;
294+ pub const FOLLOWUP_ANIMATION : & ' static str = "followup" ;
295+ pub const LENGTH : u32 = 2 ;
296+ }
297+
279298#[ derive( Component , Reflect , Default , Debug ) ]
280299#[ component( storage = "SparseSet" ) ]
281300pub struct MeleeAttacking {
@@ -350,39 +369,61 @@ fn collect_player_actions(
350369 & Inventory ,
351370 & Stats ,
352371 Option < & Holding > ,
372+ Option < & mut Chaining > ,
353373 & AvailableAttacks ,
354374 ) ,
355375 With < Player > ,
356376 > ,
357377) {
358- for ( action_state, mut transition_intents, inventory, stats, holding, available_attacks) in
359- & mut players
378+ for (
379+ action_state,
380+ mut transition_intents,
381+ inventory,
382+ stats,
383+ holding,
384+ chaining,
385+ available_attacks,
386+ ) in & mut players
360387 {
361388 // Trigger attacks
362389 //TODO: can use flop attack again after input buffer/chaining
363390 if action_state. just_pressed ( PlayerAction :: Attack ) && holding. is_none ( ) {
364- match available_attacks. current_attack ( ) . name . as_str ( ) {
365- "punch" => transition_intents. push_back ( StateTransition :: new (
366- Flopping :: default ( ) ,
367- Flopping :: PRIORITY ,
368- false ,
369- ) ) ,
370- "flop" => transition_intents. push_back ( StateTransition :: new (
371- Flopping :: default ( ) ,
372- Flopping :: PRIORITY ,
373- false ,
374- ) ) ,
375- "melee" => transition_intents. push_back ( StateTransition :: new (
376- MeleeAttacking :: default ( ) ,
377- MeleeAttacking :: PRIORITY ,
378- false ,
379- ) ) ,
380- "projectile" => transition_intents. push_back ( StateTransition :: new (
381- Shooting :: default ( ) ,
382- Shooting :: PRIORITY ,
383- false ,
384- ) ) ,
385- _ => { }
391+ if chaining. is_none ( ) {
392+ match available_attacks. current_attack ( ) . name . as_str ( ) {
393+ "chain" => transition_intents. push_back ( StateTransition :: new (
394+ //need to construct a chain with correct inputs
395+ Chaining :: default ( ) ,
396+ Chaining :: PRIORITY ,
397+ false ,
398+ ) ) ,
399+ "punch" => transition_intents. push_back ( StateTransition :: new (
400+ Punching :: default ( ) ,
401+ Punching :: PRIORITY ,
402+ false ,
403+ ) ) ,
404+ "flop" => transition_intents. push_back ( StateTransition :: new (
405+ Flopping :: default ( ) ,
406+ Flopping :: PRIORITY ,
407+ false ,
408+ ) ) ,
409+ "melee" => transition_intents. push_back ( StateTransition :: new (
410+ MeleeAttacking :: default ( ) ,
411+ MeleeAttacking :: PRIORITY ,
412+ false ,
413+ ) ) ,
414+ "projectile" => transition_intents. push_back ( StateTransition :: new (
415+ Shooting :: default ( ) ,
416+ Shooting :: PRIORITY ,
417+ false ,
418+ ) ) ,
419+ _ => { }
420+ }
421+ //todo, change to pushing states and making it additive
422+ //move variable setting/continue_chain to exit condition
423+ } else if let Some ( mut chaining) = chaining {
424+ // if chaining.can_extend {
425+ chaining. continue_chain = true ;
426+ // }
386427 }
387428 }
388429
@@ -530,6 +571,37 @@ fn transition_from_punching(
530571 }
531572}
532573
574+ fn transition_from_chain (
575+ mut commands : Commands ,
576+ mut fighters : Query < ( Entity , & mut StateTransitionIntents , & mut Chaining ) > ,
577+ ) {
578+ ' entity: for ( entity, mut transition_intents, chain) in & mut fighters {
579+ // Transition to any higher priority states
580+ let current_state_removed = transition_intents
581+ . transition_to_higher_priority_states :: < Chaining > (
582+ entity,
583+ Chaining :: PRIORITY ,
584+ & mut commands,
585+ ) ;
586+
587+ // If our current state was removed, don't continue processing this fighter
588+ if current_state_removed {
589+ continue ' entity;
590+ }
591+
592+ // If we're done attacking
593+ if chain. transition_to_final {
594+ // Go back to idle
595+ commands
596+ . entity ( entity)
597+ . remove :: < Chaining > ( )
598+ . insert ( Flopping :: default ( ) ) ;
599+ } else if chain. transition_to_idle {
600+ commands. entity ( entity) . remove :: < Chaining > ( ) . insert ( Idling ) ;
601+ }
602+ }
603+ }
604+
533605fn transition_from_ground_slam (
534606 mut commands : Commands ,
535607 mut fighters : Query < ( Entity , & mut StateTransitionIntents , & GroundSlam ) > ,
@@ -822,6 +894,123 @@ fn flopping(
822894 }
823895}
824896
897+ fn chaining (
898+ mut commands : Commands ,
899+ mut fighters : Query <
900+ (
901+ Entity ,
902+ & mut Animation ,
903+ & mut LinearVelocity ,
904+ & Facing ,
905+ & Handle < FighterMeta > ,
906+ & AvailableAttacks ,
907+ & mut Chaining ,
908+ ) ,
909+ With < Player > ,
910+ > ,
911+ fighter_assets : Res < Assets < FighterMeta > > ,
912+ ) {
913+ for (
914+ entity,
915+ mut animation,
916+ mut velocity,
917+ facing,
918+ meta_handle,
919+ available_attacks,
920+ mut chaining,
921+ ) in & mut fighters
922+ {
923+ // this seems... potentially panicky
924+ if let Some ( attack) = available_attacks
925+ . attacks
926+ . iter ( )
927+ . filter ( |a| a. name == * "chain" )
928+ . last ( )
929+ {
930+ if let Some ( fighter) = fighter_assets. get ( meta_handle) {
931+ //if we havent started the chain yet or if we have input during chain window
932+ if !chaining. has_started || chaining. continue_chain && chaining. can_extend {
933+ if !chaining. has_started {
934+ chaining. has_started = true ;
935+ animation. play ( Chaining :: ANIMATION , false ) ;
936+ }
937+ // Start the attack from the beginning
938+
939+ //if we are on chain followup, skip the first frame of the animation
940+ if chaining. continue_chain {
941+ animation. play ( Chaining :: FOLLOWUP_ANIMATION , false ) ;
942+ animation. current_frame = 2 ;
943+ chaining. continue_chain = false ;
944+ chaining. link += 1 ;
945+ if chaining. link >= Chaining :: LENGTH {
946+ chaining. transition_to_final = true ;
947+ }
948+ }
949+ chaining. can_extend = false ;
950+
951+ let mut offset = attack. hitbox . offset ;
952+ if facing. is_left ( ) {
953+ offset. x *= -1.0
954+ }
955+ offset. y += fighter. collision_offset ;
956+ // Spawn the attack entity
957+ let attack_entity = commands
958+ . spawn_bundle ( TransformBundle :: from_transform (
959+ Transform :: from_translation ( offset. extend ( 0.0 ) ) ,
960+ ) )
961+ . insert ( CollisionGroups :: new (
962+ BodyLayers :: PLAYER_ATTACK ,
963+ BodyLayers :: ENEMY | BodyLayers :: BREAKABLE_ITEM ,
964+ ) )
965+ . insert ( Attack {
966+ damage : attack. damage ,
967+ velocity : if facing. is_left ( ) {
968+ Vec2 :: NEG_X
969+ } else {
970+ Vec2 :: X
971+ } * attack. velocity . unwrap_or ( Vec2 :: ZERO ) ,
972+ hitstun_duration : attack. hitstun_duration ,
973+ hitbox_meta : Some ( attack. hitbox ) ,
974+ } )
975+ . insert ( attack. frames )
976+ . id ( ) ;
977+ commands. entity ( entity) . push_children ( & [ attack_entity] ) ;
978+
979+ // Play attack sound effect
980+ if let Some ( effects) = fighter. audio . effect_handles . get ( Chaining :: ANIMATION ) {
981+ let fx_playback = AnimationAudioPlayback :: new (
982+ Chaining :: ANIMATION . to_owned ( ) ,
983+ effects. clone ( ) ,
984+ ) ;
985+ commands. entity ( entity) . insert ( fx_playback) ;
986+ }
987+ }
988+ }
989+
990+ if animation. current_frame > attack. frames . active {
991+ chaining. can_extend = true ;
992+ }
993+ // Reset velocity
994+ * * velocity = Vec2 :: ZERO ;
995+
996+ //move forward a bit during active frames
997+ if animation. current_frame > attack. frames . startup
998+ && animation. current_frame < attack. frames . recovery
999+ {
1000+ if facing. is_left ( ) {
1001+ velocity. x -= 100.0 ;
1002+ } else {
1003+ velocity. x += 100.0 ;
1004+ }
1005+ }
1006+ }
1007+
1008+ if animation. is_finished ( ) {
1009+ chaining. transition_to_idle = true ;
1010+ }
1011+ }
1012+ }
1013+
8251014fn punching (
8261015 mut commands : Commands ,
8271016 mut fighters : Query < (
0 commit comments