Proper Invisible Solids Detection While Ducking - RetroKoH/S1Fixed GitHub Wiki
(Original guide by Devon)
Source: Sonic Retro thread
Commit: 8e930d0
There is an oversight with invisible solid block collision. At the top of its main routine, it checks if it's offscreen:
Invis_Solid: ; Routine 2
bsr.w ChkObjectVisible
bne.s .chkdel
There is a problem though. If we go to the routine:
; ---------------------------------------------------------------------------
; Subroutine to check if an object is off screen
; output:
; d0 = flag set if object is off screen
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
ChkObjectVisible:
move.w obX(a0),d0 ; get object x-position
sub.w (v_screenposx).w,d0 ; subtract screen x-position
bmi.s .offscreen
cmpi.w #320,d0 ; is object on the screen?
bge.s .offscreen ; if not, branch
move.w obY(a0),d1 ; get object y-position
sub.w (v_screenposy).w,d1 ; subtract screen y-position
bmi.s .offscreen
cmpi.w #224,d1 ; is object on the screen?
bge.s .offscreen ; if not, branch
moveq #0,d0 ; set flag to 0
rts
.offscreen:
moveq #1,d0 ; set flag to 1
rts
; End of function ChkObjectVisible
It doesn't take into account the size of the hitbox, just the position. Now, it's not very common at all to run into this issue, but you can easily see the effect if you duck the camera down in this area in Marble Zone Act 2:
To fix this, add this routine that accounts for the hitbox:
; ---------------------------------------------------------------------------
; Subroutine to check if an object is off screen
; Takes both width and height into account
; output:
; d0 = flag set if object is off screen
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
ChkSizedObjVisible:
; Ralakimus Checking For Solids Fix
moveq #0,d1 ; Get object's width
move.b obActWid(a0),d1
move.w obX(a0),d0 ; Get object's X position
sub.w (v_screenposx).w,d0 ; Get object's X position on screen
add.w d1,d0 ; Is the right side of the object on screen?
bmi.s .offscreen2 ; If not, branch
add.w d1,d1 ; Is the left side of the object on screen?
sub.w d1,d0
cmpi.w #320,d0
bge.s .offscreen2 ; If not, branch
moveq #0,d1 ; Get object's height
move.b obHeight(a0),d1
move.w obY(a0),d0 ; Get object's Y position
sub.w (v_screenposy).w,d0 ; Get object's Y position on screen
add.w d1,d0 ; Is the bottom side of the object on screen?
bmi.s .offscreen2 ; If not, branch
add.w d1,d1 ; Is the top side of the object on screen?
sub.w d1,d0
cmpi.w #224,d1
bge.s .offscreen2 ; If not, branch
moveq #0,d0 ; Visible
rts
.offscreen2:
moveq #1,d0 ; Not visible
rts
; End of function ChkSizedObjVisible
Now go back and change the branch call from ChkObjectVisible to ChkSizedObjVisible:
Invis_Solid: ; Routine 2
! bsr.w ChkSizedObjVisible
bne.s .chkdel
Problem solved! Take a look: