[Sonic 1 Hivebrain] How-To Port Sonic 2 Spring Object into Sonic 1

Discussion in 'Tutorials' started by RandomName, Aug 31, 2020.

  1. RandomName

    RandomName Newcomer Member

    Joined:
    Jun 3, 2020
    Messages:
    21
    Location:
    Russia
    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:
    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);

    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
     

    Attached Files:

  2. Elijah Rosado

    Elijah Rosado Newcomer Trialist

    Joined:
    Feb 8, 2019
    Messages:
    9
    Location:
    United States
    Is there a way that I can use this on SonED2? SonLVL doesn't work on my computer.
     
  3. RandomName

    RandomName Newcomer Member

    Joined:
    Jun 3, 2020
    Messages:
    21
    Location:
    Russia
    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
     
    DeltaWooloo likes this.
  4. Elijah Rosado

    Elijah Rosado Newcomer Trialist

    Joined:
    Feb 8, 2019
    Messages:
    9
    Location:
    United States
    It works! Thank you!
    Sonic 2 springs in Sonic 1 - Project 128.PNG
     
  5. giovanni.gen

    giovanni.gen It's still Joe-vanni, not Geo-vanni. Member

    Joined:
    Apr 16, 2015
    Messages:
    313
    Location:
    Italy
    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)
    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
     
    Last edited: Oct 13, 2021
    DeltaWooloo and RandomName like this.