@@ -60,6 +60,7 @@ impl Plugin for FighterStatePlugin {
60
60
. after ( FighterStateCollectSystems )
61
61
. run_in_state ( GameState :: InGame )
62
62
. with_system ( transition_from_idle)
63
+ . with_system ( transition_from_chain)
63
64
. with_system ( transition_from_flopping)
64
65
. with_system ( transition_from_punching)
65
66
. with_system ( transition_from_ground_slam)
@@ -75,6 +76,7 @@ impl Plugin for FighterStatePlugin {
75
76
ConditionSet :: new ( )
76
77
. run_in_state ( GameState :: InGame )
77
78
. with_system ( idling)
79
+ . with_system ( chaining)
78
80
. with_system ( flopping)
79
81
. with_system ( punching)
80
82
. with_system ( ground_slam)
@@ -238,7 +240,7 @@ impl Flopping {
238
240
pub const ANIMATION : & ' static str = "attacking" ;
239
241
}
240
242
241
- /// Component indicating the player is punching
243
+ /// Component indicating the player is performing a groundslam
242
244
#[ derive( Component , Reflect , Default , Debug ) ]
243
245
#[ component( storage = "SparseSet" ) ]
244
246
pub struct GroundSlam {
@@ -276,6 +278,23 @@ impl Punching {
276
278
pub const ANIMATION : & ' static str = "attacking" ;
277
279
}
278
280
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
+
279
298
#[ derive( Component , Reflect , Default , Debug ) ]
280
299
#[ component( storage = "SparseSet" ) ]
281
300
pub struct MeleeAttacking {
@@ -350,39 +369,61 @@ fn collect_player_actions(
350
369
& Inventory ,
351
370
& Stats ,
352
371
Option < & Holding > ,
372
+ Option < & mut Chaining > ,
353
373
& AvailableAttacks ,
354
374
) ,
355
375
With < Player > ,
356
376
> ,
357
377
) {
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
360
387
{
361
388
// Trigger attacks
362
389
//TODO: can use flop attack again after input buffer/chaining
363
390
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
+ // }
386
427
}
387
428
}
388
429
@@ -530,6 +571,37 @@ fn transition_from_punching(
530
571
}
531
572
}
532
573
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
+
533
605
fn transition_from_ground_slam (
534
606
mut commands : Commands ,
535
607
mut fighters : Query < ( Entity , & mut StateTransitionIntents , & GroundSlam ) > ,
@@ -822,6 +894,123 @@ fn flopping(
822
894
}
823
895
}
824
896
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
+
825
1014
fn punching (
826
1015
mut commands : Commands ,
827
1016
mut fighters : Query < (
0 commit comments