The Sonic 3 shield porting guide to Sonic 1

Discussion in 'Tutorials' started by DeltaWooloo, Nov 14, 2021.

  1. DeltaWooloo

    DeltaWooloo *waving hello and goodbye* Member

    Joined:
    Aug 7, 2019
    Messages:
    338
    Alright, lads and lassies, I wanted to do this for quite a while so here we are. For those who’ve seen videos of my hack on Discord, Twitter or YouTube, you may have noticed that I’ve included the insta-shield and elemental shield in my hack. Since you like those features, I figured I should demonstrate how to port them to Sonic 1. This post will show you how to port the Insta-Shield to Sonic 1. Later at some point, I’ll be sending my elemental shields guide port. If I forget, gimmie a shout. =P

    So if you want to get a bit of context as to what the Insta-Shield is and how it works in Sonic 3 and Knuckles, check out this link.

    This guide is meant for the 2005 Hivebrain disassembly, and I will reference the code in the tutorial. Original S3K code will be from GitHub’s disassembly; please download it as we will need some of its assets later throughout the tutorial. However, it should work if you want to add it into any other disassembly, such as GitHub or Sonic 2. Just be mindful of the code and label changes, especially with RAM and constants.

    Also, I don’t want to see an argument that the insta-shield will be “overused” in Sonic 1 hacks. This is an absurd view, in my opinion, as this tutorial teaches people how to port code from one disassembly to another and add new features. Also, if you are adding it, be sure to know if it’s worth porting over to your hack. I know that sounds vague, but I’d rather see the insta-shield have interesting purposes than just a gimmick.

    The tutorial still works, although I haven’t managed to get around to commentating on a few things. That will be done very soon. And also, this isn’t a copy and paste guide, and this guide teaches people how to differentiate between different disassemblies and add new moves to their hacks. Also, since this is a crosspost, I apologize about the gaps seen in code tags. That will be fixed when I have the time to do so.

    Also, be sure to install QueueDMATransfer by checking out here, and this has to be installed too. Click me. However, the uncompressed art links are dead so I uncompressed the art myself and linked it here.

    Before we add the code in, let’s check how Sonic 3 and Knuckles manages the object:


    Code:
    Obj_Insta_Shield:
    
            ; Init
    
            move.l    #Map_InstaShield,mappings(a0)
    
            move.l    #DPLC_InstaShield,DPLC_Address(a0)            ; Used by PLCLoad_Shields
    
            move.l    #ArtUnc_InstaShield,Art_Address(a0)            ; Used by PLCLoad_Shields
    
            move.b    #4,render_flags(a0)
    
            move.w    #$80,priority(a0)
    
            move.b    #$18,width_pixels(a0)
    
            move.b    #$18,height_pixels(a0)
    
            move.w    #ArtTile_Shield,art_tile(a0)
    
            move.w    #tiles_to_bytes(ArtTile_Shield),vram_art(a0)    ; Used by PLCLoad_Shields
    
            btst    #7,(Player_1+art_tile).w
    
            beq.s    .nothighpriority
    
            bset    #7,art_tile(a0)
    
    
    
        .nothighpriority:
    
            move.w    #1,anim(a0)            ; Clear anim and set prev_anim to 1
    
            move.b    #-1,LastLoadedDPLC(a0)        ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
    
            move.l    #Obj_Insta_Shield_Main,(a0)
    
    
    
    Obj_Insta_Shield_Main:
    
            movea.w    parent(a0),a2
    
            btst    #Status_Invincible,status_secondary(a2) ; Is the player invincible?
    
            bne.s    locret_195A4            ; If so, return
    
            move.w    x_pos(a2),x_pos(a0)        ; Inherit player's x_pos
    
            move.w    y_pos(a2),y_pos(a0)        ; Inherit player's y_pos
    
            move.b    status(a2),status(a0)        ; Inherit status
    
            andi.b    #1,status(a0)            ; Limit inheritance to 'orientation' bit
    
            tst.b    (Reverse_gravity_flag).w
    
            beq.s    .normalgravity
    
            ori.b    #2,status(a0)            ; Reverse the vertical mirror render_flag bit (On if Off beforehand and vice versa)
    
    
    
        .normalgravity:
    
            andi.w    #drawing_mask,art_tile(a0)
    
            tst.w    art_tile(a2)
    
            bpl.s    .nothighpriority
    
            ori.w    #high_priority,art_tile(a0)
    
    
    
        .nothighpriority:
    
            lea    (Ani_InstaShield).l,a1
    
            jsr    (Animate_Sprite).l
    
            cmpi.b    #7,mapping_frame(a0)        ; Has it reached then end of its animation?
    
            bne.s    .notover            ; If not, branch
    
            tst.b    double_jump_flag(a2)        ; Is it in its attacking state?
    
            beq.s    .notover            ; If not, branch
    
            move.b    #2,double_jump_flag(a2)        ; Mark attack as over
    
    
    
        .notover:
    
            tst.b    mapping_frame(a0)        ; Is this the first frame?
    
            beq.s    .loadnewDPLC            ; If so, branch and load the DPLC for this and the next few frames
    
            cmpi.b    #3,mapping_frame(a0)        ; Is this the third frame?
    
            bne.s    .skipDPLC            ; If not, branch as we don't need to load another DPLC yet
    
    
    
        .loadnewDPLC:
    
            bsr.w    PLCLoad_Shields
    
    
    
        .skipDPLC:
    
            jmp    (Draw_Sprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    
    locret_195A4:
    
            rts
    
    ; ---------------------------------------------------------------------------
    



    This code loads the insta shield object and makes sure it swaps checks for reverse gravity or sets different reviews when loading different frames. To get it working under Sonic 1, we need to make a few changes. Firstly, we need to remove checks that sets the gravity. Find this bit of code:

    Code:
    move.b    status(a2),status(a0)        ; Inherit status
    
            andi.b    #1,status(a0)            ; Limit inheritance to 'orientation' bit
    
            tst.b    (Reverse_gravity_flag).w
    
            beq.s    .normalgravity
    
            ori.b    #2,status(a0)            ; Reverse the vertical mirror render_flag bit (On if Off beforehand and vice versa)
    
    
        .normalgravity:

    And just remove it as we wouldn’t need it in Sonic 1.


    Now we need to compare the SSTs and RAM bytes between Sonic 3K and Sonic 1. You need to open up this and this. You need to open up sonic3k.constants.asm in the S3K disassembly during this process. Let’s do a quick example:


    Code:
    move.l    #Map_InstaShield,mappings(a0)

    We need to focus on this bit of the line: “mappings” as that is an SST we need to change. In s3k.constants.asm, it says the SST is $C. If we check here, but in Sonic 1, it’s $4, so replace mappings with $4. Do you not get the concept? Let me show you the line below what I’ve shown:


    Code:
    move.l    #DPLC_InstaShield,DPLC_Address(a0)

    We need to look at this bit “DPLC_Address” as that’s an SST byte. Copy and paste and search the file in s3k.constants.asm. The byte is $3C, and in Sonic 1, it’s doesn’t have a byte so just change DPLC_Address to $3C. It will still work as $3C is a free byte in Sonic 1. Now, if you get the gist of it, try using the two links I sent above to compare the SST byte. If there isn’t a matching SST byte


    This will take time to do, but once you do a few on your own, you should get it.


    Find this line:


    Code:
    jmp    (Draw_Sprite).l
    Draw_Sprite is an equivalent of DisplaySprite so replace that with:


    Code:
    jmp    (DisplaySprite).l

    We need to fix the priorities too to make it support Sonic 1, on so replace:


    Code:
    move.w    #$80,$18(a0)

    With


    Code:
    move.w    #$1,$18(a0)

    And find:


    Code:
    move.b    #$18,$16(a0)

    This sets the height for the object, which Sonic 1 doesn’t need to do, so remove or comment it out.


    If you did everything right, your result should be something like this:


    Code:
    ; --------------------------------------------------
    
    ; InstaShieldObj: Insta-Shield
    
    ; --------------------------------------------------
    
    
    InstaShieldObj:                    ; XREF: Obj_Index
    
            moveq    #0,d0
    
            move.b    $24(a0),d0
    
            move.w    InstaShieldObj_Index(pc,d0.w),d1
    
            jmp    InstaShieldObj_Index(pc,d1.w)
    
    ; ===========================================================================
    
    InstaShieldObj_Index:
    
            dc.w InstaShieldObj_Main-InstaShieldObj_Index
    
            dc.w loc3_1952A-InstaShieldObj_Index
    
    ; ===========================================================================
    
    InstaShieldObj_Main:
    
            tst.b   ($FFFFFE2D).w
    
            bne.w    locret3_195A4
    
            move.l    #Map_InstaShield,$4(a0)        ;Mappings
    
            move.l    #DPLC_InstaShield,$3C(a0)        ;Dynamic Pattern Load Cues
    
            move.l  #ArtUnc_InstaShield,$38(a0)
    
            move.b    #$14,1(a0)
    
            move.b    #$1,$18(a0)
    
            move.b    #$18,$16(a0)
    
            move.w     #$A820,$36(a0)
    
            move.w    #$541,2(a0)
    
    
    loc3_19518:                ; CODE XREF: ROM:00019510j
    
            move.w    #1,$1C(a0)
    
            move.b    #-1,$30(a0)
    
            addq.b    #2,$24(a0)
    
    
    loc3_1952A:                ; DATA XREF: ROM:00019524o
    
            lea     ($FFFFD000).w,a2
    
            tst.b    ($FFFFFE2D).w
    
            bne.s    locret3_195A4
    
            move.w    $8(a2),$8(a0)
    
            move.w    $C(a2),$C(a0)
    
    
    
    ;loc3_1955A:                ; CODE XREF: ROM:00019552j
    
            andi.w    #$7FFF,art_tile(a0)
    
            tst.w    $A(a2)
    
            bpl.s    loc3_1956C
    
            ori.w    #$8000,2(a0)
    
    
    loc3_1956C:                ; CODE XREF: ROM:00019564j
            lea    (Ani_InstaShield).l,a1        ; load animation script address to a1
            jsr    AnimateSprite
            cmpi.b    #7,$1A(a0)
            bne.s    loc3_1958C
            tst.b    $2F(a2)
            beq.s    loc3_1958C
            move.b    #2,$2F(a2)
    
    
    loc3_1958C:                ; CODE XREF: ROM:0001957Ej
    
                        ; ROM:00019584j
    
            tst.b    $1A(a0)
    
            beq.s    loc3_1959A
    
            cmpi.b    #3,$1A(a0)
    
            bne.s    loc3_1959E
    
    
    loc3_1959A:                ; CODE XREF: ROM:00019590j
            bsr.w    PLCLoad_Shields
    
    
    loc3_1959E:                ; CODE XREF: ROM:00019598j
            jmp    DisplaySprite
    
    ; ???????????????????????????????????????????????????????????????????????????
    
    
    locret3_195A4:                ; CODE XREF: ROM:00019534j
            rts


    Next, we need to set up PLCLoad_Shields this is where it loads the DPLCs for the insta-shield art. Here is the original code:


    Code:
    PLCLoad_Shields:
    
            moveq    #0,d0
    
            move.b    mapping_frame(a0),d0
    
            cmp.b    LastLoadedDPLC(a0),d0
    
            beq.s    locret_199E8
    
            move.b    d0,LastLoadedDPLC(a0)
    
            movea.l    DPLC_Address(a0),a2
    
            add.w    d0,d0
    
            adda.w    (a2,d0.w),a2
    
            move.w    (a2)+,d5
    
            subq.w    #1,d5
    
            bmi.s    locret_199E8
    
            move.w    vram_art(a0),d4
    
    
    PLCLoad_Shields_ReadEntry:
    
            moveq    #0,d1
    
            move.w    (a2)+,d1
    
            move.w    d1,d3
    
            lsr.w    #8,d3
    
            andi.w    #$F0,d3
    
            addi.w    #$10,d3
    
            andi.w    #$FFF,d1
    
            lsl.l    #5,d1
    
            add.l    Art_Address(a0),d1
    
            move.w    d4,d2
    
            add.w    d3,d4
    
            add.w    d3,d4
    
            jsr    (Add_To_DMA_Queue).l
    
            dbf    d5,PLCLoad_Shields_ReadEntry
    
    
    locret_199E8:
    
            rts
    
    ; End of function PLCLoad_Shields

    Like before, you need to modify the SST and change some labels such as Add_To_DMA_Queue to QueueDMATransfer. The result should be this:


    Code:
    PLCLoad_Shields:
    
            moveq    #0,d0
    
        move.b    $1A(a0),d0    ; load frame number
    
        cmp.b    $33(a0),d0
    
        beq.s    locret2_13C96
    
        move.b    d0,$33(a0)
    
        move.l  $3C(A0),a2
    
            add.w   D0,D0
    
        adda.w  (a2,D0),a2
    
        move.w  (a2)+,d5
    
        subq.w  #1,D5
    
        bmi.s    locret2_13C96
    
            move.w  $36(A0),D4
    
    
    loc_199BE:
    
            moveq    #0,d1
    
        move.b    (a2)+,d1
    
        lsl.w    #8,d1
    
        move.b    (a2)+,d1
    
        move.w    d1,d3
    
        lsr.w    #8,d3
    
        andi.w    #$F0,d3
    
        addi.w    #$10,d3
    
        andi.w    #$FFF,d1
    
        lsl.l    #5,d1
    
            add.l   $38(a0),d1
    
            move.w  D4,D2
    
        add.w   D3,D4
    
        add.w   D3,D4
    
            jsr     (QueueDMATransfer)
    
        dbf     d5,loc_199BE    ; repeat for number of entries
    
    
    locret2_13C96:
    
        rts
    
    ; End of function PLCLoad_Shields

    Now we need to load the object in your hack, so open up Object pointers.asm, and we need to find an accessible object ID. Open it up, and you’ll see a bunch of lines with “ObjectFall” in them. These are dummy object files, so you can replace one of them with the object name you wrote. Just make sure you know what object ID you selected. For example, if you replace the first ObjectFall after Obj01, it will be object ID 02.


    Now comes loading in the object within Sonic’s object. You would want to go to Obj01_Main, and at the end of the subroutine, add this:



    Code:
    move.b    #XX,$FFFFD180.w    ;  load the insta shield    object

    XX - that will be the object ID you set in the object pointers file.


    Up next is to modify Sonic_JumpHeight. The reason is due to the way Sonic 3 handles double jumping. Let me show you a snippet of code:


    Code:
    loc_118D2:
    
            cmp.w    y_vel(a0),d1        ; is y speed greater than 4? (2 if underwater)
    
            ble.w    Sonic_InstaAndShieldMoves        ; if not, branch
    
            move.b    (Ctrl_1_logical).w,d0
    
            andi.b    #$70,d0            ; are buttons A, B or C being pressed?
    
            bne.s    locret_118E8        ; if yes, branch
    
            move.w    d1,y_vel(a0)        ; cap jump height
    
    
    locret_118E8:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Sonic_UpVelCap:
    
            tst.b    spin_dash_flag(a0)    ; is Sonic charging his spin dash?
    
            bne.s    locret_118FE        ; if yes, branch
    
            cmpi.w    #-$FC0,y_vel(a0)    ; is Sonic's Y speed faster (less than) than -15.75 (-$FC0)?
    
            bge.s    locret_118FE        ; if not, branch
    
            move.w    #-$FC0,y_vel(a0)    ; cap upward speed
    
    
    locret_118FE:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Sonic_InstaAndShieldMoves:
    
            tst.b    double_jump_flag(a0)        ; is Sonic currently performing a double jump?
    
            bne.w    locret_11A14            ; if yes, branch
    
            move.b    (Ctrl_1_pressed_logical).w,d0
    
            andi.b    #$70,d0                ; are buttons A, B, or C being pressed?
    
            beq.w    locret_11A14            ; if not, branch
    
            bclr    #Status_RollJump,status(a0)
    
            tst.b    (Super_Sonic_Knux_flag).w    ; check Super-state
    
            beq.s    Sonic_FireShield        ; if not in a super-state, branch
    
            bmi.w    Sonic_HyperDash            ; if Hyper, branch
    
            move.b    #1,double_jump_flag(a0)
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Sonic_FireShield:
    
            btst    #Status_Invincible,status_secondary(a0)    ; first, does Sonic have invincibility?
    
            bne.w    locret_11A14                ; if yes, branch
    
            btst    #Status_FireShield,status_secondary(a0)    ; does Sonic have a Fire Shield?
    
            beq.s    Sonic_LightningShield            ; if not, branch
    
            move.b    #1,(Shield+anim).w
    
            move.b    #1,double_jump_flag(a0)
    
            move.w    #$800,d0
    
            btst    #Status_Facing,status(a0)        ; is Sonic facing left?
    
            beq.s    loc_11958                ; if not, branch
    
            neg.w    d0                    ; reverse speed value, moving Sonic left
    
    
    loc_11958:
    
            move.w    d0,x_vel(a0)        ; apply velocity...
    
            move.w    d0,ground_vel(a0)    ; ...both ground and air
    
            move.w    #0,y_vel(a0)        ; kill y-velocity
    
            move.w    #$2000,(H_scroll_frame_offset).w
    
            bsr.w    Reset_Player_Position_Array
    
            move.w    #sfx_FireAttack,d0
    
            jmp    (Play_Sound_2).l
    
    ; ---------------------------------------------------------------------------


    To summarise, the code checks for the double jump flag before setting it to 1 for the fire shield to do its action. It also prevents the move from happening when your Super which is a useful move to do. In order to make the move work, we need to install Sonic_InstaAndShieldMoves. Go to loc_134AE and replace


    Code:
    ble.s    locret_134C2

    With


    Code:
    ble.s    Sonic_InstaAndShieldMoves:

    Under locret_134D2, place this:


    Code:
    Sonic_InstaAndShieldMoves:
    
            tst.b    $2F(a0)
    
            bne.w    locret_134D2
    
            move.b    ($FFFFF603).w,d0
    
            andi.b    #$70,d0
    
            beq.w    locret_134D2
    
            bclr    #4,$2A(a0)
    
            bne.w    Sonic_InstaShield
    
            move.b    #1,$2F(a0)   
    
            rts
    
    
    Sonic_InstaShield:
    
            tst.b    ($FFFFFE2D).w
    
            bne.s    Shieldrts
    
            tst.b    ($FFFFFE2C).w
    
            bne.s    Shieldrts
    
            move.b    #1,($FFFFD180+$1C).w
    
            move.w   #$B8,d0
    
            jsr    (PlaySound_Special).l     
    
    
    Shieldrts:
    In my hack, I set $B8 as the sound effect, but you can change it to any sound ID you desire. $AB sounds close enough, so use that if you like. Also, if you’ve been following the code, I set double_jump_flag to be $2F since there is no equivalent in Sonic 1 and a free SST. Now we’ve come to the last two steps of modifying the code: Making sure the insta shield works when we’ve jumped after landing and expanding the touch’s size when the insta-shield is performed. Let’s go with the easiest step.



    We want to make sure the double jump flag is reset to 0 when Sonic lands on the floor; otherwise, he wouldn’t perform the insta-shield move after he jumps again. Let’s go over to Sonic_ResetOnFloor. Under the label “loc_137E4”, insert this:


    Code:
    move.b    #0,$2F(a0)

    This will reset the double jump flag the next time Sonic hits the ground.


    For the last bit, modify TouchResponse so Sonic’s hitbox largens when he makes the insta-shield attack. Go to TouchResponse, and under the label, insert this:


    Code:
    nop
    
            tst.b    ($FFFFFE2C).w    ; Does Sonic Have Shield?
    
            bne.s    Touch_NoInstaShield    ; If so, branch
    
            tst.b    ($FFFFFE2D).w    ; Does Sonic Is invincible?
    
            bne.s    Touch_NoInstaShield    ; If so,branch
    
            ; By this point, we're focussing purely on the Insta-Shield
    
            cmpi.b    #1,double_jump_flag(a0)            ; Is the Insta-Shield currently in its 'attacking' mode?
    
            bne.s    Touch_NoInstaShield            ; If not, branch
    
            move.b     #1,($FFFFFE2D).w ; make Sonic invincible
    
            move.w    x_pos(a0),d2                ; Get player's x_pos
    
            move.w    y_pos(a0),d3                ; Get player's y_pos
    
            subi.w    #$18,d2                    ; Subtract width of Insta-Shield
    
            subi.w    #$18,d3                    ; Subtract height of Insta-Shield
    
            move.w    #$30,d4                    ; Player's width
    
            move.w    #$30,d5                    ; Player's height
    
            bsr.s    Touch_Process
    
            clr.b    ($FFFFFE2D).w   
    
    
    Alreadyinvincible:
    
            moveq    #0,d0
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    ; Normal TouchResponse comes after this

    You may notice that we have a new label: Touch_Process. This allows the code to interact with the object RAM. To utilise this, just replace Touch_NoDuck with this:


    Code:
    Touch_NoDuck:
    
            move.w    #$10,d4
    
            add.w    d5,d5
    
    Touch_Process:
    
            lea    ($FFFFD800).w,a1 ; begin checking the object RAM
    
            move.w    #$5F,d6

    All right, the code side is done. Now it’s time to add the art in:


    First of all, make sure you got your SK disassembly ready. The easiest thing to do is to go to:


    “skdisasm-master\General\Sprites\Shields” and look for DPLC - Insta-Shield.asm. Since PLCLoad_Shield retains using the same S3K format when it comes to DPLC mappings, we don’t need to change anything, so copy that file and paste it in the disassembly in your hack. As for the mappings, we need to convert to format to Sonic 1. You can use one of the three tools to do this:


    One tool is called SonMapEd. Open it up and set the Game Format setting to "Sonic 3 & Knuckles", load the mappings file, change the Game Format setting to "Sonic 1", and save the mappings as “Map - Insta-Shield.asm” in the “_maps” folder of your disassembly.


    Another tool is MappingsConverter. Open it up and load the mappings with input set to Game: "Sonic 3 & Knuckles", Format: ASM and output set to Game: "Sonic 1", Format: ASM, and save the mappings as “Map - Insta-Shield.asm” in the “_maps” folder of your disassembly.


    Another tool is Flex2. Open it up and insert the mappings and make sure the format is set as S3K. Load it, convert it to Sonic 1 and save it. The tool should overwrite the original mapping, and you should go back to the shields folder of the SK disassembly, copy the mappings and place it in “_maps” folder of your disassembly.


    As for the art, you’ll need to load PaletteConverter as the player palettes between Sonic 3K and 1 are vastly different. Open it up and you will see this:


    [​IMG]


    We are going to do this according to the numbers I added.


    1. First of all, click on load BIN and open up Sonic’s S3K palette file. It should be under:

    “skdisasm-master\General\Sprites\Sonic\Palettes\SonicandTails.bin”


    2. Secondly, open up Sonic’s S1 palette file. It should be under:


    “yourdisassembly\pallet\Sonic.bin”


    You need to match the palette bytes by dragging Sonic 3K’s byte to match S1’s; and the outcome should be this:


    [​IMG]3. Open up convert uncompressed file and open up the Insta-Shield art; It should be located at:

    Code:
    “skdisasm-master\General\Sprites\Shields\Insta-Shield.bin”

    It will give you a pop-up asking if you want to overwrite the source with the converted artwork. Say no and save the file in your disassembly in artunc and name it: “Insta-Shield.bin”


    I’ve linked a .rar file with everything converted here if you struggled to get that set up. Then, just drag and drop the files to its respective folder.


    Open up sonic1.asm, and under the insta-shield object, insert this:


    Code:
    ; --------------------------------------------------------------
    
    ; Insta-Shield art and mappings
    
    ; --------------------------------------------------------------
    
    Map_InstaShield:
    
        include "_maps\Map - Insta-Shield.asm"    ; Insta-Shield mappings
    
    DPLC_InstaShield:
    
        include "_inc\DPLC - Insta-Shield.asm"    ; Insta-Shield DPLCs
    
    Ani_InstaShield:
    
            dc.w byte_199EE3-Ani_InstaShield
    
            dc.w byte_199F13-Ani_InstaShield
    
    byte_199EE3:    dc.b  $1F,   6,    $FF
    
    byte_199F13:    dc.b    0,   0,      1,   2,   3,     4,   5,   6,    6,   6,      6,   6,   6,     6,   7, $FD,    0
    
        even
    
    ArtUnc_InstaShield:        incbin    "artunc\Insta-Shield.bin"
    
            even                            ; Insta-Shield uncompressed art

    You need to make sure the object loads if you managed to obtain a shield and remove it. Go to the shield object and find the line that clears the shield RAM and place this:


    Code:
    move.b    #$XX,(a0)        ;Revert shield sprite to Insta-Shield
    
            move.b    #$0,$24(a0)    ;Reset the routine counter to trigger Insta-Shield Init


    Again, XX should be the object ID you set for the insta-shield. Build your disassembly, and it should run well. Next time, I’ll get the elemental shields to guide up and running alongside making references to GitHub’s disassembly. If you have any questions or if it works, feel free to comment down below. =D

    Also, can you please credit me if you used this guide as it took me quite a while to write this and set up with a bit of guidance?


    Until then~
     
    Last edited: Nov 14, 2021
  2. DeltaWooloo

    DeltaWooloo *waving hello and goodbye* Member

    Joined:
    Aug 7, 2019
    Messages:
    338
    OK, my lads. Now comes the meat, and we’re getting that up and running. How to port the elemental shields to Sonic 1. Now I'm aware Clownacy did this last year but some people like to learn through text than watching videos.


    So I won’t repeat myself; read the disclaimer in the first post before adding it in. Then, if you’ve done so, keep on reading. One last thing to say is rather than me referencing GitHub labels, and I will showcase how to do it if you’re considering porting the elemental shields to GitHub disassembly. (for Hivebrain 2005 users, you may want to read this too but be prepared to replace variables with RAM and constants with SSTs.)


    As I said with the insta-shield, we would need to read the code in S3K. So let’s start with one of my favourite shields: the lightning shield.


    Code:
    Obj_Lightning_Shield:
    
            ; init
    
            ; Load Spark art
    
            move.l    #ArtUnc_Obj_Lightning_Shield_Sparks,d1            ; Load art source
    
            move.w    #tiles_to_bytes(ArtTile_Shield_Sparks),d2        ; Load art destination
    
            move.w    #(ArtUnc_Obj_Lightning_Shield_Sparks_end-ArtUnc_Obj_Lightning_Shield_Sparks)/2,d3    ; Size of art (in words)
    
            jsr    (Add_To_DMA_Queue).l
    
    
            move.l    #Map_LightningShield,mappings(a0)
    
            move.l    #DPLC_LightningShield,DPLC_Address(a0)            ; Used by PLCLoad_Shields
    
            move.l    #ArtUnc_LightningShield,Art_Address(a0)            ; Used by PLCLoad_Shields
    
            move.b    #4,render_flags(a0)
    
            move.w    #$80,priority(a0)
    
            move.b    #$18,width_pixels(a0)
    
            move.b    #$18,height_pixels(a0)
    
            move.w    #ArtTile_Shield,art_tile(a0)
    
            move.w    #tiles_to_bytes(ArtTile_Shield),vram_art(a0)    ; Used by PLCLoad_Shields
    
            btst    #7,(Player_1+art_tile).w
    
            beq.s    .nothighpriority
    
            bset    #7,art_tile(a0)
    
    
        .nothighpriority:
    
            move.w    #1,anim(a0)                ; Clear anim and set prev_anim to 1
    
            move.b    #-1,LastLoadedDPLC(a0)            ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
    
            move.l    #Obj_Lightning_Shield_Main,(a0)
    
    
    Obj_Lightning_Shield_Main:
    
            movea.w    parent(a0),a2
    
            btst    #Status_Invincible,status_secondary(a2)    ; Is player invincible?
    
            bne.w    locret_197C4                ; If so, do not display and do not update variables
    
            cmpi.b    #$1C,anim(a2)                ; Is player in their 'blank' animation?
    
            beq.s    locret_197C4                ; If so, do not display and do not update variables
    
            btst    #Status_Shield,status_secondary(a2)    ; Should the player still have a shield?
    
            beq.s    Obj_Lightning_Shield_Destroy        ; If not, change to Insta-Shield
    
            btst    #Status_Underwater,status(a2)        ; Is player underwater?
    
            bne.s    Obj_Lightning_Shield_DestroyUnderwater    ; If so, branch
    
            move.w    x_pos(a2),x_pos(a0)
    
            move.w    y_pos(a2),y_pos(a0)
    
            move.b    status(a2),status(a0)            ; Inherit status
    
            andi.b    #1,status(a0)                ; Limit inheritance to 'orientation' bit
    
            tst.b    (Reverse_gravity_flag).w
    
            beq.s    .normalgravity
    
            ori.b    #2,status(a0)                ; If in reverse gravity, reverse the vertical mirror render_flag bit (On if Off beforehand and vice versa)
    
    
        .normalgravity:
    
            andi.w    #drawing_mask,art_tile(a0)
    
            tst.w    art_tile(a2)
    
            bpl.s    .nothighpriority
    
            ori.w    #high_priority,art_tile(a0)
    
    
        .nothighpriority:
    
            tst.b    anim(a0)                ; Is shield in its 'double jump' state?
    
            beq.s    Obj_Lightning_Shield_Display        ; Is not, branch and display
    
            bsr.s    Obj_Lightning_Shield_Create_Spark    ; Create sparks
    
            clr.b    anim(a0)                ; Once done, return to non-'double jump' state
    
    
    Obj_Lightning_Shield_Display:
    
            lea    (Ani_199EA).l,a1
    
            jsr    (Animate_Sprite).l
    
            move.w    #$80,priority(a0)            ; Layer shield over player sprite
    
            cmpi.b    #$E,mapping_frame(a0)            ; Are these the frames that display in front of the player?
    
            blo.s    .overplayer                ; If so, branch
    
            move.w    #$200,priority(a0)            ; If not, layer shield behind player sprite
    
    
        .overplayer:
    
            bsr.w    PLCLoad_Shields
    
            jmp    (Draw_Sprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    locret_197C4:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_DestroyUnderwater:
    
            tst.w    (Palette_fade_timer).w
    
            beq.s    Obj_Lightning_Shield_FlashWater
    
    
    Obj_Lightning_Shield_Destroy:
    
            andi.b    #$8E,status_secondary(a2)    ; Sets Status_Shield, Status_FireShield, Status_LtngShield, and Status_BublShield to 0
    
            move.l    #Obj_Insta_Shield,(a0)        ; Replace the Lightning Shield with the Insta-Shield
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_FlashWater:
    
            move.l    #Obj_Lightning_Shield_DestroyUnderwater2,(a0)
    
            andi.b    #$8E,status_secondary(a2)    ; Sets Status_Shield, Status_FireShield, Status_LtngShield, and Status_BublShield to 0
    
    
            ; Flashes the underwater palette white
    
            lea    (Water_palette).w,a1
    
            lea    (Target_water_palette).w,a2
    
            move.w    #($80/4)-1,d0            ; Size of Water_palette/4-1
    
    
    loc_197F2:
    
            move.l    (a1),(a2)+            ; Backup palette entries
    
            move.l    #$0EEE0EEE,(a1)+        ; Overwrite palette entries with white
    
            dbf    d0,loc_197F2            ; Loop until entire thing is overwritten
    
    
            move.w    #0,-$40(a1)            ; Set the first colour in the third palette line to black
    
            move.b    #3,anim_frame_timer(a0)
    
            rts
    
    
    ; =============== S U B R O U T I N E =======================================
    
    
    
    Obj_Lightning_Shield_Create_Spark:
    
            moveq    #1,d2
    
    
    Obj_Lightning_Shield_Create_Spark_Part2:
    
            lea    (SparkVelocities).l,a2
    
            moveq    #3,d1
    
    
    loc_19816:
    
            bsr.w    Create_New_Sprite        ; Find free object slot
    
            bne.s    locret_19862            ; If one can't be found, return
    
            move.l    #Obj_Lightning_Shield_Spark,(a1)    ; Make new object a Spark
    
            move.w    x_pos(a0),x_pos(a1)        ; (Spark) Inherit x_pos from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.w    y_pos(a0),y_pos(a1)        ; (Spark) Inherit y_pos from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.l    mappings(a0),mappings(a1)    ; (Spark) Inherit mappings from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.w    art_tile(a0),art_tile(a1)    ; (Spark) Inherit art_tile from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.b    #4,render_flags(a1)
    
            move.w    #$80,priority(a1)
    
            move.b    #8,width_pixels(a1)
    
            move.b    #8,height_pixels(a1)
    
            move.b    d2,anim(a1)
    
            move.w    (a2)+,x_vel(a1)            ; (Spark) Give x_vel (unique to each of the four Sparks)
    
            move.w    (a2)+,y_vel(a1)            ; (Spark) Give y_vel (unique to each of the four Sparks)
    
            dbf    d1,loc_19816
    
    
    locret_19862:
    
            rts
    
    ; End of function Obj_Lightning_Shield_Create_Spark
    
    
    ; ---------------------------------------------------------------------------
    
    SparkVelocities:dc.w  -$200, -$200
    
            dc.w   $200, -$200
    
            dc.w  -$200,  $200
    
            dc.w   $200,  $200
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_Spark:
    
            jsr    (MoveSprite2).l
    
            addi.w    #$18,y_vel(a0)
    
            lea    (Ani_199EA).l,a1
    
            jsr    (Animate_Sprite).l
    
            tst.b    routine(a0)            ; Changed by Animate_Sprite
    
            bne.s    Obj_Lightning_Shield_Spark_Delete
    
            jmp    (Draw_Sprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_Spark_Delete:
    
            jmp    (Delete_Current_Sprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_DestroyUnderwater2:
    
            subq.b    #1,anim_frame_timer(a0)        ; Is it time to end the white flash?
    
            bpl.s    locret_198BC            ; If not, return
    
            move.l    #Obj_Insta_Shield,(a0)        ; Replace Lightning Shield with Insta-Shield
    
            lea    (Target_water_palette).w,a1
    
            lea    (Water_palette).w,a2
    
            move.w    #($80/4)-1,d0            ; Size of Water_palette/4-1
    
    
    loc_198B6:
    
            move.l    (a1)+,(a2)+            ; Restore backed-up underwater palette
    
            dbf    d0,loc_198B6            ; Loop until entire thing is restored
    
    
    locret_198BC:
    
            rts
    
    ; ---------------------------------------------------------------------------


    Firstly, let’s change a few things; if you ported the S3K priority manager, you don’t need to do this, but Sonic 1 doesn’t check the height for an object, so go to Obj_Lightning_Shield_Main and comment out/remove this line:


    Code:
    move.b    #$18,height_pixels(a0)

    Next go and find:


    Code:
    move.w    #$80,priority(a0)

    Change $80 to a 1 and change move.w to move.b


    Now we need to add a routine counter so below the label to the object, insert this:


    Code:
            moveq   #0,d0
    
            move.b  obRoutine(a0), D0
    
            move.w  LightningShield_Index(pc,d0.w),d1
    
            jmp     LightningShield_Index(pc,d1.w)
    
             
    
    ; ===========================================================================
    
    
    LightningShield_Index:
    
            dc.w LightningShield_Init-LightningShield_Index
    
            dc.w LightningShield_Main-LightningShield_Index
    
            dc.w Obj_Lightning_Shield_DestroyUnderwater2-LightningShield_Index
    
    ; ===========================================================================

    Now we need to replace this:


    Code:
    move.w    #ArtTile_Shield,art_tile(a0)
    
            move.w    #tiles_to_bytes(ArtTile_Shield),vram_art(a0)    ; Used by PLCLoad_Shields

    With this:


    Code:
    move.w    #$541,art_tile(a0)
    
            move.w    #$A820,vram_art(a0)    ; Used by PLCLoad_Shields

    This will make editing VRAM miles easier. We need to do this to the sparks, above the lines replaced, replace this:


    Code:
    move.l    #ArtUnc_Obj_Lightning_Shield_Sparks,d1            ; Load art source
    
            move.w    #tiles_to_bytes(ArtTile_Shield_Sparks),d2        ; Load art destination
    
            move.w    #(ArtUnc_Obj_Lightning_Shield_Sparks_end-ArtUnc_Obj_Lightning_Shield_Sparks)/2,d3    ; Size of art (in words)
    
            jsr    (Add_To_DMA_Queue).l

    With this:


    Code:
    move.l    #ArtUnc_LightningSparks,d1
    
            move.w    #$AC00,d2
    
            move.w    #$50,d3
    
            jsr     (QueueDMATransfer)


    The next step is to go to .nothighpriority, and in the last line, replace this:


    Code:
    move.l    #Obj_Lightning_Shield_Main,(a0)

    With this:


    Code:
    addq.b    #2,obRoutine(a0) ; => LightningShield_Main

    This will load the routine counter and load LightningShield_Main’s subroutine. If you’re struggling to find similar results, be sure to look at lines of code that you believe it’s a label and if it is a label, replace it with the code above and include it under your routine counter. The is one last line we need to replace to make usage of the routine. So go to Obj_Lightning_Shield_FlashWater and replace this:


    Code:
    move.l    #Obj_Lightning_Shield_DestroyUnderwater2,(a0)
    With this:


    Code:
    addq.b    #2,obRoutine(a0)

    Also, any labels that have a dot at the beginning, please replace it with an @ (unless you’re using a Sonic 1 disassembly with an AS assembler)


    Now, we need to make sure this is compatible with Sonic 1. For that to work, we would need to change the wording of the constants and modify the RAM for it to work. So, open up constants.asm in your disassembly and replace the constants in the object with what Sonic 1 has. The wording may be a bit different but try to find equivalent constants. If you can’t, don't worry. You can cross-reference what I have for my final result. Also, add this to constants.asm you can replace any equivalent SSTs with that as I’ll showcase these constants as an example:


    Code:
    shield_LastLoadedDPLC = $33
    
    shield_DPLC_Address = $3C
    
    shield_Art_Address = $38
    
    shield_vram_art = $36


    For Hivebrain users, you would need to follow a similar procedure by comparing RAM and SST bytes. If you don’t understand how it works, please check the first post to get a better idea of how to do it. Click here for Sonic 1 RAM Editing and here for Sonic 3K RAM editing. This will take time to do in Hivebrain but if you’re struggling to find equivalents, be sure to look at the list of constants in a GitHub disassembly. If you’ve done everything successfully, your final output should be something like this:



    Code:
    ; ==========================================================================
    
    ; ---------------------------------------------------------------------------
    
    ; Object XX and XX - lightning shield and sparks
    
    ; ---------------------------------------------------------------------------
    
    Obj38:                    ; XREF: Obj_Index
    
            moveq   #0,d0
    
            move.b  obRoutine(a0), D0
    
            move.w  LightningShield_Index(pc,d0.w),d1
    
            jmp     LightningShield_Index(pc,d1.w)
    
             
    
    ; ===========================================================================
    
    
    LightningShield_Index:
    
            dc.w LightningShield_Init-LightningShield_Index
    
            dc.w LightningShield_Main-LightningShield_Index
    
            dc.w Obj_Lightning_Shield_DestroyUnderwater2-LightningShield_Index
    
    ; ===========================================================================
    
    
    LightningShield_Init:
    
            move.l    #ArtUnc_LightningSparks,d1
    
            move.w    #$AC00,d2
    
            move.w    #$50,d3
    
            jsr     (QueueDMATransfer)
    
            move.l    #Map_LightningShield,obMap(a0)
    
            move.l    #DPLC_LightningShield,shield_DPLC_Address(a0)    ; Used by PLCLoad_Shields
    
            move.l    #ArtUnc_LightningShield,shield_Art_Address(a0)    ; Used by PLCLoad_Shields
    
            move.b    #4,obRender(a0)
    
            move.b    #1,obPriority(a0)
    
            move.b    #$18,obActWid(a0)
    
            move.w    #$541,obGfx(a0)
    
            move.w    #$A820,shield_vram_art(a0)    ; Used by PLCLoad_Shields
    
            btst    #7,(v_player+obGFX).w
    
            beq.s    loc_195F0L
    
            bset    #7,obGFX(a0)
    
    
    loc_195F0L:
    
            move.w    #1,obAnim(a0)    ; Clear anim and set prev_anim to 1
    
            move.b    #-1,shield_LastLoadedDPLC(a0)    ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
    
            addq.b    #2,obRoutine(a0) ; => LightningShield_Main
    
    
    LightningShield_Main:
    
            lea    (v_player).w,a2
    
            tst.b    (v_invinc).w
    
            bne.w    locret_197C4    ; If so, do not display and do not update variables
    
            cmpi.b    #$1C,obAnim(a2)    ; Is player in their 'blank' animation?
    
            beq.s    locret_197C4    ; If so, do not display and do not update variables
    
            tst.b    (v_shield).w     ; Should the player still have a shield?
    
            beq.s    Obj_Lightning_Shield_Destroy    ; If not, change to Insta-Shield
    
            btst    #6,status(a2)    ; Is player underwater?
    
            bne.s    Obj_Lightning_Shield_DestroyUnderwater    ; If so, branch
    
            move.w    obX(a2),obX(a0)
    
            move.w    obY(a2),obY(a0)
    
            andi.w    #$7FFF,obGFX(a0)
    
            tst.w    obGFX(a2)
    
            bpl.s    @nothighpriority2
    
            ori.w    #$8000,obGFX(a0)
    
    
        @nothighpriority2:
    
            tst.b    obAnim(a0)    ; Is shield in its 'double jump' state?
    
            beq.s    Obj_Lightning_Shield_Display    ; Is not, branch and display
    
            bsr.w    Obj_Lightning_Shield_Create_Spark    ; Create sparks
    
            clr.b    obAnim(a0)    ; Once done, return to non-'double jump' state
    
    
    Obj_Lightning_Shield_Display:
    
            lea    (Ani_19A2A).l,a1
    
            jsr    (AnimateSprite).l
    
            move.b    #1,obPriority(a0)    ; Layer shield over player sprite
    
            cmpi.b    #$E,obFrame(a0)    ; Are these the frames that display in front of the player?
    
            blo.s    @overplayer1    ; If so, branch
    
            move.b    #3,obPriority(a0)    ; If not, layer shield behind player sprite
    
    
        @overplayer1:
    
            bsr.w    PLCLoad_Shields
    
            jmp    (DisplaySprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    locret_197C4:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_DestroyUnderwater:
    
            bra.s    Obj_Lightning_Shield_FlashWater
    
    
    Obj_Lightning_Shield_Destroy:
    
    ;        move.b    #$XX,(a0)        ;Revert shield sprite to Insta-Shield (uncomment this if you installed the insta-shield) and change XX to the object ID you selected for the shields
    
        ;    move.b    #$0,$24(a0)    ;Reset the routine counter to trigger Insta-Shield Init 
    
            clr.b    (v_shield).w    ; remove shield
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_FlashWater:
    
            addq.b    #2,obRoutine(a0)
    
            clr.b    (v_shield).w    ; remove shield
    
    ;        move.b    #$XX,(a0)        ;Revert shield sprite to Insta-Shield (uncomment this if you installed the insta-shield) and change XX to the object ID you selected for the shields
    
        ;    move.b    #$0,$24(a0)    ;Reset the routine counter to trigger Insta-Shield Init         
    
    
    ;    Flashes the underwater palette white
    
            lea    ($FFFFFA80).w,a1
    
            lea    ($FFFFFB80).w,a2
    
            move.w    #($80/4)-1,d0    ; Size of Water_palette/4-1
    
    
    loc_197F2:
    
            move.l    (a1),(a2)+    ; Backup palette entries
    
            move.l    #$0EEE0EEE,(a1)+    ; Overwrite palette entries with white
    
            dbf    d0,loc_197F2    ; Loop until entire thing is overwritten
    
    
            move.b    #3,obTimeFrame(a0)
    
            rts
    
    
    ; =============== S U B R O U T I N E =======================================
    
    
    
    Obj_Lightning_Shield_Create_Spark:
    
            moveq    #1,d2
    
    
    Obj_Lightning_Shield_Create_Spark_Part2:
    
            lea    (SparkVelocities).l,a2
    
            moveq    #3,d1
    
    
    loc_19816:
    
    ; Sprite_1D8F2:
    
            jsr    (FindFreeObj).l    ; Set up for a new object (SingleObjLoad for Hivebrain users instead of FindFreeObj)
    
            bne.s    locret_19862
    
            move.b    #XX,(a1)        ; Create Lightning Shield Spark (READ ME: XX WILL BE THE ID OF THE SPARKS)
    
            move.w    obX(a0),obX(a1)    ; (Spark) Inherit x_pos from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.w    obY(a0),obY(a1)    ; (Spark) Inherit y_pos from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.l    obMap(a0),obMap(a1)    ; (Spark) Inherit mappings from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.w    obGfx(a0),obGfx(a1)    ; (Spark) Inherit art_tile from source object (Lightning Shield, Hyper Sonic Stars)
    
            move.b    #4,obRender(a1)
    
            move.b    #1,obPriority(a1)
    
            move.b    #8,obActWid(a1)
    
            move.b    d2,obAnim(a1)
    
            move.w    (a2)+,obVelX(a1)    ; (Spark) Give x_vel (unique to each of the four Sparks)
    
            move.w    (a2)+,obVelY(a1)    ; (Spark) Give y_vel (unique to each of the four Sparks)
    
            dbf    d1,loc_19816
    
    
    locret_19862:
    
            rts
    
    ; End of function Lightning_Shield_Create_Spark
    
    ; ---------------------------------------------------------------------------
    
    SparkVelocities:dc.w  -$200, -$200
    
        dc.w   $200, -$200
    
        dc.w  -$200,  $200
    
        dc.w   $200,  $200
    
    ; ---------------------------------------------------------------------------
    
    Obj_Lightning_Shield_Spark:
    
            jsr    (SpeedToPos).l
    
            addi.w    #$18,obVelY(a0)
    
            lea    (Ani_19A2A).l,a1
    
            jsr    (AnimateSprite).l
    
            tst.b    obRoutine(a0)    ; Changed by Animate_Sprite
    
            bne.s    Obj_Lightning_Shield_Spark_Delete
    
            jmp    (DisplaySprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_Spark_Delete:
    
            jmp    (DeleteObject).l
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Lightning_Shield_DestroyUnderwater2:
    
            subq.b    #1,obTimeFrame(a0)    ; Is it time to end the white flash?
    
            bpl.s    locret_198BC    ; If not, return
    
            clr.b    ($FFFFFE2C).w    ; remove shield
    
            lea    ($FFFFFB80).w,a1
    
            lea    ($FFFFFA80).w,a2
    
            move.w    #($80/4)-1,d0    ; Size of Water_palette/4-1
    
    
    loc_198B6:
    
            move.l    (a1)+,(a2)+    ; Restore backed-up underwater palette
    
            dbf    d0,loc_198B6    ; Loop until entire thing is restored
    
    
    locret_198BC:
    
            rts
    
    ; ===========================================================================


    If you see $XX, that means you need to replace them with the ID of the insta-shield. If you don’t have that installed, leave it as it is. Let’s do the bubble and fire shields. For this, go back to the beginning of the tutorial and make sure you included a routine counter and have followed the areas needed to make


    Code:
    Obj_Bubble_Shield:
    
            ; Init
    
            move.l    #Map_BubbleShield,mappings(a0)
    
            move.l    #DPLC_BubbleShield,DPLC_Address(a0)            ; Used by PLCLoad_Shields
    
            move.l    #ArtUnc_BubbleShield,Art_Address(a0)            ; Used by PLCLoad_Shields
    
            move.b    #4,render_flags(a0)
    
            move.w    #$80,priority(a0)
    
            move.b    #$18,width_pixels(a0)
    
            move.b    #$18,height_pixels(a0)
    
            move.w    #ArtTile_Shield,art_tile(a0)
    
            move.w    #tiles_to_bytes(ArtTile_Shield),vram_art(a0)    ; Used by PLCLoad_Shields
    
            btst    #7,(Player_1+art_tile).w
    
            beq.s    .nothighpriority
    
            bset    #7,art_tile(a0)
    
    
        .nothighpriority:
    
            move.w    #1,anim(a0)                ; Clear anim and set prev_anim to 1
    
            move.b    #-1,LastLoadedDPLC(a0)            ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
    
            movea.w    parent(a0),a1
    
            bsr.w    Player_ResetAirTimer
    
            move.l    #Obj_Bubble_Shield_Main,(a0)
    
    
    Obj_Bubble_Shield_Main:
    
            movea.w    parent(a0),a2
    
            btst    #Status_Invincible,status_secondary(a2)    ; Is player invincible?
    
            bne.s    locret_1998A                ; If so, do not display and do not update variables
    
            cmpi.b    #$1C,anim(a2)                ; Is player in their 'blank' animation?
    
            beq.s    locret_1998A                ; If so, do not display and do not update variables
    
            btst    #Status_Shield,status_secondary(a2)    ; Should the player still have a shield?
    
            beq.s    Obj_Bubble_Shield_Destroy        ; If not, change to Insta-Shield
    
            move.w    x_pos(a2),x_pos(a0)
    
            move.w    y_pos(a2),y_pos(a0)
    
            move.b    status(a2),status(a0)            ; Inherit status
    
            andi.b    #1,status(a0)                ; Limit inheritance to 'orientation' bit
    
            tst.b    (Reverse_gravity_flag).w
    
            beq.s    .normalgravity
    
            ori.b    #2,status(a0)                ; Reverse the vertical mirror render_flag bit (On if Off beforehand and vice versa)
    
    
        .normalgravity:
    
            andi.w    #drawing_mask,art_tile(a0)
    
            tst.w    art_tile(a2)
    
            bpl.s    .nothighpriority
    
            ori.w    #high_priority,art_tile(a0)
    
    
        .nothighpriority:
    
            lea    (Ani_BubbleShield).l,a1
    
            jsr    (Animate_Sprite).l
    
            bsr.w    PLCLoad_Shields
    
            jmp    (Draw_Sprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    locret_1998A:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Bubble_Shield_Destroy:
    
            andi.b    #$8E,status_secondary(a2)    ; Sets Status_Shield, Status_FireShield, Status_LtngShield, and Status_BublShield to 0
    
            move.l    #Obj_Insta_Shield,(a0)        ; Replace the Bubble Shield with the Insta-Shield
    
            rts



    If you followed the changes successfully, this should be the final result:


    Code:
    BubbleShield_Obj:
    
            moveq    #0,d0
    
            move.b    obRoutine(a0),d0
    
            move.w    BubbleShield_Index(pc,d0.w),d1
    
            jmp    BubbleShield_Index(pc,d1.w)
    
     
    
    ; ==========================================================================
    
    
    BubbleShield_Index:
    
            dc.w BubbleShield_Init-BubbleShield_Index
    
            dc.w BubbleShield_Main-BubbleShield_Index
    
     
    
    ; ==========================================================================
    
    BubbleShield_Init:
    
    ;    Init
    
            move.l    #Map_BubbleShield,obMap(a0)
    
            move.l    #DPLC_BubbleShield,shield_DPLC_Address(a0)    ; Used by PLCLoad_Shields
    
            move.l    #ArtUnc_BubbleShield,shield_Art_Address(a0)    ; Used by PLCLoad_Shields
    
            move.b    #4,obRender(a0)
    
            move.b    #1,obPriority(a0)
    
            move.b    #$18,obActWid(a0)
    
            move.w    #$541,obGfx(a0)
    
            move.w    #$A820,shield_vram_art(a0)    ; Used by PLCLoad_Shields
    
            btst    #7,(v_player+obGfx).w
    
            beq.s    loc_195F0B
    
            bset    #7,obGFX(a0)
    
    
    loc_195F0B:
    
            move.w    #1,obAnim(a0)    ; Clear anim and set prev_anim to 1
    
            move.b    #-1,shield_LastLoadedDPLC(a0)    ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
    
            lea    (v_player).w,a1
    
            jsr    (ResumeMusic).l
    
            addq.b    #2,obRoutine(a0) ; => ObjE5_Shield
    
    ; loc_1D92C:
    
    BubbleShield_Main:
    
            lea    (v_player).w,a2 ; a2=character
    
            tst.b    (v_invinc).w
    
            bne.s    locret_1998A
    
            cmpi.b    #$1C,obAnim(a2)    ; Is player in their 'blank' animation?
    
            beq.s    locret_1998A    ; If so, do not display and do not update variables
    
            tst.b    (v_shield).w
    
            beq.s    Obj_Bubble_Shield_Destroy    ; If not, change to Insta-Shield
    
            move.w    obX(a2),obX(a0)
    
            move.w    obY(a2),obY(a0)
    
            andi.w    #$7FFF,obGFX(a0)
    
            tst.w    obGFX(a2)
    
            bpl.s    @nothighpriority1
    
            ori.w    #$8000,obGFX(a0)
    
    
        @nothighpriority1:
    
            lea    (Ani_19A7A).l,a1
    
            jsr    (AnimateSprite).l
    
            jsr    PLCLoad_Shields
    
            jmp    (DisplaySprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    locret_1998A:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Bubble_Shield_Destroy:
    
        ;    move.b    #$XX,(a0)        ;Revert shield sprite to Insta-Shield
    
            ;move.b    #$0,$24(a0)    ;Reset the routine counter to trigger Insta-Shield Init 
    
            clr.b    (v_shield).w    ; remove shield
    
            rts
    
    ; ==========================================================================


    Lastly, for the fire shield, here’s the code before:


    Code:
    Obj_Fire_Shield:
    
            ; Init
    
            move.l    #Map_FireShield,mappings(a0)
    
            move.l    #DPLC_FireShield,DPLC_Address(a0)            ; Used by PLCLoad_Shields
    
            move.l    #ArtUnc_FireShield,Art_Address(a0)            ; Used by PLCLoad_Shields
    
            move.b    #4,render_flags(a0)
    
            move.w    #$80,priority(a0)
    
            move.b    #$18,width_pixels(a0)
    
            move.b    #$18,height_pixels(a0)
    
            move.w    #ArtTile_Shield,art_tile(a0)
    
            move.w    #tiles_to_bytes(ArtTile_Shield),vram_art(a0)    ; Used by PLCLoad_Shields
    
            btst    #7,(Player_1+art_tile).w
    
            beq.s    loc_195F0
    
            bset    #7,art_tile(a0)
    
    
    loc_195F0:
    
            move.w    #1,anim(a0)                ; Clear anim and set prev_anim to 1
    
            move.b    #-1,LastLoadedDPLC(a0)            ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
    
            move.l    #Obj_Fire_Shield_Main,(a0)
    
    
    Obj_Fire_Shield_Main:
    
            movea.w    parent(a0),a2
    
            btst    #Status_Invincible,status_secondary(a2) ; Is player invincible?
    
            bne.w    locret_19690                ; If so, do not display and do not update variables
    
            cmpi.b    #$1C,anim(a2)                ; Is player in their 'blank' animation?
    
            beq.s    locret_19690                ; If so, do not display and do not update variables
    
            btst    #Status_Shield,status_secondary(a2)     ; Should the player still have a shield?
    
            beq.w    Obj_Fire_Shield_Destroy            ; If not, change to Insta-Shield
    
            btst    #Status_Underwater,status(a2)        ; Is player underwater?
    
            bne.s    Obj_Fire_Shield_DestroyUnderwater    ; If so, branch
    
            move.w    x_pos(a2),x_pos(a0)
    
            move.w    y_pos(a2),y_pos(a0)
    
            tst.b    anim(a0)                ; Is shield in its 'dashing' state?
    
            bne.s    .nothighpriority            ; If so, do not update orientation or allow changing of the priority art_tile bit
    
            move.b    status(a2),status(a0)            ; Inherit status
    
            andi.b    #1,status(a0)                ; Limit inheritance to 'orientation' bit
    
            tst.b    (Reverse_gravity_flag).w
    
            beq.s    .normalgravity
    
            ori.b    #2,status(a0)                ; If in reverse gravity, reverse the vertical mirror render_flag bit (On if Off beforehand and vice versa)
    
    
        .normalgravity:
    
            andi.w    #drawing_mask,art_tile(a0)
    
            tst.w    art_tile(a2)
    
            bpl.s    .nothighpriority
    
            ori.w    #high_priority,art_tile(a0)
    
    
        .nothighpriority:
    
            lea    (Ani_FireShield).l,a1
    
            jsr    (Animate_Sprite).l
    
            move.w    #$80,priority(a0)        ; Layer shield over player sprite
    
            cmpi.b    #$F,mapping_frame(a0)        ; Are these the frames that display in front of the player?
    
            blo.s    .overplayer            ; If so, branch
    
            move.w    #$200,priority(a0)        ; If not, layer shield behind player sprite
    
    
        .overplayer:
    
            bsr.w    PLCLoad_Shields
    
            jmp    (Draw_Sprite).l
    
    ; ---------------------------------------------------------------------------
    
    
    locret_19690:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Fire_Shield_DestroyUnderwater:
    
            andi.b    #$8E,status_secondary(a2)    ; Sets Status_Shield, Status_FireShield, Status_LtngShield, and Status_BublShield to 0
    
            jsr    (Create_New_Sprite).l        ; Set up for a new object
    
            bne.w    Obj_Fire_Shield_Destroy        ; If that can't happen, branch
    
            move.l    #Obj_FireShield_Dissipate,(a1)    ; Create dissipate object
    
            move.w    x_pos(a0),x_pos(a1)        ; Put it at shields' x_pos
    
            move.w    y_pos(a0),y_pos(a1)        ; Put it at shields' y_pos
    
    
    Obj_Fire_Shield_Destroy:
    
            andi.b    #$8E,status_secondary(a2)    ; Sets Status_Shield, Status_FireShield, Status_LtngShield, and Status_BublShield to 0
    
            move.l    #Obj_Insta_Shield,(a0)        ; Replace the Fire Shield with the Insta-Shield
    
            rts



    And here’s after:


    Code:
    FireShield_Obj:
    
            moveq    #0,d0
    
            move.b    obRoutine(a0),d0
    
            move.w    FireShield_Index(pc,d0.w),d1
    
            jmp    FireShield_Index(pc,d1.w)
    
    ; ===========================================================================
    
    
    FireShield_Index:
    
            dc.w FireShield_Init-FireShield_Index
    
            dc.w FireShield_Main-FireShield_Index
    
    ; ===========================================================================
    
    
    FireShield_Init:
    
            move.l    #Map_FireShield,mappings(a0)
    
            move.l    #DPLC_FireShield,shield_DPLC_Address(a0)    ; Used by PLCLoad_Shields
    
            move.l    #ArtUnc_FireShield,shield_Art_Address(a0)    ; Used by PLCLoad_Shields
    
            move.b    #4,obRender(a0)
    
            move.b    #1,obPriority(a0)
    
            move.b    #$18,obActWid(a0)
    
            move.w    #$541,obGfx(a0)
    
            move.w    #$A820,shield_vram_art(a0)    ; Used by PLCLoad_Shields
    
            btst    #7,(v_player+obGfx).w
    
            beq.s    loc_195F0
    
            bset    #7,obGfx(a0)
    
    
    loc_195F0:
    
            move.w    #1,obAnim(a0)    ; Clear anim and set prev_anim to 1
    
            move.b    #-1,shield_LastLoadedDPLC(a0)    ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
    
            addq.b    #2,obRoutine(a0) ; => FireShield_Main
    
    
    FireShield_Main:
    
            lea    (v_player).w,a2
    
            tst.b    (v_invinc).w
    
            bne.w    locret_19690    ; If so, do not display and do not update variables
    
            cmpi.b    #$1C,obAnim(a2)    ; Is player in their 'blank' animation?
    
            beq.s    locret_19690    ; If so, do not display and do not update variables
    
            tst.b    (v_shield).w     ; Should the player still have a shield?
    
            beq.w    Obj_Fire_Shield_Destroy    ; If not, change to Insta-Shield
    
            btst    #6,obStatus(a2)    ; Is player underwater?
    
            bne.s    Obj_Fire_Shield_DestroyUnderwater    ; If so, branch
    
            move.w    obX(a2),obX(a0)
    
            move.w    obY(a2),obY(a0)
    
            tst.b    obAnim(a0)    ; Is shield in its 'dashing' state?
    
            bne.s    @nothighpriority    ; If so, do not update orientation or allow changing of the priority art_tile bit
    
            move.b    obStatus(a2),obStatus(a0)    ; Inherit status
    
            andi.b    #1,obStatus(a0)    ; Limit inheritance to 'orientation' bit
    
            andi.w    #$7FFF,obGFX(a0)
    
            tst.w    obGFX(a2)
    
            bpl.s    @nothighpriority
    
            ori.w    #$8000,obGFX(a0)
    
    
        @nothighpriority:
    
            lea    (Ani_19A02).l,a1
    
            jsr    (AnimateSprite).l
    
            move.b    #1,obPriority(a0)    ; Layer shield over player sprite
    
            cmpi.b    #$F,mapping_frame(a0)    ; Are these the frames that display in front of the player?
    
            blo.s    @overplayer    ; If so, branch
    
            move.b    #3,obPriority(a0)    ; If not, layer shield behind player sprite
    
    
        @overplayer:
    
            jsr    PLCLoad_Shields
    
            jmp    (DisplaySprite).l
    
    ; ---------------------------------------------------------------------------
    
    locret_19690:
    
            rts
    
    ; ---------------------------------------------------------------------------
    
    
    Obj_Fire_Shield_DestroyUnderwater:
    
            clr.b    ($FFFFFE2C).w        ; remove shield
    
            jsr    (FindFreeObj).l        ; Make Smoke Puff Sprite when fire shield in water
    
            bne.w    Obj_Fire_Shield_Destroy
    
            move.b    #id_MissileDissolve,(a1)
    
            move.b    #4,obRoutine(a1)
    
            move.l    #Map_MisDissolve,obMap(a1) ; (Map_Obj24 for Hivebrain users)
    
            move.w    #$5A0,obGFX(a1)
    
            move.b    #4,obRender(a1)
    
            move.b    #1,obPriority(a1)
    
            move.b    #0,obColType(a1)
    
            move.b    #$C,obActWid(a1)
    
            move.b    #7,obTimeFrame(a1)            ; set frame duration to    7 frames
    
            move.b    #0,obFrame(a1)
    
            move.w    obX(a0),obX(a1)    ; Put it at shields' x_pos
    
            move.w    obY(a0),obY(a1)    ; Put it at shields' y_pos
    
    
    Obj_Fire_Shield_Destroy:
    
        ;    move.b    #$XX,(a0)        ;Revert shield sprite to Insta-Shield
    
            ;move.b    #$0,$24(a0)    ;Reset the routine counter to trigger Insta-Shield Init 
    
            clr.b    (v_shield).w        ; remove shield
    
            rts
    
    ; ===========================================================================


    If you haven't noticed, I've shortened the art and mappings names by removing _Obj just to clean out everything, you can name it anything you like but be careful when naming things otherwise it will lead to errors. Now we need to include the art. I will quote the steps from the first post but modify them to work with the sparks. So take it away, quote me!



    First of all, make sure you got your SK disassembly ready. We're going to the bubble shield for example but it should be straightforward to do the other shields. The easiest thing to do is to go to:


    “skdisasm-master\General\Sprites\Shields” and look for DPLC - Bubble Shield.asm. Since PLCLoad_Shield retains using the same S3K format when it comes to DPLC mappings, we don’t need to change anything, so copy that file and paste it in the disassembly in your hack. As for the mappings, we need to convert to format to Sonic 1. You can use one of the three tools to do this:


    One tool is called SonMapEd. Open it up and set the Game Format setting to "Sonic 3 & Knuckles", load the mappings file, change the Game Format setting to "Sonic 1", and save the mappings as “Map - Bubble Shield.asm” in the “_maps” folder of your disassembly.


    Another tool is MappingsConverter. Open it up and load the mappings with input set to Game: "Sonic 3 & Knuckles", Format: ASM and output set to Game: "Sonic 1", Format: ASM, and save the mappings as “Map - Bubble Shield.asm” in the “_maps” folder of your disassembly.


    Another tool is Flex2. Open it up and insert the mappings and make sure the format is set as S3K. Load it, convert it to Sonic 1 and save it. The tool should overwrite the original mapping, and you should go back to the shields folder of the SK disassembly, copy the mappings and place it in “_maps” folder of your disassembly.


    As for the art, you’ll need to load PaletteConverter as the player palettes between Sonic 3K and 1 are vastly different. Open it up and you will see this:


    [​IMG]



    We are going to do this according to the numbers I added.


    1. First of all, click on load BIN and open up Sonic’s S3K palette file. It should be under:

    “skdisasm-master\General\Sprites\Sonic\Palettes\SonicandTails.bin”


    2. Secondly, open up Sonic’s S1 palette file. It should be under:


    “yourdisassembly\pallet\Sonic.bin”


    You need to match the palette bytes by dragging Sonic 3K’s byte to match S1’s; and the outcome should be this:


    [​IMG]



    3. Open up convert uncompressed file and open up the Insta-Shield art; It should be located at:


    Code:

    “skdisasm-master\General\Sprites\Shields\Bubble Shield.bin”


    It will give you a pop-up asking if you want to overwrite the source with the converted artwork. Say no and save the file in your disassembly in artunc and name it: “Bubble Shield.bin”


    I’ve linked a .rar file with everything converted here if you struggled to get that set up. Then, just drag and drop the files to its respective folder.


    Open up sonic1.asm, and under the fire shield object, insert this:



    Code:
    Map_LightningShield:
    
            include "_maps\Lightning Shield.asm"
    
            even
    
    
    
    DPLC_LightningShield:
    
            include "_map\Lightning Shield.asm"
    
            even
    
    
    
    ArtUnc_LightningShield:
    
            incbin artunc\ArtUnc_Electroshield.bin
    
            even
    
    Ani_199EA:
    
            dc.w byte_199EE-Ani_199EA
    
            dc.w byte_199F1-Ani_199EA
    
    byte_199EE:    dc.b  $1F,   6,    $FF
    
    byte_199F1:    dc.b    0,   0,      1,   2,   3,     4,   5,   6,    6,   6,      6,   6,   6,     6,   7, $FD,    0
    
    Ani_19A02:
    
            dc.w byte_19A06-Ani_19A02
    
            dc.w byte_19A1A-Ani_19A02
    
    byte_19A06:    dc.b    1,   0,     $F,   1, $10,     2, $11,   3, $12,   4,    $13,   5, $14,     6, $15,   7, $16,   8,    $17, $FF
    
    byte_19A1A:    dc.b    1,   9,     $A,  $B,  $C,    $D,  $E,   9,  $A,  $B,     $C,  $D,  $E, $FD,   0,   0
    
    Ani_19A2A:
    
            dc.w Anibyte_19A30-Ani_19A2A
    
            dc.w byte_19A5C-Ani_19A2A
    
            dc.w byte_19A73-Ani_19A2A
    
    Anibyte_19A30:
    
            dc.b    1,   0,      0,   1,   1,     2,   2,   3,    3,   4,      4,   5,   5,     6,   6,   7,    7,   8,      8,   9,  $A,    $B, $16, $16, $15, $15,    $14, $14, $13, $13, $12, $12
    
            dc.b  $11, $11,    $10, $10,  $F,    $F,  $E,  $E,    9,  $A,     $B, $FF
    
    byte_19A5C:
    
            dc.b    0,  $C,     $D, $17,  $C,    $D, $17,  $C,  $D, $17,     $C,  $D, $17,    $C,  $D, $17,  $C,  $D,    $17,  $C,  $D, $FC, $FF
    
    byte_19A73:
    
            dc.b    3,   0,      1,   2, $FC, $FF,   0
    
    Ani_19A7A:
    
            dc.w byte_19A80-Ani_19A7A
    
            dc.w byte_19AB8-Ani_19A7A
    
            dc.w byte_19ABF-Ani_19A7A
    
    byte_19A80:
    
            dc.b    1,   0,      9,   0,   9,     0,   9,   1,  $A,   1,     $A,   1,  $A,     2,   9,   2,    9,   2,      9,   3,  $A,     3,  $A,   3,  $A,   4,      9,   4,   9,     4,   9,   5
    
            dc.b   $A,   5,     $A,   5,  $A,     6,   9,   6,    9,   6,      9,   7,  $A,     7,  $A,   7,  $A,   8,      9,   8,   9,     8,   9, $FF
    
    byte_19AB8:    dc.b    5,   9,     $B,  $B,  $B, $FD,   0
    
    byte_19ABF:    dc.b    5,  $C,     $C,  $B, $FD,     0,   0
    
        even
    
    ArtUnc_LightningSparks:
    
            incbin artunc\Lightning Sparks.bin
    
            even
    
    
    
    Map_FireShield:
    
            include "_maps\Fire Shield.asm"
    
            even
    
    
    
    DPLC_FireShield:
    
            include "_maps\Fire Shield DPLC.asm"
    
            even
    
    
    
    ArtUnc_FireShield:
    
            incbin "artunc\FireShield.bin"
    
            even
    
    Map_BubbleShield:
    
            include "_maps\Bubble Shield.asm"
    
            even
    
    
    
    DPLC_BubbleShield:
    
            include "_maps\Bubble Shield DPLC.asm"
    
            even
    
    
    
    ArtUnc_BubbleShield:
    
             incbin "artunc\BubbleShield.bin"
    
             even


    OK, let’s load the object in Object Pointers.asm (it’s in the _inc folder). We need to hunt for a free object. The best choice is to find something like this:


    Code:
    ptr_Obj05:        dc.l NullObject

    This is a dummy object. It sounds simple enough, but we can replace it with the name of our object to load the lightning shield.


    Code:
    id_Obj05:        equ ((ptr_Obj05-Obj_Index)/4)+1

    You also need to follow the same procedure with the sparks, bubble and fire shields and find free objects. I’m going to reference id_Obj05 as id_LightningShield for the remainder of the tutorial. Now to load it! I’m going to replace the contents of the shield monitor with the lightning shield, in this case, so open up 2E Monitor Content Power-Up.asm and look for Pow_ChkShield and replace this:


    Code:
            move.b    #1,(v_shield).w    ; give Sonic a shield
    
            move.b    #id_ShieldItem,(v_objspace+$180).w ; load shield object ($38)

    With this:



    Code:
            move.b    #$4,(v_shield).w   ;give Sonic a shield with lightning attribute
    
            move.b    #id_LightningShield,(v_eshield).w ; load lightning shield object
    
            move.b    #id_LightningShieldSpark,(v_eshield+$1F).w ; load lightning shield spark object
    
            clr.b    (v_eshield+obRoutine).w

    You may have noticed a new variable called v_eshield. From what I can remember, this will be used when using the elemental shields to change objects and reset routines on the fly. If you want to load the other two shields, you can place them in free monitor content. The Eggman, S or goggles could be replaced if you haven’t modified anything inside of them. Be sure to change the attribute ($8 for bubble and 2 for fire as we will need it when inserting the moves) and ID of the shields.


    Hopefully, the objects load successfully and act as normal shields. If so, well done! You have done the most challenging part of the tutorial. Next time, I will be showing you how to load the moves and install the ring attraction code for the lightning shield. If it’s alright, please credit me for writing and sharing this with everyone, as I put so much effort into writing and getting this down.

    Also, kudos to AngelKOR64 for initially improving the code I originally had intact.


    One last thing: I need to know if the code works on your side. Complimenting my guide helps but I can't tell whether something works or not on your side so please do so.


    Until then...
     
    Scrap Sorra and ProjectFM like this.