don't click here

How to fix timers on Scattered rings

Discussion in 'Engineering & Reverse Engineering' started by redhotsonic, Sep 23, 2012.

  1. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    9
    18
    United Kingdom
    YouTuber
    Hello, guys! I discovered a bug in Sonic 2 Recreation with the Scattered Rings object, and yet again, it exists in all Sonic 1, Sonic 2 and Sonic 3 and Knuckles! So, I made a fix.




    In this guide
    • Bug fix for Sonic 1 SVN Disassembly
    • Bug fix for Sonic 2 XenoWhirl's Disassembly
    • Bug fix for Sonic 2 SVN Disassembly
    • Bug fix for Sonic 3 and Knuckles SVN Disassembly






    The Bug and the fix


    [​IMG]

    When you get hurt, you lose rings (duh). All the rings have a timer of $FF which is set to a RAM address. All the scattered rings read from this RAM address. Each frame, that timer counts down by #1. When the timer finally reaches 0, the scattered rings delete themselves. This seems fair enough.


    Now, imagine you collect some rings, but get hurt again:


    [​IMG]

    Some more scattered rings are created. The thing is, these scattered rings set the RAM timer to $FF again, so they can count down. Fine, but them rings you loss earlier, if they haven't been deleted yet, their timer is also set to $FF.


    So, for example; you lose rings and the timer sets to $FF. Some frames later, that timer is on $10, so those rings are going to be deleted any moment now, but you get hurt again and lose more rings. Those scattered rings that were about to be deleted, are now going to stay a lot longer.


    And then if you have badniks explode with a ring coming out instead of an animal like I do, this will also interrupt with the RAM timer (depends on your coding, this is not included in the guide).

    [​IMG]
    Sonic 2 Recreation - showing a ring coming out of a badnik



    That's not meant to happen. What we want is the rings we're about to be deleted to keep their original timer! So when we lose more rings, the old scattered rings will still have their own timer. Let's fix this!






    Sonic 1 fix - SVN Disassembly

    First, open your "25 & 37 Rings.asm" file and go to "@makerings:" label. Find this line:

    Code (ASM):
    1.         move.b  #-1,(v_ani3_time).w

    This is where the RAM timer is set. Although, setting the RAM here is a waste of time. Depending on how many rings are being scattered, this is how many times it's being written. Example, if you lose 10 rings, it writes #-1 to this RAM 10 times. Why? It only needs to be written once. So we'll move this. For now, delete this line.


    Next, go to the "@resetcounter:" label. Find this line:

    Code (ASM):
    1.         sfx sfx_RingLoss    ; play ring loss sound

    Just before it, add this:

    Code (ASM):
    1.         moveq   #-1,d0          ; Move #-1 to d0
    2.         move.b  d0,obDelayAni(a0)   ; Move d0 to new timer
    3.         move.b  d0,(v_ani3_time).w  ; Move d0 to old timer (for animated purposes)

    This is where we're setting our timer. "obDelayAni(a0)" is our brand new timer. This means each scattered ring will now have its own personal timer. We must set the old timer still, for animated purposes. Without setting the old timer, the rings won't spin. Anyway, setting the timer here, it will only be written once; saving a bit of time when creating the rings.



    So, our new timer has been set to $FF. We still need to make it count down. So, find the "@chkdel:" label. See this?

    Code (ASM):
    1.         tst.b   (v_ani3_time).w
    2.         beq.s   RLoss_Delete

    You may delete it! No longer needed, as we're not using this as the main timer anymore. Instead, replace it with this:

    Code (ASM):
    1.         subq.b  #1,obDelayAni(a0)   ; Subtract 1
    2.         beq.w   DeleteObject        ; If 0, delete

    Now, the new personal timer will be subtracted every frame. Once it reaches 0, it will delete itself. This timer cannot be interrupted when you lose more rings, so these will delete themselves when they're supposed to!

    Done






    Sonic 2 fix - XenoWhirl's Disassembly

    First, open your ASM file and go to "loc_120BA:" label. Find this line:

    Code (ASM):
    1.     move.b  #-1,(Ring_spill_anim_counter).w

    This is where the RAM timer is set. Although, setting the RAM here is a waste of time. Depending on how many rings are being scattered, this is how many times it's being written. Example, if you lose 10 rings, it writes #-1 to this RAM 10 times. Why? It only needs to be written once. So we'll move this. For now, delete this line.


    Next, go to the "loc_12142:" label. Find this line:

    Code (ASM):
    1.     move.w  #$C6,d0
    2.     jsr (PlaySoundStereo).l

    Just before it, add this:

    Code (ASM):
    1.     moveq   #-1,d0              ; Move #-1 to d0
    2.     move.b  d0,objoff_1F(a0)        ; Move d0 to new timer
    3.     move.b  d0,(Ring_spill_anim_counter).w  ; Move d0 to old timer (for animated purposes)

    This is where we're setting our timer. "objoff_1F(a0)" is our brand new timer. This means each scattered ring will now have its own personal timer. We must set the old timer still, for animated purposes. Without setting the old timer, the rings won't spin. Anyway, setting the timer here, it will only be written once; saving a bit of time when creating the rings.



    So, our new timer has been set to $FF. We still need to make it count down. So, find the "loc_121B8:" label. See this?

    Code (ASM):
    1.     tst.b   (Ring_spill_anim_counter).w
    2.     beq.s   BranchTo5_DeleteObject

    You may delete it! No longer needed, as we're not using this as the main timer anymore. Instead, replace it with this:

    Code (ASM):
    1.     subq.b  #1,objoff_1F(a0)        ; Subtract 1
    2.     beq.w   DeleteObject            ; If 0, delete

    Now, the new personal timer will be subtracted every frame. Once it reaches 0, it will delete itself. This timer cannot be interrupted when you lose more rings, so these will delete themselves when they're supposed to!

    Done






    Sonic 2 fix - SVN Disassembly

    First, open your ASM file and go to "Obj37_Init:" label. On the 3rd + label, find this line:

    Code (ASM):
    1.     move.b  #-1,(Ring_spill_anim_counter).w

    This is where the RAM timer is set. Although, setting the RAM here is a waste of time. Depending on how many rings are being scattered, this is how many times it's being written. Example, if you lose 10 rings, it writes #-1 to this RAM 10 times. Why? It only needs to be written once. So we'll move this. For now, delete this line.


    From the "Obj37_Init:", find the 5th + label. Find this line:

    Code (ASM):
    1.     move.w  #SndID_RingSpill,d0
    2.     jsr (PlaySoundStereo).l

    Just before it, add this:

    Code (ASM):
    1.     moveq   #-1,d0              ; Move #-1 to d0
    2.     move.b  d0,objoff_1F(a0)        ; Move d0 to new timer
    3.     move.b  d0,(Ring_spill_anim_counter).w  ; Move d0 to old timer (for animated purposes)

    This is where we're setting our timer. "objoff_1F(a0)" is our brand new timer. This means each scattered ring will now have its own personal timer. We must set the old timer still, for animated purposes. Without setting the old timer, the rings won't spin. Anyway, setting the timer here, it will only be written once; saving a bit of time when creating the rings.



    So, our new timer has been set to $FF. We still need to make it count down. So, find the "loc_121B8:" label. See this?

    Code (ASM):
    1.     tst.b   (Ring_spill_anim_counter).w
    2.     beq.s   Obj37_Delete

    You may delete it! No longer needed, as we're not using this as the main timer anymore. Instead, replace it with this:

    Code (ASM):
    1.     subq.b  #1,objoff_1F(a0)        ; Subtract 1
    2.     beq.w   DeleteObject            ; If 0, delete

    Now, the new personal timer will be subtracted every frame. Once it reaches 0, it will delete itself. This timer cannot be interrupted when you lose more rings, so these will delete themselves when they're supposed to!

    Done






    Sonic 3 and Knuckles fix - SVN Disassembly

    First, open your ASM file and go to "loc_1A6B6:" label. Find this line:

    Code (ASM):
    1.         move.b  #-1,(Ring_spill_anim_counter).w

    This is where the RAM timer is set. Although, setting the RAM here is a waste of time. Depending on how many rings are being scattered, this is how many times it's being written. Example, if you lose 10 rings, it writes #-1 to this RAM 10 times. Why? It only needs to be written once. So we'll move this. For now, delete this line.


    Next, go to the "loc_1A738:" label. Find this line:

    Code (ASM):
    1.         move.w  #$FFB9,d0
    2.         jsr (Play_Sound_2).l

    Just before it, add this:

    Code (ASM):
    1.         moveq   #-1,d0              ; Move #-1 to d0
    2.         move.b  d0,height_pixels(a0)        ; Move d0 to new timer
    3.         move.b  d0,(Ring_spill_anim_counter).w  ; Move d0 to old timer (for animated purposes)

    This is where we're setting our timer. "anim_frame_timer(a0)" is our brand new timer. This means each scattered ring will now have its own personal timer. We must set the old timer still, for animated purposes. Without setting the old timer, the rings won't spin. Anyway, setting the timer here, it will only be written once; saving a bit of time when creating the rings.



    So, our new timer has been set to $FF. We still need to make it count down. So, find the "loc_1A79C:" label. See this?

    Code (ASM):
    1.         tst.b   (Ring_spill_anim_counter).w
    2.         beq.s   loc_1A7E4

    You may delete it! No longer needed, as we're not using this as the main timer anymore. Instead, replace it with this:

    Code (ASM):
    1.         subq.b  #1,height_pixels(a0)        ; Subtract 1
    2.         beq.w   Delete_Current_Sprite       ; If 0, delete

    Now, the new personal timer will be subtracted every frame. Once it reaches 0, it will delete itself. This timer cannot be interrupted when you lose more rings, so these will delete themselves when they're supposed to!


    WAIT!

    Unlike the other games, you have to do one more thing to Sonic 3 and Knuckles. You know the lightning shield? When you have rings attracting to you, and you get hurt and lose the shield, the rings that were being attracted are turned into scattered rings. We need to set the new timer for this.


    Go to "loc_1A88C:" and find:

    Code (ASM):
    1.         move.b  #-1,(Ring_spill_anim_counter).w

    and just above or below this line, add this line:

    Code (ASM):
    1.         move.b  #-1,height_pixels(a0)       ; Move #$FF to new timer

    WAIT!

    There's one more. Again, this is S3K only. You know the Egg Prisons you can jump on in Flying Battery? Some of them release rings, which are scattered rings again. They need their own timer. So, go to "loc_89D44:" and find the line:

    Code (ASM):
    1.         move.b  #-1,(Ring_spill_anim_counter).w

    and just above or below this line, add this line:

    Code (ASM):
    1.         move.b  #-1,height_pixels(a0)       ; Move #$FF to new timer

    NOTE: I do not hack S3K, but I am sure that the height_pixels SST is free for scattered rings. After trying this myself, I see no problems. If there are errors, let me know.


    Done


    Ta-dah!
     
  2. RetroKoH

    RetroKoH

    Member
    1,661
    18
    18
    Project Sonic 8x16
    I hope Mercury takes note of this. This bug is MOST evident when implementing the ring loss flash effect from ReadySonic. Glad to see it fixed! I'll wiki it sometime this week.
     
  3. Mercury

    Mercury

    His Name Is Sonic Tech Member
    Noted! I read all of RHS's fixes and guides because they're awesome. :)

    However, I don't hack anymore - far too busy on other projects - so I'm not sure the note is of much help.

    For that matter, if anyone wants to fix, expand, or take over ReadySonic they have my blessing. I had always wanted it to wind up on the mercurial and be a community thing, but I never had the time to make it good enough or make that happen.
     
  4. RetroKoH

    RetroKoH

    Member
    1,661
    18
    18
    Project Sonic 8x16
    I could take it on, considering that I already have almost all of Ready Sonic's features in Sonic 1 REV C already, AND found a fix to implementing the ring loss flash with the timer fix... AND have many other things missing from ReadySonic implemented in already. I'll post that when I get the chance, as I am technically supposed to be working right now... HA...

    Also working on the same for Sonic 2, soon to start on Sonic 3 as well.

    EDIT: BTW here is the fix for Ring Loss with this feature. This will make it so rings will flash as they should on their own timings. This guide is really easy and can be followed by anyone, even if they haven't used ReadySonic before. Here is a clip from my code directly from Sonic 1 REV C. Basically just need a branch leading to the new block entitled @flash. See here.

    Code (Text):
    1.  
    2.  
    3. @chkdel:
    4.         subq.b  #1,obDelayAni(a0)       ; Subtract 1   ; RHS Ring Timer fix
    5.                 beq.w   DeleteObject            ; If 0, delete ; RHS Ring Timer fix
    6.                 ; Accidental RHS Ring loss deletion fix.
    7.                 cmpi.w  #$FF00,($FFFFF72C).w ; is vertical wrapping enabled?
    8.                 beq.w   @flash               ; if so, branch
    9.                 ; End of RHS fix.
    10.         move.w  (v_limitbtm2).w,d0
    11.         addi.w  #$E0,d0
    12.         cmp.w   y_pos(a0),d0    ; has object moved below level boundary?
    13.         bcs.s   RLoss_Delete    ; if yes, branch
    14.         @flash: ; Add Mercury Ring Loss Flash w/ RHS timer fix
    15.         btst    #0, obDelayAni(a0)
    16.         beq.w   DisplaySprite
    17.         cmpi.b  #80,obDelayAni(a0)             ; Rings will flash during last 80 steps of their life.
    18.         bhi.w   DisplaySprite
    19.         rts
    20.  

    If you are implementing all of RHS' recent ring related fixes, your last bit of code for Obj37 (Rings) should look like this.

    If you are ONLY implementing the ring loss and timer fix, it should look like this:
    Code (Text):
    1.  
    2.  
    3. @chkdel:
    4.         subq.b  #1,obDelayAni(a0)       ; Subtract 1   ; RHS Ring Timer fix
    5.                 beq.w   DeleteObject            ; If 0, delete ; RHS Ring Timer fix
    6.         move.w  (v_limitbtm2).w,d0
    7.         addi.w  #$E0,d0
    8.         cmp.w   obY(a0),d0  ; has object moved below level boundary?
    9.         bcs.s   RLoss_Delete    ; if yes, branch   
    10. ;Mercury Lost Rings Flash
    11.  
    12.         btst    #0, obDelayAni(a0)
    13.         beq.w   DisplaySprite
    14.         cmpi.b  #80,obDelayAni(a0)             ; Rings will flash during last 80 steps of their life.
    15.         bhi.w   DisplaySprite
    16.         rts
    17. ;end Lost Rings Flash
    18.  

    Ta-da. Hope this helps any/all ReadySonic users.
     
  5. RetroKoH

    RetroKoH

    Member
    1,661
    18
    18
    Project Sonic 8x16
    Fix Ring Timers

    *Insert Sonic 1/2 extra life jingle here*

    It's very barebones, as I'm on limited time at the moment. I'll add to it when I am able to.
     
  6. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    9
    18
    United Kingdom
    YouTuber
    I have the flash effect in S2R too. I was going to put this in the guide when I posted the bug, but didn't in the end as it wasn't a bug and it's a design choice. Plus my post was long enough =P


    Anyway, nice =P