This will be split into three posts to maintain clarity
Part I - INTRODUCTION AND METHODOLOGY
Part II - EXPLORING THE SYSTEM.HITDEF (https://mugenguild.com/forum/msg.2503112)
Part III - PROPERLY FORMATTING THE HITDEF (https://mugenguild.com/forum/msg.2503113)
Part I - INTRODUCTION AND METHODOLOGY
A majority (if not all, but I'm not going to make that argument) of games have universal pausetimes, hittimes, slidetimes, etc. for all normals and most specials (barring weird things like fireballs and supers). Garou's is incredibly simple, but is highly important for correct combo timings because all frame advantages, especially breaks, align with the pause/hit/slidetime. I dunno if anyone is looking to make more MotW characters, or even do stylistic edits in the manner of Kn (totally worth it), but this would make both of those much faster.
I personally hate the hitdef. It's inefficient because, in an efficient program, all of the valuable information concerning damage, scaling, hittime, pausetime, etc would be backend values that are called upon by the hitdef; therefore, a lot of time is wasted trudging through the cns to edit states, and I thought it would be better, and more efficient, to assign all of these functions to a file that only consists of less than 500 lines that need to be edited; moreover, because no hitdef is ever active on the first frame, so we don't have to deal with theoretical 1 tick delay, it makes perfect sense to just assign these to a helper in general.
So, in Gato+ I decided to diminish the actual amount of physical editing you have to do in any file but the AutoHitdef file,
Spoiler: leading to this (click to see content)
[Statedef 200]
type = S
movetype = A
physics = S
ctrl = 0
velset = 0, 0
anim = 200
poweradd = 0
sprpriority = 2
[State 200, play voice]
type = playSnd
trigger1 = time = 0
value = 2, 0+random%2
channel = 0
[State 200, play se]
type = playSnd
trigger1 = animElem = 2
value = 1, 3
channel = 1
[State HitDef]
type = HitDef
trigger1 = animelemtime(helper(222222),var(0))=0
attr = S,NA ;SCA,NA,SA,HA,NP,SP,HP,NT,ST,HT
hitflag = MAF ;HLAFD+-
guardflag = M ;HLA
animtype = medium ;Light, Medium, Hard, Back, Up, DiagUp
priority = 3, Hit
damage = floor((helper(222222),var(1))*fvar(30)),floor((helper(222222),var(7))*fvar(30))
pausetime = (helper(222222),var(2)),(helper(222222),var(3))
sparkno = -1
guard.sparkno = -1
hitsound = S0, (helper(222222),var(15))+random%2
guardsound = S1, 0
ground.type = high ;Low, Trip, None
ground.slidetime = (helper(222222),var(4))
guard.slidetime = (helper(222222),var(4))
ground.hittime = (helper(222222),var(5))
guard.hittime = (helper(222222),var(5))
guard.ctrltime = (helper(222222),var(5))
air.hittime = (helper(222222),var(5))
ground.velocity = (-2.29473946*fvar(9))-fvar(11),0
air.velocity = -2.204769373,-6.6174449*fvar(10)
airguard.velocity = -2.204769373*fvar(9),-6.6174449*fvar(10)
getpower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
givepower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
[State Explod hit]
type = Explod
trigger1 = movehit
anim = 6000
ID = 6000
pos = (helper(222222),var(10)),(helper(222222),var(11))
postype = P1 ;P2, Front, Back, Left, Right
scale = .5,.5
sprpriority = 2
ownpal = 1
ignorehitpause = 1
persistent=0
[State Explod hit]
type = Explod
trigger1 = moveguarded
anim = 6100
ID = 6100
pos = (helper(222222),var(10)),(helper(222222),var(11))
postype = P1 ;P2, Front, Back, Left, Right
scale = .15,.15
sprpriority = 2
ownpal = 1
ignorehitpause = 1
persistent=0
[State 200, idle]
type = stateTypeSet
trigger1 = animelemtime(helper(222222),var(6))=0
movetype = I
[State 200, end of state]
type = ChangeState
trigger1 = AnimTime = 0
value = 0
ctrl = 1
As far as I'm concerned, an attack state should consist of two sections:
Spoiler: Mandatory front end, e.g. sound clips on startup that are specific to each move (click to see content)
[State 200, play voice]
type = playSnd
trigger1 = time = 0
value = 2, 0+random%2
channel = 0
[State 200, play se]
type = playSnd
trigger1 = animElem = 2
value = 1, 3
channel = 1
Spoiler: Absolute backened that should not have to be touched aside from particular specials and supers (click to see content)
[State HitDef]
type = HitDef
trigger1 = animelemtime(helper(222222),var(0))=0
attr = S,NA ;SCA,NA,SA,HA,NP,SP,HP,NT,ST,HT
hitflag = MAF ;HLAFD+-
guardflag = M ;HLA
animtype = medium ;Light, Medium, Hard, Back, Up, DiagUp
priority = 3, Hit
damage = floor((helper(222222),var(1))*fvar(30)),floor((helper(222222),var(7))*fvar(30))
pausetime = (helper(222222),var(2)),(helper(222222),var(3))
sparkno = -1
guard.sparkno = -1
hitsound = S0, (helper(222222),var(15))+random%2
guardsound = S1, 0
ground.type = high ;Low, Trip, None
ground.slidetime = (helper(222222),var(4))
guard.slidetime = (helper(222222),var(4))
ground.hittime = (helper(222222),var(5))
guard.hittime = (helper(222222),var(5))
guard.ctrltime = (helper(222222),var(5))
air.hittime = (helper(222222),var(5))
ground.velocity = (-2.29473946*fvar(9))-fvar(11),0
air.velocity = -2.204769373,-6.6174449*fvar(10)
airguard.velocity = -2.204769373*fvar(9),-6.6174449*fvar(10)
getpower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
givepower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
[State Explod hit]
type = Explod
trigger1 = movehit
anim = 6000
ID = 6000
pos = (helper(222222),var(10)),(helper(222222),var(11))
postype = P1 ;P2, Front, Back, Left, Right
scale = .5,.5
sprpriority = 2
ownpal = 1
ignorehitpause = 1
persistent=0
[State Explod hit]
type = Explod
trigger1 = moveguarded
anim = 6100
ID = 6100
pos = (helper(222222),var(10)),(helper(222222),var(11))
postype = P1 ;P2, Front, Back, Left, Right
scale = .15,.15
sprpriority = 2
ownpal = 1
ignorehitpause = 1
persistent=0
[State 200, idle]
type = stateTypeSet
trigger1 = animelemtime(helper(222222),var(6))=0
movetype = I
[State 200, end of state]
type = ChangeState
trigger1 = AnimTime = 0
value = 0
ctrl = 1
N.B. I use explods here, so if you were using hitsparks in the hitdef then you would just put "(helper(222222),var(10)),(helper(222222),var(11))" there for the x and y pos.
So, how does this work?
essentially, in a backend file we will make a list of all values that, because they never change, should be determined ahead of time, and then define whether those values are frontend or backend, the difference being that the backend are always calculations based on frontend values.
Spoiler: After doing this, I determined my list should be as so (click to see content)
Animelem var(0)
Idle var(6) = var(0)+2
Damage var(1)
Guard Damage var(7)
Pause time var(2)
p2 pause time var(3) = var(2)-1
Slide time var(4) = var(2)+(var(2)/2)
Hit time var(5) = var(4)+2
RAW POWER VALUE fvar(0)
>>>>>provided as raw value
e.g. 66537
>>>>>THUS
x/4194.305 = power corrected per 1000
e.g.
4194305/4194.305 = 1000
(1 = 0 so subtract 1 from your inital result)
>>>>>(though numbers might be so big it doesn't matter)
>>>>>formula in terms of MUGEN
floor((x - 1)/4194.305)
POWER ON HIT
Severity modification fvar(2)
formula fvar(1) = floor(((fvar(0) - 1)/4194.305)*fvar(2))
POWER ON BLOCK
Severity modification fvar(4)
formula fvar(3) = floor(((fvar(0) - 1)/4194.305)*fvar(2))
>>>>>difference between a guard and a hit?
x*2 - x
>>>>>UNLESS light b/c X appears to be the root value && SNK uses
x - x
>>>>>Some musings on special values for power gain:
>>>>>light?
x*6
>>>>>heavy?
x*7
>>>>>difference between special raw && hit is equivalent to
>>>>>light?
x*2
>>>>>heavy?
x*2 + x
Spark X var(10)
Spark Y var(11)
Hitsound var(15)
extra pausetime var(20)
For each extra hit var(50)-(59)
And to explain the idea of how the frontend and backend works in practice, take my top two: animelem and idle. In Garou I determined that the average time an idle would occur would generally be 2-3 animelems after the active frame, so my backend calculation for var(6) is as follows
; Idle is ALWAYS set two frames after an active
[State idle set]
type = VarSet
trigger1 = var(0)
v = 6 ;fv = 10
value = var(0)+2
persistent=1
ignorehitpause=1
This sort of backend caluclation makes hittime in Garou easy to work with because all hittimes in Garou are based upon the root p1 pausetime
; pause/hit/slide time notes
; THESE TIMES ARE ABSOLUTELY UNIVERSAL, NOTHING HAS TO BE CHANGED ASIDE FROM SPECIALS
[State p2 pause]
type = VarSet
trigger1 = var(2)
v = 3 ;fv = 10
value = var(2)-1
persistent=1
ignorehitpause=1
[State slide time]
type = VarSet
trigger1 = var(2)
v = 4 ;fv = 10
value = var(2)+(var(2)/2)
persistent=1
ignorehitpause=1
[State hittime time]
type = VarSet
trigger1 = var(4)
v = 5 ;fv = 10
value = var(4)+2
persistent=1
ignorehitpause=1
Thus, a light punch pausetime of 8 will return p2 pausetime of 7, slidetime of 12, and hittime of 14, all of which are accurate to source.
A note on severity and power gain
Prior to revealing the code we need to go over how power works in Garou.
In Garou there is a root power value, 66537 (or, when corrected, 15), that I found is compounded by degrees of severity, so that each move will be allocated a certain level of severity based on movetype, strength, whiffing, hitting, etc.
You can see in my musings above that I thought quite a bit about how the patterns point to a possible methodology for assigning power to moves, and what I decided to do was take this raw value, 66537, set it to fvar(0), and apply two sets of calculations to it:
; Calculation of raw power value
[State raw calculation]
type = VarSet
trigger1 = 1
fv = 1 ;fv = 10
value = floor(((fvar(0) - 1)/4194.305)*fvar(2))
ignorehitpause=1
persistent=1
; Calculation of guard power value
[State guard calculation]
type = VarSet
trigger1 = 1
fv = 3 ;fv = 10
value = floor(((fvar(0) - 1)/4194.305)*fvar(4))
ignorehitpause=1
persistent=1
The severity modifiers here are fvar(2) and fvar(4), which, like p1 pausetime earlier, are the front end that we will edit manually, and the resulting values will be plugged into the hitdef automatically, giving correct power gain values.
With that, let's move on to Part II, where I'll explain how the auto-hitdef file works.
Part II - EXPLORING THE SYSTEM.HITDEF
Download the system.hitdef file (https://doubletrend-zeta.neocities.org/system.hitdef) and open it up in notepad/fighter factory/whatever. Go to line 70, which shows an example of the main type of string for normals
trigger1 = root,stateno = 200 && root,animelemtime(1)=0
trigger1 = var(0):=4 && var(1):=4 && var(7):=0 && var(2):=8 && fvar(2):=1 && fvar(4):=1 && var(10):=46 && var(11):=-69 && var(15)=0
If you've been following so far, or even already looked through the file, each of these variable assignments represents a frontend value. They're explained in the previous part, but I will explain all of them directly here
var(0) = animelem, this is the active frame of your animation (the one with the red clsn)
var(1) = damage, this is either your raw damage, to be corrected later, or the fully corrected version, whatever method you prefer
var(7) = guard damage, follows the same methodology as damage
var(2) = p1 pausetime, this is the only hittime value you need to edit, the rest is done automatically through the backend
fvar(2) = severity modifier on hit
fvar(4) = severity modifier on block
var(10) = spark pos x
var(11) = spark pos y
var(15) = hitsound
What you might have noticed is that I took all of the hitdef values that can be assigned to a variable (i.e. intergers and floats) in MUGEN and had them assigned here. This means that within about 200 lines or so you can assign all normals without having to go through your giant CNS files, saving a great deal of time!
HOWEVER, for a two hit move there is a problem! There are some issues I came up with attempting to rewrite var(0) midway through the state and recalling it as trigger2, so, since the range of 50-59 was free I decided to allocate multihits up to 10 with those.
Take note of this on line 153:
trigger1 = root,stateno = 215 && root,animelemtime(5)=0 ; *2-pt
trigger1 = var(50):=6 && var(1):=6 && var(7):=0 && var(2):=8 && fvar(2):=1 && fvar(4):=1 && var(10):=40 && var(11):=-86 && var(15)=0
Next, specials, because they give power on whiff, follow a similar pattern, but we remove the two severity modifier fvars.
Go to line 166 (and line 198 for the two hit moves):
trigger1 = root,stateno = 1000 && root,animelemtime(1)=0
trigger1 = var(0):=8 && var(1):=10 && var(7):=2 && var(2):=12 && var(10):=60 && var(11):=-63 && var(15)=12
severity for these moves is applied for each move specifically because the power gain values are different for each.
On line 207 this set of assignments shows how it works:
; These cover specials that lie outside of standard hitdef qualities
[State severity determination]
type = Null
trigger1 = root,movetype != A
trigger1 = fvar(2):=0
; throw
trigger2 = (root,stateno = 800)
trigger2 = fvar(2):=3
; light specials whiff
trigger3 = root,time<=4
trigger3 = (root,stateno = 1000) || (root,stateno = 1100) || (root,stateno = 1200)
trigger3 = fvar(2):=6
; light specials hit
trigger4 = root,time>4
trigger4 = (root,stateno = 1000) || (root,stateno = 1100) || (root,stateno = 1200)
trigger4 = fvar(2):=2
; heavy specials whiff
trigger5 = root,time<=4
trigger5 = (root,stateno = 1050) || (root,stateno = 1100) || (root,stateno = 1250)
trigger5 = fvar(2):=7
; heavy specials hit
trigger6 = root,time>4
trigger6 = (root,stateno = 1050) || (root,stateno = 1100) || (root,stateno = 1250)
trigger6 = fvar(2):=3
And for the guard values, go to line 259:
; Guard constant
[State guard determination]
type = Null
trigger1 = root,movetype != A
trigger1 = fvar(4):=0
; light specials
trigger2 = (root,stateno = 1000) || (root,stateno = 1100) || (root,stateno = 1200)
trigger2 = fvar(4):=1
; heavy specials
trigger3 = (root,stateno = 1050) || (root,stateno = 1100) || (root,stateno = 1250)
trigger3 = fvar(4):=2
Much of this should be incredibly straightforward, especially with normals, so what should be difficult is dealing with specific situations with special moves, and sometimes that means you might not be able to use the autohitdef and would be better just making a manual hitdef.
That being said, in Part III I'll explain how this all comes together into the generic hitdef.
Part III - PROPERLY FORMATTING THE HITDEF
Having defined all our values, we now need to get the hitdef to recall them. This will be the easiest part and will mostly come down to copy-pasting certain sections into the hitdef.
So here's the hitdef from before, but I've bolded the areas that you can automate and italicized those you can't:
type = HitDef
trigger1 = animelemtime(helper(222222),var(0))=0
attr = S,NA ;SCA,NA,SA,HA,NP,SP,HP,NT,ST,HT
hitflag = MAF ;HLAFD+-
guardflag = M ;HLA
animtype = medium ;Light, Medium, Hard, Back, Up, DiagUp
priority = 3, Hit
damage = floor((helper(222222),var(1))*fvar(30)),floor((helper(222222),var(7))*fvar(30))
pausetime = (helper(222222),var(2)),(helper(222222),var(3))
sparkno = -1
guard.sparkno = -1
hitsound = S0, (helper(222222),var(15))+random%2
guardsound = S1, 0
ground.type = high ;Low, Trip, None
ground.slidetime = (helper(222222),var(4))
guard.slidetime = (helper(222222),var(4))
ground.hittime = (helper(222222),var(5))
guard.hittime = (helper(222222),var(5))
guard.ctrltime = (helper(222222),var(5))
air.hittime = (helper(222222),var(5))
ground.velocity = (-2.29473946*fvar(9))-fvar(11),0
air.velocity = -2.204769373,-6.6174449*fvar(10)
airguard.velocity = -2.204769373*fvar(9),-6.6174449*fvar(10)
getpower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
givepower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
essentially, this hitdef is complete, all you have to do is follow the variables defined in the system.hitdef. Certain things such as the attributes that don't use ints or floats have to be manually changed, and as you can see I did not automate any velocities because those are highly specific to most moves.
For example, take note of how Gato's close D move works. Not only is it a two hit move, but one with drastically different attributes and velocities between hitdefs.
[State HitDef]
type = HitDef
trigger1 = animelemtime(helper(222222),var(0))>=0
attr = S,NA ;SCA,NA,SA,HA,NP,SP,HP,NT,ST,HT
hitflag = MAF ;HLAFD+-
guardflag = M ;HLA
animtype = hard ;Light, Medium, Hard, Back, Up, DiagUp
priority = 4, Hit
damage = floor((helper(222222),var(1))*fvar(30)),floor((helper(222222),var(7))*fvar(30))
pausetime = (helper(222222),var(2)),(helper(222222),var(3))
sparkno = -1
guard.sparkno = -1
hitsound = S0, (helper(222222),var(15))+random%2
guardsound = S1, 1
ground.type = low ;Low, Trip, None
ground.slidetime = (helper(222222),var(4))
guard.slidetime = (helper(222222),var(4))
ground.hittime = (helper(222222),var(5))
guard.hittime = (helper(222222),var(5))
guard.ctrltime = (helper(222222),var(5))
air.hittime = (helper(222222),var(5))
ground.velocity = cond(animelemtime(helper(222222),var(0))=0,(-.5),(-2.29473946*fvar(9)-fvar(11))),0
air.velocity = -2.204769373,-6.6174449*fvar(10)
airguard.velocity = -2.204769373*fvar(9),-6.6174449*fvar(10)
getpower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
givepower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
[State HitDef]
type = HitDef
trigger1 = animelemtime(helper(222222),var(50))>=0
attr = S,NA ;SCA,NA,SA,HA,NP,SP,HP,NT,ST,HT
hitflag = MAF ;HLAFD+-
guardflag = M ;HLA
animtype = hard ;Light, Medium, Hard, Back, Up, DiagUp
priority = 4, Hit
damage = floor((helper(222222),var(1))*fvar(30)),floor((helper(222222),var(7))*fvar(30))
pausetime = (helper(222222),var(2)),(helper(222222),var(3))
sparkno = -1
guard.sparkno = -1
hitsound = S0, (helper(222222),var(15))+random%2
guardsound = S1, 1
ground.type = high ;Low, Trip, None
ground.slidetime = (helper(222222),var(4))
guard.slidetime = (helper(222222),var(4))
ground.hittime = (helper(222222),var(5))
guard.hittime = (helper(222222),var(5))
guard.ctrltime = (helper(222222),var(5))
air.hittime = (helper(222222),var(5))
ground.velocity = cond(animelemtime(helper(222222),var(0))=0,(-.5),(-2.29473946*fvar(9)-fvar(11))),0
air.velocity = -2.204769373,-6.6174449*fvar(10)
airguard.velocity = -2.204769373*fvar(9),-6.6174449*fvar(10)
getpower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
givepower = floor((helper(222222),fvar(1))),floor((helper(222222),fvar(1))-(helper(222222),fvar(3)))
So, with a little patience, and a tiny bit of work, you'll have consistent, easily editable hitdefs according to a Garou standard. And, realistically, with a little work on the backend you could modify this for any other kind of game!
If you have any questions, don't hesitate to ask!