This was something I ended up studying around late 2018 when I ran into issues involving my own code, so the majority of this code is loosely based on a similar method used by 41's characters.
If you've ever played any fighter that allows P2 to air tech out of a combo, such as Blazblue or Melty Blood, you'll know that the game usually gives you some type of indicator when you perform an invalid combo, where the opponent could have recovered during your combo but decided not to. This tutorial attempts to replicate such a system.
NOTE: You'll need at least 1 free variable in your character's code. For this example, I'll be using Var(35).
Assuming that you have the sprites needed for your "INVALID" indicator, you'll want to have 4 different animations: a fade in, and a fade out, one for each side (p1 and p2). Make sure that the last animation element of your fade-in animation has an infinite (-1) duration, while your fade-out animations have a finite time, otherwise you'll get weird instances of multiple explods:
;Invalid - Left fadein
[Begin Action 7944]
7900,8, -100,0, 1
7900,8, -80,0, 1
7900,8, -60,0, 1
7900,8, -40,0, 1
7900,8, -20,0, 1
7900,8, 0,0, -1
;Invalid - Left fadeout
[Begin Action 7946]
7900,8, 0,0, 1,, AS256D0
7900,8, 0,0, 1,, AS224D32
7900,8, 0,0, 1,, AS192D64
7900,8, 0,0, 1,, AS160D96
7900,8, 0,0, 1,, AS128D128
7900,8, 0,0, 1,, AS96D160
7900,8, 0,0, 1,, AS64D192
7900,8, 0,0, 1,, AS32D224
;Invalid - Right Fadein
[Begin Action 7945]
7901,8, 100,0, 1
7901,8, 80,0, 1
7901,8, 60,0, 1
7901,8, 40,0, 1
7901,8, 20,0, 1
7901,8, 0,0, -1
;Invalid - Right Fadeout
[Begin Action 7947]
7901,8, 0,0, 1,, AS256D0
7901,8, 0,0, 1,, AS224D32
7901,8, 0,0, 1,, AS192D64
7901,8, 0,0, 1,, AS160D96
7901,8, 0,0, 1,, AS128D128
7901,8, 0,0, 1,, AS96D160
7901,8, 0,0, 1,, AS64D192
7901,8, 0,0, 1,, AS32D224
Next, place the following code in your -2 state:
[State -2, Invalid Combo Explod]
type = Explod
trigger1 = numhelper(27944)
trigger1 = numexplod(27944)=0
trigger1 = helper(27944),var(59)=1
anim = 1998 ;<-Invisible anim
ID = 27944
pos = 0,0
postype = p1 ;p2,front,back,left,right
facing = 1
vel = 0,0
removetime = 999999999
pausemovetime = 999999999
supermovetime = 999999999
sprpriority = 5
ownpal = 1
removeongethit = 0
ignorehitpause = 1
[State -2, Invalid Combo RemoveExplod]
type = RemoveExplod
triggerall = numexplod(27944)
trigger1 = numhelper(27944)
trigger1 = helper(27944),var(59)=1
trigger1 = (helper(27944),var(0)=0)||(helper(27944),var(0)>100)
trigger1 = numenemy
trigger1 = enemynear(0),movetype!=H
trigger2 = numhelper(27944)=0
id = 27944
ignorehitpause = 1
[State -2, Invalid Combo Helper]
type = helper
trigger1 = numexplod(27944)=0
trigger1 = numenemy
trigger1 = P2StateType = A && P2MoveType = H
trigger1 = EnemyNear,CanRecover
trigger1 = (p2stateno != [120,155]) && (p2stateno!=[250,262]) && (p2stateno!=[450,451])
trigger1 = movehit=0
helpertype = normal
name = "Invalid Combo"
ID = 27944
pos = 0,0
postype = p1
stateno = 27944
ownpal = 1
facing = 1
pausemovetime = 999999999
supermovetime = 999999999
[State -2, Helper Movehit Flag]
type = varadd
trigger1 = var(35)>0
var(35)=-1
ignorehitpause = 1
In addition, add the following to each of your Helper states that have any Hitdefs (NOTE: Make sure these Helpers are created directly by the root and do not have another helper as its parent, otherwise this method will not work)
[State 2011, Helper Hit Flag]
type = parentvarset
trigger1 = MoveHit=1
trigger1 = p2stateno != [120,159]
var(35)=2
ignorehitpause = 1
persistent = 0
This code will create a helper that will handle the check for the conditions for displaying the Invalid Combo explod. It also creates an invisible Explod whenever this helper is active and removes it whenever this helper does not exist or when the opponent is not in a hitstate. The helper will also check to see if this invisible explod exists and, if at any point it does not, then the conditions for displaying the actual explod are satisfied. For this example, anim 1998 is being used as a blank animation.
Var(35) acts as a flag to tell us when a helper-based projectile has hit an opponent, and then counts down. The reason we use a variable here instead of helper(xxx),movehit is not only to streamline the code instead of having to write down all Helper IDs, but also to avoid situations where multiple helpers with the same ID exist, such as any move that spawns multiple projectiles, which can easily cause the movehit triggers to return false negative results.
Next is the helper state:
;=====================================================
; Invalid Combo Helper
[StateDef 27944]
type = A
physics = N
moveType = I
anim = 1998
velSet = 0,0
ctrl = 0
;var(0) - Elapsed time measurement after hit (for Invalid)
;var(2) - helper attack or hit flag (for invalid display)
;var(59) - Flag (0=Default, 1=Invalid)
[State -2, Varset]
type = varset
triggerall = var(2)=0
trigger1 = root,var(35)>0
trigger2 = root,projhittime(XXXX)=1 ;PROJECTILE CONTROLLER IDS GO HERE
var(2)=1
[State -2, Varset]
type = varset
triggerall = time=0
triggerall = var(59)=0
trigger1 = root,numexplod(27944)=0
trigger1 = numenemy
trigger1 = p2statetype=A
trigger1 = root,movehit=0
trigger1 = EnemyNear(0),CanRecover
trigger1 = (p2stateno != [120,155]) && (p2stateno!=[250,262]) && (p2stateno!=[450,451])
var(59)=1
[State -2, Varset]
type = varset
triggerall = var(0)=0 && var(59)=1
trigger1 = root,movehit=1
trigger1 = numenemy
trigger1 = p2statetype=A
trigger1 = enemynear(0),hitshakeover
trigger2 = var(2)!=0
var(0)=1
[State -2, Counterhit Explod]
type = Explod
triggerall = numexplod(7944)=0
trigger1 = var(59)=1
trigger1 = var(0)>0
anim = 7944+1*(root,teamside!=1)
ID = 7944
pos = ifelse(teamside=1,0,320),120
postype = left
facing = 1
vfacing = 1
bindtime = -1
removetime = -1
pausemovetime = 999999999
supermovetime = 999999999
sprpriority = 999
scale = 0.5,0.5
ontop = 1
ownpal = 1
ignorehitpause = 1
[State -2, Counterhit Explod]
type = Explod
triggerall = numexplod(7945)=0
triggerall = numexplod(7944)=1
triggerall = numenemy
trigger1 = enemynear(0),stateno=5150 || enemynear(0),alive=0
trigger2 = p2movetype!=H && p2stateno!=5120
trigger3 = enemynear(0),ctrl = 1
anim = 7946+1*(root,teamside!=1)
ID = 7945
pos = ifelse(teamside=1,0,320),120
postype = left
facing = 1
vfacing = 1
bindtime = -1
removetime = -2
pausemovetime = 999999999
supermovetime = 999999999
sprpriority = 999
scale = 0.5,0.5
ontop = 1
ownpal = 1
ignorehitpause = 1
[State -2, REX]
type = removeexplod
triggerall = numenemy
trigger1 = enemynear(0),stateno=5150 || enemynear(0),alive=0
trigger2 = p2movetype!=H && p2stateno!=5120
trigger3 = enemynear(0),ctrl = 1
id = 7944
ignorehitpause = 1
[State -2, Timer]
type = varadd
trigger1 = var(0)>0
trigger1 = var(59)=1
var(0)=1
[State -2, Destroyself]
type = destroyself
trigger1 = var(59)=1
trigger1 = var(0)=0 || var(0)>101
trigger1 = numenemy
trigger1 = (p2movetype!=H && p2stateno!=5120)||(enemynear(0),ctrl = 1)||(enemynear(0),stateno=5150 || enemynear(0),alive=0)
trigger2 = var(59)=0
trigger2 = time>=1
One important thing to note is the first varset controller. If you have any attacks that utilize any projectiles using a Projectile state controller (as opposed to a Helper-based projectile), you'll need to write down each of their IDs as a separate trigger here, as this method won't be able to account for them otherwise. Effectively what this helper does is toggle between states where the opponent is in a state where they can recover (using var(59)). The reason why can't just use the canrecover trigger by itself is that this will immediately be reset the next time P2 is hit, so we need to be able to keep track of their previous state before the next hit connects. The way this helper is set up is that once these conditions are true, the Fade-in animation for our explod will display until the combo ends, at which point it will transition into the Fade-out animation.
Posted: December 12, 2018, 10:00:37 pm
Psuedocode for the above in case anyone wants to devise their own implementation of this method:
--------------------------
In the player's State -2:
--------------------------
If the Invalid Check Helper exists and has its var(59)=1, create a permanent explod(99) if it does not exist.
The explod should be active during all pauses.
Remove the explod if one of the following cases is true:
Case 1:
- Invalid Helper exists and its var(59)=1
- Enemy is not in a hitstate
- Helper's var(0) is either 0 or greater than 100 (see below)
Case 2:
- Invalid Helper does not exist
Create the Invalid Helper under one of the following conditions:
Case 1:
- Explod(99) does not exist
- P2 is in an aerial state and a hit state
- P2 can recover
- P2 is not in a guarding state or any other custom state
- P1's move did not hit (movehit=0)
Case 2:
- Explod(99) does not exist
- P2 is in Specific Custom State for a certain duration
The Invalid Helper must be active during all pauses
----------------------------
In the Invalid Helper State:
----------------------------
var(0) - Elapsed time measurement after hit (for Invalid)
var(2) - Helper attack hit flag (for invalid display)
var(59) - Call type determination flag (0 = default, 1 = invalid)
When var(2)=0, if root,numhelper(x) and (helper(x),movehit=1), set var(2)=1
At Invalid Helper time=0, when var(59)=0, set var(59)=1 under any of the following conditions:
Case 1:
- Root,numexplod(99)=0 (see above)
- Enemy is in an aerial state
- root's move did not hit (movehit=0)
- Enemy can recover, and is not in any Custom States
Set var(0)=1 when one of the following cases are true:
Case 1:
- var(59)=1 and var(0)=0
- root,movehit=1
- Opponent is in an aerial state with hitshakeover = 1
Case 2:
- var(59)=1 and var(0)=0
- var(2)!=0
Display the Invalid Combo explod when all of the following are true:
- Explod does not exist
- var(59)=1
- var(0)>0
Remove the explod when one of the following are true:
- enemy is not in hitstate and is not in state 5120
- enemy regains control
- enemy is KOed
Increase var(0) by 1 when the following are true:
- var(59) = 1
- var(0) is nonzero
Destroy the helper when one of the following is true:
Case 1:
- Var(59)=1
- var(0)=0 OR >=102
- Opponent is not in a hitstate and not in 5120, has control, or is KOed
Case 3:
- Var(59)=0
- helper's time>=1