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. Spoiler: Must read before following the tutorial 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: Spoiler: Code 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: Spoiler: Code 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: We are going to do this according to the numbers I added. 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: 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~
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. Spoiler: Code 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: Spoiler: Code 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 Spoiler: Code 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: Spoiler: Code 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: Spoiler: Code 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: Spoiler: Code 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: We are going to do this according to the numbers I added. 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: 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: Spoiler: Code 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...