Sonic and Sega Retro Message Board: Some changes and fixes for Sonic 2 - Sonic and Sega Retro Message Board

Jump to content

Hey there, Guest!  (Log In · Register) Help
  • 16 Pages +
  • ◄ First
  • 14
  • 15
  • 16
    Locked
    Locked Forum

Some changes and fixes for Sonic 2

#226 User is online Clownacy 

Posted 06 October 2017 - 06:17 PM

  • Posts: 762
  • Joined: 06-July 13
  • Gender:Female
Ever notice how rings disappear while still onscreen if you move them offscreen slightly, but only off the top of the screen? I sure didn't until my last playthrough. Anyhow, it looks like this was fixed in S3K, so let's just do what that does.

The code that causes this is under BuildRings:

	move.w	4(a0),d2	; get ring Y pos
	sub.w	4(a3),d2	; subtract camera Y pos
	andi.w	#$7FF,d2
	addi_.w	#8,d2
	bmi.s	BuildRings_NextRing	; dunno how this check is supposed to work
	cmpi.w	#240,d2
	bge.s	BuildRings_NextRing	; if the ring is not on-screen, branch


What's important to note is how d2 is AND'd, and then 8 is added to it. The reason this is a problem is, consider that the ring is slightly offscreen: d2 will be $FFFF or so, which, after ANDing, becomes $7FF. Now, when adding 8 to it, it won't wrap back around to $0007, but $0807. It's also worth noting that the 'bmi' there is broken, also because of the position of this AND.

That said, the solution is pretty obvious: just move the 'andi' to before the 'cmpi' instead.

EDIT: Bonus change - Rings don't shake with the rest of the screen in places like the HTZ earthquake sequencies. If you want to correct this (like S3K looks like it did), just change the 'lea (Camera_X_pos).w,a3' to 'lea (Camera_X_pos_copy).w,a3'.
This post has been edited by Clownacy: 06 October 2017 - 06:27 PM

#227 User is online Clownacy 

Posted 29 October 2018 - 05:21 PM

  • Posts: 762
  • Joined: 06-July 13
  • Gender:Female
...It's seriously been a year? Anyway, there's this one really annoying bug where if you enter Debug Mode underwater, and then exit it above-water, Sonic will still have his underwater physics.

This is because Sonic will only trigger his 'exiting water' code if he's above the water's Y-coordinate and his 'is underwater' flag is set. The problem is, when Sonic exits Debug Mode, it clears his 'is underwater' flag. This happens in sub_41CB8:

; sub_41CB8:
Debug_ResetPlayerStats:
	move.b	d0,anim(a1)
	move.w	d0,2+x_pos(a1) ; subpixel x
	move.w	d0,2+y_pos(a1) ; subpixel y
	move.b	d0,obj_control(a1)
	move.b	d0,spindash_flag(a1)
	move.w	d0,x_vel(a1)
	move.w	d0,y_vel(a1)
	move.w	d0,inertia(a1)
	; note: this resets the 'is underwater' flag, causing the bug where
	; if you enter Debug Mode underwater, and exit it above-water, Sonic
	; will still move as if he's underwater
	move.b	#2,status(a1)
	move.b	#2,routine(a1)
	move.b	#0,routine_secondary(a1)
	rts
; End of function Debug_ResetPlayerStats



As the comment points out, it's that 'move.b #2,status(a1)' line. To fix the bug, the 6th bit of Sonic's status needs to be preserved, like this:

	andi.b	#1<<6,status(a1)
	ori.b	#2,status(a1)



Funnily enough, this bug doesn't exist in Sonic 1 since Debug Mode resets way fewer variables.
This post has been edited by Clownacy: 29 October 2018 - 07:38 PM

#228 User is offline biggestsonicfan 

Posted 29 October 2018 - 08:14 PM

  • Model2wannaB
  • Posts: 817
  • Joined: 09-May 07
  • Gender:Male
  • Project:Formerly Sonic the Fighters

View Postredhotsonic, on 07 June 2012 - 05:00 PM, said:

Something Cool for CPZ boss!

THIS IS JUST FOR LAUGHS, but I thought I'd share it anyway! Back at the label "loc_2E692:". See these two lines?

	btst	#2,$2D(a1)
	beq.s	loc_2E6CA


Comment them out! And everytime Eggman drops the gunge, this will happen!

Posted Image


Well, I found it funny =P

Since I just found this thread, can this be converted into a GameGenie code for use on hardware? I have an itching to try it out but I've never compiled S2 from scratch and feel that the time and effort spent would decrease my overall enjoyment levels.

I plan to go through this whole thread because it's absolutely fascinating. I'd love to do stuff to Sonic the Fighters like this once I understand it a little better (and I finish my decompile).

EDIT1: Actually it would make more sense to have it implemented as Esrael had it, but the download link is broken. :argh:
This post has been edited by biggestsonicfan: 29 October 2018 - 08:20 PM

#229 User is offline Wafer 

Posted 30 October 2018 - 04:14 PM

  • Posts: 10
  • Joined: 28-November 17

View Postbiggestsonicfan, on 29 October 2018 - 08:14 PM, said:

Since I just found this thread, can this be converted into a GameGenie code for use on hardware? I have an itching to try it out but I've never compiled S2 from scratch and feel that the time and effort spent would decrease my overall enjoyment levels.

I plan to go through this whole thread because it's absolutely fascinating. I'd love to do stuff to Sonic the Fighters like this once I understand it a little better (and I finish my decompile).

EDIT1: Actually it would make more sense to have it implemented as Esrael had it, but the download link is broken. :argh:/>

Honestly, don't be intimidated by the prospect of building from assembly. It's just downloading the source from Github and running build.bat. You could do it in like 5 minutes, and then you'll be able to look at doing other fixes yourself, and maybe move onto your own ideas.

#230 User is online Clownacy 

Posted 16 November 2018 - 04:50 AM

  • Posts: 762
  • Joined: 06-July 13
  • Gender:Female
This vine switch.

Posted Image

I'm sure some of you know exactly what bug I'm about to describe. Normally, when you press a button to release yourself from a vine, you jump. But not this one. If you try to jump off too soon, you drop like a rock.

So why? Well, believe it or not, the issue isn't with the vine object, but that drawbridge next to it.

This object's interesting: it uses the multi-sprite system to draw all of its sprites using only one object. Sonic 1 lacked this feature, meaning the bridges in GHZ are actually multiple objects, one for each segment.

The multi-sprite system works by turning most of the object's RAM into an array of sprite metadata. But the drawbridge object needs that RAM for other things. Its solution? Make the bridge use two objects instead: one master object that controls everything, and one sprite-mule object whose only purpose is to store sprite data.

So if the drawbridge is made up of multiple sprites, does that mean its collision is made up of numerous small hitboxes as well?

No. The object only uses one hitbox. Depending on what angle the drawbridge is pointing, the hitbox is either a thin vertical box, or a thin horizontal one. This means the drawbridge's appearance doesn't reflect its hitbox at all: if the bridge is at 45 degress, the hitbox will actually behave as if it's at 0.

So when does the hitbox swap? Well, it's set according to this code...

loc_2A18A:
	move.w	#$13,d1	; width
	move.w	#$40,d2	; height
	move.w	#$41,d3	; other height
	move.b	angle(a0),d0
	beq.s	loc_2A1A8
	cmpi.b	#$40,d0
	beq.s	loc_2A1B4
	cmpi.b	#-$40,d0
	bhs.s	loc_2A1B4

loc_2A1A8:
	move.w	#$4B,d1	; width
	move.w	#8,d2	; height
	move.w	#9,d3	; other height

loc_2A1B4:


angle(a0) is... the angle, only the range is 0-256 instead of 0-360, so $40 is 90 and -$40 ($C0) is 270.

At 0 degrees, the bridge is pointing to the right, at 90, it's pointing down. So, if the bridge is pointing straight down, or between up and right, it uses its vertical hitbox. Otherwise, it uses its horizontal one.

The problematic drawbridge spawns pointing down, and moves to point right after you pull the switch. So, in effect, the moment the bridge moves just one degree, it should change its hitbox, basically allowing you to walk straight through the drawbridge if you're fast enough.

So where is this hitbox? It actually belongs to the invisible master object, so the hitbox should be whereever that is. But it's invisible... at least until I do a little hackery. So I change that, and...

Posted Image

Oh dear.

Like I said, the moment the drawbridge starts moving, the hitbox switches from a tall one to a wide one, but the object where the hitbox applies to is still in the tall position. That's why you drop like a rock: the vine object releases you, only for the game to realise you're embedded in a solid object's hitbox, and push you down.

It's not until the drawbridge finishes moving that the master object updates its position, and applies collision to the correct area. The reason it waits is because of a different drawbridge, which spawns pointing up, and points right after you pull the switch. In that case, if the master object moves right away, then you'd be able to walk on an invisible bridge before the real one even gets there.

Oddly enough, this doesn't apply to the bridges that point up and move to the left instead: those change collision instantly. Or at least, they try to. You know what bridge points up, moves left, and has this same dropping-like-a-rock bug? The one above The Pit. But yeah, this one actually does change its collision and position immediately, but for some reason it only updates the object's X coordinate, meaning the hitbox still floats in the air, ready to bump Sonic's head. It doesn't even set the object's collision width properly, meaning even if it was positioned properly, Sonic would still fall through it if he tried walking on it.

This object really does feel broken and unfinished. Why would the up-to-left version change collision right away, but not the up-to-right one?

Here's my best attempt at fixing the "intended" behaviour:

Go to Obj81_BridgeUp, and replace this:

	cmpi.b	#$81,status(a0)
	bne.s	+
	move.w	objoff_30(a0),x_pos(a0)
	subi.w	#$48,x_pos(a0)
+


With this:

	move.b	status(a0),d0
	andi.b	#3,d0
	beq.s	+
	move.b	#$40,width_pixels(a0)
	move.w	objoff_32(a0),y_pos(a0)
	move.w	objoff_30(a0),x_pos(a0)
	moveq	#$48,d0
	btst	#1,status(a0)
	beq.s	.notyflipped
	neg.w	d0
.notyflipped:
	btst	#0,status(a0)
	beq.s	.notxflipped
	neg.w	d0
.notxflipped:
	add.w	d0,x_pos(a0)
+


But yeah, I don't think this behaviour's right at all. Being able to walk on a bridge that isn't there yet just doesn't feel right. Meanwhile, being blocked by an upside-down bridge that isn't even in the way isn't right either. So in my opinion, the normal bridges shouldn't change collision instantly, but the upside-down ones should.

To correct this, replace the above block of code with this instead:

	btst	#1,status(a0)
	beq.s	+
	move.b	#$40,width_pixels(a0)
	move.w	objoff_32(a0),y_pos(a0)
	move.w	objoff_30(a0),x_pos(a0)
	moveq	#$48,d0
	btst	#0,status(a0)
	beq.s	.notxflipped
	neg.w	d0
.notxflipped:
	add.w	d0,x_pos(a0)
+


This changes the master object repositioning. To change the hitbox-swapping, go to loc_2A18A and replace this:

	move.b	angle(a0),d0
	beq.s	loc_2A1A8
	cmpi.b	#$40,d0
	beq.s	loc_2A1B4
	cmpi.b	#-$40,d0
	bhs.s	loc_2A1B4


With this:

	move.b	angle(a0),d0
	cmpi.b	#$40,d0
	beq.s	loc_2A1B4	; Straight down
	cmpi.b	#$80,d0
	bhi.s	loc_2A1B4	; Anywhere upwards between left and right

This post has been edited by Clownacy: 16 November 2018 - 04:52 AM

#231 User is online Clownacy 

Posted 01 December 2018 - 12:35 PM

  • Posts: 762
  • Joined: 06-July 13
  • Gender:Female
Fix 1

Thanks to djohe for pointing this out. The 'X rings to go' prompt is bugged in the Special Stage: if you need 101 or more rings, the prompt will show the wrong number.

The cause is a buggy BCD converter, found just above loc_3577A:

	moveq	#0,d0
	cmpi.w	#100,d1
	blt.s	+

-	addi.w	#$100,d0
	subi.w	#100,d1
	bgt.s	-
+


The correct version goes like this:

	moveq	#0,d0
	cmpi.w	#100,d1
	blt.s	+

-	addi.w	#$100,d0
	subi.w	#100,d1
	cmpi.w	#100,d1
	bge.s	-
+


How about another bug?

Fix 2

At the very start of ARZ, near the first Grounder that's hiding in a wall, the water is shallow but just deep enough for Sonic to drown in it. However, if you do start drowning there, the countdown numbers are corrupted.

Obviously it has something to do with the number reaching the surface of the water, but for the life of me I couldn't find what the actual cause was. On a whim, I decided to document the S3K version of the object, to find any differences that may contain a fix. Surprisingly, it did.

For starters, the fix involves splitting Obj0A_DisplayNumber and Obj0A_Display, shown below for reference:

; loc_1D40E:
Obj0A_DisplayNumber:
	movea.l	objoff_3C(a0),a2 ; a2=character
	cmpi.b	#$C,air_left(a2)
	bhi.s	JmpTo5_DeleteObject

; loc_1D41A:
Obj0A_Display:
	bsr.s	Obj0A_ShowNumber
	lea	(Ani_obj0A).l,a1
	jsr	(AnimateSprite).l
	jmp	(DisplaySprite).l


What's interesting about Obj0A is that it has two Display functions. Here's the other one:

; loc_1D452:
Obj0A_Display2:
	lea	(Ani_obj0A).l,a1
	jsr	(AnimateSprite).l
	bsr.w	Obj0A_LoadCountdownArt
	tst.b	render_flags(a0)
	bpl.s	JmpTo6_DeleteObject
	jmp	(DisplaySprite).l


As you might guess, the fix involves that call to Obj0A_LoadCountdownArt.

What S3K does is split Obj0A_DisplayNumber and Obj0A_Display, and add a call to Obj0A_LoadCountdownArt to Obj0A_Display, like this:

; loc_1D40E:
Obj0A_DisplayNumber:
	movea.l	objoff_3C(a0),a2 ; a2=character
	cmpi.b	#$C,air_left(a2)
	bhi.s	JmpTo5_DeleteObject
	bsr.s	Obj0A_ShowNumber
	lea	(Ani_obj0A).l,a1
	jsr	(AnimateSprite).l
	jmp	(DisplaySprite).l
; ===========================================================================

; loc_1D41A:
Obj0A_Display:
	bsr.s	Obj0A_ShowNumber
	lea	(Ani_obj0A).l,a1
	jsr	(AnimateSprite).l
	bsr.w	Obj0A_LoadCountdownArt
	jmp	(DisplaySprite).l


And that's it. I did some reading, to figure out what was going on, and as it turns out, when the bubble reaches the surface of the water, it switches from Display2 to Display1. Since Display1 doesn't update the art, the number corrupts. Kind of simple in hindsight.

How about a third fix?

Fix 3

If you die in ARZ, chances are you'll see a bunch of question mark sprites appear. This is the actually the sprite for the invisible hitbox that makes those falling leaves appear.

This is caused by some quirky behaviour: when you die, the game pauses. But it doesn't actually pause: it just stops updating most of the objects. But it still needs to draw those objects, and objects need to update to display themselves. So the game does something weird: it displays every object on its own. But this is where the root of the problem is: the game is displaying an object that isn't meant to be displayed.

So how does the game determine what objects should be displayed? Well, it's actually quite a bit more clever than I was expecting. I was expecting it to check if the object's mappings pointer was set. That would explain why it shows the leaves spawner. But no. There are actually other invisible objects in the game that do the same thing, without this problem: take for example the invisible solid block object, or the MTZ wall spring object (the sprites are actually part of the level). They don't display when you die at all.

So how does it actually determine what to show? Well, it checks the high-bit of the object's render_flags. This bit is set by BuildSprites if the object is on-screen. Here's the catch: BuildSprites is only called if the object displays. Since the invisible objects never display, this makes sense.

So then what's the leaves spawner doing differently to the others?

Obj74_Init:
	[...]
	ori.b	#4,render_flags(a0)


Obj66_Init:
	[...]
	ori.b	#4,render_flags(a0)


Obj2C_Init:
	[...]
	move.b	#$84,render_flags(a0)


Oops.

So the object accidentally sets that bit when it spawns, meaning when Sonic dies, the game notices it, assumes that means the object has displayed before, and draws it.

So how do we fix it? Well, the obvious answer is to just change the above line to match the others. And that'd be the correct answer too.

But not to Sonic Team. Check out Obj31, for its own dumb fix: this is another object that accidentally sets that bit, but instead of fixing that, Sonic Team just did this:

	move.l	#Obj31_MapUnc_20E6C,mappings(a0)
	tst.w	(Debug_placement_mode).w
	beq.s	+
	move.l	#Obj31_MapUnc_20E74,mappings(a0)
+


Obj31_MapUnc_20E6C is actually blank, so nothing displays. This means you'll only be able to see this object in Debug Mode if it spawns while you're in Debug Mode.

Hey, how about a fourth fix?

Fix 4

Even though the leaves spawner has everything set up so it can display, it actually doesn't appear in Debug Mode. That's right: dying is the only way to make it appear. The problem is that the object never calls DisplaySprite, even in Debug Mode, so the fix is pretty straightforward:

Just go to Obj2C_Main, and under 'bhi.w JmpTo29_DeleteObject', add this:

	tst.w	(Debug_placement_mode).w
	beq.s	+
	jsr	(DisplaySprite).l
+


Whew, I think that's enough fixes for one day.
This post has been edited by Clownacy: 01 December 2018 - 01:58 PM

#232 User is online Clownacy 

Posted 01 December 2018 - 02:28 PM

  • Posts: 762
  • Joined: 06-July 13
  • Gender:Female
Sod it, here's another one. Once again, thanks to djohe for pointing it out: in 2-player mode, there's an option to replace all monitors with either teleport monitors or random monitors. Yet, no matter which option you choose, the monitors will still show their random icon. Fixing this one's simple - just add this code above Obj26_Main:

	tst.w	(Two_player_items).w	; are monitors set to 'teleport only'?
	beq.s	Obj26_Main		; if not, branch
	subq.b	#1,anim(a0)		; use teleport icon

This post has been edited by Clownacy: 02 December 2018 - 05:47 PM

#233 User is online Clownacy 

Posted 19 December 2018 - 12:19 PM

  • Posts: 762
  • Joined: 06-July 13
  • Gender:Female
Here's a fun one: if you get caught by a Grabber while you're charging a spin dash, Sonic/Tails won't actually leave their spin dash state: the dust graphic will still appear, just floating in the air, and when you touch the ground, you'll dash off.

To fix this, just go to loc_38EEE and add a 'clr.b spindash_flag(a1)` with the other instructions that use a1.
This post has been edited by Clownacy: 19 December 2018 - 12:21 PM

#234 User is online Clownacy 

Posted 20 December 2018 - 01:32 PM

  • Posts: 762
  • Joined: 06-July 13
  • Gender:Female
Here's a dumb one. I found it by complete accident while reading the code.

When you get a Game Over, the game stops letting you pause. This also happens in 2-player... but only when Sonic gets the Game Over. If Tails gets it, the game will still let you pause.

Even weirder, getting a Time Over won't stop you from pausing either, even if it's Sonic that gets it.

To fix this, just add these lines before the check for Game_paused at the start of PauseGame:

	tst.b	(Life_count_2P).w
	beq.w	Unpause
	tst.b	(Time_Over_flag).w
	bne.w	Unpause
	tst.b	(Time_Over_flag_2P).w
	bne.w	Unpause

This post has been edited by Clownacy: 20 December 2018 - 01:32 PM

  • 16 Pages +
  • ◄ First
  • 14
  • 15
  • 16
    Locked
    Locked Forum

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users