Everyone knows there are random elements in the SF2 engine, including the amount of damage and stun done by most attacks. But what kind of randomness, exactly? Here are the general rules:
attack type | random dizzy | random damage | lifebar scaling* |
---|---|---|---|
non-throws | yes | yes | yes |
normal throws | yes | no | yes |
multi-hit throws | yes* | no | yes |
command throws | yes | depends* | yes |
- I didn't examine lifebar scaling in depth, but a little testing can confirm T.Akiba's findings. Just set the opponent's life to 30 and see the difference.
- Some holds like Blanka's and Balrog's always do no dizzy.
- Throws do fixed damage except the following:
- Gief's SPD (SSF2 and SSF2T versions only)
- Gief's close 360+K, 2nd hit
- Gief's far 360+K
- Gief's super, 3rd hit
- Hawk's super, 2nd hit
- Vega's super, 3rd hit
- Honda's oicho, both hits
Now for the details. To reveal the exact damage and dizzy formula for all attacks and help compile more accurate attack data, I developed the following breakpoints for use in the MAME debugger:
breakpoints | helper Lua script |
---|---|
(local copy) | (local copy) |
You run MAME with the -debug
switch on, then copypaste each of the bp
lines for your game into the console. Then anytime either player gets attacked, the damage and stun info is displayed there. Since the FBA/MAME-rr Lua implementation doesn't support breakpoints, there's no pure-Lua way to get the formulas. What the helper script does is it prints out damage/stun/timeout dealt whenever someone gets hurt. You can use it to more easily verify that the formulas jive with the real figures.
(There's supposed to be a way to enter multiple commands at once, but it doesn't work. Also, these will only work on the current parent ROMs, not clones. However, every ROM address is given relative to the breakpoint address, the pc
, so getting these to work with a clone should only require offsetting the bp
by a constant value.)
How random damage works: there's a base damage for every hit, then a random value is added. The random values are looked up from a table of 32 possibilities, with the actual one selected determined by the output of a pseudorandom number generator. The sum is then scaled down by a certain factor and rounded up to produce the final damage.
The lookup table is dependent on the attack, and all the relevant random number tables are included in the bp file. The scale factor, however, is dependent on the opponent: it's a defense rating.
damage scaling | ST characters |
---|---|
27/32 | Ryu, Guile, Ken, Chun Li, Dhalsim, Claw, Fei Long, Cammy, Dee Jay, Akuma |
25/32 | E.Honda, Blanka, Boxer, Sagat, Dictator |
22/32 | Zangief, T.Hawk |
Random dizzy works the same way, but there's only one lookup table for all attacks, and there's no scaledown afterward. Dizzy timeout increases are not random, but each type of attack has different hardcoded values and it would be a needless hassle to use breakpoints to find them. It's much easier to use the helper script.
Here's an example of how to get the formulas for some attacks in SSF2T. Start by running MAME with the debugger on:
mame ssf2t -debug -lua sf2-damage-watcher.lua
Paste each breakpoint line from the ssf2t section into the console and hit enter after each one. Press F5 to release the debugger lock and allow the game to run. Now click over to the game window and set up a Chun Li vs. Ken match.
Try throwing Ken. The debugger prints out "
throw dizzy: 10 + randval@ $(07F39A + 00~1F)
" and then "throw damage (A): 32
". (Notice how the dizzy is applied when the opponent is released, and the damage is applied when they hit the ground or wall.) Throw damage is fixed, but it will change depending on the round and who won what round. There may not be any significance to whether it's a type (A), (B) or (C) throw. I added those labels just to keep track of the different bps.
The base dizzy is 10, plus a random one of these values from the table in the bp file:
07F39A: 01 FF 02 00 01 00 00 FE 00 01 FF FE 00 00 02 00
07F3AA: FF FD 00 03 00 01 00 01 FF 00 FE 02 FE 02 00 FF
+3 | 1/32 |
+2 | 4/32 |
+1 | 5/32 |
0 | 12/32 |
-1 | 5/32 |
-2 | 4/32 |
-3 | 1/32 |
These numbers are in hex, so 0xFF = -1, 0xFE = -2, etc. Looking them over, there's a certain chance of getting each value.
This distribution happens to be symmetric about zero, but not all are. If you only care about the extremes, you could put this move down as 7~13 dizzy. As for the stun timeout, it increases by 100, which you can tell from the Lua console.
Now hit with c.HK. The debugger gives "
hit damage: (22 + randval@ $(0E7996 + 40~5F)) x 27/32
" and the dizzy data. Look up the random values from the damage table. This attack doesn't read from the top, but from offset 0x40:
0E79D6: 01 02 00 00 00 FF 03 02 00 FE 00 03 04 01 FD 00 offset $040
0E79E6: 01 04 00 01 04 02 00 00 00 02 00 00 00 00 00 02
The min is -3 and the max is +4, for a range of 19~26. Since this is vs. Ken it's then reduced by 27/32, or 17~22 after rounding up. Against T.Hawk it would be 22/32, or 14~18.
Now grab Chun Li with Ken's MK throw, the knee bash. It's a straight 26 damage for the first hit and 4 for any subsequent hits. This particular hold does no dizzy damage. (The breakpoints and the script work for both players.)
And that's all there is to using it. One last note is MAME-rr will crash if a ROM is unloaded while breakpoints or watchpoints are installed, so enter bpclear
in the debugger before quitting. And, be sure to report any errors, or else I'll never be able to fix them.
Great job on this research, this was a very interesting read. One related thing I'd like to mention is that each attack actually has two different random lookup tables set: One for when the attacker's life is 60 or higher, and another for when it's 59 or lower. IIRC, the latter tends to have a wider range.
Posted by: felineki | 06/27/2011 at 11:50 PM
Huh, it's true. I can just see the developers going "What's better than randomness? More randomness near KO!" Luckily the BPs work correctly either way.
BTW, I don't know if any attack uses the tables past offset 0x100 or so, but I included that far just in case.
Posted by: dammit | 06/28/2011 at 12:32 AM
This is great! This will make gathering damage and stun for all moves much easier, and mostly not anymore a huge hassle. Thank you very much for this.
Posted by: oldschool_BR | 06/28/2011 at 08:35 PM
You say that throw damage is not random, but is based on win/loss factors? Is this true? There is actually a throw counter for each player, and I do wonder if that comes in to play when determining the amount of damage done by a throw.
What are the factors that go into a determine how much damage a throw gives? If you can shoot me a bp address, I'll work on it.
Posted by: James | 06/28/2011 at 08:59 PM
really cool, probley should be noted that the speed of mash throws/number of hits is random
Posted by: error1 | 06/28/2011 at 09:58 PM
James: Here's what happens when you release someone from a throw in ssf2t:
004AE8: movea.l #$24e614, A1
004AEE: lea (A1,D2.w), A1
004AF2: move.w ($58,A6), D2
004AF6: move.b (A1,D2.w), D4
004AFA: ext.w D4
D4 is eventually the throw damage, whenever the impact happens.
D2 I think depends on the throw.
A6 is the attacker, and ($58,A6) changes from round to round. It's usually $10 but increases if the opponent is a round up. Try messing with that value and you'll see the damage changes.
error: Haha, you don't mean random random right?
Posted by: dammit | 06/28/2011 at 10:09 PM
I think I mean random random
mashing buttons for ether player can change it but there is a base speed
basically if you do max mashing of a mash throw ce ( what I tested but ST is the same I think ) the number of hits you get is random ( within constraints ). It can do between ~20%-100% damage
Posted by: error1 | 06/29/2011 at 02:29 AM
Oh right. I guess the random thing is not the max hits per se but the max hit rate.
If the attacker mashes the hits come out faster, and the duration is increased, but only up to that random maximum. There is also some random variation in how much the victim has to mash to end the hold early, added to 0x23:
ssf2t
0052C6: 00 F4 FA 09 06 09 06 FA 09 00 0C 00 F7 FA 00 09
0052D6: 06 F7 00 FA 0F 00 F7 06 FA 00 06 00 F7 F4 F1 0C
The durations are at 0x1E (byte), 0x88 (word) or 0x94 (word), and the escape meter is at 0x7C (word).
(Pasky has some info on this here.)
Posted by: dammit | 06/29/2011 at 03:52 PM
Thanks. It makes sense now. ($58,A6) would the the power index that Felineki talks about in his notes. It's the same index that is being used to determine the which base damage is selected among the possibilities an attack before the random damage is added.
Posted by: James | 06/29/2011 at 05:47 PM
Even the base damage changes? Whoever's filling out the wiki has got some work cut out for them.
Posted by: dammit | 06/29/2011 at 07:00 PM
Sup, dammit! Is it possible to add an option so that it will show the base damage and the range? It looks like you have it all figured out, but I don't know how easy it would be to make such things also visible.
Some codes for removing the background should be in that http://pastey.net domain, according to your page about hitboxes. But that domain seems down. Would it be possible for you to share it, if you have a copy?
Best regards
Posted by: oldschool_BR | 07/04/2011 at 09:46 PM
You mean, add to a Lua script? Because the base is already given and the range can be mapped to the offsets if you take a minute with a pen and paper.
It actually is possible to make a Lua script print those numbers by writing them in some unused RAM...
And sorry about pastey.net, I don't know wth happened to them and they might be gone for good. I shouldn't have thrown away the file that had all the codes together. An updated cheat pack with everything should surface soon enough. If just want the [ssf2t] code, here it is:
<cheat desc="Hide background">
<comment>Activate before match start; set to Claw or Dictator stage for no BG sprites</comment>
<script state="on">
<action>
temp0=maincpu.rb@FF8049 band 0F, temp1=maincpu.rb@FF804F band 0F,
temp2=maincpu.ow@00D7CA, temp3=maincpu.od@00D7CC, temp4=maincpu.od@00D7D0,
temp5=maincpu.ow@00D80E, temp6=maincpu.ow@006A44, temp7=maincpu.ow@0093F8, temp8=maincpu.ow@009526
</action>
</script>
<script state="run">
<action>
maincpu.pb@FF8049=maincpu.rb@FF8049 bor temp0, maincpu.pb@FF804F=maincpu.rb@FF804F bor temp1,
maincpu.ow@00D80E=temp5, maincpu.ow@006A44=temp6, maincpu.ow@0093F8=temp7, maincpu.ow@009526=temp8,
maincpu.ow@00D7CA=4E71, maincpu.od@00D7CC=4E714E71, <!--initial HUD draw-->
maincpu.od@00D7D0=4E714E71 <!--initial timer draw-->
</action>
<action condition="(maincpu.rw@FF8008 > 6 and maincpu.rb@FF8478 == 0 and maincpu.rb@FF8878 == 0)">
<!--activate only before and during match-->
maincpu.ow@00D80E=6016, <!--HUD updates-->
maincpu.ow@006A44=4E75 <!--score updates-->
</action>
<action condition="(maincpu.rw@FF8008 == A and maincpu.rw@FF8DD2 == 0)"> <!--activate only during match-->
maincpu.pb@FF8049=maincpu.rb@FF8049 band F0, maincpu.pb@FF804F=maincpu.rb@FF804F band F0, <!--BG layers-->
maincpu.ow@0093F8=4E71, maincpu.ow@009526=4E71 <!--flashing text-->
</action>
</script>
<script state="off">
<action>
maincpu.pb@FF8049=maincpu.rb@FF8049 bor temp0, maincpu.pb@FF804F=maincpu.rb@FF804F bor temp1,
maincpu.ow@00D7CA=temp2, maincpu.od@00D7CC=temp3, maincpu.od@00D7D0=temp4,
maincpu.ow@00D80E=temp5, maincpu.ow@006A44=temp6, maincpu.ow@0093F8=temp7, maincpu.ow@009526=temp8
</action>
</script>
</cheat>
Posted by: dammit | 07/04/2011 at 10:28 PM
Funny thing is that pastebin is back up.
Posted by: jedpossum | 07/04/2011 at 11:30 PM
Thanks, man! I hadn't used the -debug option, damn. The damage rounding is always towards the smaller integer, is it not? I guess that's the default for basically all processors.
Tracking the minimum and maximum in damage by writing it down, is OK, I guess, but there is already the stun to keep tracking. Some minimum and maximum values for those may take some time to be obtained. Still, just for having it appear in the screen directly and not have to do the math all the time, even if it is just adding and subtracting, helps. So, thanks again.
Posted by: oldschool | 07/05/2011 at 12:28 PM