The Sonic 3 shields porting guide to Sonic 1 - Hivebrain and GitHub

DeltaWooloo

シャットダウンします!
Member
Messages
41
Location
England, United Kingdom
Alright, lads and lassies, this is the first guide to a successful internet forum. 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 shields 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 shields 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, you need to make sure you 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.b    #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.

We also need to add an index so under the object's label, add this:

Code:
        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
; ===========================================================================

Then replace this:
Code:
        move.w    #1,$1C(a0)            ; Clear anim and set prev_anim to 1
        move.b    #-1,$30(a0)        ; Reset LastLoadedDPLC (used by PLCLoad_Shields)
        move.l    #Obj_Insta_Shield_Main,(a0)

with this:

Code:
        move.w    #1,$1C(a0)
        move.b    #-1,$30(a0)
        addq.b    #2,$24(a0)

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    8(a0),d2                ; Get player's x_pos
        move.w    $A(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:

jM-S5BmfJwVLqeqMAphIRvNbdYMyng2PvIHahm4oCHOLCgFiKkmhDS3EaUXOEbj9lX9LwgE1qUf-igAogatPD61kmgmX_jTzpJYFPsd67QAa9DGbawYl4G17boqhQBZoS0xfg-B9


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:

tkbKDmdNrEy8b7lwPVSRcPOtlsCMhY4ceyqVXE1iTmd3fFgEyIUqDtCbe0ssahIQt44jrSiM-IpEKi-AHDQwX7BwevQhYU28hIj53JbykrHvUjfyf4ip9MJlLiLxpJr_e3kzdMZS


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\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...

EDIT: changed the constants to be SSTs
 
Last edited:
I'm currently in the process of writing up how to get the elemental shields running alongside referencing labels for the GitHub/Hive2021 disassembly.

Since it'll be a two-parter, this Saturday, I should be able to demonstrate how to get the objects and moves set up. Then the Saturday after to modify the touch code to allow shield defection and being invulnerable to certain objects.

If you haven't added the insta-shield and are waiting for me to post the elemental shields port, please double-check you have the following steps set up in your disassembly in order not to repeat myself when finishing writing up the tutorial:

1. Make sure QueueDMATransfer is installed
2. You've added PLCLoad_Shields
3. You uncompressed the art for the invincibility and shields art.

I've updated the first post to mention that.

As I said on Discord: Those who added the insta-shield to their hacks, can you please let me know if it works and if you had to do extra modifications yourself that I missed?

Thank you and I'll see you next time~
 
Last edited:
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    (v_shield).w    ; remove shield
        lea    (v_pal_water).w,a1
        lea    (v_pal_water_dup).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    (v_shield).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:

jM-S5BmfJwVLqeqMAphIRvNbdYMyng2PvIHahm4oCHOLCgFiKkmhDS3EaUXOEbj9lX9LwgE1qUf-igAogatPD61kmgmX_jTzpJYFPsd67QAa9DGbawYl4G17boqhQBZoS0xfg-B9



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:

tkbKDmdNrEy8b7lwPVSRcPOtlsCMhY4ceyqVXE1iTmd3fFgEyIUqDtCbe0ssahIQt44jrSiM-IpEKi-AHDQwX7BwevQhYU28hIj53JbykrHvUjfyf4ip9MJlLiLxpJr_e3kzdMZS



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 "_maps\Lightning Shield DPLC.asm"
        even

ArtUnc_LightningShield:
        incbin artunc\LightningShield.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\LightningSparks.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...
 
Last edited:
Back
Top