;;-----------------------------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
;;-------------------------------------------------------------------------------

; v4 major overhaul adds support for multiple dragon types

; the collsion detection in this file only deals with collsions between player bullets and
; enemy sprites or rings.  For collsions on the player, see DA_PlayerSpriteDisplay10.asm

;EnemyYX equ &7d80 ; &20 for co-ords
;EnemyData1 equ &7da0 ; &20 state,hits
;EnemyData2 equ &7dc0 ; &20 frame,score
.EnemyCollision
    ld a,(DragonType)
    or a
    jp z,ECStandardDragon
    dec a
    jp z,ECLongDragon
; if >1 then must be collision on the final boss dragon
; process head of standard dragon
;.ECBossDragon
; this processes enemy collision consequences, as well as animation of exploding frames
    ld a,(FinalBossActiveHead)
    add a,&26 ; enemydata1 = &a820
    ld h,EnemyData1/256 ;l,EnemyData1+8 ; collision status of core sprite
    ld l,a ; hl points to active head collision status
    ld a,(hl)
    bit 7,a
    jr nz,CoreFinalIsExplodingChk
; Not exploding or offscreen, so check collision status
    or a
;    and a,&1f ; filter out invulnerable bit
    jp z,CoreColDoneFinalSprite
; have received hits this frame
    ld c,a ; preserve hits
    inc l
    ld a,(hl)
    sub a,c
    jp c,CoreColSpriteDest ; exit whole check from here, all remaining components will explode
; core sprite survives for another day, write back hits and clear frame hits
    dec l
    ld (hl),0 ; clear frame hits
; need to write hits total to all core sprites
    ld hl,EnemyData1+11 ; current hits of right core sprite
    ld (hl),a ; new hits total
    dec l
    dec l
    ld (hl),a
    dec l
    dec l
    ld (hl),a
    ld c,a
; at this point, compare if FinalBossHitCheck is higher than c, if so, change active head
    ld a,(FinalBossHitCheck)
    cp a,c
    jr c,CoreColDoneFinalSprite
    or a ; dont change on last hit
    jr z,CoreColDoneFinalSprite
    sub a,32
    ld (FinalBossHitCheck),a ; write new boss hit check value
    call cpct_getRandom_lcg_u8_asm
    rla
    ld a,2
    jr c,ECDontNegHeadPtrShift
    neg
.ECDontNegHeadPtrShift
    ld c,a
    ld a,(FinalBossActiveHead)
    add a,c
    cp a,5
    jr c,ECDontCorrectPtrShift
    add a,c
    and a,7 ; restrict a to 0,2,4
.ECDontCorrectPtrShift
    ld (FinalBossActiveHead),a ; write new head offset
; now set up parameters for new current head
    ld hl,EnemyData1+10 ; collision status of right core sprite
    or a
    jr z,ECSetupLeftHead
    sub a,2
    jr z,ECSetupCentreHead
; set up right head
    ld (hl),0 ; set status to vulnerable
    dec l
    dec l
    ld (hl),128+32 ; set status to invulnerable
    dec l
    dec l
    ld (hl),128+32
    ld hl,Round12Right
    jr ECSetupCommon
.ECSetupCentreHead
    ld (hl),128+32 ; set status to invulnerable
    dec l
    dec l
    ld (hl),0 ; set status to vulnerable
    dec l
    dec l
    ld (hl),128+32
    ld hl,Round12Centre+4
    jr ECSetupCommon
.ECSetupLeftHead
    ld (hl),128+32 ; set status to invulnerable
    dec l
    dec l
    ld (hl),128+32
    dec l
    dec l
    ld (hl),0 ; set status to invulnerable
    ld hl,Round12Left
.ECSetupCommon
    ld de,FireSeqTable+64+4
    ld bc,11
    ldir
    jr CoreColDoneFinalSprite
.CoreFinalIsExplodingChk
; if core is exploding or offscreen we should assume all sprites are doing so - they should be
; not able to be collided with, so just check if exploding 
    bit 6,a
    ret z ; core sprite offscreen, no further action
; core is in process of exploding, so animate it
    ld hl,EnemyData2+10 ; frame of right core sprite
    ld a,(hl)
    add a,2
    cp a,SpriteExplosionFrameEnd ; reached end of explosion sequence?
    jp nc,CoreCollClearSpr ; time to clear all remaining sprites
; still exploding, so write back new frame to core sprite
    ld (hl),a
    dec l
    dec l
    ld (hl),a
    dec l
    dec l
    ld (hl),a
.CoreColDoneFinalSprite
; now check tails of the boss dragon type
; first check the left tail sprites
    ld hl,EnemyData1+4 ; check the sprites starting from here
    ld b,3 ; check this many sprites
    call CSEnemyLeftColLp ; check those sprites, moving left
; then check the right tail sprites
    ld hl,EnemyData1+12
    ld b,3
    jp CSEnemyRightColLp
; process head of long dragon
.ECLongDragon
; this processes enemy collision consequences, as well as animation of exploding frames
    ld hl,EnemyData1+16 ; collision status of core sprite
    ld a,(hl)
    bit 7,a
    jr nz,CoreLongIsExplodingChk
; Not exploding or offscreen, so check collision status
    or a
    jr z,CoreColDoneLongSprite
; have received hits this frame
    ld c,a
    inc l
    ld a,(hl)
    sub a,c
    jp c,CoreColSpriteDest ; exit whole check from here, all remaining components will explode
; core sprite survives for another day, write back hits and continue to other sprites
    ld (hl),a
    dec l
    ld (hl),0
    jr CoreColDoneLongSprite
.CoreLongIsExplodingChk
; if core is exploding or offscreen we should assume all sprites are doing so - they should be
; not able to be collided with, so check if exploding 
    bit 6,a
    ret z ; core sprite offscreen, no further action
; core is in process of exploding, so animate it
    ld c,l
    ld a,&20
    add a,l
    ld l,a
    ld a,(hl)
    add a,2
    cp a,SpriteExplosionFrameEnd ; check if explosion animation has ended
    jp nc,CoreCollClearSpr ; time to clear all remaining sprites
; still exploding, so write back new frame to core sprite
    ld (hl),a
    ld l,c
.CoreColDoneLongSprite
; now check tails of the long dragon type
; check the tail sprites moving left through the list
    ld hl,EnemyData1+14
    ld b,8
    jp CSEnemyLeftColLp
; process head of standard dragon
.ECStandardDragon
; this processes enemy collision consequences, as well as animation of exploding frames
    ld hl,EnemyData1+8 ; collision status of core sprite
    ld a,(hl)
    bit 7,a
    jr nz,CoreIsExplodingChk
; Not exploding or offscreen, so check collision status
    or a
    jr z,CoreColDoneStandardSprite
; have received hits this frame
    ld c,a
    inc l
    ld a,(hl)
    sub a,c
    jp c,CoreColSpriteDest ; exit whole check from here, all remaining components will explode
; core sprite survives for another day, write back hits and continue to other sprites
    ld (hl),a
    dec l
    ld (hl),0
    jr CoreColDoneStandardSprite
.CoreIsExplodingChk
; perhaps if core is exploding or offscreen we should assume all sprites are doing so - they should be
; not able to be collided with, so check if exploding 
    bit 6,a
    ret z ; core sprite offscreen, no further action
; core is in process of exploding, so animate it
    ld c,l
    ld a,&20
    add a,l
    ld l,a
    ld a,(hl)
    add a,2
    cp a,SpriteExplosionFrameEnd ; check if explosion complete
    jp nc,CoreCollClearSpr ; time to clear all remaining sprites
; still exploding, so write back new frame to core sprite
    ld (hl),a
    ld l,c
; continue animation checks on other sprite independently
.CoreColDoneStandardSprite
; now check tails of the standard dragon type
; first check the left tail sprites
    ld hl,EnemyData1+6
    ld b,4
    call CSEnemyLeftColLp
; then check the right tail sprites
    ld hl,EnemyData1+10
    ld b,4
    jp CSEnemyRightColLp

; check the left tail sprites
; enter with hl pointing to state byte list, b the number of segments to check
.CSEnemyLeftColLp
    ld a,(hl)
    bit 7,a
    jr nz,CSEnemyLeftExplodeChk
; Sprite is not currently exploding or offscreen, so check collision status
    or a
    jr z,CSEnemyLeftColDoneSprite ; no hits received this frame
; have received hits this frame
    ld c,a
    inc l
    ld a,(hl)
    sub a,c
    jr c,CSEnemyLeftColSpriteDest ; sprite hit and destroyed, this and sprites further left must explode
; sprite has received hits and survived, so continue
    ld (hl),a
    dec l
    ld (hl),0
    jr CSEnemyLeftColDoneSprite
.CSEnemyLeftColSpriteDest
; sprite is destroyed this frame, zero some things, destroy all remaining sprites to left of this one
    dec l
    push hl
    pop ix
    ld c,1 ; at least one sprite, count how many segments or sprites are destroyed
    ld a,(ix+&21) ; get score for this sprite
    ld (ix+0),128+64 ; make collision transparent+exploding
    ld (ix+&20),SpriteExplosionFrame ; write first explosion frame
    dec b
    jr z,CSEnemyLeftColWasLastSprite
.CSEnemyLeftDestLp
    dec ix
    dec ix
    bit 7,(ix+0)
    jr nz,CSEnemyLeftColWasLastSprite ; this sprite not in play
    add a,(ix+&21) ; get score for this sprite
    ld (ix+&20),SpriteExplosionFrame ; write first explosion frame
    ld (ix+0),128+64 ; make collision transparent+exploding
    inc c
    djnz CSEnemyLeftDestLp
.CSEnemyLeftColWasLastSprite
; at this point, need to act on the score and multiplier, trigger sound
    ld hl,FrameScore
    ld (hl),a ; total score
    inc l
    ld (hl),c ; total multiplier
; sound trigger
    ld hl,SfxTrigger
    set SFXBitBExp,(hl)
; end sfx trigger
    ret
.CSEnemyLeftExplodeChk
; not able to be collided with, so check if exploding 
    bit 6,a
    ret z ; sprite is offscreen, so all subsequent left sprites will be too
; is in process of exploding, so animate it
    ld c,l
    ld a,&20
    add a,l
    ld l,a
    ld a,(hl)
    add a,2
    cp a,SpriteExplosionFrameEnd
    jr nc,CSEnemyLeftCollClearSpr
; still exploding, so just write it back
;.CSEnemyLeftStillExplodingLp
    ld (hl),a
    ld l,c
    jr CSEnemyLeftColDoneSprite ; continue animation checks on other sprite independently
; as they may have started exploding in an earlier frame
.CSEnemyLeftCollClearSpr
; has done explosion, so clear markers and co-ords
    ld (hl),0
    ld a,l
    sub a,&40
    ld l,a
    ld (hl),0 ; y offscreen
    inc l
    ld (hl),192 ; x offscreen
    ld l,c ; +&20 and also a dec l
    ld (hl),128 ; transparent but not exploding
.CSEnemyLeftColDoneSprite
    dec l
    dec l
    djnz CSEnemyLeftColLp
    ret

; check the right tail sprites
; enter with hl pointing to state byte list, b the number of segments to check
.CSEnemyRightColLp
    ld a,(hl)
    bit 7,a
    jr nz,CSEnemyRightExplodeChk
; Not exploding or offscreen, so check collision status
    or a
    jr z,CSEnemyRightColDoneSprite ; no hits received this frame
; have received hits this frame
    ld c,a
    inc l
    ld a,(hl)
    sub a,c
    jr c,CSEnemyRightColSpriteDest ; sprite hit and destroyed, this and sprites further left must explode
; sprite has received hits and survived, so continue
    ld (hl),a
    dec l
    ld (hl),0
    jr CSEnemyRightColDoneSprite
.CSEnemyRightColSpriteDest
; sprite is destroyed this frame, zero some things, destroy all remaining left sprites
    dec l
    push hl
    pop ix
    ld c,1 ; at least one sprite
    ld a,(ix+&21) ; get score for this sprite
    ld (ix+0),128+64 ; make collision transparent+exploding
    ld (ix+&20),SpriteExplosionFrame ; write first explosion frame
    dec b
    jr z,CSEnemyRightColWasLastSprite
.CSEnemyRightDestLp
    inc ix
    inc ix
    bit 7,(ix+0)
    jr nz,CSEnemyRightColWasLastSprite ; this sprite not in play
    add a,(ix+&21) ; get score for this sprite
    ld (ix+&20),SpriteExplosionFrame ; write first explosion frame
    ld (ix+0),128+64 ; make collision transparent+exploding
    inc c
    djnz CSEnemyRightDestLp
.CSEnemyRightColWasLastSprite
; at this point, need to act on the score and multiplier
    ld hl,FrameScore
; in unlikely event that two partial arms are destroyed at once, add to possible left arm score
    add a,(hl)
    ld (hl),a ; total score
    inc l
    ld a,(hl)
    add a,c
    ld (hl),a ; total multiplier
; sound trigger
    ld hl,SfxTrigger
    set SFXBitBExp,(hl)
; end sfx trigger
    ret
.CSEnemyRightExplodeChk
; not collision able, so check if exploding 
    bit 6,a
    ret z ; sprite is offscreen, so all subsequent right sprites will be too
; is in process of exploding, so animate it
    ld c,l
    ld a,&20
    add a,l
    ld l,a
    ld a,(hl)
    add a,2
    cp a,SpriteExplosionFrameEnd
    jr nc,CSEnemyRightCollClearSpr
; still exploding, so just write it back
;.EnemyRightStillExplodingLp
    ld (hl),a
    ld l,c
    jr CSEnemyRightColDoneSprite ; continue animation checks on other sprite independently
; as they may have started exploding in an earlier frame
.CSEnemyRightCollClearSpr
; has done explosion, so clear markers and co-ords
    ld (hl),0
    ld a,l
    sub a,&40
    ld l,a
    ld (hl),0 ; y offscreen
    inc l
    ld (hl),192 ; x offscreen
    ld l,c ; +&20 and also a dec l
    ld (hl),128 ; transparent but not exploding
.CSEnemyRightColDoneSprite
    inc l
    inc l
    djnz CSEnemyRightColLp
    ret

; common to all dragon types, this is called when core begins exploding, starts explosion of all
; remaining active segments, and determines total score and multiplier for those segments
.CoreColSpriteDest
; sound trigger
    ld hl,SfxTrigger
    set SFXBitBExp,(hl)
; set time bonus to current time
    ld a,(RoundTime)
    ld (TimeBonus),a
; when core is destroyed, all other segment checks unrequired, all sprites remaining will explode
    ld hl,EnemyData1
    ld bc,&900 ; c to count sprites destroyed for multiplier, check all 9 sprites
    ld d,c ; d zeroed, will add up score total
.CorExplodeLp
; this will loop through all 9 sprites, destroy all active ones and count for bonus score
    ld a,(hl)
    bit 5,a
    jr nz,CorExplodeInvCore ; is an invulnerable core of final boss, so destroy
    rla
    jr c,CorExpLpNotThisSprite
.CorExplodeInvCore
; destroy this sprite, zero some things
    ld (hl),128+64 ; make collision transparent+exploding
    ld e,l
    ld a,&20
    add a,l
    ld l,a
    ld (hl),SpriteExplosionFrame
; at this point, should also grab score and increase boss destruction multiplier
    inc l
    ld a,d
    add a,(hl)
    ld d,a ; add score to d
    inc c ; increment multiplier
    ld l,e ; get l back to base of EnemyData1 for this sprite
.CorExpLpNotThisSprite
    inc l
    inc l
    djnz CorExplodeLp
; sprites dealt with, now need to set triggers for the score and multiplier
    ld hl,FrameScore
    ld (hl),d ; total score
    inc l
    ld (hl),c ; total multiplier
; score handling dealt with elsewhere
; check if just destroyed last boss, and trigger game end if so
    ld a,(Round)
    cp a,FinalRound
    ret c
; destroyed final round boss, game won! So check player lives and trigger end
    ld a,(Lives)
    or a
    ret z
    ld a,1
    ld (KeepPlayerInv),a ; if game not already over, set player to permanent invulnerability
; set player hit timer to invulnerable.  If player is currently exploding (PlayerExp>0) then
; when explosion completes, playerhittimer will be set accordingly, and KeepPlayerInv will be cleared
; if playerhittimer > 127
    ld a,100
    ld (PlayerHitTimer),a
    ret ; exit collision checks

; common to all dragon types, this is called when core completes exploding, resets all required sprite
; variables, triggers a time bonus if applicable and sets timer to begin next round
.CoreCollClearSpr
; has done explosion, so clear markers and co-ords for all sprites
; wont matter if non core sprites already off screen
; at this point, it would actually be time to check if a new boss is due, or game is ended
; first step is to increment dragon destroyed count for determining which ending player gets
    ld hl,DragonsDestroyed
    inc (hl)
; clear dragon sprite co-ords
    ld hl,EnemyYX
    ld d,h
    ld e,l
    ld (hl),0 ; y co-ord
    inc l
    ld (hl),128+64 ; x co-ord
    inc l
    ex de,hl
    ld bc,16
    ldir
; now reset state and hits
    ld hl,EnemyData1
    ld d,h
    ld e,l
    ld (hl),128 ; collision status
    inc l
    ld (hl),0 ; hits
    inc l
    ex de,hl
    ld bc,16
    ldir
; dont worry about resetting frame and score
; make boss move value 0
    xor a
    ld hl,BossMvY
    ld (hl),a
    inc l
    ld (hl),a
    inc l
    ld (hl),a
    inc l
    ld (hl),a
; set timer to begin next round
    ld a,50
    ld (RoundEnd),a
    ld a,(Lives)
    or a
    jr z,CoreCollPlayerNoLives ; if player out of lives, game end music playing, dont fade
    ld (Int4Game_PlayMusic+1),a ; trigger fade with non zero value
.CoreCollPlayerNoLives
; set up round time bonus if applicable
    ld a,(TimeBonus)
    or a
    ret z ; all sprites removed from play, no time bonus
; make use of normal scoring to give time bonus
    ld hl,FrameScore
    ld (hl),a
    inc l
    ld (hl),1 ; no segment multiplier for score bonus
; trigger visual display of time bonus text on screen
    ld c,10 ; a still holds time bonus
    call FindNumber8bit
; b now holds 10's, a the units
    ld hl,BCD_TimeBonus+1
    ld (hl),a
    dec l
; check for leading 0 and make a null char if so
    ld a,b
    or a
    jr nz,CCClrSpr_NoLeading0inTimer
    ld a,128
.CCClrSpr_NoLeading0inTimer
    ld (hl),a
; now set diplay timer for time bonus
    ld a,47
    ld (TimeBonusDspTimer),a
; now clear time bonus trigger and exit
    xor a
    ld (TimeBonus),a
    ret ; all sprites removed from play and time bonus set up

.CollisionPBullets
; occurs after move
; this code finds if there were any hits on sprites under interrupt and adds to a totaliser
; the code earlier in this file is used to determine the consequences against enemy sprites
; 17 lines with all sprites on screen
; 14 lines if all off screen
; so total max around 51 lines over course of one game frame - called at 50hz
    ld (CoPBSaveSP+1),sp
    ld hl,PlayerYX+15
    ld b,6
    ld a,(FrameHits)
    ld c,a ; track frame hits this screen refresh
.CoPBSOutloop2
    ld a,(hl) ; get player bullet x
    dec l
    ex af,af'
    ld a,(hl) ; get player bullet y
    add a,2 ; add 2 to the bullet y as it is 4 pixels high
    exx
    ld sp,EnemyYX ; set sp to point to enemy sprite co-ordinate list
    ld l,a ; l=y co-ord
    ex af,af'
    ld h,a ; h = x co-ord
    ld bc,&908 ; 9 sprites to check, c=8 for speeding a comparison in the loop
.ColPBSloop2
    pop de ; get enemy yx co-ords
; compare the x co-ord between player bullet and  enemy sprite
    ld a,h
    sub a,d
    jr c,CPBSNoColl2
    cp a,c
    jr nc,CPBSNoColl2
; compare the y co-ord between player bullet and  enemy sprite
    ld a,l
    sub a,e
    jr c,CPBSNoColl2
    cp a,18
    jr c,HandlePBColl2 ; if got here, a potential sprite collision is detected
.CPBSNoColl2
    djnz ColPBSloop2
    exx
.CPBSCollRet2
    dec l
    djnz CoPBSOutloop2
; write framehits back
    ld a,c
    ld (FrameHits),a ; total of hits on this sprite writen back to temp holder for this frame
; now check for collisions against ring enemy bullets for triggering early detonation
; this process is identical to the above, except sprite dimensions are smaller
    ld hl,EBRingHits+3
    ld b,4
.CoPBRingOutloop
    ld a,(hl)
    or a
    jr z,CPBSCollRet ; skip this ball/ring, not valid target
; ring is valid target, get co-ords and check
    res 5,l
    ld a,(hl) ; get ring x
    dec l
    dec l
    ex af,af'
    ld a,(hl) ; get ring y
; move back to status byte for next iteration
    inc l
    inc l
    set 5,l
;
    exx
    ld sp,PlayerYX+4
    ld l,a ; l has ring y
    ex af,af'
    ld h,a ; h has ring x
    ld bc,&603
.ColPBRingInloop
    pop de
    ld a,d
    sub a,h
    jr c,CPBSNoColl
    cp a,c
    jr nc,CPBSNoColl
    ld a,e
    sub a,l
    jr c,CPBSNoColl
    cp a,10
    jr c,HandlePBColl
.CPBSNoColl
    djnz ColPBRingInloop
    exx
.CPBSCollRet
    ld a,4
    add a,l
    ld l,a ; move to next ball status byte
    djnz CoPBRingOutloop
; have completed checks of player bullets to enemy sprites
.CoPBSaveSP
    ld sp,0
    ret

.HandlePBColl2
; first check is valid target (sprite not exploding)
    ex de,hl ; (dont need de anymore)
    ld a,b
    neg
    add a,9
    rlca
    add a,&20 ; point to EnemyDataF - frame hits
    ld l,a
    ld h,EnemyYX/256
    ld a,(hl)
    bit 7,a
    jr z,HPBCol2Valid ; was a valid hit on an enemy sprite, so add to hits on it
; was a special case, check if explosion or if invulnerable
    bit 5,a
    jr nz,HPBCol2ValidInvulnerable ; hit an invulernable sprite, so no further action
    ex de,hl
; snake bosses mean sprites can overlap, so keep checking other sprites
    jr CPBSNoColl2
.HPBCol2Valid
    inc (hl) ; add to 'this turn' count hits for sprite, to be dealt with in main code
    ex de,hl
; hit inv head sound effect trigger
    ld hl,SfxTrigger
    set SFXBitNMEHit,(hl)
    exx
; increment current framehits
    inc c
.HPBCol2ValidCommon
; now remove player bullet to offscreen position
    ld (hl),0
    inc l
    ld (hl),128
    dec l
    jr CPBSCollRet2 ; cease checking for this player bullet, removed
.HPBCol2ValidInvulnerable
; hit inv head sound effect trigger
    ld hl,SfxTrigger
    set SFXBitNMEInv,(hl)
    exx
    jr HPBCol2ValidCommon

.HandlePBColl
; player bullet has hit a ring, so we need to clear ring collision state, and remove bullet
; first determine which bullet it was and clear
    ld a,b
    neg
    add a,6
    rlca
    add a,&74 ; point to PlayerYX+4
    ld l,a
    ld h,PlayerYX/256 ; point to entry of bullet just collided with
    ld (hl),0 ; set y=0
    inc l
    ld (hl),128 ; set x=128
    exx
    ld (hl),0 ; clear the collision byte, will cause ring to detonate
    jr CPBSCollRet ; skip further checks against further rings, redundant