@@ -2089,7 +2089,7 @@ func (e *Explod) update() {
20892089 for i := range e .velocity {
20902090 e .velocity [i ] *= e .friction [i ]
20912091 e .velocity [i ] += e .accel [i ]
2092- if math . Abs (float64 ( e .velocity [i ])) < 0.1 && math . Abs (float64 ( e .friction [i ]) ) < 1 {
2092+ if Abs (e .velocity [i ]) < 0.1 && Abs (e .friction [i ]) < 1 {
20932093 e .velocity [i ] = 0
20942094 }
20952095 }
@@ -4340,7 +4340,8 @@ func (c *Char) loadPalettes() {
43404340
43414341 // Allocate space if necessary
43424342 gi .palettedata .palList .SetSource (targetIdx , pl )
4343- gi .palettedata .palList .PalTable [[... ]uint16 {1 , uint16 (i + 1 )}] = targetIdx
4343+ gi .palettedata .palList .PalTable [[2 ]uint16 {1 , uint16 (i + 1 )}] = targetIdx
4344+ gi .palettedata .palList .numcols [[2 ]uint16 {1 , uint16 (i + 1 )}] = 256 // ACT files are always 256 colors
43444345
43454346 // Redirect index 0 without destroying the unique SFFv1 palette at physical index 0
43464347 if tmp == 0 && i > 0 {
@@ -6013,7 +6014,7 @@ func (c *Char) roundsExisted() int32 {
60136014 return 0
60146015 }
60156016 }
6016- return sys .round - 1
6017+ return sys .roundNo - 1
60176018 }
60186019 return sys .roundsExisted [c .playerNo & 1 ]
60196020}
@@ -6471,14 +6472,6 @@ func (c *Char) stateChange1(no int32, pn int) bool {
64716472 LogMessage ("Maximum ChangeState loops: %v, %v, %v -> %v -> %v" , sys .changeStateNest , c .name , c .ss .prevno , c .ss .no , no )
64726473 return false
64736474 }
6474- var ctrlsps_backup []int32
6475- if c .hitPause () {
6476- // If in hitpause, back up the current state's persistent.
6477- ctrlsps_backup = make ([]int32 , len (c .ss .sb .ctrlsps ))
6478- copy (ctrlsps_backup , c .ss .sb .ctrlsps )
6479- } else {
6480- ctrlsps_backup = nil
6481- }
64826475
64836476 c .ss .prevno = c .ss .no
64846477 c .ss .no = Max (0 , no )
@@ -6527,7 +6520,7 @@ func (c *Char) stateChange1(no int32, pn int) bool {
65276520
65286521 c .localscl = newLs
65296522 }
6530- var ok bool
6523+
65316524 // Check if player is trying to change to a negative state.
65326525 if no < 0 {
65336526 sys .appendToConsole (c .warn () + "attempted to change to negative state" )
@@ -6542,7 +6535,16 @@ func (c *Char) stateChange1(no int32, pn int) bool {
65426535 LogMessage ("Changed to out of bounds state number: P%v:%v" , pn + 1 , no )
65436536 }
65446537 }
6538+
6539+ // If in hitpause, back up the current state's persistent.
6540+ var ctrlsps_backup []int32
6541+ if c .hitPause () {
6542+ ctrlsps_backup = make ([]int32 , len (c .ss .sb .ctrlsps ))
6543+ copy (ctrlsps_backup , c .ss .sb .ctrlsps )
6544+ }
6545+
65456546 // Always attempt to change to the state we set to.
6547+ var ok bool
65466548 if c .ss .sb , ok = sys .cgi [pn ].states [c .ss .no ]; ! ok {
65476549 sys .appendToConsole (c .warn () + fmt .Sprintf ("changed to invalid state %v (from state %v)" , no , c .ss .prevno ))
65486550 if ! sys .ignoreMostErrors {
@@ -6551,6 +6553,7 @@ func (c *Char) stateChange1(no int32, pn int) bool {
65516553 c .ss .sb = * newStateBytecode (pn )
65526554 c .ss .sb .stateType , c .ss .sb .moveType , c .ss .sb .physics = ST_U , MT_U , ST_U
65536555 }
6556+
65546557 // Reset persistent counters for this state (Ikemen chars)
65556558 // This used to belong to (*StateBytecode).init(), but was moved outside there
65566559 // due to a MUGEN 1.1 problem where persistent was not getting reset until the end
@@ -6575,6 +6578,7 @@ func (c *Char) stateChange1(no int32, pn int) bool {
65756578 c .hitStateChangeIdx = - 1
65766579 }
65776580 }
6581+
65786582 c .stchtmp = true
65796583 return true
65806584}
@@ -8975,12 +8979,12 @@ func (c *Char) rdDistX(rd *Char, oc *Char) BytecodeValue {
89758979 return BytecodeUndefined ()
89768980 }
89778981 dist := c .facing * c .distX (rd , oc )
8978- if c .stWgi ().ikemenver [0 ] == 0 && c .stWgi ().ikemenver [1 ] == 0 {
8979- if c .stWgi ().mugenver [0 ] != 1 {
8980- // Before Mugen 1.0, rounding down to the nearest whole number was performed.
8981- dist = float32 (int32 (dist ))
8982- }
8982+
8983+ // Before Mugen 1.0, rounding down to the nearest whole number was performed.
8984+ if c .stWgi ().ikemenver [0 ] == 0 && c .stWgi ().ikemenver [1 ] == 0 && c .stWgi ().mugenver [0 ] != 1 {
8985+ dist = float32 (int32 (dist ))
89838986 }
8987+
89848988 return BytecodeFloat (dist )
89858989}
89868990
@@ -8989,12 +8993,12 @@ func (c *Char) rdDistY(rd *Char, oc *Char) BytecodeValue {
89898993 return BytecodeUndefined ()
89908994 }
89918995 dist := c .distY (rd , oc )
8992- if c .stWgi ().ikemenver [0 ] == 0 && c .stWgi ().ikemenver [1 ] == 0 {
8993- if c .stWgi ().mugenver [0 ] != 1 {
8994- // Before Mugen 1.0, rounding down to the nearest whole number was performed.
8995- dist = float32 (int32 (dist ))
8996- }
8996+
8997+ // Before Mugen 1.0, rounding down to the nearest whole number was performed.
8998+ if c .stWgi ().ikemenver [0 ] == 0 && c .stWgi ().ikemenver [1 ] == 0 && c .stWgi ().mugenver [0 ] != 1 {
8999+ dist = float32 (int32 (dist ))
89979000 }
9001+
89989002 return BytecodeFloat (dist )
89999003}
90009004
@@ -11551,18 +11555,19 @@ func (c *Char) actionPrepare() {
1155111555 if c .minus != 3 || c .csf (CSF_destroy ) || c .scf (SCF_disabled ) {
1155211556 return
1155311557 }
11558+
1155411559 c .pauseBool = false
11555- if c .cmd != nil {
11556- if sys .supertime > 0 {
11557- c .pauseBool = c .superMovetime == 0
11558- } else if sys .pausetime > 0 && c .pauseMovetime == 0 {
11559- c .pauseBool = true
11560- }
11560+ if sys .supertime > 0 {
11561+ c .pauseBool = c .superMovetime == 0
11562+ } else if sys .pausetime > 0 && c .pauseMovetime == 0 {
11563+ c .pauseBool = true
1156111564 }
11562- c .acttmp = - int8 (Btoi (c .pauseBool )) * 2
1156311565 // Due to the nature of how pauses are processed, these are needed to fix an "off by 1" error in the PauseTime trigger
1156411566 c .prevSuperMovetime = c .superMovetime
1156511567 c .prevPauseMovetime = c .pauseMovetime
11568+
11569+ c .acttmp = - int8 (Btoi (c .pauseBool )) * 2
11570+
1156611571 if ! c .pauseBool {
1156711572 // Perform basic actions
1156811573 if c .keyctrl [0 ] && c .cmd != nil && (c .helperIndex == 0 || c .controller >= 0 ) {
@@ -11623,7 +11628,7 @@ func (c *Char) actionPrepare() {
1162311628 c .setCSF (CSF_playerpush )
1162411629 }
1162511630 }
11626- // Reset player pushing priority
11631+ // Reset player pushing properties
1162711632 c .pushPriority = 0
1162811633 c .pushAffectTeam = 1
1162911634 // HitBy timers
@@ -11668,6 +11673,11 @@ func (c *Char) actionPrepare() {
1166811673 // This AssertSpecial flag is special in that it must always reset regardless of hitpause
1166911674 c .unsetASF (ASF_animatehitpause )
1167011675
11676+ // This variable is necessary because NoStandGuard is reset before the walking instructions are checked
11677+ // https://github.com/ikemen-engine/Ikemen-GO/issues/1966
11678+ // Update: No longer strictly necessary but we'll leave it in for now
11679+ c .prevNoStandGuard = c .asf (ASF_nostandguard )
11680+
1167111681 // The flags in this block are to be reset even during hitpause
1167211682 // Exception for WinMugen chars, where they persisted during hitpause
1167311683 if c .stWgi ().ikemenver [0 ] != 0 || c .stWgi ().ikemenver [1 ] != 0 || c .stWgi ().mugenver [0 ] == 1 || ! c .hitPause () {
@@ -11677,8 +11687,10 @@ func (c *Char) actionPrepare() {
1167711687 c .alpha = [2 ]int32 {255 , 0 }
1167811688 c .offset = [2 ]float32 {}
1167911689 // Reset all AssertSpecial flags except the following, which are reset elsewhere in the code
11680- c .assertFlag = (c .assertFlag & ASF_nostandguard | c .assertFlag & ASF_nocrouchguard | c .assertFlag & ASF_noairguard |
11681- c .assertFlag & ASF_runfirst | c .assertFlag & ASF_runlast )
11690+ // TODO: Maybe these don't need special treatment anymore either
11691+ // All this does right now is make IsAsserted more accurate, but that's already inaccurate in other places
11692+ keptflags := ASF_runfirst | ASF_runlast
11693+ c .assertFlag &= keptflags
1168211694 }
1168311695
1168411696 // The flags below also reset during hitpause, but are new to Ikemen and don't need the exception above
@@ -11777,6 +11789,7 @@ func (c *Char) actionRun() {
1177711789 c .minus = 0
1177811790 c .ss .sb .run (c )
1177911791 }
11792+
1178011793 // Guarding instructions
1178111794 c .unsetSCF (SCF_guard )
1178211795 if ((c .scf (SCF_ctrl ) || c .ss .no == 52 ) &&
@@ -11787,6 +11800,7 @@ func (c *Char) actionRun() {
1178711800 c .ss .stateType == ST_A && ! c .asf (ASF_noairguard )) {
1178811801 c .setSCF (SCF_guard )
1178911802 }
11803+
1179011804 if ! c .pauseBool {
1179111805 if c .keyctrl [0 ] && c .cmd != nil {
1179211806 if c .ctrl () && (c .controller >= 0 || c .helperIndex == 0 ) {
@@ -12005,6 +12019,7 @@ func (c *Char) actionFinish() {
1200512019 if c .minus < 1 || c .csf (CSF_destroy ) || c .scf (SCF_disabled ) {
1200612020 return
1200712021 }
12022+
1200812023 if ! c .pauseBool {
1200912024 if c .palfx != nil && c .ownpal {
1201012025 c .palfx .step ()
@@ -12013,18 +12028,18 @@ func (c *Char) actionFinish() {
1201312028 c .ghv .frame = false
1201412029 c .mhv .frame = false
1201512030 }
12031+
1201612032 // Reset inguarddist flag before running hit detection (where it will be updated)
1201712033 // https://github.com/ikemen-engine/Ikemen-GO/issues/2328
1201812034 c .inguarddist = false
12019- // This variable is necessary because NoStandGuard is reset before the walking instructions are checked
12020- // https://github.com/ikemen-engine/Ikemen-GO/issues/1966
12021- c .prevNoStandGuard = c .asf (ASF_nostandguard )
12022- c .unsetASF (ASF_nostandguard | ASF_nocrouchguard | ASF_noairguard )
12035+
1202312036 // Save current HitFall value before hit detection
1202412037 c .prevfallflag = c .ghv .fallflag
12038+
1202512039 // Update Z scale
1202612040 // Must be placed after posUpdate()
1202712041 c .zScale = sys .updateZScale (c .pos [2 ], c .localscl )
12042+
1202812043 // KO behavior
1202912044 if ! c .hitPause () && ! c .pauseBool {
1203012045 if c .alive () && c .life <= 0 && ! sys .gsf (GSF_globalnoko ) && ! c .asf (ASF_noko ) && (! c .ghv .guarded || ! c .asf (ASF_noguardko )) {
@@ -12050,13 +12065,15 @@ func (c *Char) actionFinish() {
1205012065 }
1205112066 }
1205212067 }
12068+
1205312069 // Over flags (char is finished for the round)
1205412070 if c .alive () && c .life > 0 && ! sys .roundEnded () {
1205512071 c .unsetSCF (SCF_over_alive | SCF_over_ko )
1205612072 }
1205712073 if c .ss .no == 5150 && ! c .scf (SCF_over_ko ) { // Actual KO is not required in Mugen
1205812074 c .setSCF (SCF_over_ko )
1205912075 }
12076+
1206012077 // Signal that "actionFinish" has finished
1206112078 c .minus = 2
1206212079}
@@ -13669,30 +13686,11 @@ func (cl *CharList) pushDetection(getter *Char) {
1366913686 gybot := (getter .pos [1 ] + gbox [3 ]) * getter .localscl
1367013687
1367113688 overlapY := Min (cybot , gybot ) - Max (cytop , gytop )
13672- if overlapY <= 0 {
13673- continue
13674- }
1367513689
13676- // Clamp width
13677- // Mugen secretly does this for some reason
13678- // https://github.com/ikemen-engine/Ikemen-GO/issues/3164
13679- if c .stWgi ().ikemenver [0 ] == 0 && c .stWgi ().ikemenver [1 ] == 0 {
13680- minwidth := 5.0 / c .localscl
13681- if cbox [0 ] > - minwidth {
13682- cbox [0 ] = - minwidth
13683- }
13684- if cbox [2 ] < minwidth {
13685- cbox [2 ] = minwidth
13686- }
13687- }
13688- if getter .stWgi ().ikemenver [0 ] == 0 && getter .stWgi ().ikemenver [1 ] == 0 {
13689- minwidth := 5.0 / getter .localscl
13690- if gbox [0 ] > - minwidth {
13691- gbox [0 ] = - minwidth
13692- }
13693- if gbox [2 ] < minwidth {
13694- gbox [2 ] = minwidth
13695- }
13690+ // For the y-axis, an overlap of exactly 0 is also valid for pushing characters away from each other, hence '<'
13691+ // We don't need a "zero-height case" because the y-overlap is only used as a filter, not to calculate the push distance
13692+ if overlapY < 0 {
13693+ continue
1369613694 }
1369713695
1369813696 // X-axis check
@@ -13716,25 +13714,84 @@ func (cl *CharList) pushDetection(getter *Char) {
1371613714
1371713715 overlapX := Min (gxright , cxright ) - Max (gxleft , cxleft )
1371813716
13719- // X-axis fail
13717+ // Zero width case
13718+ // These can also push in Mugen
13719+ // https://github.com/ikemen-engine/Ikemen-GO/issues/3164
13720+ if overlapX == 0 && (cxleft == cxright || gxleft == gxright ) {
13721+ cHalfW := (cxright - cxleft ) * 0.5
13722+ gHalfW := (gxright - gxleft ) * 0.5
13723+ cCenterX := (cxright + cxleft ) * 0.5
13724+ gCenterX := (gxright + gxleft ) * 0.5
13725+
13726+ overlapX = (cHalfW + gHalfW ) - Abs (cCenterX - gCenterX )
13727+ }
13728+
13729+ /*
13730+ // In addition to the normal width check, Mugen also checks overlap between an undocumented "internal width" of 5 pixels
13731+ // The normal and fallback width checks are not mixed with each other
13732+ // Update: The addition of the zero width case makes this seemingly unnecessary
1372013733 if overlapX <= 0 {
13734+ // We will only do it for Mugen characters because it defeats the purpose of lowering width
13735+ cIsOld := c.stWgi().ikemenver[0] == 0 && c.stWgi().ikemenver[1] == 0
13736+ gIsOld := getter.stWgi().ikemenver[0] == 0 && getter.stWgi().ikemenver[1] == 0
13737+ if cIsOld || gIsOld {
13738+ if cIsOld {
13739+ minwidth := 5.0 / c.localscl
13740+ cxleft = cposx - minwidth
13741+ cxright = cposx + minwidth
13742+ }
13743+ if gIsOld {
13744+ minwidth := 5.0 / getter.localscl
13745+ gxleft = gposx - minwidth
13746+ gxright = gposx + minwidth
13747+ }
13748+ overlapX = Min(gxright, cxright) - Max(gxleft, cxleft)
13749+ }
13750+ }
13751+ */
13752+
13753+ // X-axis fail
13754+ // An overlap of exactly 0 is still valid because pushing may happen along the z-axis
13755+ if overlapX < 0 {
1372113756 continue
1372213757 }
1372313758
1372413759 // Z-axis check
1372513760 // We don't use the zAxisCheck function because we need the actual overlap amount
13726- cposz := c .pos [2 ] * c .localscl
13727- cztop := cposz - c .sizeDepth [0 ]* c .localscl
13728- czbot := cposz + c .sizeDepth [1 ]* c .localscl
13761+ // We'll also declare all the vars upfront but only use them if z-axis is enabled
13762+ var overlapZ float32
13763+ var cposz , cztop , czbot , gposz , gztop , gzbot float32
13764+
13765+ if sys .zEnabled () {
13766+ cposz = c .pos [2 ] * c .localscl
13767+ cztop = cposz - c .sizeDepth [0 ]* c .localscl
13768+ czbot = cposz + c .sizeDepth [1 ]* c .localscl
13769+
13770+ gposz = getter .pos [2 ] * getter .localscl
13771+ gztop = gposz - getter .sizeDepth [0 ]* getter .localscl
13772+ gzbot = gposz + getter .sizeDepth [1 ]* getter .localscl
13773+
13774+ overlapZ = Min (gzbot , czbot ) - Max (gztop , cztop )
1372913775
13730- gposz := getter .pos [2 ] * getter .localscl
13731- gztop := gposz - getter .sizeDepth [0 ]* getter .localscl
13732- gzbot := gposz + getter .sizeDepth [1 ]* getter .localscl
13776+ // Zero depth case
13777+ if overlapZ == 0 && (cztop == czbot || gztop == gzbot ) {
13778+ cHalfD := (czbot - cztop ) * 0.5
13779+ gHalfD := (gzbot - gztop ) * 0.5
13780+ cCenterZ := (czbot + cztop ) * 0.5
13781+ gCenterZ := (gzbot + gztop ) * 0.5
1373313782
13734- overlapZ := Min (gzbot , czbot ) - Max (gztop , cztop )
13783+ overlapZ = (cHalfD + gHalfD ) - Abs (cCenterZ - gCenterZ )
13784+ }
13785+ }
1373513786
1373613787 // Z-axis fail
13737- if overlapZ <= 0 {
13788+ // An overlap of exactly 0 is still valid because pushing may happen along the x-axis
13789+ if overlapZ < 0 {
13790+ continue
13791+ }
13792+
13793+ // If players are barely touching but the pushing distance will be 0, just skip the no-op math
13794+ if overlapX == 0 && overlapZ == 0 {
1373813795 continue
1373913796 }
1374013797
0 commit comments