And finally, here's the big boss. Expect him to put up quite a fight once he is completed. Note that currently you cannot kill the last part.
First of all, the boss is not just simply there, it is entering with a few flashes. We reuse Dean's shot flash code for this. To mark the final boss entry we use a new bit in the level config byte:
;final boss intro
lda LEVEL_CONFIG
and #$08
beq ++
jsr HandleFinalBossIntro
jmp .NotDoneYet
++
The boss is compiled of several sprites which we create once the flashes are done. Also, the boss bit is removed to avoid restarting the boss intro.
!zone HandleFinalBossIntro
HandleFinalBossIntro
inc FINAL_INTRO_TIMER_DELAY
lda FINAL_INTRO_TIMER_DELAY
and #$03
beq ++
rts
++
inc FINAL_INTRO_TIMER
lda FINAL_INTRO_TIMER
cmp #5
beq .Flash
cmp #8
beq .Flash
cmp #10
beq .Flash
cmp #11
beq .SpawnBoss
rts
.Flash
;use dean's shot flash
lda #5
sta PLAYER_RELOAD_FLASH_POS
lda #1
sta VIC_BACKGROUND_COLOR
rts
.SpawnBoss
;disable intro flag
lda LEVEL_CONFIG
and #$f7
sta LEVEL_CONFIG
;spawn boss
lda #0
sta BOSS_PARTS_KILLED
lda #19
sta PARAM1
lda #6
sta PARAM2
lda #TYPE_BOSS7
sta PARAM3
jsr FindEmptySpriteSlot
jsr SpawnObject
stx PARAM10
;torso
lda #19
sta PARAM1
lda #10
sta PARAM2
lda #TYPE_BOSS5
sta PARAM3
jsr FindEmptySpriteSlot
jsr SpawnObject
lda #TYPE_BOSS_PART
sta SPRITE_ACTIVE,x
lda #128
sta SPRITE_STATE,x
jsr MoveSpriteUp
jsr MoveSpriteUp
lda PARAM10
sta SPRITE_VALUE,x
lda #2
sta VIC_SPRITE_COLOR,x
;left arm
lda #17
sta PARAM1
lda #9
sta PARAM2
lda #TYPE_BOSS3
sta PARAM3
jsr FindEmptySpriteSlot
jsr SpawnObject
lda #TYPE_BOSS_PART
sta SPRITE_ACTIVE,x
lda #0
sta SPRITE_STATE,x
lda PARAM10
sta SPRITE_VALUE,x
lda #2
sta VIC_SPRITE_COLOR,x
;right arm
lda #21
sta PARAM1
lda #9
sta PARAM2
lda #TYPE_BOSS4
sta PARAM3
jsr FindEmptySpriteSlot
jsr SpawnObject
lda #TYPE_BOSS_PART
sta SPRITE_ACTIVE,x
lda #0
sta SPRITE_STATE,x
lda PARAM10
sta SPRITE_VALUE,x
lda #2
sta VIC_SPRITE_COLOR,x
;left foot
lda #18
sta PARAM1
lda #13
sta PARAM2
lda #TYPE_BOSS2
sta PARAM3
jsr FindEmptySpriteSlot
jsr SpawnObject
lda #TYPE_BOSS_PART
sta SPRITE_ACTIVE,x
lda #0
sta SPRITE_STATE,x
lda PARAM10
sta SPRITE_VALUE,x
lda #2
sta VIC_SPRITE_COLOR,x
;right foot
lda #20
sta PARAM1
lda #13
sta PARAM2
lda #TYPE_BOSS
sta PARAM3
jsr FindEmptySpriteSlot
jsr SpawnObject
lda #TYPE_BOSS_PART
sta SPRITE_ACTIVE,x
lda #0
sta SPRITE_STATE,x
lda PARAM10
sta SPRITE_VALUE,x
lda #2
sta VIC_SPRITE_COLOR,x
jmp .Flash
Also, the boss is required to be killed in several steps. First the limbs, followed by torso and finally the head. This is done by setting states accordingly (remember, states >= 128 mark invincibility) increasing the killed body part count and making other parts vulnerable:
cpy #TYPE_BOSS_PART
bne ++
inc BOSS_PARTS_KILLED
lda BOSS_PARTS_KILLED cmp #5
beq .MakeBossHeadVulnerable
cmp #4
bne ++
;make boss torso vulnerable
ldy #1
-
lda SPRITE_ACTIVE,y
cmp #TYPE_BOSS_PART
beq +
iny
bne -
+
lda #0
sta SPRITE_STATE,y
jmp ++
.MakeBossHeadVulnerable
ldy #1
-
lda SPRITE_ACTIVE,y
cmp #TYPE_BOSS7
beq +
iny
bne -
+
lda #0
sta SPRITE_STATE,y
++
Since the boss object is controlled by the head but bigger with all parts attached the possible movement area needs to be limited (the boss body should not move outside the screen). Therefore a little check is added to all movement routines:
lda SPRITE_CHAR_POS_X,x
cmp MOVE_BORDER_LEFT
beq .BlockedLeft
..and similar to the other three directions.
The bosses limbs are shooting beams horizontally and diagonally. These require a few changes to the existing collision check routines to allow for partial beams:
;------------------------------------------------------------
;check player vs. beam H
; YPos in PARAM3
; player index in y
;------------------------------------------------------------
!zone CheckIsPlayerCollidingWithYPosH
CheckIsPlayerCollidingWithYPosH
lda SPRITE_ACTIVE,y
bne .PlayerIsActive
.PlayerNotActive
rts
.PlayerIsActive
cmp #TYPE_PLAYER_DEAN
beq +
cmp #TYPE_PLAYER_SAM
beq +
rts
+
lda SPRITE_STATE,y
cmp #128
bcs .PlayerNotActive
lda SPRITE_CHAR_POS_X,y
cmp PARAM1
bcc .PlayerNotActive
cmp PARAM2
bcs .PlayerNotActive
;compare char positions in y
lda PARAM3
cmp SPRITE_CHAR_POS_Y,y
beq .PlayerHit
clc
adc #1
cmp SPRITE_CHAR_POS_Y,y
beq .PlayerHit
sec
sbc #2
cmp SPRITE_CHAR_POS_Y,y
beq .PlayerHit
;not hit
rts
.PlayerHit
;player killed
jmp KillPlayer
;------------------------------------------------------------
;check player vs. diagonal beam
; X start in PARAM1
; Y start in PARAM2
; player index in y
;------------------------------------------------------------
!zone CheckIsPlayerCollidingWithDiagonalLLUR
CheckIsPlayerCollidingWithDiagonalLLUR
lda SPRITE_ACTIVE,y
bne .PlayerIsActive
.PlayerNotActive
rts
.PlayerIsActive
lda SPRITE_STATE,y
cmp #128
bcs .PlayerNotActive
;compare char positions in x
lda PARAM1
sec
sbc SPRITE_CHAR_POS_X,y
bpl .PositiveX
;player is to the right
rts
.PositiveX
sta PARAM3
lda PARAM2
sec
sbc SPRITE_CHAR_POS_Y,y
bpl .PositiveY
lda SPRITE_CHAR_POS_Y,y
sec
sbc PARAM2
.PositiveY
sta PARAM4
lda PARAM3
cmp PARAM4
beq .PlayerHit
lda PARAM3
sec
sbc PARAM4
bpl .PositiveDelta
lda PARAM4
sec
sbc PARAM3
.PositiveDelta
cmp #1
beq .PlayerHit
;not hit
rts
.PlayerHit
;player killed
jmp KillPlayer
;------------------------------------------------------------
;check player vs. diagonal beam
; X start in PARAM1
; Y start in PARAM2
; player index in y
;------------------------------------------------------------
!zone CheckIsPlayerCollidingWithDiagonalULLR
CheckIsPlayerCollidingWithDiagonalULLR
lda SPRITE_ACTIVE,y
bne .PlayerIsActive
.PlayerNotActive
rts
.PlayerIsActive
lda SPRITE_STATE,y
cmp #128
bcs .PlayerNotActive
;compare char positions in x
lda PARAM1
sec
sbc SPRITE_CHAR_POS_X,y
bpl .PlayerNotActive
;player is to the right
lda SPRITE_CHAR_POS_X,y
sec
sbc PARAM1
sta PARAM3
lda PARAM2
sec
sbc SPRITE_CHAR_POS_Y,y
bpl .PositiveY
lda SPRITE_CHAR_POS_Y,y
sec
sbc PARAM2
.PositiveY
sta PARAM4
lda PARAM3
cmp PARAM4
beq .PlayerHit
lda PARAM3
sec
sbc PARAM4
bpl .PositiveDelta
lda PARAM4
sec
sbc PARAM3
.PositiveDelta
cmp #1
beq .PlayerHit
;not hit
rts
.PlayerHit
;player killed
jmp KillPlayer
All the beams are controlled by the boss head. Attack states, firing and collision checks:
;------------------------------------------------------------
;boss #7
;state = 128 -> random movements
;state = 129 -> attack with beams
;------------------------------------------------------------
!zone BehaviourBoss7
BehaviourBoss7
BOSS_MOVE_SPEED = 1
lda SPRITE_STATE,x
beq .RandomMovements
cmp #129
bne +
jmp .AttackWithBeams
+
.RandomMovements
inc SPRITE_MODE_POS,x
bne +
;attack mode
lda #129
sta SPRITE_STATE,x
rts
+
lda SPRITE_MOVE_POS,x
bne .DoMove
;find new random dir
lda #25
sta SPRITE_MOVE_POS,x
jsr GenerateRandomNumber
and #$01
sta SPRITE_DIRECTION,x
jsr GenerateRandomNumber
and #$01
sta SPRITE_DIRECTION_Y,x
.DoMove
dec SPRITE_MOVE_POS,x
lda #12
sta MOVE_BORDER_LEFT
lda #39 - 12
sta MOVE_BORDER_RIGHT
lda #4
sta MOVE_BORDER_TOP
lda #12
sta MOVE_BORDER_BOTTOM
lda SPRITE_DIRECTION,x
beq ++
jsr ObjectMoveLeftBlocking
beq .DoMoveY
;move other parts
lda #5
sta PARAM10
-
inx
jsr ObjectMoveLeft
dec PARAM10
bne -
jmp .DoMoveY
++
jsr ObjectMoveRightBlocking
beq .DoMoveY
;move other parts
lda #5
sta PARAM10
-
inx
jsr ObjectMoveRight
dec PARAM10
bne -
.DoMoveY
ldx CURRENT_INDEX
lda SPRITE_DIRECTION_Y,x
beq ++
jsr ObjectMoveUpBlocking
beq .DoMoveDone
;move other parts
lda #5
sta PARAM10
-
inx
jsr ObjectMoveUp
dec PARAM10
bne -
jmp .DoMoveDone
++
jsr ObjectMoveDownBlocking
beq .DoMoveDone
;move other parts
lda #5
sta PARAM10
-
inx
jsr ObjectMoveDown
dec PARAM10
bne -
.DoMoveDone
lda #0
sta MOVE_BORDER_LEFT
sta MOVE_BORDER_TOP
lda #39
sta MOVE_BORDER_RIGHT
lda #23
sta MOVE_BORDER_BOTTOM
rts
.AttackWithBeams
inc SPRITE_MOVE_POS,x
lda SPRITE_MOVE_POS,x
and #$03
beq +
rts
+
inc SPRITE_MODE_POS,x
lda SPRITE_MODE_POS,x
cmp #5
bcs +
jmp .BeamNotDangerous
+
cmp #12
bcc +
jmp .BeamNotDangerous
+
;does player hit beam? ;modify x to point to arm object ;TODO - only check left/right segment!
lda SPRITE_ACTIVE + 2,x
beq ++
;left arm
lda SPRITE_CHAR_POS_Y,x
clc
adc #3
sta PARAM3
lda #0
sta PARAM1
lda SPRITE_CHAR_POS_X,x
sec
sbc #2
sta PARAM2
ldy #0
jsr CheckIsPlayerCollidingWithYPosH
ldy #1
jsr CheckIsPlayerCollidingWithYPosH
++
lda SPRITE_ACTIVE + 3,x
beq ++
;right arm
lda SPRITE_CHAR_POS_Y,x
clc
adc #3
sta PARAM3
lda SPRITE_CHAR_POS_X,x
clc
adc #2
sta PARAM1
lda #39
sta PARAM2
ldy #0
jsr CheckIsPlayerCollidingWithYPosH
ldy #1
jsr CheckIsPlayerCollidingWithYPosH
++
lda SPRITE_ACTIVE + 4,x
beq ++
lda SPRITE_CHAR_POS_Y,x
clc
adc #8
sta PARAM2
lda SPRITE_CHAR_POS_X,x
sec
sbc #1
sta PARAM1
ldy #0
jsr CheckIsPlayerCollidingWithDiagonalLLUR
ldy #1
jsr CheckIsPlayerCollidingWithDiagonalLLUR
++
lda SPRITE_ACTIVE + 5,x
beq ++
lda SPRITE_CHAR_POS_X,x
clc
adc #2
sta PARAM1
lda SPRITE_CHAR_POS_Y,x
clc
adc #8
sta PARAM2
ldy #0
jsr CheckIsPlayerCollidingWithDiagonalULLR
ldy #1
jsr CheckIsPlayerCollidingWithDiagonalULLR
++
.BeamNotDangerous
lda SPRITE_MODE_POS,x
cmp #5
beq .BeamStep1 cmp #6
beq .BeamStep2 cmp #7
beq .BeamStep3 cmp #8
beq .BeamStep4 cmp #9
beq .BeamStep3 cmp #10
beq .BeamStep4 cmp #11
beq .BeamStep3 cmp #12
bne +
lda #128
sta SPRITE_STATE,x
lda #0
sta SPRITE_MODE_POS,x
;remove beam
lda #0
sta PARAM3
lda SPRITE_CHAR_POS_Y,x
clc
adc #3
sta PARAM4
lda #39
sta PARAM5
jsr RestoreBeamHSegment
lda SPRITE_CHAR_POS_X,x
sec
sbc #1
sta PARAM3
lda SPRITE_CHAR_POS_Y,x
clc
adc #8
sta PARAM4
jsr RestoreBeamDiagonalLLUR
lda SPRITE_CHAR_POS_X,x
clc
adc #2
sta PARAM3
lda SPRITE_CHAR_POS_Y,x
clc
adc #8
sta PARAM4
jsr RestoreBeamDiagonalULLR
+
rts
.BeamStep1
;beam
ldy #BEAM_TYPE_DARK
jmp .HandleBeam
.BeamStep2
;beam
ldy #BEAM_TYPE_MEDIUM
jmp .HandleBeam
.BeamStep3
;beam
ldy #BEAM_TYPE_LIGHT
jmp .HandleBeam
.BeamStep4
;beam
ldy #BEAM_TYPE_LIGHT2
jmp .HandleBeam
.HandleBeam
;PARAM1 = beam h char ;PARAM2 = beam color ;PARAM3 = x char pos ;PARAM4 = y char pos ;PARAM5 = x end pos
tya
pha
lda BEAM_CHAR_H,y
sta PARAM1
lda BEAM_COLOR,y
sta PARAM2
;left arm
lda SPRITE_ACTIVE + 2,x
beq ++
lda #0
sta PARAM3
lda SPRITE_CHAR_POS_Y,x
clc
adc #3
sta PARAM4
lda SPRITE_CHAR_POS_X,x
sec
sbc #2
sta PARAM5
jsr DrawBeamHSegment
++
lda SPRITE_ACTIVE + 3,x
beq ++
lda SPRITE_CHAR_POS_X,x
clc
adc #3
sta PARAM3
lda SPRITE_CHAR_POS_Y,x
clc
adc #3
sta PARAM4
lda #39
sta PARAM5
jsr DrawBeamHSegment
++
;diagonal left
pla
pha
tay
lda SPRITE_ACTIVE + 4,x
beq ++
lda BEAM_CHAR_NESW,y
sta PARAM1
lda SPRITE_CHAR_POS_X,x
sec
sbc #1
sta PARAM3
lda SPRITE_CHAR_POS_Y,x
clc
adc #8
sta PARAM4
jsr DrawBeamDiagonalLLUR
++
pla
tay
lda SPRITE_ACTIVE + 5,x
beq ++
lda BEAM_CHAR_NWSE,y
sta PARAM1
lda SPRITE_CHAR_POS_X,x
clc
adc #2
sta PARAM3
lda SPRITE_CHAR_POS_Y,x
clc
adc #8
sta PARAM4
jsr DrawBeamDiagonalULLR
++
rts
Firing the beams (and restoring background) is handled by new sub routines:
;PARAM1 = beam h char
;PARAM2 = beam color
;PARAM3 = x char pos
;PARAM4 = y char pos
!zone DrawBeamDiagonalLLUR
DrawBeamDiagonalLLUR
.NextLine
ldy PARAM4
lda SCREEN_LINE_OFFSET_TABLE_LO,y
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
lda SCREEN_LINE_OFFSET_TABLE_HI,y
sta ZEROPAGE_POINTER_1 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
;left
ldy PARAM3
lda PARAM1
sta (ZEROPAGE_POINTER_1),y
lda PARAM2
sta (ZEROPAGE_POINTER_2),y
inc PARAM4
lda PARAM4
cmp #22
beq .LowerPartDone
;left border reached?
lda PARAM3
beq .LowerPartDone
dec PARAM3
jmp .NextLine
.LowerPartDone
rts
;PARAM3 = x char pos;PARAM4 = y char pos
!zone RestoreBeamDiagonalLLUR
RestoreBeamDiagonalLLUR
.NextLine
ldy PARAM4
lda SCREEN_LINE_OFFSET_TABLE_LO,y
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
sta ZEROPAGE_POINTER_3
sta ZEROPAGE_POINTER_4
lda SCREEN_LINE_OFFSET_TABLE_HI,y
sta ZEROPAGE_POINTER_1 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
sec
sbc #( ( SCREEN_COLOR - SCREEN_BACK_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_3 + 1
sec
sbc #( ( SCREEN_BACK_CHAR - SCREEN_BACK_COLOR ) >> 8 )
sta ZEROPAGE_POINTER_4 + 1
;left
ldy PARAM3
lda (ZEROPAGE_POINTER_3),y
sta (ZEROPAGE_POINTER_1),y
lda (ZEROPAGE_POINTER_4),y
sta (ZEROPAGE_POINTER_2),y
inc PARAM4
lda PARAM4
cmp #22
beq .LowerPartDone
;left border reached?
lda PARAM3
beq .LowerPartDone
dec PARAM3
jmp .NextLine
.LowerPartDone
rts
;PARAM1 = beam h char;PARAM2 = beam color;PARAM3 = x char pos;PARAM4 = y char pos
!zone DrawBeamDiagonalULLR
DrawBeamDiagonalULLR
.NextLine
ldy PARAM4
lda SCREEN_LINE_OFFSET_TABLE_LO,y
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
lda SCREEN_LINE_OFFSET_TABLE_HI,y
sta ZEROPAGE_POINTER_1 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
ldy PARAM3
lda PARAM1
sta (ZEROPAGE_POINTER_1),y
lda PARAM2
sta (ZEROPAGE_POINTER_2),y
inc PARAM4
lda PARAM4
cmp #22
beq .LowerPartDone
;left border reached?
lda PARAM3
cmp #39
beq .LowerPartDone
inc PARAM3
jmp .NextLine
.LowerPartDone
rts
;PARAM3 = x char pos;PARAM4 = y char pos
!zone RestoreBeamDiagonalULLR
RestoreBeamDiagonalULLR
.NextLine
ldy PARAM4
lda SCREEN_LINE_OFFSET_TABLE_LO,y
sta ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_2
sta ZEROPAGE_POINTER_3
sta ZEROPAGE_POINTER_4
lda SCREEN_LINE_OFFSET_TABLE_HI,y
sta ZEROPAGE_POINTER_1 + 1
clc
adc #( ( SCREEN_COLOR - SCREEN_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_2 + 1
sec
sbc #( ( SCREEN_COLOR - SCREEN_BACK_CHAR ) >> 8 )
sta ZEROPAGE_POINTER_3 + 1
sec
sbc #( ( SCREEN_BACK_CHAR - SCREEN_BACK_COLOR ) >> 8 )
sta ZEROPAGE_POINTER_4 + 1
ldy PARAM3
lda (ZEROPAGE_POINTER_3),y
sta (ZEROPAGE_POINTER_1),y
lda (ZEROPAGE_POINTER_4),y
sta (ZEROPAGE_POINTER_2),y
inc PARAM4
lda PARAM4
cmp #22
beq .LowerPartDone
;left border reached?
lda PARAM3
cmp #39
beq .LowerPartDone
inc PARAM3
jmp .NextLine
.LowerPartDone
rts
The behaviour of the boss parts is surprisingly simple, just react on being hit:
;------------------------------------------------------------
;boss helper
;------------------------------------------------------------
!zone BehaviourBossHelper
BehaviourBossHelper
lda SPRITE_HITBACK,x
beq .NoHitBack
dec SPRITE_HITBACK,x
ldy SPRITE_HITBACK,x
lda BOSS_FLASH_TABLE,y
sta VIC_SPRITE_COLOR,x
cpy #0
bne .NoHitBack
;make vulnerable again
lda #0
sta SPRITE_STATE,x
lda #2
sta VIC_SPRITE_COLOR,x
.NoHitBack
rts
Have fun!