The Sonic 2 Spring Object has some differences comparing to its predecessor (like diagonal springs). So, let’s take a look at Sonic 2 Spring Object: Code: Obj41: moveq #0,d0 move.b routine(a0),d0 move.w Obj41_Index(pc,d0.w),d1 jsr Obj41_Index(pc,d1.w) First we are making sure, that d0 is clear (moveq #0,d0), then we move the routine counter into d0 (move.b routine(a0),d0), then it moves index value + value of d0 to d1 and jumps to correct routine based on d1. Code: jmp (MarkObjGone).l Next, we can see the difference. After ‘’jsr Obj41_Index(pc,d1.w)’’ there’s MarkObjGone call. MarkObjGone is a function, that deletes the object and making sure that it will be loaded again if an object is offscreen(rounding to 128 pixels). If not it will just display the object. In S1 It is doing inline call of a bit modified MarkObjGone(removed check for remembering state, because spring is never remembered its state), so we can skip this. Next up is this: Code: ; =========================================================================== ; off_1889C: Obj41_Index: offsetTable offsetTableEntry.w Obj41_Init ; 0 offsetTableEntry.w Obj41_Up ; 2 offsetTableEntry.w Obj41_Horizontal ; 4 offsetTableEntry.w Obj41_Down ; 6 offsetTableEntry.w Obj41_DiagonallyUp ; 8 offsetTableEntry.w Obj41_DiagonallyDown ; $A ; =========================================================================== This is the offset table. It is used to jump on different routines depending on the routine counter - SST $24. SST is RAM, that object has ($40 bytes in S1 and S2 and $50 bytes in S3). Some SST’s are free, some are not. With offset table we can to things like finite state machine or use it like this: Code: switch(d0) { Case 0: // do something Break. Case 2: Break. } , except without ‘’ default:’’. The number at comment is the routine number (when the value of the routine counter is equal to routine number it will jump to that routine). In this case, Routine counter must be in multiples of 2 Comparing routine tables in S1 and S2 we can see, that S2 uses offsetTable at the start and instead of dc.w Obj41_Init-Obj41_Index it uses offsetTableEntry.w Obj41_Init. OffsetTable sets at from where to offset (Obj41_Index in this case) and offsetTableEntry.x is creating offset table entry with a defined size. If we adapt offset table to S1 we’ll get this: Code: ; =========================================================================== Obj41_Index: dc.w Obj41_Init-Ooffset by ndex dc.w Obj41_Up-Obj41_Index dc.w Obj41_Horizontal-Obj41_Index dc.w Obj41_Down-Obj41_Index dc.w Obj41_DiagonallyUp-Obj41_Index dc.w Obj41_DiagonallyDown-Obj41_Index ; =========================================================================== To have equivalent routine names change all “Obj41_Main” to” Obj41_Init”, “Obj41_LR” to “Obj41_Horizontal”, “Obj41_Dwn” to “Obj41_Down”. Delete “Obj41_Powers”, because we’ll use “Obj41_Strength” from S2(they are identical data, just in different places). Next up we see this: Code: ; =========================================================================== ; loc_188A8: Obj41_Init: addq.b #2,routine(a0) move.l #Obj41_MapUnc_1901C,mappings(a0) move.w #make_art_tile(ArtTile_ArtNem_VrtclSprng,0,0),art_tile(a0) ori.b #4,render_flags(a0) move.b #$10,width_pixels(a0) move.b #4,priority(a0) move.b subtype(a0),d0 lsr.w #3,d0 andi.w #$E,d0 move.w Obj41_Init_Subtypes(pc,d0.w),d0 jmp Obj41_Init_Subtypes(pc,d0.w) ; =========================================================================== Insert this code instead of Obj41_Init and change SST constants to SST offsets. Here’s equivalency list between SST constants and SST offsets: Spoiler: Equvivalency list render_flags = 1 art_tile = 2 mappings = 4 x_pos = 8 y_pos = $C x_vel = $10 y_vel = $12 y_radius = $16 x_radius = $17 priority = $18 width_pixels = $19 mapping_frame = $1A anim_frame = $1B anim = $1C next_anim = $1D anim_frame_duration = $1E collision_flags = $20 collision_property = $21 status = $22 respawn_index = $23 routine = $24 routine_secondary = $25 angle = $26 subtype = $28 objoff_XX = $XX First 6 lines are nothing special. It starts off with setting routine counter to next routine, making sure, that we won’t initialize object again. Next, the object is setting mappings address (Roughly speaking, mappings is how sprite will use the object’s art) (move.l #Obj41_MapUnc_1901C, mappings(a0) ). Change ‘’Obj41_MapUnc_1901C’’ to ‘’Map_obj41_Red” (In S2 mappings for red and green spring are separate, because diagonal springs are using 2 palette lines). Then, It’s setting which art tile will object use (move.w #make_art_tile(ArtTile_ArtNem_VrtclSprng,0,0),art_tile(a0) ) , following by setting render flags to use world coordinates and setting object’s width in pixels. Change ‘’make_art_tile(ArtTile_ArtNem_VrtclSprng,0,0)’’ to $52F. Sixth line is setting priority of object (move.b #4,priority(a0) ). If the object has the lowest priority (priority 0) it will be displayed in front of all objects, the priority of which is higher. If the object has the highest priority (priority 7) it will be displayed behind of all objects, the priority of which is lower. Because First 6 lines in Obj41_S1 is doing the same function, we won’t change them. Next lines are more interesting. They are initializing different depending on Spring’s type (Up, Down, Horizontal, Diagonally Up, Diagonally Down). It starts with moving subtype of object into d0 (move.b subtype(a0),d0). The subtype is the only SST (except object ID, of course), that is predefined in object layout. Then it divides d0 by 6 (lsr.w #3,d0), following bitwise and by $E (andi.w #$E,d0). The object is doing “bitwise and” because some bits are used for colour and other things, which is not what we need. Last 2 lines of code have the same function as last two lines of code in “Obj41:”. Making a conclusion, insert this code between after “move.b #4,$18(a0)” and before “Obj41_Up:”: Code: move.b $28(a0),d0 ; move subtype into d0 lsr.w #3,d0 ; divide it to 6 andi.w #$E,d0 ; and it by $E move.w Obj41_Init_Subtypes(pc,d0.w),d0 jmp Obj41_Init_Subtypes(pc,d0.w) ; Jump to different initialization subroutines depending on subtype After that we again see an offset table. Code: ; =========================================================================== ; off_188DE: Obj41_Init_Subtypes: offsetTable offsetTableEntry.w Obj41_Init_Up ; 0 offsetTableEntry.w Obj41_Init_Horizontal ; 2 offsetTableEntry.w Obj41_Init_Down ; 4 offsetTableEntry.w Obj41_Init_DiagonallyUp ; 6 offsetTableEntry.w Obj41_Init_DiagonallyDown ; 8 ; =========================================================================== Because I already told how offsetTable and offsetTableEntry macros works and showed an example of adapting, adapt this offset table by yourself. Next Up is Initialization Subroutines themselves: Code: ; =========================================================================== ; loc_188E8: Obj41_Init_Horizontal: move.b #4,routine(a0) move.b #2,anim(a0) move.b #3,mapping_frame(a0) move.w #make_art_tile(ArtTile_ArtNem_HrzntlSprng,0,0),art_tile(a0) move.b #8,width_pixels(a0) bra.s Obj41_Init_Common ; =========================================================================== ; loc_18908: Obj41_Init_Down: move.b #6,routine(a0) move.b #6,mapping_frame(a0) bset #1,status(a0) bra.s Obj41_Init_Common ; =========================================================================== ; loc_1891C: Obj41_Init_DiagonallyUp: move.b #8,routine(a0) move.b #4,anim(a0) move.b #7,mapping_frame(a0) move.w #make_art_tile(ArtTile_ArtNem_DignlSprng,0,0),art_tile(a0) bra.s Obj41_Init_Common ; =========================================================================== ; loc_18936: Obj41_Init_DiagonallyDown: move.b #$A,routine(a0) move.b #4,anim(a0) move.b #$A,mapping_frame(a0) move.w #make_art_tile(ArtTile_ArtNem_DignlSprng,0,0),art_tile(a0) bset #1,status(a0) Mostly, it’s just object initialization like at start of Obj41_Init. The only new thing is “bset #1,status(a0)”, “move.b #$XX,anim(a0)”, “move.b #$XX,mapping_frame(a0)”, “bra.s Obj41_Init_Common”. First instruction flips the object’s sprite by Y-axis. Insert that code and change SST constants to SST offsets. Second instruction sets which animation in animations script will be used. Third instruction sets which mapping frame will be used. Fourth instruction branches to subroutine we’ll talk about later. Also change ‘’make_art_tile(ArtTile_ArtNem_HrzntlSprng,0,0)’’ to $523 and “make_art_tile(ArtTile_ArtNem_DignlSprng,0,0)” to any free $20 art tiles space in VRAM (if you haven’t free VRAM, there’s guide at the end of tutorial to free some VRAM in GHZ). If you were attentive, you saw, that I don’t mention Obj41_Init_Up and Obj41_Init_Common. That’s because that subroutine (Obj41_Init_Up and Obj41_Init_Common are labelled to the same subroutine) is setting colour and power of spring. So, let’s take a look at the code of this subroutine. Code: Obj41_Init_Up: Obj41_Init_Common: ; checks color of spring move.b subtype(a0),d0 andi.w #2,d0 move.w Obj41_Strengths(pc,d0.w),objoff_30(a0) btst #1,d0 beq.s + bset #palette_bit_0,art_tile(a0) move.l #Obj41_MapUnc_19032,mappings(a0) + bsr.w Adjust2PArtPointer rts Insert this code and change SST constants to SST offsets. Now let’s take a look at the code. It starts off with moving subtype SST into d0. Then, it “bitwise and” by 2(clear all bits, except bit 1), because bit 1 used to define colour and power of spring (if it’s set to spring is green, else it’s red). Next two lines test bit 1 of object subtype and branches to + label if bit 1 is clear. It is doing this because bit 1 of object subtype is used to define colour and power of spring, as was said earlier. Change + label to @red. Fifth line is telling sprite to use palette line 1 instead of palette line 0 (best #palette_bit_0,art_tile(a0) ). Delete thiss line, because my mappings will do that instead. Sixth line is setting mappings for green spring (move.l #Obj41_MapUnc_19032,mappings(a0) ). Change “Obj41_MapUnc_19032” to “Map_obj41_green”. In the end, there’s the call of Sonic 2 player competitive mode related subroutine (delete this line) and its. Next Up is powers of spring. Code: ; =========================================================================== ; word_1897C: Obj41_Strengths: ; Speed applied on Sonic dc.w -$1000 dc.w -$A00 Insert it. Next is Spring Type Up subroutine: Code: ; =========================================================================== ; loc_18980: Obj41_Up: move.w #$1B,d1 move.w #8,d2 move.w #$10,d3 move.w x_pos(a0),d4 lea (MainCharacter).w,a1 ; a1=character moveq #p1_standing_bit,d6 movem.l d1-d4,-(sp) bsr.w SolidObject_Always_SingleCharacter btst #p1_standing_bit,status(a0) beq.s loc_189A8 bsr.s loc_189CA loc_189A8: movem.l (sp)+,d1-d4 lea (Sidekick).w,a1 ; a1=character moveq #p2_standing_bit,d6 bsr.w SolidObject_Always_SingleCharacter btst #p2_standing_bit,status(a0) beq.s loc_189C0 bsr.s loc_189CA loc_189C0: lea (Ani_obj41).l,a1 bra.w AnimateSprite ; =========================================================================== loc_189CA: move.w #$100,anim(a0) addq.w #8,y_pos(a1) move.w objoff_30(a0),y_vel(a1) bset #1,status(a1) bclr #3,status(a1) move.b #AniIDSonAni_Spring,anim(a1) move.b #2,routine(a1) move.b subtype(a0),d0 bpl.s loc_189FE move.w #0,x_vel(a1) loc_189FE: btst #0,d0 beq.s loc_18A3E move.w #1,inertia(a1) move.b #1,flip_angle(a1) move.b #AniIDSonAni_Walk,anim(a1) move.b #0,flips_remaining(a1) move.b #4,flip_speed(a1) btst #1,d0 bne.s loc_18A2E move.b #1,flips_remaining(a1) loc_18A2E: btst #0,status(a1) beq.s loc_18A3E neg.b flip_angle(a1) neg.w inertia(a1) loc_18A3E: andi.b #$C,d0 cmpi.b #4,d0 bne.s loc_18A54 move.b #$C,top_solid_bit(a1) move.b #$D,lrb_solid_bit(a1) loc_18A54: cmpi.b #8,d0 bne.s loc_18A66 move.b #$E,top_solid_bit(a1) move.b #$F,lrb_solid_bit(a1) loc_18A66: move.w #SndID_Spring,d0 jmp (PlaySound).l ; =========================================================================== Let’s analyze key differences in “Obj41_Up:”. “SolidObject_Always_SingleCharacter” used in same situations like “SolidObject”, except with player address(a1) predefined (because Sonic 2 can have 2 character object at once); Because Spring animation script is different, it branches straight to animation code, if Sonic isn’t standing on spring (It’ll be explained a bit later what SolidObject is doing) instead of moving routine counter back and forth like in Sonic 1; Object tests p1_standing_bit/p2_standing (bit 3/4) in status SST of $25 SST (Sonic 2’s SolidObject does not have SST like this, because bit 3 of status SST can be used in most situations instead) for checking is Sonic/Tails collided with spring (loc_189CA handles collision reaction); Spoiler: SolidObject Description SolidObject is a subroutine, that handles collision between an object with all solid sides and player. It Sets Objects: $25 SST to 2, if Sonic is Standing on Object (I’ll recommend use Bit 3 of Status SST instead) Bit 3 of Status SST, if Sonic is Standing on Object Bit 5 of Status SST, if Sonic is Pushing the Object It returns d4 as: 0, if Sonic doesn’t collide with this object or already standing on it 1, if Sonic collided with left/right side of the object -1, if Sonic collided with top/bottom of the object Right before calling SolidObject some registers should be initrealised: d1 – collision box width, d2 – collision box height / 2 (when jumping/rolling), d3 – collision box height / 2 (when walking), d4 – X-axis position Making a conclusion, we need to replace Code: bne.s Obj41_BounceUp ; if yes, branch rts in Obj41_Up: with Code: beq.s Obj41_AniUp ; if no, branch “loc_189C0” works identical to “Obj41_AniUp”, so we don’t need to change anything here. “loc_189CA” comparing to “Obj41_BounceUp”: Resets Spring animation to 1 (“move.w #$100,anim(a0)”, if animation SST and next animation SST are different, it’ll reset animation to value from animation SST), Don’t Go into next routine (addq.b #2,$24(a0) ), Don’t Clear Object’s $25 SST (“clr.b $25(a0)”, Sonic 2 doesn’t need it compared to Sonic 1, because if don’t do it in this case slope glitch will occur), Reset Player X velocity if bit 7 of subtype is Set Code: move.b subtype(a0),d0 bpl.s loc_189FE move.w #0,x_vel(a1) So, delete “addq.b #2,$24(a0)” in Obj41_BounceUp, insert this code between “move.b #10,$1C(a1)” and “move.b #2,$24(a1)” in “Obj41_BounceUp” Code: tst.b $28(a0) ; is bit 7 of subtype is set? bpl.s @skip ; if not, branch clr.w $10(a1) ; clear Sonic's X velocity , Insert “move.w #$100,$1C(a0)” at Start of “Obj41_BounceUp:”. Code between “loc_189FE” and “loc_18A66” is handling corkscrew animation, which we won’t do in this tutorial and “Obj41_BounceUp” have code that does the same thing, that “loc_18A66” does. Delete “Obj41_ResetUp” routine, because now it’s unused. Next up is Horizontal Spring Code: Code: ; =========================================================================== ; loc_18A70: Obj41_Horizontal: move.w #$13,d1 move.w #$E,d2 move.w #$F,d3 move.w x_pos(a0),d4 lea (MainCharacter).w,a1 ; a1=character moveq #p1_standing_bit,d6 movem.l d1-d4,-(sp) bsr.w SolidObject_Always_SingleCharacter btst #p1_pushing_bit,status(a0) beq.s loc_18AB0 move.b status(a0),d1 move.w x_pos(a0),d0 sub.w x_pos(a1),d0 bcs.s loc_18AA8 eori.b #1,d1 loc_18AA8: andi.b #1,d1 bne.s loc_18AB0 bsr.s loc_18AEE loc_18AB0: movem.l (sp)+,d1-d4 lea (Sidekick).w,a1 ; a1=character moveq #p2_standing_bit,d6 bsr.w SolidObject_Always_SingleCharacter btst #p2_pushing_bit,status(a0) beq.s loc_18AE0 move.b status(a0),d1 move.w x_pos(a0),d0 sub.w x_pos(a1),d0 bcs.s loc_18AD8 eori.b #1,d1 loc_18AD8: andi.b #1,d1 bne.s loc_18AE0 bsr.s loc_18AEE loc_18AE0: bsr.w loc_18BC6 lea (Ani_obj41).l,a1 bra.w AnimateSprite ; =========================================================================== loc_18AEE: move.w #$300,anim(a0) move.w objoff_30(a0),x_vel(a1) addq.w #8,x_pos(a1) bset #0,status(a1) btst #0,status(a0) bne.s loc_18B1C bclr #0,status(a1) subi.w #$10,x_pos(a1) neg.w x_vel(a1) loc_18B1C: move.w #$F,move_lock(a1) move.w x_vel(a1),inertia(a1) btst #2,status(a1) bne.s loc_18B36 move.b #AniIDSonAni_Walk,anim(a1) loc_18B36: move.b subtype(a0),d0 bpl.s loc_18B42 move.w #0,y_vel(a1) loc_18B42: btst #0,d0 beq.s loc_18B82 move.w #1,inertia(a1) move.b #1,flip_angle(a1) move.b #AniIDSonAni_Walk,anim(a1) move.b #1,flips_remaining(a1) move.b #8,flip_speed(a1) btst #1,d0 bne.s loc_18B72 move.b #3,flips_remaining(a1) loc_18B72: btst #0,status(a1) beq.s loc_18B82 neg.b flip_angle(a1) neg.w inertia(a1) loc_18B82: andi.b #$C,d0 cmpi.b #4,d0 bne.s loc_18B98 move.b #$C,top_solid_bit(a1) move.b #$D,lrb_solid_bit(a1) loc_18B98: cmpi.b #8,d0 bne.s loc_18BAA move.b #$E,top_solid_bit(a1) move.b #$F,lrb_solid_bit(a1) loc_18BAA: bclr #p1_pushing_bit,status(a0) bclr #p2_pushing_bit,status(a0) bclr #5,status(a1) move.w #SndID_Spring,d0 jmp (PlaySound).l ; =========================================================================== loc_18BC6: cmpi.b #3,anim(a0) beq.w return_18C7E move.w x_pos(a0),d0 move.w d0,d1 addi.w #$28,d1 btst #0,status(a0) beq.s loc_18BE8 move.w d0,d1 subi.w #$28,d0 loc_18BE8: move.w y_pos(a0),d2 move.w d2,d3 subi.w #$18,d2 addi.w #$18,d3 lea (MainCharacter).w,a1 ; a1=character btst #1,status(a1) bne.s loc_18C3C move.w inertia(a1),d4 btst #0,status(a0) beq.s loc_18C10 neg.w d4 loc_18C10: tst.w d4 bmi.s loc_18C3C move.w x_pos(a1),d4 cmp.w d0,d4 blo.w loc_18C3C cmp.w d1,d4 bhs.w loc_18C3C move.w y_pos(a1),d4 cmp.w d2,d4 blo.w loc_18C3C cmp.w d3,d4 bhs.w loc_18C3C move.w d0,-(sp) bsr.w loc_18AEE move.w (sp)+,d0 loc_18C3C: lea (Sidekick).w,a1 ; a1=character btst #1,status(a1) bne.s return_18C7E move.w inertia(a1),d4 btst #0,status(a0) beq.s loc_18C56 neg.w d4 loc_18C56: tst.w d4 bmi.s return_18C7E move.w x_pos(a1),d4 cmp.w d0,d4 blo.w return_18C7E cmp.w d1,d4 bhs.w return_18C7E move.w y_pos(a1),d4 cmp.w d2,d4 blo.w return_18C7E cmp.w d3,d4 bhs.w return_18C7E bsr.w loc_18AEE return_18C7E: rts ; =========================================================================== Most Things we need to change was explained in “Obj41_Up” analysis: Remove Code: cmpi.b #2,$24(a0) bne.s loc_DC0C move.b #8,$24(a0) loc_DC0C: in “Obj41_Horizontal:”, Code: bne.s Obj41_BounceLR rts in “Obj41_Horizontal:” with Code: beq.s Obj41_AniLR , Remove “addq.b #2,$24(a0)” in “Obj41_BounceLR”, Insert “move.w #$300,$1C(a0)” at start of “Obj41_BounceLR”, Remove “Obj41_ResetLR:” routine. If you go to the grey part of the spring in Sonic 1 you will stick bouncing back and forth. This happens, because Sonic 1 horizontal spring object bounce if Sonic is pushing this object, without checking sides. Sonic 2 fixed that by this code: Code: move.b status(a0),d1 move.w x_pos(a0),d0 sub.w x_pos(a1),d0 bcs.s loc_18AD8 eori.b #1,d1 loc_18AD8: andi.b #1,d1 bne.s loc_18AE0 Insert this code right before “Obj41_BounceLR:”, change SST constants to SST offsets and replace “bne.s loc_18AE0” with “bne.s Obj41_AniLR”. Code starts off with moving status SST to d1 and checking if spring on the right to Sonic. If not it flips bit 1 in d1(If bit 1 in Status SST is set, then object is facing left. If not, then it’s facing right.) and checks if bit 1 in d1 is clear. If it is, then spring will bounce. Code between “loc_18BC6” and “Obj41_Down” is also doing collision. The only difference, that it will check even if you are in debug placement mode (SolidObject doesn’t check collision If Sonic is in debug placement mode), so let’s skip it. Next Up is Facing Down Spring: Code: ; =========================================================================== ; loc_18C80: Obj41_Down: move.w #$1B,d1 move.w #8,d2 move.w #$10,d3 move.w x_pos(a0),d4 lea (MainCharacter).w,a1 ; a1=character moveq #p1_standing_bit,d6 movem.l d1-d4,-(sp) bsr.w SolidObject_Always_SingleCharacter cmpi.w #-2,d4 bne.s loc_18CA6 bsr.s loc_18CC6 loc_18CA6: movem.l (sp)+,d1-d4 lea (Sidekick).w,a1 ; a1=character moveq #p2_standing_bit,d6 bsr.w SolidObject_Always_SingleCharacter cmpi.w #-2,d4 bne.s loc_18CBC bsr.s loc_18CC6 loc_18CBC: lea (Ani_obj41).l,a1 bra.w AnimateSprite ; =========================================================================== loc_18CC6: move.w #$100,anim(a0) subq.w #8,y_pos(a1) move.w objoff_30(a0),y_vel(a1) neg.w y_vel(a1) move.b subtype(a0),d0 bpl.s loc_18CE6 move.w #0,x_vel(a1) loc_18CE6: btst #0,d0 beq.s loc_18D26 move.w #1,inertia(a1) move.b #1,flip_angle(a1) move.b #AniIDSonAni_Walk,anim(a1) move.b #0,flips_remaining(a1) move.b #4,flip_speed(a1) btst #1,d0 bne.s loc_18D16 move.b #1,flips_remaining(a1) loc_18D16: btst #0,status(a1) beq.s loc_18D26 neg.b flip_angle(a1) neg.w inertia(a1) loc_18D26: andi.b #$C,d0 cmpi.b #4,d0 bne.s loc_18D3C move.b #$C,top_solid_bit(a1) move.b #$D,lrb_solid_bit(a1) loc_18D3C: cmpi.b #8,d0 bne.s loc_18D4E move.b #$E,top_solid_bit(a1) move.b #$F,lrb_solid_bit(a1) loc_18D4E: bset #1,status(a1) bclr #3,status(a1) move.b #2,routine(a1) move.w #SndID_Spring,d0 jmp (PlaySound).l ; =========================================================================== Most Things we need to change was explained in “Obj41_Up” analysis: Change Code: cmpi.b #2,$24(a0) bne.s loc_DCA4 move.b #$E,$24(a0) loc_DCA4: tst.b $25(a0) bne.s locret_DCAE tst.w d4 bmi.s Obj41_BounceDwn locret_DCAE: rts In “Obj41_Down” to Code: loc_DCA4: tst.b $25(a0) bne.s Obj41_AniDwn tst.w d4 bpl.s Obj41_AniDwn , Delete “addq.b #2,$24(a0)” in “Obj41_BounceDwn”, Insert “move.w #$100,anim(a0)” at start of “Obj41_BounceDwn”, Insert Code: tst.b $28(a0) ; is bit 7 of subtype is set? bpl.s @skip ; if not, branch clr.w $10(a1) ; clear Sonic's X velocity between “neg.w $12(a1)” and “bset #1,$22(a1)” in “Obj41_BounceDwn”, Delete “Obj41_ResetDwn” routine. You can see that Sonic 2 spring object, when facing down checks collision a bit differently: Code: cmpi.w #-2,d4 bne.s loc_18EC4 bsr.s loc_18EE6 In Sonic 2 SolidObject Sets d4 to -2, if top collision occur. To Implement this in Sonic 1 Change “moveq #-1,d4” to “moveq #-2,d4” in “loc_FBD2” and “loc_FBD6” and Change: Code: loc_DCA4: tst.b $25(a0) bne.s Obj41_AniDwn tst.w d4 bpl.s Obj41_AniDwn to: Code: loc_DCA4: cmpi.w #-2,d4 bne.s Obj41_AniDwn Next Up Is Diagonal Up spring: Code: ; =========================================================================== ; loc_18D6A: Obj41_DiagonallyUp: move.w #$1B,d1 move.w #$10,d2 move.w x_pos(a0),d4 lea Obj41_SlopeData_DiagUp(pc),a2 lea (MainCharacter).w,a1 ; a1=character moveq #p1_standing_bit,d6 movem.l d1-d4,-(sp) bsr.w SlopedSolid_SingleCharacter btst #p1_standing_bit,status(a0) beq.s loc_18D92 bsr.s loc_18DB4 loc_18D92: movem.l (sp)+,d1-d4 lea (Sidekick).w,a1 ; a1=character moveq #p2_standing_bit,d6 bsr.w SlopedSolid_SingleCharacter btst #p2_standing_bit,status(a0) beq.s loc_18DAA bsr.s loc_18DB4 loc_18DAA: lea (Ani_obj41).l,a1 bra.w AnimateSprite ; =========================================================================== loc_18DB4: btst #0,status(a0) bne.s loc_18DCA move.w x_pos(a0),d0 subq.w #4,d0 cmp.w x_pos(a1),d0 blo.s loc_18DD8 rts ; =========================================================================== loc_18DCA: move.w x_pos(a0),d0 addq.w #4,d0 cmp.w x_pos(a1),d0 bhs.s loc_18DD8 rts ; =========================================================================== loc_18DD8: move.w #$500,anim(a0) move.w objoff_30(a0),y_vel(a1) move.w objoff_30(a0),x_vel(a1) addq.w #6,y_pos(a1) addq.w #6,x_pos(a1) bset #0,status(a1) btst #0,status(a0) bne.s loc_18E10 bclr #0,status(a1) subi.w #$C,x_pos(a1) neg.w x_vel(a1) loc_18E10: bset #1,status(a1) bclr #3,status(a1) move.b #AniIDSonAni_Spring,anim(a1) move.b #2,routine(a1) move.b subtype(a0),d0 btst #0,d0 beq.s loc_18E6C move.w #1,inertia(a1) move.b #1,flip_angle(a1) move.b #AniIDSonAni_Walk,anim(a1) move.b #1,flips_remaining(a1) move.b #8,flip_speed(a1) btst #1,d0 bne.s loc_18E5C move.b #3,flips_remaining(a1) loc_18E5C: btst #0,status(a1) beq.s loc_18E6C neg.b flip_angle(a1) neg.w inertia(a1) loc_18E6C: andi.b #$C,d0 cmpi.b #4,d0 bne.s loc_18E82 move.b #$C,top_solid_bit(a1) move.b #$D,lrb_solid_bit(a1) loc_18E82: cmpi.b #8,d0 bne.s loc_18E94 move.b #$E,top_solid_bit(a1) move.b #$F,lrb_solid_bit(a1) loc_18E94: move.w #SndID_Spring,d0 jmp (PlaySound).l Instead of SolidObject Diagonal Springs use SlopedSolid. SlopedSolid works similar to SolidObject, except it, uses only d2 for height/2 and a2 for slope data. The first byte in slope data is base offset, while others are slope offsets going from left to right (the number of slope offsets must be equal to collision box width). Roughly speaking, SlopedSolid test checks X-axis collision and if it collides select slope offset depending on X pos of sonic, following by checking Y-axis collision with collision box, where base offset + slope offset is collision box top Y position and d2*2 is collision height. Because of this diagonal spring have some collision problems in S2. In S1 is SolidObject2F is similar to SlopedSolid except collision offset are unsigned. To fix it Change this in “loc_FA94”: Code: moveq #0,d3 move.b (a2,d5.w),d3 sub.b (a2),d3 to this: Code: move.b (a2,d5.w),d3 sub.b (a2),d3 ext.w d3 and Change this in “loc_854E”: Code: moveq #0,d1 move.b (a2,d0.w),d1 to this: Code: move.b (a2,d0.w),d1 ext.w d1 So, let’s go back to spring code. Insert this right after “Obj41_AniUp” code: Code: ; =========================================================================== ;byte_18FAA: Obj41_SlopeData_DiagUp: dc.b $10,$10,$10,$10,$10,$10,$10,$10,$10,$10,$10,$10, $E, $C, $A, 8 dc.b 6, 4, 2, 0,$FE,$FC,$FC,$FC,$FC,$FC,$FC,$FC; 16 ; =========================================================================== This is Diagonal Up Spring Slope Data. Create label “Obj41_DiagonallyUp”right after Diagonal Up Spring Slope Data, insert first 4 lines of code from Sonic 2’s “Obj41_DiagonallyUp” and change “x_pos” to 8. These lines are Setting Arguments for SolidObject. Then insert SolidObject2F call(“jsr (SolidObject2F).l”. After that we need to insert code, that checks if sonic is standing on Spring and Branch on animation code if it’s not: Code: btst #3,$22(a0) beq.s Obj41_AniDiagUp Following by bouncing if sonic is touching center parts of springs(insert it next): Code: btst #0,$22(a0) bne.s @flipped subq.w #4,d0 cmp.w 8(a1),d0 blo.s Obj41_BounceDiagUp bra.s Obj41_AniDiagUp @flipped: addq.w #4,d0 cmp.w 8(a1),d0 blo.s Obj41_AniDiagUp Next, Insert this code: Code: ; =========================================================================== Obj41_BounceDiagUp: move.w #$500,$1C(a0) move.w $30(a0),$10(a1) move.w $30(a0),$12(a1) addq.w #6,8(a1) addq.w #6,$C(a1) bset #0,$22(a1) btst #0,$22(a0) bne.s @flipped bclr #0,$22(a1) subi.w #6*2,8(a1) neg.w $10(a1) @flipped: bset #1,$22(a1) bclr #3,$22(a1) move.b #2,$24(a1) bclr #3,$22(a0) clr.b $25(a0) move.w #$CC,d0 jsr (PlaySound_Special).l ; play spring sound Obj41_AniDiagUp: lea (Ani_Obj41).l,a1 bra.w AnimateSprite ; =========================================================================== Diagonal Down Spring Code Has nothing new or interesting so I’ll just provide the code: Code: ; =========================================================================== Obj41_SlopeData_DiagDwn: dc.b -$C,-$10,-$10,-$10,-$10,-$10,-$10,-$10,-$10,-$10,-$10,-$10,-$E,-$C,-$A,-8 dc.b -6,-4,-2, 0, 2, 4, 4, 4, 4, 4, 4, 4; 16 Obj41_DiagonallyDown: ; Routine $A move.w #$1B,d1 move.w #$10,d2 move.w 8(a0),d4 lea Obj41_SlopeData_DiagDwn(pc),a2 jsr (SolidObject2F).l cmpi.w #-2,d4 bne.s Obj41_AniDiagDwn ; =========================================================================== Obj41_BounceDiagDwn: move.w #$500,$1C(a0) move.w $30(a0),$10(a1) move.w $30(a0),$12(a1) neg.w $12(a1) subq.w #6,$C(a1) addq.w #6,8(a1) bset #0,$22(a1) btst #0,$22(a0) bne.s @flipped bclr #0,$22(a1) subi.w #6*2,8(a1) neg.w $10(a1) @flipped: bset #1,$22(a1) bclr #3,$22(a1) move.b #2,$24(a1) bclr #3,$22(a0) clr.b $25(a0) move.w #$CC,d0 jsr (PlaySound_Special).l ; play spring sound Obj41_AniDiagDwn: lea (Ani_obj41).l,a1 bra.w AnimateSprite ; =========================================================================== Next up is setting up art for the spring. Change in Pattern Load Cues.asm every: Code: dc.l Nem_HSpring ; horizontal spring dc.w $A460 dc.l Nem_VSpring ; vertical spring dc.w $A660 to: Code: dc.l Nem_HSpring ; horizontal spring dc.w $A460 dc.l Nem_VSpring ; vertical spring dc.w $A5E0 Insert artnem,_maps,_anim from Files.zip to your hack's folder. Insert this after “Nem_VSpring”: Code: Nem_DiagSpring: incbin artnem\springdiag.bin ; diagonal spring even Change this: Code: ; =========================================================================== Ani_obj41: include "_anim\obj41.asm" ; --------------------------------------------------------------------------- ; Sprite mappings - springs ; --------------------------------------------------------------------------- Map_obj41: include "_maps\obj41.asm" To this: ; =========================================================================== Ani_obj41: include "_anim\Springs.asm" ; --------------------------------------------------------------------------- ; Sprite mappings - springs ; --------------------------------------------------------------------------- Map_obj41: Map_obj41_Red: include "_maps\Red Springs.asm" Map_obj41_Green: include "_maps\Green Springs.asm" Insert Spring.xml from Files.ziip in Sonic 1 2005 INIs/Common folder. Change 11 color of palette line 1 (A,E,8) to 6,6,6 in the zone, where you want to use diagonal spring. The only art that use that color is SLZ orbinaut (but it is not very noticeable) and SBZ ballhog (it is very noticeable). Guide to free $26 tiles in GHZ: Insert this right after “dc.w PLC_EggmanSBZ2-ArtLoadCues, PLC_FZBoss-ArtLoadCues” in Pattern load cues.asm: Code: dc.w PLC_GHZBoss-ArtLoadCues Change this in PLC_GHZ2 in Patter load cues.asm: Code: dc.l Nem_Ball ; giant ball dc.w $7540 To this: Code: dc.l Nem_DiagSpring ; diagonal spring dc.w $7540 Insert this right at the end of Pattern load cues.asm: Code: ; --------------------------------------------------------------------------- ; Pattern load cues - act 3 boss ; --------------------------------------------------------------------------- PLC_GHZBoss: dc.w 3 dc.l Nem_Eggman ; Eggman main patterns dc.w $8000 dc.l Nem_Weapons ; Eggman's weapons dc.w $8D80 dc.l Nem_Prison ; prison capsule dc.w $93A0 dc.l Nem_Ball ; giant ball dc.w $7540 even Change this in loc_6ED0 in sonic1.asm: Code: moveq #$11,d0 To this: Code: moveq #$20,d0 ; load GHZ boss pattern load cues Change this code in Obj41_Init_DiagonallyUp and Obj41_Init_DiagonallyDown: Code: move.w #make_art_tile(ArtTile_ArtNem_DignlSprng,0,0),2(a0) To this: Code: move.w #$3AA,2(a0) That’s all for now! Credits: DeltaWooloo - Some Gramatical help
Yes, there is. Find s1obj.lst file in the objdef folder in sonED 2 project files and change this: Code: Description: Spring Num Bitfields: 5 Field 0: 00000001 Desc: unknown Desc: unknown Field 1: 00000010 Desc: red Desc: yellow Field 2: 00001100 Desc: unknown Desc: unknown Desc: unknown Desc: unknown Field 3: 01110000 Desc: up Desc: horizontal Desc: down Desc: invalid Desc: invalid Desc: invalid Desc: invalid Desc: invalid Field 4: 10000000 Desc: unknown Desc: unknown Into this: Code: Description: Spring Num Bitfields: 5 Field 0: 00000001 Desc: unknown Desc: unknown Field 1: 00000010 Desc: red Desc: yellow Field 2: 00001100 Desc: unknown Desc: unknown Desc: unknown Desc: unknown Field 3: 01110000 Desc: up Desc: horizontal Desc: down Desc: diagonalUp Desc: diagonalDown Desc: invalid Desc: invalid Desc: invalid Field 4: 10000000 Desc: unknown Desc: unknown Diagonal Springs in SonEd2 will use vertical spring graphics, but it's still better than nothing
Apologies for bumping this very old thread, but I think I did something wrong when implementing the springs. Diagonal springs that face upwards to the left do not behave correctly. I can stand on them, jump on them and use them only if I am at the tip of the springs, and if I am, indeed, at the tip of the springs, I can choose to go right and use the spring normally, or jump to control the height at which I go. Here is footage of the issue in action: And here is the code for the springs. (slightly modified because my disassembly does not support "@" labels) Spoiler: Block of code Code: Obj41_SlopeData_DiagUp: dc.b $10,$10,$10,$10,$10,$10,$10,$10,$10,$10,$10,$10, $E, $C, $A, 8 dc.b 6, 4, 2, 0,$FE,$FC,$FC,$FC,$FC,$FC,$FC,$FC; 16 ; =========================================================================== Obj41_DiagonallyUp: move.w #$1B,d1 move.w #$10,d2 move.w $8(a0),d4 lea Obj41_SlopeData_DiagUp(pc),a2 jsr (SolidObject2F).l btst #3,$22(a0) beq.w Obj41_AniDiagUp btst #0,$22(a0) bne.s Obj41_DiagFlipped subq.w #4,d0 cmp.w 8(a1),d0 blo.s Obj41_BounceDiagUp bra.s Obj41_AniDiagUp Obj41_DiagFlipped: addq.w #4,d0 cmp.w 8(a1),d0 blo.s Obj41_AniDiagUp ; =========================================================================== Obj41_BounceDiagUp: move.w #$500,$1C(a0) move.w $30(a0),$10(a1) move.w $30(a0),$12(a1) addq.w #6,8(a1) addq.w #6,$C(a1) bset #0,$22(a1) btst #0,$22(a0) bne.s Obj41_BDUFlipped bclr #0,$22(a1) subi.w #6*2,8(a1) neg.w $10(a1) Obj41_BDUFlipped: bset #1,$22(a1) bclr #3,$22(a1) bset #4,$22(a1) clr.w $3A(a0) move.b #2,$24(a1) bclr #3,$22(a0) move.b #$10,$1C(a1) clr.b $25(a0) move.w #$CC,d0 jsr (PlaySound_Special).l ; play spring sound Obj41_AniDiagUp: lea (Ani_obj41).l,a1 bra.w AnimateSprite ; =========================================================================== EDIT: In the end, I fixed the issue myself. If this issue occurs to you, comment out any reference to the "@flipped" or "Obj41_DiagFlipped" label, which in the case of my code, are the following: Right below the beq.w to Obj41_AniDiagUp: Code: btst #0,$22(a0) bne.s Obj41_BDUFlipped And everything between the branch to Obj41_AniDiagUp and the Obj41_BounceDiagUp label: Code: Obj41_DiagFlipped: addq.w #4,d0 cmp.w 8(a1),d0 blo.s Obj41_AniDiagUp