Tuesday, September 26, 2017

Responsive object collisions, part 2

Last time, we saw how responsive object collision is further split into four collision types, depending on the high bits of the object's collision_flags attribute. Each collision type elicits a different type of response from the object, hence the term "responsive collision".
Touch_ChkValue:
    move.b  collision_flags(a1),d1      ; Get its collision_flags
    andi.b  #$C0,d1                     ; Get only collision type bits
    beq.w   Touch_Enemy                 ; If 00, enemy, branch
    cmpi.b  #$C0,d1
    beq.w   Touch_Special               ; If 11, "special thing for starpole", branch
    tst.b   d1
    bmi.w   Touch_ChkHurt               ; If 10, "harmful", branch
    ...                                 ; If 01...
Let's start with an easy one. When the collision type is 10, the code branches to Touch_ChkHurt, which is responsible for collision with most harmful objects: things like enemy projectiles, fireballs, lasers. This comes with built-in checks for stuff like invincibility and immunity granted by an elemental barrier.
Touch_ChkHurt:
    move.b  status_secondary(a0),d0
    andi.b  #$73,d0                     ; Does player have any shields or is invincible?
    beq.s   Touch_ChkHurt_NoPowerUp     ; If not, branch
    and.b   shield_reaction(a1),d0      ; Does one of the player's shields grant immunity to this object??
    bne.s   Touch_ChkHurt_Return        ; If so, branch
    ...

When the collision type is 00, the code branches to Touch_Enemy, which handles collision with most enemies, as well as bosses. This comes with all the rigamole necessary to determine whether the player can currently damage their foe. In the event that none of these checks pass, the code defaults to calling Touch_ChkHurt.
Touch_Enemy:
    btst    #2,status_secondary(a0)     ; Is player invincible?
    bne.s   .checkhurtenemy             ; If so, branch
    cmpi.b  #9,anim(a0)                 ; Is player in their spin dash animation?
    beq.s   .checkhurtenemy             ; If so, branch
    cmpi.b  #2,anim(a0)                 ; Is player in their rolling animation?
    beq.s   .checkhurtenemy             ; If so, branch

    cmpi.b  #2,character_id(a0)         ; Is player Knuckles?
    bne.s   .notknuckles                ; If not, branch
    cmpi.b  #1,double_jump_flag(a0)     ; Is Knuckles gliding?
    beq.s   .checkhurtenemy             ; If so, branch
    cmpi.b  #3,double_jump_flag(a0)     ; Is Knuckles sliding across the ground after gliding?
    beq.s   .checkhurtenemy             ; If so, branch
    bra.w   Touch_ChkHurt
; ---------------------------------------------------------------------------

.notknuckles:
    cmpi.b  #1,character_id(a0)             ; Is player Tails
    bne.w   Touch_ChkHurt                   ; If not, branch
    tst.b   double_jump_flag(a0)            ; Is Tails flying ("gravity-affected")
    beq.w   Touch_ChkHurt                   ; If not, branch
    btst    #Status_Underwater,status(a0)   ; Is Tails underwater
    bne.w   Touch_ChkHurt                   ; If not, branch
    ...
However, if any of the checks pass, this code goes on to increase the current bonus chain, add the appropriate number of points to the player's score, and make the enemy explode. How it achieves the latter is quite interesting: it overwrites the enemy's own code pointer with the address for the explosion object, and resets the object's routine counter to zero, ensuring the explosion is initialized properly, and that it spawns both a small animal and a score popup.
    move.w  (Chain_bonus_counter).w,d0      ; Get copy of chain bonus counter
    addq.w  #2,(Chain_bonus_counter).w      ; Add 2 to chain bonus counter
    cmpi.w  #6,d0                           ; Has the counter already surpassed 5?
    blo.s   loc_101C4                       ; If not, branch
    moveq   #6,d0                           ; Cap counter at 6

loc_101C4:
    move.w  d0,objoff_3E(a1)
    move.w  Enemy_Points(pc,d0.w),d0        ; Get appropriate number of points
    cmpi.w  #16*2,(Chain_bonus_counter).w   ; Have 16 enemies been destroyed?
    blo.s   loc_101DE                       ; If not, branch
    move.w  #1000,d0                        ; Fix bonus to 10000 points
    move.w  #$A,objoff_3E(a1)

loc_101DE:
    movea.w a0,a3
    bsr.w   HUD_AddToScore
    move.l  #Obj_Explosion,(a1)             ; Create enemy destruction explosion
    move.b  #0,routine(a1)
    ...
Now, obviously, the explosion object doesn't need collision, so it never calls the Add_SpriteToCollisionResponseList function. This ensures that Touch_Enemy only runs once per enemy... or does it?


Remember how I mentioned that in a Sonic and Tails game, the collision response list is actually processed twice, once by each player? If Sonic and Tails happen to defeat the same enemy on the same exact frame, Touch_Enemy actually runs twice, the bonus chain is increased twice and the player is awarded points as if two enemies were defeated.

Next time, we'll take a look at the remaining two collision types.

1 comment:

  1. Perhaps I can use this in Launch Base Zone to speed up my infinite lives... (evil laugh)

    ReplyDelete