How to fix weird monitor collision errors

Discussion in 'Tutorials' started by CuckyDev, Jan 1, 2020.

  1. CuckyDev

    CuckyDev Newcomer Trialist

    May 1, 2016
    You ever notice some really weird issues with monitor collision? I'll go over most of what I can find and fix, I will be using the Sonic 2 Github Disassembly, but you should be able to translate between all 4 games as the code itself isn't very different between all of them, but please, don't just copy paste the code all over into your disassembly, it's very important to learn and understand what you're doing, and just because the code isn't *very* different between games doesn't mean they aren't.

    Spindash Uncurl
    This is a pretty well known one, when you spindash next to and into a monitor, you'll uncurl after you break it, what's up with that?
    upload_2020-1-1_14-30-25.png upload_2020-1-1_14-30-36.png

    What's happening is that Sonic is put into the air state when he breaks the monitor here, which doesn't make any sense because you're not standing on the monitor or anything like that. So why does it do this?

    Well, since you're touching the side of the monitor, it has the "pushing" bit set to know that you're next to it, which is expected, well lets look at the code to make you fall when it's broken. (Obj26_Break in the S2 Github Disassembly, routine 4 in Object 26 / Monitor Object)

        move.b    status(a0),d0
        andi.b    #standing_mask|pushing_mask,d0    ; is someone touching the monitor?
        beq.s    Obj26_SpawnIcon    ; if not, branch
        move.b    d0,d1
        andi.b    #p1_standing|p1_pushing,d1    ; is it the main character?
        beq.s    +        ; if not, branch
        andi.b    #$D7,(MainCharacter+status).w
        ori.b    #2,(MainCharacter+status).w    ; prevent Sonic from walking in the air
        andi.b    #p2_standing|p2_pushing,d0    ; is it the sidekick?
        beq.s    Obj26_SpawnIcon    ; if not, branch
        andi.b    #$D7,(Sidekick+status).w
        ori.b    #2,(Sidekick+status).w    ; prevent Tails from walking in the air
    Wait, what the hell? As you can see, the code checks if you're touching the monitor, pushing or standing on it, and if so, lazily clears your pushing bit and puts you in the air state. You see where the issue comes in?

    Well, if you want to fix it, just replace that entire block of code there with this.
        btst    d6,status(a0)    ; if we're standing on the object
        beq.s    +
        bset    #1,status(a1)    ; set 'in air' bit
        bclr    #3,status(a1)    ; clear 'should not fall' bit
        addq.b    #pushing_bit_delta,d6
        btst    d6,status(a0)    ; if we're pushing against the object
        beq.s    +
        bclr    #5,status(a1)    ; clear 'pushing' bit
        moveq    #p1_standing_bit,d6
        lea        (MainCharacter).w,a1
        bsr.s    Obj26_CheckRelease    ; Release player 1 -  d6 = p1 standing bit, a1 = player 1 address
        moveq    #p2_standing_bit,d6
        lea        (Sidekick).w,a1
        bsr.s    Obj26_CheckRelease    ; Release player 2 - d6 = p2 standing bit, a1 = player 2 address
    Basically, all this does is check the standing and pushing bits separately and handles them separately, so only if you're standing on the object will you be put into the air state, and only if you're pushing on the object will you have the pushing bit cleared.

    Now, if you spindash into the side of a monitor...
    upload_2020-1-1_14-57-35.png upload_2020-1-1_14-58-14.png

    Super Transformation Bug
    This is another common bug to come across, more so in Sonic 2, now what is it? If you jump from pushing against a monitor, and transform into Super Sonic, you'll play the walk animation instead of the transformation animation.

    upload_2020-1-1_15-6-5.png upload_2020-1-1_15-6-20.png

    Seems weird, huh? Why would pushing against the monitor screw up the transformation sequence? Well here's what happens, it has to do with the pushing and standing bits from before. Basically, monitors have specific conditions for calling the SolidObject routine, in Sonic 2 and beyond, it's just if you're not in the rolling animation (if you're already on the monitor it won't drop you to break it), and during this, the standing and pushing bits won't be cleared, meaning that once you enter the collision conditions (leaving the rolling animation), the object will still act as if you're pushing against it. The outcome of this is that if you're no longer in range of the object (such as by jumping away from it), it'll release you from pushing it, which sets you into the walk animation.

    That's a lot of stuff going wrong, ain't it? Let's fix that. Go to the player collision routines in the monitor code (SolidObject_Monitor_Sonic and SolidObject_Monitor_Tails in the S2 Github Disassembly), they should look about like this.

        btst    d6,status(a0)            ; is Sonic standing on the monitor?
        bne.s    Obj26_ChkOverEdge        ; if yes, branch
        cmpi.b    #AniIDSonAni_Roll,anim(a1)        ; is Sonic spinning?
        bne.w    SolidObject_cont        ; if not, branch
    There's the problem, when you're not in the rolling animation, it just returns without changing the collision state, this is a *really* easy fix, after the branch to SolidObject_cont, just add this bit of code (and do the same for the Tails routine).

        addq.b    #pushing_bit_delta,d6
        btst    d6,status(a0)    ; check if we're pushing
        beq.s    +
        bclr    #5,status(a1)    ; clear 'pushing' bit
        bclr    d6,status(a0)    ; clear object's 'pushing' bit
    This code should check the pushing bit and clear it when you're not meeting the collision conditions, meaning the above issue shouldn't happen, the standing bit won't be set as this code occurs since it branches to Obj26_ChkOverEdge, so there's no point in checking and clearing it.

    Now let's check the bug in-game...
    upload_2020-1-1_15-19-0.png upload_2020-1-1_15-19-13.png
    Yep, that's better!

    Errors on uphill slopes.
    I have the feeling Sonic Team knew about this one, because they made sure to avoid any scenarios where this would occur, and it was (possibly indirectly?) addressed in S&K, but there is an example of the issue in GHZ3 with Sonic 2's monitors.

    As you can see, you're completely going through the monitor until you reach the flat part, what's up with this? Well let's look at the code that checks if you should actually break a monitor. (Touch_Monitor in the S2 Github Disassembly, it's a part of the object touch routine, which is TouchResponse in the same disassembly)

        tst.w    y_vel(a0)    ; is Sonic moving upwards?
        bpl.s    loc_3F768    ; if not, branch
        move.w    y_pos(a0),d0
        subi.w    #$10,d0
        cmp.w    y_pos(a1),d0
        blo.s    return_3F78A
        neg.w    y_vel(a0)    ; reverse Sonic's y-motion
        move.w    #-$180,y_vel(a1)
        tst.b    routine_secondary(a1)
        bne.s    return_3F78A
        move.b    #4,routine_secondary(a1) ; set the monitor's routine counter
    ; ===========================================================================
        cmpa.w    #MainCharacter,a0
        beq.s    +
        tst.w    (Two_player_mode).w
        beq.s    return_3F78A
        cmpi.b    #AniIDSonAni_Roll,anim(a0)
        bne.s    return_3F78A
        neg.w    y_vel(a0)    ; reverse Sonic's y-motion
        move.b    #4,routine(a1)
        move.w    a0,parent(a1)
    Alright, well here's the issue, it checks if y_vel is positive, where if it's not, it'll try to bounce the monitor up, now this is usually right because well, you're probably jumping from below into the bottom of it in this case, but when you're on the ground, y_vel will be negative if you're going uphill, which means it will not check to break, but instead check to bounce the monitor. What S3K does is just completely remove bouncing all together, although this probably has more to do with reverse gravity, it fixes this error too, but I'm not a big fan of that, we can do better.

    The fix is ridiculously easy, just add this simple check right after the Touch_Monitor label.

        btst    #1,status(a0)
        beq.s    loc_3F768
    Now if you're on the ground, it will never check to bounce it, since that's weird and wouldn't work properly anyways, which should thwart this issue.

    Let's check in game... (using a modified layout)
    Perfect, now the monitor breaks as we're on the ground, and you can still bounce monitors from below!

    Well, that's all I have, hope this guide helps you with all the weird issues of monitors.
    Last edited: Jan 2, 2020