Update: How to port S3K Priority Manager into S2 (let's speed up your hack) [updated 15/07/2012]

Discussion in 'Approved' started by redhotsonic, Jun 24, 2012.

Thread Status:
Not open for further replies.
  1. redhotsonic

    redhotsonic Also known as RHS Retired Staff

    Joined:
    Aug 10, 2007
    Messages:
    2,967
    Location:
    England
    You can read up my guide from the Sonic Retro wiki found here. But if for some odd reason you want to read the guide from this topic which has formatting errors (due to the board keep getting updates), you may do so by opening up the spoiler:

    WARNING:
    Please back-up your disassembly before attempting to use this guide. I will not be held responsible for anything that goes wrong.
    SSRG members! You've heard this guide first!


    This guide is mainly following XenoWhirl's 2007 disassembly. But, if you're an SVN user, I have tried to make this guide as friendly as possible for you.


    Anything in red is for Xenowhirls 2007 disassembly users only


    Anything in green is for SVN disassembly users only


    Anything not coloured, means it's for both disassemblies.


    Also, EVERY SST MUST BE EQUATED. So, if you're using Hivebrain's disassembly, you won't be able to follow this. Also, for any objects you've made yourself or have ported, make sure that it's all equated (so instead of $18(a0), it should be priority(a0).) Anything that has not been equated may start to cause problems; you have been warned!


    Today, I bring you probably the best guide I've typed-up to-date:


    How to port S3K's Priority Manager into S2


    What is priority?


    In all Sonic games, there is a universal SST called Priority. Each sprite has one. The higher the priority number, the lower priority it has. Then the objects can be displayed accordingly. For example, if Sonic has a priority of 2, and Dr.Eggman's priority is 3, then when both sprites are displayed over each other, Sonic will ALWAYS be displayed in front of him.


    Differences between both managers


    In Sonic 2, the priority's universal SST is a byte. It's start's from #0 (highest) upto #7 (lowest). When each object jumps to the "DisplaySprite" subroutine, it firsts, converts the priority byte into a word. And then it can check whether to display the sprite, and if so, display it. It has to do this every frame for every object for the sprite to be displayed.


    In Sonic 3 and Knuckles, priority is already a word, again, it's start's from #0 (highest) but goes upto #$380 (lowest). That doesn't mean there's $380 types of priority. It goes up in $80's (#0, $80, $100, $180, etc). Because it's already set as a word, when it jumps to the "DisplaySprite" subroutine, it doesn't have to do them calculations, and can just check whether to display the sprite or not and if so, display it.


    So basically, S3K's "DisplaySprite" has shaven a few commands away. Because S3K does not have to do them calculations changing from a byte to a word like S2 does on every single frame, it can save a lot of time, and can use the processor on other things instead. When you see a lot of sprites on screen, this can make a huge difference to gameplay.


    [​IMG]


    Look at this picture. In every single frame, the game has to display each scattered ring (15 of them there), each and every single bubble, and also the bubble maker on the gorund, and the two sharks, and Sonic, and Tails, and Tails' tails, and the rotating platforms on the side, and the HUD, and if we were a bit higher, the water line. Sonic 2 will have to do these calculations for every single little object on screen here; every single frame, before asking whether to display it or not, and if so, display it. Wow, that's going to take a lot of time. Whereas S3K, it already knows the word for each object, so it doesn't need to do the calculations for every object in every frame. So it can just get on and ask whether to display it or not and if so, display it. Saving a HUGE amount of time.


    Porting S3K's Priority Manager into S2 can help get rid of some of the lag your hack may be experiencing. If you follow this guide step-by-step carefully, and make sure you have some time on your hands because it can take a little while, everything should go fine.


    Please be aware, once you've started this, you cannot rebuild your ROM until you've finished. Otherwise, you'll get errors and crashes.


    Step 1 - Getting a free universal SST


    The biggest problem is freeing a universal SST, seeming as they are all being used. Luckily, quite a while back, I showed a guide on how to free two universal SST's! If you have already done part 2 of this guide, you do not need to carry on with this step. Part 2 is the essential SST we need for this guide. Part 1 isn't as important.


    If you haven't done this already, you will need to do so. Here is a quote from the guide On how to free up ONE universal SST.


    Because $19 is univerally free, priority can now be used as a word ($18 and $19)!


    Step 2 - Setting up DisplaySprite and DisplaySprite2


    You should now have width_pixels at $14 instead of $19. Because priority is at $18, and $19 is now free, theoretically, the priority SST can now be used as a word.


    Next, we need to change DisplaySprite and DisplaySprite2. Do NOT edit/change or replace DisplaySprite3! Leave that as it is.


    At "DisplaySprite:", you'll see this:



    move.w priority(a0),d0
    lsr.w #1,d0


    andi.w #$380,d0


    adda.w d0,a1



    These are the lines that does the calculations every single frame. This is what converts the byte into a word. Like said in S3K, as priority is already a word, these lines are now useless. As this is what we're trying to acheive, we can do the same thing.


    So, at "DisplaySprite:", change this:



    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

    ; sub_164F4:


    DisplaySprite:


    lea (Sprite_Table_Input).w,a1


    move.w priority(a0),d0


    lsr.w #1,d0


    andi.w #$380,d0


    adda.w d0,a1


    cmpi.w #$7E,(a1)


    bcc.s return_16510


    addq.w #2,(a1)


    adda.w (a1),a1


    move.w a0,(a1)


    return_16510:


    rts


    ; End of function DisplaySprite


    ; ---------------------------------------------------------------------------


    ; Subroutine to display a sprite/object, when a1 is the object RAM


    ; ---------------------------------------------------------------------------


    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||


    ; sub_16512:


    DisplaySprite2:


    lea (Sprite_Table_Input).w,a2


    move.w priority(a1),d0


    lsr.w #1,d0


    andi.w #$380,d0


    adda.w d0,a2


    cmpi.w #$7E,(a2)


    bcc.s return_1652E


    addq.w #2,(a2)


    adda.w (a2),a2


    move.w a1,(a2)


    return_1652E:


    rts


    ; End of function DisplaySprite2



    To this:



    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

    ; sub_164F4:


    DisplaySprite:


    lea (Sprite_Table_Input).w,a1


    adda.w priority(a0),a1


    cmpi.w #$7E,(a1)


    bcc.s return_16510


    addq.w #2,(a1)


    adda.w (a1),a1


    move.w a0,(a1)


    return_16510:


    rts


    ; End of function DisplaySprite


    ; ---------------------------------------------------------------------------


    ; Subroutine to display a sprite/object, when a1 is the object RAM


    ; ---------------------------------------------------------------------------


    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||


    ; sub_16512:


    DisplaySprite2:


    lea (Sprite_Table_Input).w,a2


    adda.w priority(a1),a2


    cmpi.w #$7E,(a2)


    bcc.s return_1652E


    addq.w #2,(a2)


    adda.w (a2),a2


    move.w a1,(a2)


    return_1652E:


    rts


    ; End of function DisplaySprite2



    Both of these subroutines are now shorter. May not look by much, but remember, as this is getting repeated all the time for EACH object for each frame, this will make a lot of difference.


    DisplaySprite3 does not need changing. This is because it already does a similar thing. Some objects move a priority word to d0, then DisplaySprite3 uses that d0. As it's already a word, nothing needs changing.


    Step 3 - Set objects that uses tables for priority to the new manager


    Now, a lot of objects just move a byte to the object's priority. But some objects use a table for priority, width, mappings, etc.


    Now, editing the table can cause problems, it can start making the objects code out of line, or even the rest of the game, which will cause the game to randomly crash at certain times. But it has to be a word, it cannot stay as a byte.


    So, the best thing I found it to actually use them calculations from Sonic 2's DisplaySprite to convert them into a word. So, when creating the object, it will have the priority as a byte, and then do Sonic 2's calculations to convert it into a word. Priority will then remain as a word for the rest of the time that object is there for. Because of this, it will only need to do this calculation once, then carry on the S3K way. Doing it once then the S3K way, is better then doing them calculations all the time, right?


    So, first, go to "loc_112A4:" ("Obj1C_Init:") This is "Object 1C - Bridge stake in Emerald Hill Zone and Hill Top Zone, falling oil in Oil Ocean Zone" object.


    You can see it's moving a lot of data to mappings and etc. It does this because there's so many subtypes to the object, and this is the best and quickest way for that object to load.


    So, underneath "move.b (a1)+,priority(a0)", add this:



    move.w priority(a0),d0
    lsr.w #1,d0


    andi.w #$380,d0


    move.w d0,priority(a0)



    So you have something like this (SVN user, your code will look slightly different, this is only a reference):



    loc_112A4:
    addq.b #2,routine(a0)


    moveq #0,d0


    move.b subtype(a0),d0


    move.w d0,d1


    lsl.w #3,d0


    lea dword_111E6(pc),a1


    lea (a1,d0.w),a1


    move.b (a1),mapping_frame(a0)


    move.l (a1)+,mappings(a0)


    move.w (a1)+,art_tile(a0)


    bsr.w Adjust2PArtPointer


    ori.b #4,render_flags(a0)


    move.b (a1)+,width_pixels(a0)


    move.b (a1)+,priority(a0)


    move.w priority(a0),d0


    lsr.w #1,d0


    andi.w #$380,d0


    move.w d0,priority(a0)


    lea byte_1128E(pc),a1


    move.b (a1,d1.w),d1


    beq.s BranchTo_MarkObjGone


    move.b d1,y_radius(a0)


    bset #4,render_flags(a0)



    Whatever data is grabbed from the table, it's now converted it to a word, and it will never do it again. It will always remain as a word. So, when it goes to DisplaySprite over and over, it can just get on with it.


    Next, go to "loc_1131A:" ("Obj71_Init:") (Object 71 - Bridge stake and pulsing orb from Hidden Palace Zone) and do the same thing, so you end up with this (SVN users, reference only):



    loc_1131A:
    addq.b #2,routine(a0)


    move.b subtype(a0),d0


    andi.w #$F,d0


    lsl.w #3,d0


    lea dword_11302(pc),a1


    lea (a1,d0.w),a1


    move.b (a1),mapping_frame(a0)


    move.l (a1)+,mappings(a0)


    move.w (a1)+,art_tile(a0)


    bsr.w Adjust2PArtPointer


    ori.b #4,render_flags(a0)


    move.b (a1)+,width_pixels(a0)


    move.b (a1)+,priority(a0)


    move.w priority(a0),d0


    lsr.w #1,d0


    andi.w #$380,d0


    move.w d0,priority(a0)


    move.b subtype(a0),d0


    andi.w #$F0,d0


    lsr.b #4,d0


    move.b d0,anim(a0)



    Go to "loc_3F228:" (Object 3E - Egg prison) and almost do the same thing again. Just look at the registers though, as they are different (it's a1 instead of a0). You should end up with this (SVN users, reference only):



    loc_3F228:
    _move.b 0(a0),0(a1) ; load obj


    move.w x_pos(a0),x_pos(a1)


    move.w y_pos(a0),y_pos(a1)


    move.w y_pos(a0),objoff_30(a1)


    move.l #Obj3E_MapUnc_3F436,mappings(a1)


    move.w #$2680,art_tile(a1)


    move.b #$84,render_flags(a1)


    moveq #0,d0


    move.b (a2)+,d0


    sub.w d0,y_pos(a1)


    move.w y_pos(a1),objoff_30(a1)


    move.b (a2)+,routine(a1)


    move.b (a2)+,width_pixels(a1)


    move.b (a2)+,priority(a1)


    move.w priority(a1),d0


    lsr.w #1,d0


    andi.w #$380,d0


    move.w d0,priority(a1)


    move.b (a2)+,mapping_frame(a1)



    And finally, go to "LoadSubObject_Part3:". Now this loads all the priorities, widths, mappings, etc, to all objects past Obj8C. This is a life saver. So this will save us a lot of time from doing it to a lot of other objects.


    So, change this:



    LoadSubObject_Part3:
    move.l (a1)+,mappings(a0)


    move.w (a1)+,art_tile(a0)


    jsr Adjust2PArtPointer


    move.b (a1)+,d0


    or.b d0,render_flags(a0)


    move.b (a1)+,priority(a0)


    move.b (a1)+,width_pixels(a0)


    move.b (a1),collision_flags(a0)


    addq.b #2,routine(a0)


    rts



    to this:



    LoadSubObject_Part3:
    move.l (a1)+,mappings(a0)


    move.w (a1)+,art_tile(a0)


    jsr Adjust2PArtPointer


    move.b (a1)+,d0


    or.b d0,render_flags(a0)


    move.b (a1)+,priority(a0)


    move.w priority(a0),d0


    lsr.w #1,d0


    andi.w #$380,d0


    move.w d0,priority(a0)


    move.b (a1)+,width_pixels(a0)


    move.b (a1),collision_flags(a0)


    addq.b #2,routine(a0)


    rts



    That's half the objects done already!


    Step 4 - Set the rest of the objects to the new manager


    Now, this step is easy, but time consuming. You will be a while doing this. You need to search through the whole of your asm file, and change all priority's to a word. Here is an example.


    At "Obj5E:", you'll see this:



    move.b #0,priority(a0)



    Change it to this:



    move.w #0,priority(a0)



    The move command has now changed from .b to .w as priority is now a word. The number will also need changing, but #0 in S2 is the same as S3K. So 0 doesn't need changing here.


    Another example. Go to "loc_7158:" ("Obj5F_Init:") and change this:



    move.b #1,priority(a0)



    to this:



    move.w #$80,priority(a0)



    Again, the .b has change to .w (this must be done ALL the time, even if the number is 0). This time, the number is 1. After calculations (or in S3K), the number will be $80. So it's been changed to $80. Remember, in S2, the numbers were only #. With the S3K way (unless 0), it will need to be #$.


    You need to find all these and change all .b to .w and change the numbers to the relavant number it would of become after the original calculations.


    To help you, I've made this table:



    Code:
    ; ===============================
    
    ; TABLE CONVERSION FROM S2 TO S3K
    
    ; ===============================
    
    ; |   | S 2 | S3K |
    
    ; |---|-----|-----|
    
    ; | ~ |  #  |  #$ |
    
    ; | P |  0  |  0  |
    
    ; | R |  1  |  80 |
    
    ; | I |  2  | 100 |
    
    ; | O |  3  | 180 |
    
    ; | R |  4  | 200 |
    
    ; | I |  5  | 280 |
    
    ; | T |  6  | 300 |
    
    ; | Y |  7  | 380 |
    
    ; ===============================



    Want one more example? Okay. Go to "ObjDB_Sonic_Init:" and change this:



    move.b #2,priority(a0)



    to this:



    move.w #$100,priority(a0)



    It's that simple. Now go! Convert all them priorities!


    Tip - click spoiler

    To make this step miles quicker, you can do a search and replace. Just be careful when doing this. Make sure you do it right. Remember that not all priorities use a0. Some use a1, or a2. You could do something similar to this:


    Search:



    move.b #2,priority(a



    Replace with:



    move.w #$100,priority(a



    ...and etc. That way, it will replace them all, no matter what register it has. Do the same from #0 - #7.


    Be warned though! If you've edited the tabs (spaces) on any coding, or on your own made objects, the "Search and Replace" may not be the best way to go, and you may have to do this manually.


    If you do do the "search and replace" idea, I'd highly reccommend you still search through the ASM file after to check you've done it right.







    Step 5 - Make sure the copiers are copying correctly


    At these locations:


    XenoWhirl users:




    • loc_10B9E:
    • loc_15E46:
    • Obj79_MakeSpecialStars:
    • loc_25C24:
    • loc_28A6E:




    SVN users ONLY:


    For first label, do a search for this line:






    _move.b d4,id(a1) ; load obj1F






    Second label is "BreakObjectToPieces_InitObject:"






    Third label is "Obj79_MakeSpecialStars:"






    Fourth Label is "loc_25C24:"






    And fifth label is "Obj73_LoadSubObject:"



    Under each of these labels, you should see this line:



    move.b priority(a0),priority(a1)



    Some objects like these, move the priority to a1 from a0. As you can see, it's only moving a byte. Change the line at all these locations to this:



    move.w priority(a0),priority(a1)



    All the registers at these locations are the same, so the above line is fine to use.


    Step 6 - Make sure some objects are calculating correctly


    Similar to step 5, some objects copy the priority to another address register or data register, then does a small subtraction, then moves it back. As it's going by S2's priority, this needs changing also.


    Go to "loc_34864:" (Tails in Special Stage) and change this bit only:



    move.b priority(a0),priority(a1)
    subi.b #1,priority(a1)



    to this:



    move.w priority(a0),priority(a1)
    subi.w #$80,priority(a1)



    So you have this (SVN users, reference only):



    loc_34864:
    move.w #$400,objoff_32(a0)


    move.b #$40,angle(a0)


    move.b #1,($FFFFF7DE).w


    clr.b collision_property(a0)


    clr.b respawn_index(a0)


    bsr.w loc_349C8


    movea.l #Object_RAM+$180,a1


    move.b #$63,(a1) ; load obj63 (shadow) at $FFFFB180


    move.w x_pos(a0),x_pos(a1)


    move.w y_pos(a0),y_pos(a1)


    addi.w #$18,y_pos(a1)


    move.l #Obj63_MapUnc_34492,mappings(a1)


    move.w #$623C,art_tile(a1)


    move.b #4,render_flags(a1)


    move.w #$200,priority(a1)


    move.l a0,objoff_38(a1)


    movea.l #Object_RAM+$1C0,a1


    move.b #$88,(a1) ; load obj88


    move.w x_pos(a0),x_pos(a1)


    move.w y_pos(a0),y_pos(a1)


    move.l #Obj88_MapUnc_34DA8,mappings(a1)


    move.w #$4316,art_tile(a1)


    move.b #4,render_flags(a1)


    move.w priority(a0),priority(a1)


    subi.w #$80,priority(a1)


    move.l a0,objoff_38(a1)


    movea.l a1,a0


    move.b #1,($FFFFF7DF).w


    clr.b respawn_index(a0)


    movea.l objoff_38(a0),a0 ; load 0bj address


    rts



    Now, it's moving a WORD to a1, then subtracting $80 instead of 1. So now it's going how S3K would do it.


    Similar story here. Go to "Obj88:" (Tails' tails in Special Stage) and change this bit only:



    move.b priority(a1),d0
    subq.b #1,d0


    move.b d0,priority(a0)



    to this:



    move.w priority(a1),d0
    subi.w #$80,d0


    move.w d0,priority(a0)



    One more. Go to "loc_3C1F4:" (Breakable plating from WFZ) and change this bit only:



    move.b priority(a0),d4
    subq.b #1,d4



    to this:



    move.w priority(a0),d4
    subi.w #$80,d4



    There's a little more to this object. Next, go to "loc_3C20E:" and change this bit only:



    move.b d4,priority(a1)



    to this:



    move.w d4,priority(a1)



    These objects will now display correctly.


    Step 7 - Make sure EHZ's boss uses the right compares


    At these locations:

    • loc_2F714:
    • loc_2F77E:
    • loc_2F7A6:




    The EHZ boss is showing these commands:



    cmpi.b #2,priority(a0)



    Change them to this:



    cmpi.w #$100,priority(a0)



    They were still treating priority as a byte, so we just needed to change these to a word.


    Step 8 - Fix ARZ, CNZ, MCZ and OOZ's boss's priority


    These are a pain in the ass to fix! These bosses use the priority and (the old) width_pixel's SST's together for a complete different reason (The hammer, drills and catchers). It's moves odd numbers to the priority SST (well, it's actually positions, but the number is odd to DisplaySprite) and because of this, it cannot display the word any longer and therefore, the game freezes pretty much instantly when you approach one of these bosses.


    Luckily, there are many objects that do a similar thing; using priority for a complete different reason. This is where "DisplaySprite3" comes into play. Objects that use priority SST for other reasons, instead, moves a word to d0, then jumps to DisplaySprite3. So, we're going to do something similar for ARZ, CNZ, MCZ and OOZ's bosses.


    ARZ boss


    Go to "loc_304D4:" and delete the line:



    move.w #$100,priority(a0)



    This is no longer needed, as it's going to be replaced with other things later in the ARZ boss code anyway.


    Still at this label, you should also see these commands:



    move.w #$200,priority(a1)



    Code:
        move.w    #$100,priority(a1)
    

    You might as well get rid of them too.


    Make sure you do NOT delete this line:



    move.w #$488,priority(a0)



    This is for the y_pos of the hammer, one of the reason why the priority needs changing of the boss.


    Next, go to "loc_30BC8:" and find and delete the line:



    move.w #$200,priority(a0)



    Again, no point in this!


    Next, find the label "JmpTo37_DisplaySprite". Only the ARZ boss jumps to this location for displaying a sprite, so we know we can freely edit this for our ARZ boss without affecting anything else. Anyway, you should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):



    JmpTo37_DisplaySprite
    jmp DisplaySprite



    Now, because them odd numbers are set as priority, when it jumps to DisplaySprite, it will try to process it and freeze. So, this is what we're going to do. Change it to this:



    JmpTo37_DisplaySprite
    move.w #$200,d0 ; move $200 to ARZ boss' priority


    jmp DisplaySprite3 ; Dispaly it



    Now, everytime the boss needs displaying, instead of grabbing the data from the priority SST, it will process what we've just moved to d0. #$200 seems good, I use that.


    I would of tried changing these priority commands to a different universal SST, but the ARZ boss unfortunately uses them all, so there's nothing free. So, the priority for Eggman, arrows, hammer and totem poles now have to be the same priority, but really, you won't notice any different to the way the boss displays it's sprites.


    Viola! ARZ boss is sorted and will no longer freeze.


    CNZ boss


    The problem with this boss is the exact same problem as ARZ. Priority is being used for something else. Same sort of fix applies here.


    Go to "loc_31904:" and delete the line:



    move.w #$180,priority(a0)



    Also, go to "loc_31F48:" and delete the line:



    move.w #$380,priority(a0)



    Next, find the label "JmpTo39_DisplaySprite". Only the CNZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):



    JmpTo39_DisplaySprite
    jmp DisplaySprite



    Change it to this:



    JmpTo39_DisplaySprite
    move.w #$180,d0 ; move $180 to CNZ boss' priority


    jmp DisplaySprite3 ; Dispaly it



    CNZ boss should no longer freeze. #$180 seems good here. Again, CNZ boss shares the same problem as ARZ boss, so Eggman, the catchers and spikeball have to be the same priority, and you shouldn't notice any difference.


    MCZ boss


    Same issue. Priority is being used for something else. Same sort of fix applies here. But luckily, all the priorties are #$180, so at least the priority won't change after this edit unlike the previous two bosses we've edited.


    Go to "loc_30FB8:" and delete the line:



    move.w #$180,priority(a0)



    Also, go to "loc_313DA:" ("Obj57_LoadStoneSpike:") and delete the same(ish) line (I say 'ish' because it's using a1 instead of a0):



    move.w #$180,priority(a1)



    Next, find the label "JmpTo38_DisplaySprite". Only the MCZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):



    JmpTo38_DisplaySprite
    jmp DisplaySprite



    Change it to this:



    JmpTo38_DisplaySprite
    move.w #$180,d0 ; move $180 to MCZ boss' priority


    jmp DisplaySprite3 ; Dispaly it



    Seeming as both priority lines we just deleted was #$180, we know that we can use #$180 here. MCZ boss should no longer freeze.


    OOZ boss


    This boss uses Priority as a x_pos for the sub objects. Again, we're going to have to do a similar thing like we did with the other bosses.


    Go to "loc_32FA8:" ("Obj55_Init:")and delete the line:



    move.w #$180,priority(a0)



    Also, go to "loc_33586:" ("Obj55_Laser_Init:") and delete the line:



    move.w #$200,priority(a0)



    Also, go to "loc_33640:" ("Obj55_Laser_CreateWave:") and delete the line:



    move.w #$100,priority(a1)



    Next, find the label "JmpTo41_DisplaySprite". Only the OOZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):



    JmpTo41_DisplaySprite
    jmp DisplaySprite



    Change it to this:



    JmpTo41_DisplaySprite
    move.w #$180,d0 ; move $180 to OOZ boss' priority


    jmp DisplaySprite3 ; Dispaly it



    OOZ boss should no longer freeze. #$180 seems good here. Again, all boss objects have to be the same priority, and you shouldn't notice any difference.


    Notice: I have compared the priorities to the original Sonic 2's priorities for these bosses with our new "set" priorities (that we've moved to d0) and I saw no difference whatsover. So all the above is fine. For example, with Sonic 2's original priorities set, Sonic was always in front of the ARZ boss, hammer, totem poles and arrows. He was never behind them. The way we have set up the bosses above, this is still the case. That's why you shouldn't notice any difference.


    Step 9 - Fix MTZ's boss's priority


    You're probably thinking, "What? This boss gets it's very own step?" Yup, and the reason why, is because there is a free universal SST! Hoozah! That means we can fix this boss without making everything use the same priority!


    Like the others, the boss uses priority for other things (you know the little flame on Eggman's ship? Priority is used for that y_pos. But luckily for us, anim_frame_duration's SST is completely free. Also, the SST after it (objoff_1F) is also free! So we can use this as a word!


    Unfortunately, it seems that I couldn't make the flame use "anim_frame_duration" and I'm not sure why. So instead, we're going to make the DisplaySprite read from anim_frame_duration.


    Okay, let's fix this damn boss.


    Go to "loc_3229E:" ("Obj54_Init:") and change this:



    move.w #$180,priority(a0)



    to this:



    move.w #$180,anim_frame_duration(a0)



    Still at this location, change:



    move.w #$300,priority(a1)



    to this:



    move.w #$300,anim_frame_duration(a1)



    Make sure you do NOT edit/change this line:



    move.w y_pos(a0),priority(a0)



    This is for the y_pos of the flame.


    Go to these locations:


    XenoWhirl users:




    • loc_32966:
    • loc_32B1A:
    • loc_32B34:
    • loc_32B42:
    • loc_32B56:
    • loc_32CC0:





    SVN users ONLY:


    For first label, do a search for this line:






    move.l objoff_34(a0),objoff_34(a1)






    Second label is "Obj53_SetAnimPriority:"






    Third label is directly underneath the "Obj53_SetAnimPriority:" label






    Fourth Label is directly underneath the previous label we were at






    Fifth label is directly underneath the previous label we were at






    And sixth label is "Obj54_Laser_Init:"



    And change the priority to anim_frame_duration. For example (SVN users, reference only):



    loc_32B34:
    move.b #4,mapping_frame(a0)


    move.w #$100,anim_frame_duration(a0)


    rts



    Next, find the label "JmpTo40_DisplaySprite". Only the MTZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):



    JmpTo40_DisplaySprite
    jmp DisplaySprite



    Change it to this:



    JmpTo40_DisplaySprite
    move.w anim_frame_duration(a0),d0


    jmp DisplaySprite3



    There we go! This boss is now fixed and will no longer freeze. And the priorities haven't been changed! Hooray! If only the other bosses we had to edit would play ball.


    Step 10 update (15/07/12) - Fix the priority for when the main character is dead


    As previously stated, some objects use the priority SST for other purposes. That's why a lot of them use DisplaySprite3 instead.


    The problem is, when Sonic (main character) dies, all objects freeze (well, most of them anyway). When they've frozen, they all jump to "RunObjectDisplayOnly:"



    RunObjectDisplayOnly:
    moveq #0,d0


    move.b (a0),d0 ; get the object's ID


    beq.s + ; if it's obj00, skip it


    tst.b render_flags(a0) ; should we render it?


    bpl.s + ; if not, skip it


    bsr.w DisplaySprite


    +


    lea next_object(a0),a0 ; load 0bj address


    dbf d7,RunObjectDisplayOnly


    rts


    ; End of function RunObjectDisplayOnly



    As you can see, ALL objects will jump to DisplaySprite. This wasn't a problem before because it just took a byte from the priority and then convert it to a word, which was always even. But now, it doesn't with our new manager. It just takes the word. So, if the priority SST is odd, it will still try to display it, and then freeze.


    Most of the time, when Sonic dies, everything will be fine. But if Sonic dies near the EHZ bridge, CNZ spring, ARZ boss, most stuff that use DisplaySprite3, the game will freeze. Because instead of displaying whatever is at d0, it's jumping to DisplaySprite, and taking it from priority SST instead, which is more-than-likely odd (because it was used for something else).


    What we need to do, is set a new priority for objects that use priority for other reasons when Sonic is dead. So, we're going to change the branch to DisplaySprite to something else. So, go to "RunObjectDisplayOnly:" and change it to this:



    RunObjectDisplayOnly:
    moveq #0,d0 ; Clear d0 quickly


    move.b (a0),d0 ; get the object's ID


    beq.s ++ ; if it's obj00, skip it


    tst.b render_flags(a0) ; should we render it?


    bpl.s ++ ; if not, skip it


    move.w priority(a0),d0 ; move object's priority to d0


    btst #6,render_flags(a0) ; is the compound sprites flag set?


    beq.s + ; if not, branch


    move.w #$200,d0 ; move $200 to d0


    +


    bsr.w DisplaySprite3 ; Display the object with whatever is set at d0


    +


    lea next_object(a0),a0 ; load 0bj address


    dbf d7,RunObjectDisplayOnly


    rts


    ; End of function RunObjectDisplayOnly



    So now, when Sonic dies, all objects that use priority for something else that are frozen will have their priority set to #$200. Any objects that use priority for priority, will use their original priority. This is what most objects use anyway. I didn't actually notice any difference to when Sonic normally dies.


    Further explaination; when all objects jump here, it will ask first, does it have the compound sprite flag set? If not, branch and continue, and use the object's original priority. If it IS set, it means that the current object's status table also contains information about other child sprites which need to be drawn using the current object's mappings. In other words, for our sake, it's using priority for something different. Therefore, it will NOT branch and it will move $200 to d0 instead.


    Because of this now, objects that were originally DisplaySprite3, when Sonic dies, it won't try taking the priority SST anymore, and just use the #$200 we've set. If you enter debug when in the middle of dying, then exit so you're back alive, all objects will start using their orignial priority (or d0 if they were using DisplaySprite3).


    Step 11 - Fix the priority for Special Stages


    This took me fucking forever to fix! But luckily for you, I've figured it out without having to change any of the priorities (unlike the bosses we did earlier). It's going to be easy for you!


    Playing the special stages as they are now will make the game freeze within 2 seconds. That can't be good, right? We will need to fix this.


    Go to "Obj87:". This is the "Number of rings in Special Stage" object. This is what cause the special stage to freeze in a matter of seconds.


    You may notice that it's not moving anything to a priority SST. That's because the priority for this object is 0. The problem is, it's moving numbers from SST $19(a0) onwards. Mainly at $19(a0) is #$20. Now, because we've got the new priority manager, it will grab it's priority (which is 0), but as it's now a word, it will grab that #$20. So, it will have the data #$0020. DisplaySprite cannot process that, and crashes. We need to make it grab #$0000 and not #$0020, but we can't change anything at SST $19(a0), otherwise the special stage will start to misbehave. The fix? It's quite simple.


    At these locations:


    XenoWhirl users:




    • loc_7536:
    • loc_758C:
    • loc_75BC:
    • loc_75C8:
    • loc_7648:




    SVN users ONLY:





    First label is "loc_7536:"






    For second label, do a search for this line:






    move.b d2,sub2_mapframe-sub2_x_pos(a1) ; sub2_mapframe






    Third label is directly underneath the previous label we were at






    Fourth Label is directly underneath the previous label we were at






    And, for the fith label, do a seach for this line:






    move.w #$D8,(a1) ; sub?_x_pos






    and it's the + label directly underneath it



    You will see the code branching (in some way) to "JmpTo_DisplaySprite". We're going to create a new label for this object to jump to instead. So, at these locations, change all:



    JmpTo_DisplaySprite



    to this:



    JmpTo_DisplaySpriteSpecial



    Example (SVN users, reference only):



    loc_7536:
    move.b d3,objoff_F(a0)


    bra.w JmpTo_DisplaySpriteSpecial



    Make sure you only change it to the new label at these locations!


    Next, go to the label itself "JmpTo_DisplaySprite" to see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):



    JmpTo_DisplaySprite
    jmp DisplaySprite.l



    Directly underneath it, add this:



    JmpTo_DisplaySpriteSpecial
    move.w #0,d0


    jmp DisplaySprite3.l



    So you end up with this (SVN users, reference only):



    JmpTo_DisplaySprite
    jmp DisplaySprite.l


    JmpTo_DisplaySpriteSpecial


    move.w #0,d0


    jmp DisplaySprite3.l



    Now, obj87 only, will jump here for displaying the sprite. It will force #$0000 to be displayed instead. And it doesn't affect any of the object itself. We couldn't edit the "JmpTo_DisplaySprite" label itself because other objects are jumping to that label, and we don't want to edit other object's priority.


    The special stage will no longer freeze within seconds...


    But hold on there!


    I'm sorry, the special stage can still freeze halfway through. Yeah, another object does not want to play ball. The "Messages/checkpoint from Special Stage" object.


    This object displays the "Rings to go!" text and the "Collect 40 rings" text and to make them fly apart from each other. It also displays the "Cool" and "Not enough rings" text and the hand with the blue emblem, etc. To be totally honest, this object is almost perfect, it's the "Rings to go" that's causing the freezing. The problem is pretty much the same as "Number of rings in Special Stage" object.


    Go to "loc_357B2:" ("Obj5A_FlashMessage:") And you should see (SVN users, reference only):



    loc_357B2:
    tst.b ($FFFFDBA0).w


    bne.w return_357D0


    tst.b ($FFFFDBA6).w


    bne.s return_357D0


    move.b ($FFFFFE0F).w,d0


    andi.b #7,d0


    cmpi.b #6,d0


    bcs.w JmpTo44_DisplaySprite



    That branch to "JmpTo44_DisplaySprite" is the problem. Looking at the object, this bit's priority is always 0. So, change the "JmpTo44_DisplaySprite" to "JmpTo_DisplaySpriteSpecial2", so you have this (SVN users, reference only):



    loc_357B2:
    tst.b ($FFFFDBA0).w


    bne.w return_357D0


    tst.b ($FFFFDBA6).w


    bne.s return_357D0


    move.b ($FFFFFE0F).w,d0


    andi.b #7,d0


    cmpi.b #6,d0


    bcs.w JmpTo_DisplaySpriteSpecial2



    Yes, we're creating another new label! It's the best way to do it. Next, go to "loc_35F76:". Right above that label, paste this:



    JmpTo_DisplaySpriteSpecial2:
    move.w #0,d0


    jmp Displaysprite3.l



    So you end up with this (SVN users, reference only):



    JmpTo_DisplaySpriteSpecial2:
    move.w #0,d0


    jmp Displaysprite3.l


    loc_35F76:


    add.w d0,d0


    move.w d0,d1


    add.w d0,d0


    add.w d1,d0


    move.w word_35F92(pc,d0.w),(Normal_palette_line4+$16).w


    move.w word_35F92+2(pc,d0.w),(Normal_palette_line4+$18).w


    move.w word_35F92+4(pc,d0.w),(Normal_palette_line4+$1A).w


    rts



    Fixed! That part of the object will be forced to use #$0000 again and to display it. The rest of the object is completely fine though, and does not need editing (plus, the rest of it isn't using 0).


    Step 12 - Fix the CNZ pull-spring


    [​IMG]


    This CNZ spring has lost it's width_pixels SST. Similar problem to what the priorty manager has done, it's width_pixel is being over-written by something else in the objects code.


    If you've already applied this fix to your hack from my "How to free up 2 universal SST's" guide, you can skip this step. Otherwise, here is a quote from the topic:


    Now, the object will work as it should do.


    Step 13 update (15/07/12) - Fix the priority for Tails' tails


    This bug I only noticed the other day (was also present in S2R). The fix was a bit more involved than I thought.


    The problem is, Tails' tail's priority has been affected. Even though Tails' priority is $100, and his tails' priority is $100. Here is an example:


    [​IMG]


    It seems that his tails are not as much as a priority anymore. Anyway, flamewing had a solution, which he uses for his hack; Sonic 2 Heroes.


    Anyway, the fix:


    First, you're going to have to use some RAM. Only a word. So, go to your list of equates. I used $FFFFF5C0 (it's free whether you use the S1 sound driver or not). Call the RAM "Tails_Tails_ptr"



    Tails_Tails_ptr = ramaddr( $FFFFF5C0 )



    Our new RAM is ready for use. First, go to "InitPlayers:" and under the line:



    move.b #2,(Sidekick).w ; load Obj02 Tails object at $FFFFB040



    SVN users, yours will say:



    move.b #ObjID_Tails,(Sidekick+id).w ; load Obj02 Tails object at $FFFFB040



    Insert this:



    move.w #Tails_Tails,(Tails_Tails_ptr).w



    So you have something like this (SVN users, reference only):



    move.b #2,(Sidekick).w ; load Obj02 Tails object at $FFFFB040
    move.w #Tails_Tails,(Tails_Tails_ptr).w


    move.w (MainCharacter+x_pos).w,(Sidekick+x_pos).w


    move.w (MainCharacter+y_pos).w,(Sidekick+y_pos).w


    subi.w #$20,(Sidekick+x_pos).w


    addi.w #4,(Sidekick+y_pos).w


    move.b #8,(Tails_Dust).w ; load Obj08 Tails' spindash dust/splash object at $FFFFD140



    Do the same at label "InitPlayers_TailsAlone:", so you have something like this (SVN users, reference only):



    InitPlayers_TailsAlone:
    move.b #2,(MainCharacter).w ; load Obj02 Tails object at $FFFFB000


    move.w #Tails_Tails,(Tails_Tails_ptr).w


    move.b #8,(Tails_Dust).w ; load Obj08 Tails' spindash dust/splash object at $FFFFD100


    addi.w #4,(MainCharacter+y_pos).w


    rts


    ; End of function InitPlayers



    Next, go to "loc_A2F2:". This is for when you're at the cutscene at the end of the game.


    XenoWhirl users. Change this:



    loc_A2F2:
    moveq #$E,d0


    move.b #2,(a1) ; load Tails object


    move.b #$81,$2A(a1)


    move.b #5,(Object_RAM+$80).w ; load Obj05 (Tails' tails) at $FFFFB080


    move.w a1,(Object_RAM+$80+parent).w


    rts



    to this:



    loc_A2F2:
    moveq #$E,d0


    move.b #2,(a1) ; load Tails object


    move.b #$81,$2A(a1)


    move.w #Object_RAM+$80,(Tails_Tails_ptr).w


    rts



    SVN users, change this:



    loc_A2F2:
    moveq #$E,d0


    move.b #ObjID_Tails,id(a1) ; load Tails object


    move.b #$81,obj_control(a1)


    move.b #ObjID_TailsTails,(Tails_Tails_Cutscene+id).w ; load Obj05 (Tails' tails) at $FFFFB080


    move.w a1,(Tails_Tails_Cutscene+parent).w


    rts



    to this:



    loc_A2F2:
    moveq #$E,d0


    move.b #ObjID_Tails,id(a1) ; load Tails object


    move.b #$81,obj_control(a1)


    move.w #Tails_Tails_Cutscene,(Tails_Tails_ptr).w ; Tails' tails at $FFFFB080


    rts



    Next, go to "Obj02_Init_Continued:", and change the last few lines. Change this:



    move.b #5,(Tails_Tails).w ; load Obj05 (Tails' Tails) at $FFFFD000
    move.w a0,(Tails_Tails+parent).w ; set its parent object to this



    SVN users, yours will say:



    move.b #ObjID_TailsTails,(Tails_Tails+id).w ; load Obj05 (Tails' Tails) at $FFFFD000
    move.w a0,(Tails_Tails+parent).w ; set its parent object to this



    And change it to this:



    movea.w (Tails_Tails_ptr).w,a1
    move.b #5,0(a1) ; load Obj05 (Tails' Tails) at $FFFFD000


    move.w a0,parent(a1) ; set its parent object to this



    And change it to this:



    movea.w (Tails_Tails_ptr).w,a1
    move.b #ObjID_TailsTails,id(a1) ; load Obj05 (Tails' Tails)


    move.w a0,parent(a1) ; set its parent object to this



    One more step! Go to "Obj02:" See the command jmp Obj02_States(pc,d1.w) (jmp Obj02_Index(pc,d1.w))? Change the "jmp" to a "jsr".


    And directly underneath it, add this:



    movea.w (Tails_Tails_ptr).w,a1
    tst.b routine(a1)


    beq.s +


    jmp (DisplaySprite2).l


    +


    rts



    So you have something like this (SVN users, reference only):



    Obj02:
    ; a0=character


    cmpi.w #2,(Player_mode).w


    bne.s +


    move.w (Camera_Min_X_pos).w,(Tails_Min_X_pos).w


    move.w (Camera_Max_X_pos).w,(Tails_Max_X_pos).w


    move.w (Camera_Max_Y_pos_now).w,(Tails_Max_Y_pos).w


    +


    moveq #0,d0


    move.b routine(a0),d0


    move.w Obj02_States(pc,d0.w),d1


    jsr Obj02_States(pc,d1.w)


    movea.w (Tails_Tails_ptr).w,a1


    tst.b routine(a1)


    beq.s +


    jmp (DisplaySprite2).l


    +


    rts



    Done! Tails' tails should now work with the right priority! Tails will now queue his tails for drawing right after himself. Without this, the tails will be queued for drawing after all objects between Tails and the tails that have the same priority. And that's why before, Tails' tails had less priority.


    [​IMG]


    Step 14 - Improve ObjectMove and ObjectMoveandFall (optional)


    Want to speed up your hack even more? This step actually has nothing to do with the priority manager, but it wasn't worth making a new topic of it's own. And as this topic is about speeding up Sonic 2, I might as well post it here.


    In S2 and S3K, there's a subroutine called "ObjectMove" and another subroutine called "ObjectMoveandFall". ObjectMove allows objects to move about and "ObjectMoveandFall" does the same, except it applies gravity to it.


    S3K has the same subroutines except the code is a bit shorter and therefor, it's faster. It may not look much faster, but again, with all the objects on screen, it has to apply it for every frame.


    [​IMG]


    Just like before, in every single frame, the game has to "ObjectMoveandFall" each scattered ring, "ObjectMove" each and every single bubble, and the two sharks, "ObjectMoveandFall" Sonic and Tails, and "ObjectMove" the rotating platforms on the side. It has to happen to these objects every single frame. So, with S3K's shorter code, it can do all this quicker, and again, get on with other things.


    To do this, it's really simple.


    Go to "ObjectMoveAndFall:" and change this:



    ObjectMoveAndFall:
    move.l x_pos(a0),d2 ; load x position


    move.l y_pos(a0),d3 ; load y position


    move.w x_vel(a0),d0 ; load x speed


    ext.l d0


    asl.l #8,d0 ; shift velocity to line up with the middle 16 bits of the 32-bit position


    add.l d0,d2 ; add x speed to x position ; note this affects the subpixel position objoff_A(a0) = 2+x_pos(a0)


    move.w y_vel(a0),d0 ; load y speed


    addi.w #$38,y_vel(a0) ; increase vertical speed (apply gravity)


    ext.l d0


    asl.l #8,d0 ; shift velocity to line up with the middle 16 bits of the 32-bit position


    add.l d0,d3 ; add old y speed to y position ; note this affects the subpixel position objoff_E(a0) = 2+y_pos(a0)


    move.l d2,x_pos(a0) ; store new x position


    move.l d3,y_pos(a0) ; store new y position


    rts


    ; End of function ObjectMoveAndFall



    To this:



    ObjectMoveAndFall:
    move.w x_vel(a0),d0


    ext.l d0


    lsl.l #8,d0


    add.l d0,x_pos(a0)


    move.w y_vel(a0),d0


    addi.w #$38,y_vel(a0) ; apply gravity


    ext.l d0


    lsl.l #8,d0


    add.l d0,y_pos(a0)


    rts


    ; End of function ObjectMoveAndFall



    Next, go to "ObjectMove:" and change this:



    ObjectMove:
    move.l x_pos(a0),d2 ; load x position


    move.l y_pos(a0),d3 ; load y position


    move.w x_vel(a0),d0 ; load horizontal speed


    ext.l d0


    asl.l #8,d0 ; shift velocity to line up with the middle 16 bits of the 32-bit position


    add.l d0,d2 ; add to x-axis position ; note this affects the subpixel position objoff_A(a0) = 2+x_pos(a0)


    move.w y_vel(a0),d0 ; load vertical speed


    ext.l d0


    asl.l #8,d0 ; shift velocity to line up with the middle 16 bits of the 32-bit position


    add.l d0,d3 ; add to y-axis position ; note this affects the subpixel position objoff_E(a0) = 2+y_pos(a0)


    move.l d2,x_pos(a0) ; update x-axis position


    move.l d3,y_pos(a0) ; update y-axis position


    rts


    ; End of function ObjectMove



    to this:



    ObjectMove:
    move.w x_vel(a0),d0


    ext.l d0


    lsl.l #8,d0


    add.l d0,x_pos(a0)


    move.w y_vel(a0),d0


    ext.l d0


    lsl.l #8,d0


    add.l d0,y_pos(a0)


    rts


    ; End of function ObjectMove



    That's that. For any object that has to move, it will now do this coding a lot quicker each frame.


    Step 15 - Finish


    That's it guys! All done! You have now successfully ported Sonic 3 and Knuckles' Priority Manager into Sonic 2. And if you followed step 14, you've improved (optimised) ObjectMove(andFall). Your hack should now be experiencing less lag! And just in time for the hacking contest, hey?


    Please be aware, that this will not elimate all lag from your hack. It just helps to get rid of some of it. To get rid of more lag, you need to do more; like Object Managers and etc.


    To demonstrate how things may of improved, here are two videos to compare the Priority Managers (and the ObjectMove(andFall)).


    Demonstration of lag with S2's Priority Manager and ObjectMove(andFall)



    Demonstration of lag with S3K's Priority Manager and ObjectMove(andFall)



    ROM to download to test yourself


    To download Sonic 2 With S3K's Priority Manager and ObjectMove(andFall) already ported, then click here to download and to try for yourself!


    That's it to it. If you have any problems/questions/comments, please say.


    But may I ask? If you follow this guide, when you announce to the community that you have S3K's Priority Manager (due to following my guide), please say that you were guided by my guide. It's nice to know that I have helped to make a difference and that all this was not a waste of time. Although, this is optional.


    Many thanks, members! Enjoy your speedier hack!


    Cheers,


    redhotsonic
     
  2. Mike B Berry

    Mike B Berry A grandiose return Member

    Joined:
    Jun 6, 2012
    Messages:
    372
    Location:
    New places, newer motivation
    Fuck yeah RHS, your guides always help out, now ARZ doesn't have an aggressive lag when I dip into the drink. Your information always helps. keep up the excellent work.
     
  3. redhotsonic

    redhotsonic Also known as RHS Retired Staff

    Joined:
    Aug 10, 2007
    Messages:
    2,967
    Location:
    England
    Glad you like it, I think you're the only one to not report problems yet either =P
     
  4. Mike B Berry

    Mike B Berry A grandiose return Member

    Joined:
    Jun 6, 2012
    Messages:
    372
    Location:
    New places, newer motivation
    Who reported this.


    Also Is there a way to free up a 4th SST? I've read your post on freeing the 3rd SST, and I've fixed the CNZ Manuel Spring objects collision, but I have a feeling that there is a hole in the programming that we could free one more. I've gotten rid of all the rehashed/ unused Sonic 1 objects and now I have some extra ram space.
     
  5. redhotsonic

    redhotsonic Also known as RHS Retired Staff

    Joined:
    Aug 10, 2007
    Messages:
    2,967
    Location:
    England
    You can get rid of a fair few of Sonics/Tails SST, but not universals ones. I haven't figured a way to free another universal SST except the 2 I've already guided.
     
  6. Mike B Berry

    Mike B Berry A grandiose return Member

    Joined:
    Jun 6, 2012
    Messages:
    372
    Location:
    New places, newer motivation
    I thought you freed 3, although I could be wrong...
     
  7. redhotsonic

    redhotsonic Also known as RHS Retired Staff

    Joined:
    Aug 10, 2007
    Messages:
    2,967
    Location:
    England
    Hello, all! There is an update to the priority guide. 2 extra steps! These steps are actually bug fixes caused by the new priority manager, and you may want to implement these if you've followed my guide.


    Thanks to flamewing for the help and advice on these fixes.


    Step 10 update - Fix the priority for when the main character is dead


    In my priority guide, for step 10, I explained that when you're dead, all the objects stop moving. And they all jump to "DisplaySprite:" via "RunObjectDisplayOnly:". Because of this, some objects that use priority for different reason will cause the game to crash. The way I showed you was that we had to make all objects use the same priority, so that it wouldn't freeze. Most of the time, this isn't a problem, but then, you'll may notice things like this:


    [​IMG]


    [​IMG]


    This is because, when you die, all objects jump to the "RunObjectDisplayOnly:". As they're all using the same priority, a newer one will overlap. Take the EHZ boss. His drill gets displayed first, then his wheels (so they overlap), then the cockpit (so they overlap the rest), then finally Eggman (which overlaps all). See what I mean?


    To fix this, it's simple, and I should have thought of it before (Sonic 2 Recreation has different coding and doesn't suffer this). Anyway, go to "RunObjectDisplayOnly:", and change from this:



    RunObjectDisplayOnly:
    moveq #0,d0


    move.b (a0),d0 ; get the object's ID


    beq.s + ; if it's obj00, skip it


    tst.b render_flags(a0) ; should we render it?


    bpl.s + ; if not, skip it


    move.w #$200,d0


    bsr.w DisplaySprite3


    +


    lea next_object(a0),a0 ; load 0bj address


    dbf d7,RunObjectDisplayOnly


    rts


    ; End of function RunObjectDisplayOnly



    to this:



    RunObjectDisplayOnly:
    moveq #0,d0 ; Clear d0 quickly


    move.b (a0),d0 ; get the object's ID


    beq.s ++ ; if it's obj00, skip it


    tst.b render_flags(a0) ; should we render it?


    bpl.s ++ ; if not, skip it


    move.w priority(a0),d0 ; move object's priority to d0


    btst #6,render_flags(a0) ; is the compound sprites flag set?


    beq.s + ; if not, branch


    move.w #$200,d0 ; move $200 to d0


    +


    bsr.w DisplaySprite3 ; Display the object with whatever is set at d0


    +


    lea next_object(a0),a0 ; load 0bj address


    dbf d7,RunObjectDisplayOnly


    rts


    ; End of function RunObjectDisplayOnly



    So now, when all objects jump here, it will ask first, does it have the compound sprite flag set? If not, branch and continue, and use the object's original priority. If it IS set, it means that the current object's status table also contains information about other child sprites which need to be drawn using the current object's mappings. In other words, for our sake, it's using priority for something different. Therefore, it will NOT branch and it will move $200 to d0 instead.


    That's it. Done, you'll now get stuff like this:


    [​IMG]


    [​IMG]


    It was that simple.


    Step 13 - Fix the priority for Tails' tails


    This bug I only noticed the other day (was also present in S2R). The fix was a bit more involved than I thought.


    The problem is, Tails' tail's priority has been affected. Even though Tails' priority is $100, and his tails' priority is $100. Here is an example:


    [​IMG]


    It seems that his tails are not as much as a priority anymore. Anyway, flamewing had a solution, which he uses for his hack; Sonic 2 Heroes.


    Anyway, the fix:


    First, you're going to have to use some RAM. Only a word. So, go to your list of equates. I used $FFFFF5C0 (it's free whether you use the S1 sound driver or not). Call the RAM "Tails_Tails_ptr"



    Tails_Tails_ptr = ramaddr( $FFFFF5C0 )



    Our new RAM is ready for use. First, go to "InitPlayers:" and under the line:



    move.b #2,(Sidekick).w ; load Obj02 Tails object at $FFFFB040



    SVN users, yours will say:



    move.b #ObjID_Tails,(Sidekick+id).w ; load Obj02 Tails object at $FFFFB040



    Insert this:



    move.w #Tails_Tails,(Tails_Tails_ptr).w



    So you have something like this (SVN users, reference only):



    move.b #2,(Sidekick).w ; load Obj02 Tails object at $FFFFB040
    move.w #Tails_Tails,(Tails_Tails_ptr).w


    move.w (MainCharacter+x_pos).w,(Sidekick+x_pos).w


    move.w (MainCharacter+y_pos).w,(Sidekick+y_pos).w


    subi.w #$20,(Sidekick+x_pos).w


    addi.w #4,(Sidekick+y_pos).w


    move.b #8,(Tails_Dust).w ; load Obj08 Tails' spindash dust/splash object at $FFFFD140



    Do the same at label "InitPlayers_TailsAlone:", so you have something like this (SVN users, reference only):



    InitPlayers_TailsAlone:
    move.b #2,(MainCharacter).w ; load Obj02 Tails object at $FFFFB000


    move.w #Tails_Tails,(Tails_Tails_ptr).w


    move.b #8,(Tails_Dust).w ; load Obj08 Tails' spindash dust/splash object at $FFFFD100


    addi.w #4,(MainCharacter+y_pos).w


    rts


    ; End of function InitPlayers



    Next, go to "loc_A2F2:". This is for when you're at the cutscene at the end of the game.


    XenoWhirl users. Change this:









    loc_A2F2:

    moveq #$E,d0



    move.b #2,(a1) ; load Tails object



    move.b #$81,$2A(a1)



    move.b #5,(Object_RAM+$80).w ; load Obj05 (Tails' tails) at $FFFFB080



    move.w a1,(Object_RAM+$80+parent).w



    rts












    to this:










    loc_A2F2:

    moveq #$E,d0



    move.b #2,(a1) ; load Tails object



    move.b #$81,$2A(a1)



    move.w #Object_RAM+$80,(Tails_Tails_ptr).w



    rts





    SVN users, change this:









    loc_A2F2:

    moveq #$E,d0



    move.b #ObjID_Tails,id(a1) ; load Tails object



    move.b #$81,obj_control(a1)



    move.b #ObjID_TailsTails,(Tails_Tails_Cutscene+id).w ; load Obj05 (Tails' tails) at $FFFFB080



    move.w a1,(Tails_Tails_Cutscene+parent).w



    rts












    to this:










    loc_A2F2:

    moveq #$E,d0



    move.b #ObjID_Tails,id(a1) ; load Tails object



    move.b #$81,obj_control(a1)



    move.w #Tails_Tails_Cutscene,(Tails_Tails_ptr).w ; Tails' tails at $FFFFB080



    rts





    Next, go to "Obj02_Init_Continued:", and change the last few lines. Change this:



    move.b #5,(Tails_Tails).w ; load Obj05 (Tails' Tails) at $FFFFD000
    move.w a0,(Tails_Tails+parent).w ; set its parent object to this



    SVN users, yours will say:



    move.b #ObjID_TailsTails,(Tails_Tails+id).w ; load Obj05 (Tails' Tails) at $FFFFD000
    move.w a0,(Tails_Tails+parent).w ; set its parent object to this



    And change it to this:



    movea.w (Tails_Tails_ptr).w,a1
    move.b #5,0(a1) ; load Obj05 (Tails' Tails) at $FFFFD000


    move.w a0,parent(a1) ; set its parent object to this



    And change it to this:



    movea.w (Tails_Tails_ptr).w,a1
    move.b #ObjID_TailsTails,id(a1) ; load Obj05 (Tails' Tails)


    move.w a0,parent(a1) ; set its parent object to this



    One more step! Go to "Obj02:" See the command jmp Obj02_States(pc,d1.w) (jmp Obj02_Index(pc,d1.w))? Change the "jmp" to a "jsr".


    And directly underneath it, add this:



    movea.w (Tails_Tails_ptr).w,a1
    tst.b routine(a1)


    beq.s +


    jmp (DisplaySprite2).l


    +


    rts



    So you have something like this (SVN users, reference only):



    Obj02:
    ; a0=character


    cmpi.w #2,(Player_mode).w


    bne.s +


    move.w (Camera_Min_X_pos).w,(Tails_Min_X_pos).w


    move.w (Camera_Max_X_pos).w,(Tails_Max_X_pos).w


    move.w (Camera_Max_Y_pos_now).w,(Tails_Max_Y_pos).w


    +


    moveq #0,d0


    move.b routine(a0),d0


    move.w Obj02_States(pc,d0.w),d1


    jsr Obj02_States(pc,d1.w)


    movea.w (Tails_Tails_ptr).w,a1


    tst.b routine(a1)


    beq.s +


    jmp (DisplaySprite2).l


    +


    rts



    Done! Tails' tails should now work with the right priority! Tails will now queue his tails for drawing right after himself. Without this, the tails will be queued for drawing after all objects between Tails and the tails that have the same priority. And that's why before, Tails' tails had less priority.


    [​IMG]


    I'll update my first post containing the guide with this information. Any bugs/troubles, reply here.


    Cheers,


    redhotsonic
     
  8. redhotsonic

    redhotsonic Also known as RHS Retired Staff

    Joined:
    Aug 10, 2007
    Messages:
    2,967
    Location:
    England
    Hey, guys!


    There seems to be a bug with following this guide with the SVN disassembly. If you're a XenoWhirl's user, you're safe and can ignore this post and go on with your day. SVN user? Read on.


    Seems that if you follow this guide as an SVN user, there may be a glitch with the Special Stages. Sonic and Tails act like they're being hit by the bombs object over and over, making it impossible to get any emeralds in the special stages. The reason? inertia is being used for something else in the special stages. This doesn't seem to be the case in Xenowhirl's dis.


    So, in your SST table, you're better off switching inertia with something else. invulnerable_time seems to be the best to swap it with. So change them so you end up with this:



    invulnerable_time = $20 ; and $21 ; time remaining until you stop blinking



    Code:
    inertia = $30 ; and $31 ; directionless representation of speed... not updated in the air
    

    Any more problems, with either disassembly, give me a shout.


    My SST guide has been updated as well to provide this bit of information
     
    Last edited by a moderator: Sep 20, 2012
Thread Status:
Not open for further replies.