;;-----------------------------LICENSE NOTICE------------------------------------
;;  This file is part of Dragon Attack - An entry for CPCRetroDev2016
;;  Copyright (C) 2016  Paul Kooistra
;;
;;  This program is free software: you can redistribute it and/or modify
;;  it under the terms of the GNU General Public License as published by
;;  the Free Software Foundation, either version 3 of the License, or
;;  (at your option) any later version.
;;
;;  This program is distributed in the hope that it will be useful,
;;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;  GNU General Public License for more details.
;;
;;  You should have received a copy of the GNU General Public License
;;  along with this program.  If not, see <http://www.gnu.org/licenses/>.
;;
;;  For questions about the source you can PM me, Axelay, on the CPCWiki forums
;;-------------------------------------------------------------------------------
; version 2 adds checks for player exploding in clear and print
; version 6 is for narrow nosed version with lower cockpit

; clear player routine below exits to this if player is exploding and only 'shrapnel' needs clearing
.CPlySkipHighClearPlyExp
    ld bc,&60f ; sprite and explosion has 6 addresses, c holds mask to remove all but bullet colour
.CPlyExpLoop
    pop hl ; get screen address
    ld a,(hl) ; get byte
    and a,c ; and out the player colours
    ld (hl),a ; write byte back
    set 3,h ; repeat for byte below
    ld a,(hl)
    and a,c
    ld (hl),a
    djnz CPlyExpLoop
    jp ClrPlyBullets ; no return to main clearplayer routine at point of bullet clearing

.ClearPlayer
; called from interrupt, so stack used for reading addresses
; inactive bullets assumed to provide legal dummy to avoid checking
    ld (CPlyExit+1),sp ; preserve sp at end of routine
    ld a,(WorkScr)
    bit 6,a
    jr z,CPlyHighClear ; inverted from normal test, want to operate on active screen
; current active screen is low &8080
    ld sp,EPlyLowAddr
    ld ix,EPlyLowAddr
    ld d,LowScrLUTable/256 ; put high byte of scr addr lu table in d for use later
; now check for whether an explosion frame or sprite frame is needed to be cleared from last write
    ld hl,PlayerExp
    bit 0,(hl) ; check exlosion 'tag' bit
    jr z,CPlySkipHighClearSprite ; was player sprite last frame
    res 0,(hl) ; clear the tag, and then divert to explosion clear
    jr CPlySkipHighClearPlyExp
.CPlyHighClear
; current active screen is high &c080
    ld sp,EPlyHighAddr
    ld ix,EPlyHighAddr
    ld d,HighScrLUTable/256
; now check for whether an explosion frame or sprite frame is needed to be cleared
    ld hl,PlayerExp
    bit 1,(hl) ; check exlosion 'tag' bit
    jr z,CPlySkipHighClearSprite ; was player sprite last frame
    res 1,(hl) ; clear the tag, and then divert to explosion clear
    jr CPlySkipHighClearPlyExp
.CPlySkipHighClearSprite
    ld bc,&30f ; sprite has 6 addresses, only 3 identical clears in the loop though, c mask to remove all but bullet colours
; first unrolled line pair
    pop hl ; get screen addr
    inc l
    ld a,(hl) ; get byte
    and a,c ; mask out player sprite colours
    ld (hl),a ; write back byte
    set 3,h ; repeat for line below - player sprite limited to even y co-ords, so set can be used
    ld a,(hl)
    and a,c
    ld (hl),a
; second unrolled line pair
    pop hl
    inc l
    ld a,(hl)
    and a,c
    ld (hl),a
    set 3,h
    ld a,(hl)
    and a,c
    ld (hl),a
; next 3 line pairs identical, so looped
.CPlyLoop
    pop hl
    ld a,(hl)
    and a,c
    ld (hl),a
    inc l
    ld a,(hl)
    and a,c
    ld (hl),a
    inc l
    ld a,(hl)
    and a,c
    ld (hl),a
    set 3,h
    ld a,(hl)
    and a,c
    ld (hl),a
    dec l
    ld a,(hl)
    and a,c
    ld (hl),a
    dec l
    ld a,(hl)
    and a,c
    ld (hl),a
    djnz CPlyLoop
; third unrolled line pair - the 6th and final line pair
    pop hl
    ld a,(hl)
    and a,c
    ld (hl),a
    inc l
    ld a,(hl)
    and a,c
    ld (hl),a
    inc l
    ld a,(hl)
    and a,c
    ld (hl),a
    set 3,h
    dec l
    ld a,(hl)
    and a,c
    ld (hl),a
.ClrPlyBullets
; now remove player bullets
    ld bc,&6cf ; 6 player bullets, c mask will remove white colour only
.ClrPlyBulLoop
; process below is same as used for player, player bullets also aligned to even y co-ords, bullets 4 pixels high
    pop hl ; remove first two pixels
    ld a,(hl)
    and a,c
    ld (hl),a
    set 3,h
    ld a,(hl)
    and a,c
    ld (hl),a
; remove second two pixels
    pop hl
    ld a,(hl)
    and a,c
    ld (hl),a
    set 3,h
    ld a,(hl)
    and a,c
    ld (hl),a
;
    djnz ClrPlyBulLoop
; finsihed removing player sprite and bullets
; now time to calc new addr for new screen position
    ld a,d ; grab high byte of current addr lu table
    exx
    ld h,a ; and put in h'
    exx
; first calc player bullets
    ld hl,PlayerYX+15 ; start at end of table and work way down
;
    ld iy,CalcPlyBulReturn ; using stack to push to address, so put return address in iy
    ld bc,&602 ; 6 player bullets, y address has 2 added to it to start from bottom
.CalcPlyBulLoop
    ld a,(hl) ; get x
; assume either valid (0-127) or 128
    and a,&7f ; remove bit 7 if off screen
    srl a ; make x valid screen address offset (0-63)
    dec l ; point to y
    ex af,af' ; preserve x
    ld a,(hl) ; get y
    add a,c ; add sprite height stored in c (c = offset for bullet height)
    exx
    ld l,a ; hl' now points to bottom of sprite lu table for scr addr for this sprite
    ex af,af' ; get x back
    ld c,a ; and store in c'
    jp GetPlyBulAddr ; jump to unrolled loop of address calculation used by all sprites at end of this file
.CalcPlyBulReturn ; will return here from address preloaded in iy, exx has been issued prior to return
    dec l ; move to next bullet x
    djnz CalcPlyBulLoop
; have now calculated player bullet addresses, time to work out whether player sprite or explosion
; should be displayed and addresses calculated
    ld a,(PlayerExp)
    cp a,4
    jp nc,PlayerExplodingDisplay ; player exploding, so print shrapnel
; player is not exploding, so perform collision checks, and display sprite normally
    ld iy,CalcPlayerReturn ; change iy to next reurn address for unrolled calc loop
    ld a,(PlayerTestX)
; can assume is valid (0-127)
    srl a ; make byte offset (0-63)
    ex af,af' ; preserve x
    ld a,(PlayerTestY)
    add a,10 ; add height-2 to y to start at bottom
    exx
    ld l,a ; hl' now points to lowest scr address in lu table required for current player pos
    ex af,af' ; get x back
    ld c,a ; store in c for addr calc routine
    jp GetSpriteAddrPly ; and jump to it
.CalcPlayerReturn ; iy ld above ensures GetSpriteAddrPly returns here
; have calculated addresses, time to do bullet collisions on player
; this simply involves checking the bitmap under the player, as the enemy bullets also
; have exclusive use of one bit of the screen bitmap
    ld a,(PlayerTestX)
    ld b,a ; preserve player x for grazing check later
    rrca
    jp c,CheckPlayerCollRight ; if x is odd, player sprite is using right shifted bitmap
; is left, so check left sprite centre pixels for collisions
; but first, check if player has just started a new life and is invulnerable
    ld a,(PlayerHitTimer)
    ld c,a ; preserve Player hit timer for later
    rlca ; check bit 7, which indicates game over in progress
    jp c,PrintPlayerSkip ; if game over, ignore collision and dont print player
    or a ; now check if timer non zero, which indicates player invulnerable
    jr nz,CheckPlyGrazeLeft ; if so, skip kill collision check, go straight to grazing check
.CheckPlyKillHitLeft
; player not invulnerable, so check for kill hit
; only need to check left bit of centre byte
    ld l,(ix+6)
    ld h,(ix+7)
    inc l ; move to centre byte of sprite
    set 3,h ; and down one byte to get to vulnerable white cockpit pixel
    bit 1,(hl)
    jp nz,PlayerCollided ; if bullet pixel set, player has collided
    ld l,(ix+8)
    ld h,(ix+9)
    inc l ; get to centre byte
    bit 1,(hl) ; and check second cockpit pixel
    jp nz,PlayerCollided
.CheckPlyGrazeLeft
; sp is still pointing to top of scr add list for player sprite, so pop addresses for grazing check
; check 5 pixels centred on player nose
    pop hl
    ld a,(hl)
    inc l
    or a,(hl)
    inc l
    bit 1,(hl)
; no set 3,h required, as enemy bullets are all at least two pixels high, so only need check every other line
    jp nz,PlyIsGrazing
; now do second top line pair, again checking 5 pixels centred on player nose
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    bit 1,(hl)
    jp nz,PlyIsGrazing
; for next three line pairs, we check 7 pixels across, centred on player
    ex af,af' ; preserve a so can text player x
    ld a,b ; get x back to check
    or a ; if player is left shifted in bytes, check if player hard against left of screen
    jr nz,ChkPlyGrazeLeftFull ; can perform full check
; dont check byte to left of player as is hard left on screen, so that byte is 'offscreen'
    ex af,af' ; get current grazing check byte back
; third line pair
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
; fourth line pair
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
; now do fifth line pair
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    jr ChkPlyGrazeLeftCommon ; re-enter at common check code
.ChkPlyGrazeLeftFull
; check the full 7 pixels across, centred on player sprite
    ex af,af'
; third line pair
    pop hl
    dec l
    bit 0,(hl) ; right pixel of byte to left of player
    jp nz,PlyIsGrazing
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
; fourth line pair
    pop hl
    dec l
    bit 0,(hl) ; right pixel of byte to left of player
    jp nz,PlyIsGrazing
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
; now do second bottom line pair
    pop hl
    dec l
    bit 0,(hl)
    jp nz,PlyIsGrazing
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
.ChkPlyGrazeLeftCommon
; no check for grazing on bottom line pair of player
    and a,3 ; mask out everything except enemy bullet bits
    jp nz,PlyIsGrazing
    jp PlyNoCol ; was no collision or grazing
.CheckPlayerCollRight
; is right, so check right sprite centre pixels for collisions, this is more or less identical
; to left check code above, except that pixel checks are right shifted
; but first, check if player has just started a new life and is invulnerable
    ld a,(PlayerHitTimer)
    ld c,a ; preserve Player hit timer for later
    rlca
    jp c,PrintPlayerSkip ; if game over, ignore collision and dont print player
    or a
    jr nz,CheckPlyGrazeRight
.CheckPlyKillHitRight
; player not invulnerable, so check for kill hit
; only need to check right bit in centre byte
    ld l,(ix+6)
    ld h,(ix+7)
    inc l
    bit 0,(hl)
    jr nz,PlayerCollided
    ld l,(ix+8)
    ld h,(ix+9)
    inc l
    bit 0,(hl)
    jr nz,PlayerCollided
.CheckPlyGrazeRight
; sp is still pointing to top of scr add list for player sprite, so pop addresses for grazing check
; check 5 pixels centred on player nose
    pop hl
    bit 0,(hl)
    jr nz,PlyIsGrazing
    inc l
    ld a,(hl)
    inc l
    or a,(hl)
; now do second top line pair
    pop hl
    bit 0,(hl)
    jr nz,PlyIsGrazing
    inc l
    or a,(hl)
    inc l
    or a,(hl)
; for next three line pairs, we check 7 pixels across, centred on player
; must check player x to see if hard against right side of screen first
    ex af,af'
    ld a,b ; get x back to check
    cp a,123 ; for right shifted check, see if player hard right against screen
    jr c,ChkPlyGrazeRightFull ; if not, perform full 7 pixel check
    ex af,af'
; dont check byte to right of player as is hard right on screen
; now do third line pair
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
; now do fourth line pair
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
; now do fifth line pair
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    jr ChkPlyGrazeRightCommon
.ChkPlyGrazeRightFull
    ex af,af'
; now do third bottom line pair
    pop hl
    ld a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    bit 1,(hl) ; left pixel in byte right of player
    jr nz,PlyIsGrazing
; now do fourth bottom line pair
    pop hl
    ld a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    bit 1,(hl) ; left pixel in byte right of player
    jr nz,PlyIsGrazing
; now do fifth bottom line pair
    pop hl
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    or a,(hl)
    inc l
    bit 1,(hl)
    jr nz,PlyIsGrazing
.ChkPlyGrazeRightCommon
; now check if remaining bytes conatained bullets
    and a,3
    jr nz,PlyIsGrazing
    jr PlyNoCol
.PlayerCollided ; player collided with bullet while not invulnerable
; trigger player explosion
    ld a,26*4
    ld (PlayerExp),a
; trigger multiplier progress bar reset
    ld a,128
    ld (MultProgBarFrame),a
; reduce lives
    ld a,(Lives)
if InfiniteLives = 1
    or a
endif
if InfiniteLives = 0
    dec a
endif
    ld (Lives),a ; reduce lives on explosion start so can determine if game won if last boss dies after player death
    jr nz,PlyColNotFinal ; was not loss of last life
; last player life was lost, trigger game over music
    ld a,13
    ld (Int4Game_MusicControl+1),a ; set the trigger for game over music
    jr PlyPrint ; print the player one last time before exploding
.PlyColNotFinal
; player has more lives, so just trigger explosion sfx
    ld hl,SfxTrigger ;Chan0
    set SFXBitPlyExp,(hl)
; end sound trigger
    jr PlyPrint ; print the player one last time before exploding
.PlyIsGrazing
    ld a,(MultProgBarFrame)
    add a,3 ; add to multiplier progress bar for this frame
    ld (MultProgBarFrame),a
.PlyNoCol
    ld sp,ix
    ld a,c ; c holds playerhittimer, which indicates if player needs displaying or flashing
    bit 7,a
    jp nz,PrintPlayerSkip ; game over, dont display player
    or a
    jr z,PlyPrint ; player not invulnerable, so just display
    dec a ; reduce player hit timer by one if invulnerable
    ld (PlayerHitTimer),a ; and store updated timer
; use a to make player flash faster when timer is below 50
    cp a,50
    jr c,PlyFlickerFast
    bit 2,a
    jp nz,PrintPlayerSkip ; skip if bit 2 set
    jr PlyPrint
.PlyFlickerFast
; check less significant bit to flash faster
    bit 1,a
    jp nz,PrintPlayerSkip ; skip if bit 1 set
.PlyPrint
; now print the player
    ld iy,PrintPlayerReturn ; return here after print to continue to 
;    ld a,(PlayerTestX)
;    bit 0,a
;    jp z,PlayerLeft
    ld a,b ; b still holds player x
    rrca ; check right bit
    jp nc,PlayerLeft
    jp PlayerRight
.PrintPlayerReturn
; now display player bullets
    ld de,PlayerYX+5 ; x of first bullet of the 6
    ld b,6 ; number of bullets to display
.PrnPlyBulLp
; print all bullets whether on screen or not - will usually all be onscreen, so checking if they
; are on screen will be cpu use for no practical saving
    ld a,(de)
    rra
    jr c,PrnPlyBulRight
; print left player bullet
    pop hl ; get first addr for current bullet
    set 5,(hl) ; set bit for player bullet - white on left of byte
    set 3,h ; next pixel down, as y co-ord of player bullets is always even
    set 5,(hl)
    pop hl ; get second address for current bullet
    set 5,(hl)
    set 3,h
    set 5,(hl)
; all pixels for current bullet written, move de to x of next bullet
    inc e
    inc e
    djnz PrnPlyBulLp
    jr CPlyExit
.PrnPlyBulRight
; print right player bullet
    pop hl
    set 4,(hl) ; set bit for player bullet - white on right of byte
    set 3,h
    set 4,(hl)
    pop hl
    set 4,(hl)
    set 3,h
    set 4,(hl)
    inc e
    inc e
    djnz PrnPlyBulLp
    
.CPlyExit
; all player clear, collision and printing done, return sp to original location and return
    ld sp,0
    ret

.PlayerExplodingDisplay
; player is exploding, so skip all collision checks, calculate addresses for player shrapnel
; and then display the shrapnel
; these routines are just a cut down variation on the player bullet routines
; first mark the explosion as displayed, so appropriate clear is used to remove it 
    ld hl,PlayerExp
    ld a,(WorkScr)
    bit 6,a
    jr z,CPlyExpHighDsp ; inverted from normal test, want to operate on active screen
    set 0,(hl) ; mark bit for low screen shrapnel displayed
    jr CPlyExpHighDspSkipHigh
.CPlyExpHighDsp
    set 1,(hl) ; mark bit for high screen shrapnel displayed
.CPlyExpHighDspSkipHigh
; get hl holding co-ords of current shrapnel bits
    ld hl,PlayerYXShrapnel+11
; number of shrapnel bits
    ld b,6
.CalcPlyShrapLoop
    ld a,(hl)
; assume either valid (0-63) or 128
    and a,&7f
    srl a ; convert from pixel to byte
    dec l
    ex af,af' ; preserve x
    ld a,(hl) ; get y of player exp shrapnel
    exx
    ld l,a ; hl points to lookup table of scr addr
    ex af,af' ; get x back
;    ld c,a
    or a,(hl)
    ld e,a
    inc h
    ld d,(hl)
    push de ; single address for each shrapnel piece calculated and stored
    dec h ; put h to base of lu table again
    exx
    dec l
    djnz CalcPlyShrapLoop
; now that's done, time to display the shrapnel
    ld de,PlayerYXShrapnel+1 ; x of first player shrapnel piece
    ld b,6
.PrnPlyShrapnelLp
    ld a,(de)
    rra
    jr c,PrnPlyShrapnelRight ; shrapnel piece is right pixel of byte
; print left pixel in byte for player shrapnel
    pop hl
    ld a,(hl)
    or a,160 ; write bits in to current screen byte for shrapnel on left (purple)
    ld (hl),a
    set 3,h ; shrapnel is always even y co-ord, so this can be assumed to get to next pixel line
    ld a,(hl)
    or a,160
    ld (hl),a
; shrapnel pixels written, move to next x value 
    inc e
    inc e
    djnz PrnPlyShrapnelLp
    jr PrintPlayerReturn
.PrnPlyShrapnelRight
; print right player shrapnel, identical to above apart from pixel data
    pop hl
    ld a,(hl)
    or a,80
    ld (hl),a
    set 3,h
    ld a,(hl)
    or a,80
    ld (hl),a
    inc e
    inc e
    djnz PrnPlyShrapnelLp
    jp PrintPlayerReturn

.PrintPlayerSkip
; if player print skipped, need to move sp along to player bullets
    pop hl
    pop hl
    pop hl
    pop hl
    pop hl
    pop hl
    jp PrintPlayerReturn

; this routine pair is used by player and player bullets
; it enters with iy holding return address
; sp pointing to top of table space for scr addr to go
; c is preloaded with x, hl preloaded to exact location in lu table
.GetSpriteAddrPly
    or a,(hl) ; add offset to low byte scr addr
    ld e,a ; and put in e
    inc h ; point to high byte of screen addr
    ld d,(hl) ; and get it into d
    push de ; push calculated addr to table pointed to by sp
;
    dec l
    dec l ; move y ptr in lu table up 2, only calc evry other addr line
    ld d,(hl) ; get next high byte of scr addr
    dec h ; point to low byte of scr addr
    ld a,c ; put x offset in a
    or a,(hl) ; or with low byte
    ld e,a ; put result in e
    push de ; push to table again
; repeat
    dec l
    dec l
    ld a,c
    or a,(hl)
    ld e,a
    inc h
    ld d,(hl)
    push de
;
    dec l
    dec l
    ld d,(hl)
    dec h
    ld a,c
    or a,(hl)
    ld e,a
    push de
;
    dec l
    dec l
    ld a,c
.GetPlyBulAddr
    or a,(hl)
    ld e,a
    inc h
    ld d,(hl)
    push de
;
    dec l
    dec l
    ld d,(hl)
    dec h
    ld a,c
    or a,(hl)
    ld e,a
    push de
;
    exx
;
    jp (iy) ; return to calling routine

