Hello, guys. I come forth with another two (maybe three) bug fixes! These bugs are present in Sonic 1 and Sonic 2, but has been fixed in Sonic 3 (and Knuckles). There is a drowning bug. You know, you're underwater, timer starts, can't find air, you drown. But what happens if you get hurt as soon as you're about to drown? Well, this little video will demonstrate: If you're hurt and you haven't landed yet, when you drown, Sonic will still be using the gravity from his falling back from hurt state. He will also be able to detect the floors and walls. Hence why in this video, I was able to move about after drowning. However, there is a timer present, and it will force you to the bottom of the screen and restart within seconds. Also, in both Sonic 1 and 2, there is another bug, although, hard to pull off. If you drown around the 9:58 mark, then the timer hits 9:59 and you're still drowning, Sonic will all of a sudden zoom to the top of the screen then back down again, really fast, in his death animation. The Time over appears. In Sonic 2, there is an extra bug. When you and Tails (sidekick) drowns (normally), when Sonic reaches to the bottom of the screen, if Tails hasn't yet, he will suddenly start flying and move closer to where you drowned, but will continue to fall downwards. In Sonic 3 and Knuckles, all of these have been fixed. It doesn't matter if you're in the hurt state, Sonic will still fall normally and will ignore the floor. And unless Tails entered the water at the exact same time as you (like he does at the beginning of HCZ1), Tails will automatically start swimming to where you drowned but will not fall. If he did enter the water at the same time, he will drown with you, but won't have time to do that bug where he would start flying suddenly. Time over will still make an appearance if you drown near the 9:58 mark, but Sonic won't misbehave. Today, I'm going to show you how to fix these bugs in Sonic 1 and Sonic 2, the way Sonic 3 and Knuckles did it. It's not that hard, I've done all the research for you. I will show you Sonic 1 SVN, and for Sonic 2, both Xenowhirls 2007 and SVN disassembly. Sonic 1 fix - SVN Disassembly First, we need to make little edit to the drowning-routine. You need to open the "0A - Drowning Countdown.asm". Go to "@reduceair:" and just after the "move.w #0,obInertia(a0)" command, insert this: Code (ASM): move.b #$A,obRoutine(a0) ; Force the character to drown And just after "move.b #1,(f_nobgscroll).w", insert this: Code (ASM): move.b #0,(f_timecount).w ; Stop the timer immediately So you have this: Code (ASM): @reduceair: subq.w #1,(v_air).w ; subtract 1 from air remaining bcc.w @gotomakenum ; if air is above 0, branch ; Sonic drowns here bsr.w ResumeMusic move.b #$81,(f_lockmulti).w ; lock controls sfx sfx_Drown ; play drowning sound move.b #$A,$34(a0) move.w #1,$36(a0) move.w #$78,$2C(a0) move.l a0,-(sp) lea (v_player).w,a0 bsr.w Sonic_ResetOnFloor move.b #$17,obAnim(a0) ; use Sonic's drowning animation bset #1,obStatus(a0) bset #7,obGfx(a0) move.w #0,obVelY(a0) move.w #0,obVelX(a0) move.w #0,obInertia(a0) move.b #$A,obRoutine(a0) ; Force the character to drown move.b #1,(f_nobgscroll).w move.b #0,(f_timecount).w ; Stop the timer immediately movea.l (sp)+,a0 rts Next, go to "@loc_13F86:" and change all this: Code (ASM): @loc_13F86: subq.w #1,$2C(a0) bne.s @loc_13F94 move.b #6,(v_player+obRoutine).w rts ; =========================================================================== @loc_13F94: move.l a0,-(sp) lea (v_player).w,a0 jsr SpeedToPos addi.w #$10,obVelY(a0) movea.l (sp)+,a0 bra.s @nochange To this: Code (ASM): @loc_13F86: subq.w #1,$2C(a0) bne.s @nochange ; Make it jump straight to this location move.b #6,(v_player+obRoutine).w rts Right, that's the drowning-routine finished. Now, we need to make Sonic apply to this. Now, open the main ASM file. Go to "Sonic_Index:" and add this line at the end of the table: Code (ASM): dc.w Sonic_Drowned-Sonic_Index So you have: Code (ASM): Sonic_Index: dc.w Sonic_Main-Sonic_Index dc.w Sonic_Control-Sonic_Index dc.w Sonic_Hurt-Sonic_Index dc.w Sonic_Death-Sonic_Index dc.w Sonic_ResetLevel-Sonic_Index dc.w Sonic_Drowned-Sonic_Index Next, find "include "_incObj\Sonic Loops.asm"" and just below it, insert this: Code (ASM): include "_incObj\Sonic Drowns.asm" Then, in the "_incObj" folder, make a new ASM file called "Sonic Drowns.asm" and insert this: Code (ASM): ; --------------------------------------------------------------------------- ; Sonic when he's drowning ; --------------------------------------------------------------------------- ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| Sonic_Drowned: bsr.w SpeedToPos ; Make Sonic able to move addi.w #$10,y_vel(a0) ; Apply gravity bsr.w Sonic_RecordPosition ; Record position bsr.s Sonic_Animate ; Animate Sonic bsr.w Sonic_LoadGfx ; Load Sonic's DPLCs bra.w DisplaySprite ; And finally, display Sonic There, all done. When you drown, everything will be normal. If you got hurt then drown, everything will still be normal and you won't be able to hit the floor, nor will you fall fast. Also, as soon as Sonic drowns, the timer will stop immediately, rather than continuing to countdown. Because of this, there is no way you can get Time Over when drowning. Sonic 2 fix - XenoWhirl's Disassembly First, we need to make little edit to the drowning-routine. Go to "Obj0A_ReduceAir:" and just after the "move.w #0,inertia(a0)" command, insert this: Code (ASM): move.b #$C,routine(a0) ; Force the character to drown And just after "move.b #1,($FFFFEEDC).w", insert this: Code (ASM): move.b #0,(Update_HUD_timer).w ; Stop the timer immediately So you have this: Code (ASM): Obj0A_ReduceAir: subq.b #1,air_left(a2) ; subtract 1 from air remaining bcc.w BranchTo_Obj0A_MakeItem ; if air is above 0, branch move.b #$81,obj_control(a2) ; lock controls move.w #$32+$80,d0 jsr (PlaySound).l ; play drowning sound move.b #$A,objoff_34(a0) move.w #1,objoff_36(a0) move.w #$78,objoff_2C(a0) movea.l a2,a1 bsr.w ResumeMusic move.l a0,-(sp) movea.l a2,a0 bsr.w Sonic_ResetOnFloor_Part2 move.b #$17,anim(a0) ; use Sonic's drowning animation bset #1,status(a0) bset #7,art_tile(a0) move.w #0,y_vel(a0) move.w #0,x_vel(a0) move.w #0,inertia(a0) move.b #$C,routine(a0) ; Force the character to drown movea.l (sp)+,a0 ; load 0bj address ; restore a0 = obj0A cmpa.w #MainCharacter,a2 bne.s + ; if it isn't player 1, branch move.b #1,($FFFFEEDC).w move.b #0,(Update_HUD_timer).w ; Stop the timer immediately + rts Next, go to "loc_1D708:" and change all this: Code (ASM): loc_1D708: subq.w #1,objoff_2C(a0) bne.s + move.b #6,routine(a2) rts ; --------------------------------------------------------------------------- + move.l a0,-(sp) movea.l a2,a0 jsr ObjectMove addi.w #$10,y_vel(a0) movea.l (sp)+,a0 ; load 0bj address bra.s loc_1D72C To this: Code (ASM): loc_1D708: subq.w #1,objoff_2C(a0) bne.s loc_1D72C ; Make it jump straight to this location move.b #6,routine(a2) rts Right, that's the drowning-routine finished. Now, we need to make the characters apply to this. Sonic. Go to "Obj01_States:" and add this line at the end of the table: Code (ASM): dc.w Obj01_Drowned - Obj01_States ;$C So you have: Code (ASM): Obj01_States: dc.w Obj01_Init - Obj01_States ; 0 dc.w Obj01_Control - Obj01_States ; 2 dc.w Obj01_Hurt - Obj01_States ; 4 dc.w Obj01_Dead - Obj01_States ; 6 dc.w Obj01_Gone - Obj01_States ; 8 dc.w Obj01_Respawning - Obj01_States ;$A dc.w Obj01_Drowned - Obj01_States ;$C Next, go to "Sonic_Animate:" and just above it, insert this: Code (ASM): ; --------------------------------------------------------------------------- ; Sonic when he's drowning ; --------------------------------------------------------------------------- Obj01_Drowned: bsr.w ObjectMove ; Make Sonic able to move addi.w #$10,y_vel(a0) ; Apply gravity bsr.w Sonic_RecordPos ; Record position bsr.w Sonic_Animate ; Animate Sonic bsr.w LoadSonicDynPLC ; Load Sonic's DPLCs bra.w DisplaySprite ; And finally, display Sonic That's Sonic done. Tails is next. Go to "Obj02_States:" and again, insert this line at the end of the table: Code (ASM): dc.w Obj02_Drowned - Obj02_States ;$C So you have: Code (ASM): Obj02_States: dc.w Obj02_Init - Obj02_States ; 0 dc.w Obj02_Control - Obj02_States ; 2 dc.w Obj02_Hurt - Obj02_States ; 4 dc.w Obj02_Dead - Obj02_States ; 6 dc.w Obj02_Gone - Obj02_States ; 8 dc.w Obj02_Respawning - Obj02_States ;$A dc.w Obj02_Drowned - Obj02_States ;$C Next, go to "Tails_Animate:" and just above it, insert this: Code (ASM): ; --------------------------------------------------------------------------- ; Tails when he's drowning ; --------------------------------------------------------------------------- Obj02_Drowned: bsr.w ObjectMove ; Make Tails able to move addi.w #$10,y_vel(a0) ; Apply gravity bsr.w Tails_RecordPos ; Record position bsr.s Tails_Animate ; Animate Tails bsr.w LoadTailsDynPLC ; Load Tails's DPLCs bra.w DisplaySprite ; And finally, display Tails There, all done. When you drown, everything will be normal. If you got hurt then drown, everything will still be normal and you won't be able to hit the floor, nor will you fall fast. If Tails is with you, he will fly out of the water as soon as you drown. Unless he entered the water at the exact same time as you; then he will drown with you, but won't have time to suddenly fly again. Now, as soon as Sonic drowns, the timer will stop immediately, rather than continuing to countdown. Because of this, there is no way you can get Time Over when drowning. Sonic 2 fix - SVN Disassembly First, we need to make little edit to the drowning-routine. Go to "Obj0A_ReduceAir:" and just after the "move.w #0,inertia(a0)" command, insert this: Code (ASM): move.b #$C,routine(a0) ; Force the character to drown And just after "move.b #1,(Deform_lock).w", insert this: Code (ASM): move.b #0,(Update_HUD_timer).w ; Stop the timer immediately So you have this: Code (ASM): Obj0A_ReduceAir: subq.b #1,air_left(a2) ; subtract 1 from air remaining bcc.w BranchTo_Obj0A_MakeItem ; if air is above 0, branch move.b #$81,obj_control(a2) ; lock controls move.w #SndID_Drown,d0 jsr (PlaySound).l ; play drowning sound move.b #$A,objoff_34(a0) move.w #1,objoff_36(a0) move.w #$78,objoff_2C(a0) movea.l a2,a1 bsr.w ResumeMusic move.l a0,-(sp) movea.l a2,a0 bsr.w Sonic_ResetOnFloor_Part2 move.b #$17,anim(a0) ; use Sonic's drowning animation bset #1,status(a0) bset #high_priority_bit,art_tile(a0) move.w #0,y_vel(a0) move.w #0,x_vel(a0) move.w #0,inertia(a0) move.b #$C,routine(a0) ; Force the character to drown movea.l (sp)+,a0 ; load 0bj address ; restore a0 = obj0A cmpa.w #MainCharacter,a2 bne.s + ; if it isn't player 1, branch move.b #1,(Deform_lock).w move.b #0,(Update_HUD_timer).w ; Stop the timer immediately + rts Next, go to "loc_1D708:" and change all this: Code (ASM): loc_1D708: subq.w #1,objoff_2C(a0) bne.s + move.b #6,routine(a2) rts ; --------------------------------------------------------------------------- + move.l a0,-(sp) movea.l a2,a0 jsr (ObjectMove).l addi.w #$10,y_vel(a0) movea.l (sp)+,a0 ; load 0bj address bra.s loc_1D72C To this: Code (ASM): loc_1D708: subq.w #1,objoff_2C(a0) bne.s loc_1D72C ; Make it jump straight to this location move.b #6,routine(a2) rts Right, that's the drowning-routine finished. Now, we need to make the characters apply to this. Sonic. Go to "Obj01_Index:" and add this line at the end of the table: Code (ASM): offsetTableEntry.w Obj01_Drowned ; $C So you have: Code (ASM): Obj01_Index: offsetTable offsetTableEntry.w Obj01_Init ; 0 offsetTableEntry.w Obj01_Control ; 2 offsetTableEntry.w Obj01_Hurt ; 4 offsetTableEntry.w Obj01_Dead ; 6 offsetTableEntry.w Obj01_Gone ; 8 offsetTableEntry.w Obj01_Respawning ; $A offsetTableEntry.w Obj01_Drowned ; $C Next, go to "Sonic_Animate:" and just above it, insert this: Code (ASM): ; --------------------------------------------------------------------------- ; Sonic when he's drowning ; --------------------------------------------------------------------------- Obj01_Drowned: bsr.w ObjectMove ; Make Sonic able to move addi.w #$10,y_vel(a0) ; Apply gravity bsr.w Sonic_RecordPos ; Record position bsr.w Sonic_Animate ; Animate Sonic bsr.w LoadSonicDynPLC ; Load Sonic's DPLCs bra.w DisplaySprite ; And finally, display Sonic That's Sonic done. Tails is next. Go to "Obj02_Index:" and again, insert this line at the end of the table: Code (ASM): offsetTableEntry.w Obj02_Drowned ; $C So you have: Code (ASM): Obj02_Index: offsetTable offsetTableEntry.w Obj02_Init ; 0 offsetTableEntry.w Obj02_Control ; 2 offsetTableEntry.w Obj02_Hurt ; 4 offsetTableEntry.w Obj02_Dead ; 6 offsetTableEntry.w Obj02_Gone ; 8 offsetTableEntry.w Obj02_Respawning ; $A offsetTableEntry.w Obj02_Drowned ; $C Next, go to "Tails_Animate:" and just above it, insert this: Code (ASM): ; --------------------------------------------------------------------------- ; Tails when he's drowning ; --------------------------------------------------------------------------- Obj02_Drowned: bsr.w ObjectMove ; Make Tails able to move addi.w #$10,y_vel(a0) ; Apply gravity bsr.w Tails_RecordPos ; Record position bsr.s Tails_Animate ; Animate Tails bsr.w LoadTailsDynPLC ; Load Tails's DPLCs bra.w DisplaySprite ; And finally, display Tails There, all done. When you drown, everything will be normal. If you got hurt then drown, everything will still be normal and you won't be able to hit the floor, nor will you fall fast. If Tails is with you, he will fly out of the water as soon as you drown. Unless he entered the water at the exact same time as you; then he will drown with you, but won't have time to suddenly fly again. Now, as soon as Sonic drowns, the timer will stop immediately, rather than continuing to countdown. Because of this, there is no way you can get Time Over when drowning. There you have it! All fixed! Enjoy!
Thanks. My sister was playing my hack the night before the contest updates were due, then ran into this bug. I then checked Sonic 1 and 2 and realised it was a bug there too, but fixed in S3K. I came up with a very very quick shitty fix for it and submitted my hack. So in the contest, my hack has the bug fixed, but Sonic zooms to the bottom. It looks weird, but it works. The day after, I had time to investigate it properly, and then came up with proper fixes. This works perfectly now in my hack and Sonic drowns like normal no matter what, shame it wasn't in time. Sorry I couldn't come up with this quicker, guys!
This reminds me of a bug in both Sonic's 1 & 2. I don't know if it is caused by the same EXACT bug you just covered... BUT, to see it, go to Chemical Plant Zone Act 2, and go to the lower spike section with the extra life. Wait underwater just before you drown, and get hit by the spikes. As you mentioned, when drowning in the hurt animation, things go awry... well, spikes make it even worse. If done right, you will drown, then die due to being hit by the spikes... losing 2 lives. One could pull this off and underflow the lives counter, setting it to a glitchy 255. I do this all the time to keep a high number of lives, but it'd be nice to fix.
Yes, this does fix this glitch. But this glitch you mention is heavily misunderstood. When you're drowning, you can't hit any more objects, even from the hurt state. It's impossible to hit them spikes when you're drowning. If you try it again, if you look very closely, Sonic actually drowns through the spikes just fine. It's the bottom of the screen that is causing it. If you get hurt, then drown, then hit the bottom of the screen, this will cause the losing 2 lives glitch; not the spikes itself. Try it. Use debug, make a spike or badnik underwater right by the bottom of the screen (CPZ or ARZ), then get hurt then drown; same thing will happen. Anyway, this glitch will now be fixed. I should explain how and why my fixes helps. Before the fix Object 0A (Small bubbles from Sonic's face while underwater), also controls the timer for when the drown jingle should start and when you should drown (Obj0A_ReduceAir. "Obj0A_ReduceAir:" will make you drown, and once your time was up, it just pretty much do these commands: Code (ASM): jsr ObjectMove addi.w #$10,y_vel(a0) This code is in the Obj0A object. So, it would basically make Sonic move downwards, and that's it. There is a quick timer running, and when it reaches 0, it will change Sonic's routine to dead, taking a life. That's how the drowning system works in Sonic 1 and 2, and it normally works fine. Because it's in the Obj0A code, Sonic's original code was still running. When Sonic gets hurt, his routine changes (to his hurt routine), and this is what causes the trouble. So, when Sonic now drowns, his hurt routine will still run. In Sonic's hurt routine, he can check for floors and walls. So if you get hurt, and then drown, he will wait 'til he hits the floor, and when he does, Sonic's routine will go back to normal, making Sonic himself normal and able to move. The Obj0A code is still running, so when that quick timer is up, it will force sonic to die and take a life. In his hurt state, Sonic will still check for boundaries, so if hurt then drown, as soon as he hits the bottom of the screen, Sonic will change to his dead routine instantly. The dead routine will take 1 life. That quick timer is still running and once up, it will restarts Sonic dead routine, so it will take another life. The reason why he moves so fast in these glitches is because his hurt/dead routine and Obj0A are both adding to his gravity, a bit too much. Sonic's hurt routine does not do the checks for Touch_Response, so even in his hurt routine, he can't interact with many objects, and that's why it wasn't the spikes causing the 2-lives glitch. With the fix With the fix above, we gave Sonic himself a brand new routine (Obj01_Drowned). Sonic's new routine, only contains this code: Code (ASM): bsr.w ObjectMove ; Make Sonic able to move addi.w #$10,y_vel(a0) ; Apply gravity bsr.w Sonic_RecordPos ; Record position bsr.w Sonic_Animate ; Animate Sonic bsr.w LoadSonicDynPLC ; Load Sonic's DPLCs bra.w DisplaySprite ; And finally, display Sonic As you can see, there are no checks for floors, no checks for walls, no checks for level boundaries, etc. All it simply does is make Sonic move, add a little gravity, and display him. Because of this, there is NO WAY Sonic can now interact with anything. Then at the "Obj0A_ReduceAir:", we made it so as soon as Sonic drowns, it will force Sonic to his new routine (so if Sonic is hurt, the routine will be changed to the new drown routine anyway). Then in "Obj0A_ReduceAir:" we took out the code where it applies gravity to Sonic (no point now as Sonic has his routine). So now, hurt or not, when Sonic drowns, it will force Sonic to his new routine no matter what. His new routine has no checks for floors or boundaries, so Sonic can just fall and drown peacefully. The quick timer from Obj0A is still running, and once up, it will change to Sonic's dead routine, and take 1 life only.
Implemented this in my Sonic 1 REV C rom. Works great, only 1 nitpick. No bubbles come from Sonic's mouth when he is in his drowning animation. Bubbles come out of his mouth normally, and the countdown numbers work fine... but after he drowns, no bubbles come out. Haven't tried for Sonic 2, but I'd assume it would be a similar result.
Gah, sorry about that. In Sonic 2 Recreation, objects never freeze, so this wasn't a problem for me. Anyway, the fix is extremely simple. Sonic 1 fix - SVN Disassembly Go to label "loc_D362:" and between the label and it's first command, insert this: Code (ASM): cmpi.b #$A,(v_player+obRoutine).w ; Has Sonic drowned? beq.s loc_D348 ; If so, run objects a little longer So you have this: Code (ASM): loc_D362: cmpi.b #$A,(v_player+obRoutine).w ; Has Sonic drowned? beq.s loc_D348 ; If so, run objects a little longer moveq #$1F,d7 bsr.s loc_D348 moveq #$5F,d7 Done. Sonic 2 fix - XenoWhirl's Disassembly AND SVN Disassembly Go to label "RunObjectsWhenPlayerIsDead:" and between the label and it's first command, insert this: Code (ASM): cmpi.b #$C,(MainCharacter+routine).w ; Has Sonic drowned? beq.s RunObject ; If so, run objects a little longer Done. Explanation "RunObjects:" is always running during a level. It asks the game every single frame, "Is Sonic's routine #6 or higher?" and if so, to branch to a newer routine. Otherwise, it will go onto the "RunObject" routine. The routine it branches to is "RunObjectsWhenPlayerIsDead". The routine to freeze all objects when Sonic dies. So, at this routine, we've made it ask, "Has Sonic drowned?" and if so, to continue running the objects a bit longer. So now, the bubbles have a chance to come out of Sonic when he drowns. Remember, that quick timer is still running, which will change Sonic's routine to his dead routine, and that's when finally, "RunObjectsWhenPlayerIsDead" will start running. The reason why we've put the new command here, is because then it only asks this question when Sonic is dead or drowned. You can put it in the "Runobjects" routine instead, but that means it asks it every single frame when playing the game normally. And that's just a waste of time on the processor. So it is not recommended. Asking it at "RunObjectsWhenPlayerIsDead" is much better, because it then only asks it when Sonic is dead or drowned, which is what we want. So basically, this new fix won't slow your game down at all.
Sweet deal, I'm now ready to implement this in the SCHG... along with a few others that slipped by lately. I just barely got to Esrael's Sonic 2 Starpost fix... I'll get to it this afternoon... got another question for you though RHS, but I'll PM this to you later. http://info.sonicretro.org/SCHG_How-to:Correct_Drowning_Bugs_in_Sonic_1 http://info.sonicretro.org/SCHG_How-to:Correct_Drowning_Bugs_in_Sonic_2 Oh, and I figured it out on my own so no need to ask, but if you could check the post I made for any errors, would be grand. That right there is my first ever fix. Kinda owe you thanks though, for pointing out the particular code I was looking for, in THIS guide.