; v4 adds block on enemy firing so segments dont fire when completely offscreen (Y=0)
; v5 adds capability to shoot shrapnel rings
.FirePlayer
; if player leaving screen, do not act on fire button press
    ld a,(PlayerAutoPilot)
    or a
    ret nz
; if player timer indicates game ended, do not fire
    ld a,(PlayerHitTimer)
    rla
    ret c
; get status of fire button and check auto fire delay
    ld hl,KeyinY+2 ;Fire
    ld de,PBullDelay ; pointer to current delay to next shot
    ld a,(hl)
    or a ; is fire pressed?
    jr nz,CheckAutoFireDelay ; if so, check delay
; fire not pressed, so reset delay and exit
    xor a
    ld (de),a ; reset auto fire delay
    ret
.CheckAutoFireDelay
    ld a,(de)
    or a ; check if auto fire delay has ended
    jr z,FirePReady ; if so, can fire another shot
; if not, reduce fire delay timer and exit
    dec a
    ld (de),a
    ret
.FirePReady
; able to fire, so set up new auto fire delay
    ld a,6
    ld (de),a
; now check for free bullet slot - auto fire timer means there will always be one
    ld hl,PlayerYX+15
    ld b,6
.FindFreePBLp
    bit 7,(hl)
    jr nz,FoundFreePB
    dec l
    dec l
    djnz FindFreePBLp
; this should not happen because of auto fire timer, but if there are no free bullets
; simply exit, do not replace a bullet currently in play
    ret ; no free bullets
.FoundFreePB
; free bullet slot found, set it up
    ld de,PlayerTestY+1 ; points to player x
    ld a,(de) ; get player current x
    add a,2 ; move to centre of player x pos
    ld (hl),a ; write new bullet x
    dec l ; point to new bullet y
    dec e ; and player y
    ld a,(de) ; get player y
    sub a,4 ; move bullet height above player
    ld (hl),a ; and write new bullet y
; sound trigger
    ld hl,SfxTrigger
    set SFXBitPlyFire,(hl)
; end sound trigger
    ret

; enemy bullet firing is handled as a loop through 4 lists stored at
; FireSeqTable equ &9000
; which is four 32 byte lists set up for each round
.CheckFire
; check to see what bullets need firing by looping through the list
    ld a,0
    add a,32
    and a,&7f
    ld (CheckFire+1),a ; save result back for next time
    ld h,FireSeqTable/256
    ld l,a ; hl points to fire list for this frame
    exx
    ld hl,EBY ; bull coord list, only loop through list once for all triggered shots
    exx
; now work through list until encounter end marker (0 byte)
.CheckFireLp
    ld a,(hl)
    or a
    ret z ; end marker reached, no more bullets to create
    dec a
    jr z,ChkFireStream ; create a new bullet in a stream
    dec a
    jr z,ChkFireSpread ; check if required to fire a spread shot
    dec a
    jp z,ChkFireShrapnel ; check if required to fire a shrapnel ring
    dec a
    jp z,ChkFireBall ; check if required to fire a solid ball
    dec a
    jp z,ChkFireLaser ; check if time to fire a fast laser shot
    dec a
    jp z,ChkFireNarrowStream ; create a new bullet for a narrow stream
; if here, must be indicating time to check status of a ball sprite, or in error
    dec a
    cp a,4
    ret nc ; valid values are 0-3, if not, something is wrong, take no further action
    add a,a 
    add a,a ; x4 to find co-ord of ball to get to table entry and determine if ball specified is in play
    add a,&41 ; point to ball y
    ld e,a
    ld d,EBRingYX/256 ; de now points to y co-ord of current ball, will be 0 if not in play
    ld a,(de)
    or a
    ret z ; y=0 means ring is off screen, no further action, and always last entry in fire list
    inc l ; set hl for next read, so if bullets created, will not re-read this entry
    jp CheckRingAction

.ChkFireStream
; will be standard waving stream as already under test in prototype
; which is a 180 degree arc that constantly moves left or right
    inc l ; point to next byte, will be firing sprite pointer
    ld e,(hl) ; get sprite pointer, will allready be calced up to point to status in EnemyData1 list
    inc l ; move hl pointer to y offset for shot start
    ld d,EnemyData1/256 ; now point to sprite status
    ld a,(de)
    rla
    jr c,ChkFStreamAbort ; if bit 7 of sprite status is set, it is offscreen or exploding, do not fire
; sprite is valid, so get sprite co-ords as base for firing a bullet
    ld a,e
    sub a,&20 ; point to this sprites yx
    ld e,a ; de now points to co-ords
    ld a,(de)
    or a
    jr z,ChkFStreamAbort ; if sprite completely offscreen at top, y=0, dont fire
    add a,(hl) ; have y for new bullet
    ld c,a ; put y in c
    inc e
    ld a,(de) ; get sprite x
    add a,4 ; have x for new bullet
    ld b,a ; put x in b
    push bc ; preserve new bullet start co-ordinates and find direction
    inc l ; point to new bullet move dir -change- amount
    ld a,(hl)
    inc l ; point to pointer -current- direction in arc list
    add a,(hl) ; have moved pointer through arc by specified change amount
    ld (hl),a ; update current pointer
    inc l ; now point to next entry in the firing list for next read
; can now discard de, create bullet using direction lookup table pointer and modifier to pointer as have now, 2 more bytes
    ld iy,CheckFireLp ; repeat main loop when bullet created
    jr FindNewBullet
.ChkFStreamAbort
; this sprite has been destroyed, move pointers on to skip this fire request and return to loop
    inc l
.ChkFireSpreadAbort ; entry from below
    inc l
.ChkFireSpreadAbort2 ; entry from slightly further below
    inc l
    jr CheckFireLp

.ChkFireSpread
; will be a once off spray of 16 shots in an arc, periodically to a timer
    inc l ; point to next byte, will be sprite pointer
    ld a,(hl) ; get sprite pointer, will allready be calced up to point to status in EnemyData1 list
    inc l ; move hl pointer to timer between spread shots
    ld d,EnemyData1/256
    ld e,a
    ld a,(de)
    rla ; check if sprite still alive
    jr c,ChkFStreamAbort ; can use same abort exit as stream fire, same number of parameters
; sprite is active and not exploding, now need to check timer
    dec (hl)
    jr nz,ChkFStreamAbort
; have timed out, need to restore timer, and create new bullet spread shot
    inc l
    ld a,(hl) ; get spread shot timer reset
    dec l
    ld (hl),a ; write back to spread shot timer
    inc l
    inc l ; leave hl pointing to y offset for this spread shot
; sprite is valid and timer up, so get co-ords as base for firing a bullet
    ld a,e
    sub a,&20
    ld e,a ; de now points to sprite co-ords
    ld a,(de)
    or a
    jr z,ChkFireSpreadAbort2 ; if sprite completely offscreen at top, y=0, dont fire
    add a,(hl) ; add y offset
    inc l ; now point to next entry in the firing list
    ld c,a
    inc e
    ld a,(de)
    add a,4 ; have x for new bullet spread shot
    ld b,a
; can now discard de, create bullets looping through entire lookup table
    push bc
    exx
    pop bc ; bc hold start pos
    ld de,ShotgunDirList ; this is a list of pre-determined directions for spread shot spray
    exx
    jr FindNewBullet16


.FindNewBullet
    exx
    ld e,a ; e holds pointer for direction
    pop bc ; bc hold start pos for new bullet
    xor a ; want to find a bullet that is offscreen
.FindNBSlotLp ; paritally unrolled loop to save some cpu time
    cp a,(hl)
    jr z,FoundFreeNB
    inc l
    cp a,(hl)
    jr z,FoundFreeNB
    inc l
    cp a,(hl)
    jr z,FoundFreeNB
    inc l
    cp a,(hl)
    jr z,FoundFreeNB
    inc l
    jr nz,FindNBSlotLp ; if have not looped round all 256 bullet entries yet, repeat
; no free bullet, so abort
    exx
    ret ; no point in spending more cpu checking for further bullet entries, exit checkfire completely
.FoundFreeNB
; hl points to y table entry for new bullet
; sub pixel component will be zeroed already
    ld (hl),c ; y
    inc h
    ld (hl),b ; x 
    inc h
    inc h
; hl now points to yx move, so now get bullet direction
;    ld e,a ; done at start of this routine
    ld d,EBDirY/256 ; de now points to direction yx move steps for this bullet
    ld a,(de)
    ld (hl),a ; y move set up
    inc h
    inc d
    ld a,(de)
    ld (hl),a ; x move set up
; now reset free bullet pointer back to nearest multiple of 4 so that free bullet check loop
; which checks entries in multiples of 4 before checking if l=0, does not get stuck in endless loop
    ld a,l
    and a,&fc
    ld l,a
    ld h,EBY/256 ; and reset h to y co-ord page
    exx
    jp (iy)

.FindNewBullet16
; finds 16 new bullets for spread shots
; de will be pre-loaded to point to list of 16 direction pairs for a 180 degree arc for shrapnel rings
; and a 90 degree arc for shotgun spread shots
    ld b,16 ; both fire 16 shots
.FindNB16Lp
; the process in this is almost identical to that in FindNewBullet, see that routine
; above for comments
    exx
    xor a
.FindNBSlot16Lp
    cp a,(hl)
    jr z,FoundFreeNB16
    inc l
    cp a,(hl)
    jr z,FoundFreeNB16
    inc l
    cp a,(hl)
    jr z,FoundFreeNB16
    inc l
    cp a,(hl)
    jr z,FoundFreeNB16
    inc l
    jr nz,FindNBSlot16Lp
; no free bullet
    exx
    ret
.FoundFreeNB16
; ignore sub pixel components, already zeroed when cleared
; hl points to y table entry
    ld (hl),c ; y
    inc h
    ld (hl),b ; x 
    inc h
    inc h
; hl points to yx move, so now get bullet direction
;    ld e,a ; done at start of this routine
; unlike FindNewBullet, for a little extra speed, there are seperate linear direction lists
; for each type of spread shot, so less manipulation of de is required
    ld a,(de)
    ld (hl),a
    inc h
    inc e
    ld a,(de)
    ld (hl),a
; move pointer to de along
    inc e
; align co-ord pointer to multiple of 4 so next search for free bullet will not skip end
; and loop endlesly
    ld a,l
    and a,&fc
    ld l,a
    ld h,EBY/256
    exx
    djnz FindNB16Lp
    jp CheckFireLp

; currently have direction stored in type, but this prevents ring from having a direction
; may be better to dual use counter with type (0-ring 1+ the counter, if used as is currently)
; then spare byte can be direction for either type
.ChkFireBall
; create ball or ring use same code so set up type variable
    ld a,1 ; define ball type
; this will never be direction 0, so is possible to serve dual purpose like this
    jr ChkFireRingCommon
; ball and shrapnel are same apart from type, so set up register at start to determine type and
; then use same code to determine valid, entry, direction.
.ChkFireShrapnel
    xor a ; define ring type
.ChkFireRingCommon
    ld (ChkFireBallTypeSet+1),a ; save type for later use
    xor a,1 ; invert shrapnel type to 1, ball type to 0
    ld (ChkFireBallSetHit+1),a ; defines ting type as able to be shot
; get sprite pointer as for bullet creation and see if launching sprite still active
    inc l ; point to next byte, will be sprite pointer
    ld a,(hl) ; get sprite pointer, will allready be calced up to point to status in EnemyData1 list
    ld d,EnemyData1/256
    ld e,a
    ld a,(de)
    rla ; check bit 7 of sprite state, if set the sprite is not active
    jp c,ChkFStreamAbort ; can use same abort exit as stream fire, same number of parameters
; sprite still active, now need to check timer to see if time to fire
    inc l
    dec (hl) ; reduce timer
    jp nz,ChkFireSpreadAbort ; can use same abort exit as spread fire, same number of parameters
; have timed out, need to restore timer, and create ring/ball
    inc l
    ld a,(hl) ; get timer reset
    dec l
    ld (hl),a ; reset timer until next shot
    inc l
    inc l ; leave hl pointing to next entry in fire list
; sprite is valid and timer up, so get co-ords as base for firing a ball/ring
    ld a,e
    sub a,&20
    ld e,a ; de now points to co-ords
    ld a,(de) ; get sprite y
    or a
    jp z,CheckFireLp ; if sprite completely offscreen at top, y=0, dont fire
    add a,14 ; have y for new ring/ball
    ld c,a
    inc e
    ld a,(de)
    add a,2 ; have x
    ld b,a ; bc now contains xy
; now find free ball
    ld de,EBRingYX+1 ; point to y co-ord in ring table
    ex de,hl ; preserve fire list in de
    xor a
    cp a,(hl) ; only 4 rings active at once, check all 4 to see if any are inactive - y=0
    jr z,ChkFireShrapnelFoundFree
    inc l:inc l:inc l:inc l
    cp a,(hl)
    jr z,ChkFireShrapnelFoundFree
    inc l:inc l:inc l:inc l
    cp a,(hl)
    jr z,ChkFireShrapnelFoundFree
    inc l:inc l:inc l:inc l
    cp a,(hl)
    jr z,ChkFireShrapnelFoundFree
; no free rings to fire, clean up and exit
    ex de,hl
    jp CheckFireLp
.ChkFireShrapnelFoundFree
    ld (hl),c ; y
    inc l
    inc l
    ld (hl),b ; x
; addition, set hit type so rings can be shot and detonated prematurely
    set 5,l
.ChkFireBallSetHit
    ld (hl),0 ; 0 for ball, 1 for ring - modified by code above
    res 5,l
; end addition for setting up shrapnel rings to be destroyed
    set 4,l
.ChkFireBallTypeSet
    ld (hl),0 ; type & counter for ball (starts at 1), 0 for ring
    dec l
; now find direction of ball based on x pos to start with
; if ring fired from left of screen, will move to right, if fired from left, will move to right
    ld a,b ; b holds x
    cp a,32
    jr c,ChkFireLeftShrapnelX
    cp a,96
    jr nc,ChkFireRightShrapnelX
; is in centre half of screen
    ld b,128 ; base move is straight down
    jr ChkFireFoundShrapnelX
.ChkFireRightShrapnelX
    ld b,128+16 ; move towards left
    jr ChkFireFoundShrapnelX
.ChkFireLeftShrapnelX
    ld b,128-16 ; move towards right
.ChkFireFoundShrapnelX
; now use random number to tilt shot left or right
    call cpct_getRandom_lcg_u8_asm
    cp a,170
    jr nc,ChkFireLeftShpTiltX ; tilt move to the left
    cp a,85
    jr c,ChkFireRightShpTiltX ; tilt move to the right
; not high or low, so stick with default move
    xor a
    jr ChkFireFoundShpTiltX
.ChkFireRightShpTiltX
    ld a,16
    jr ChkFireFoundShpTiltX
.ChkFireLeftShpTiltX
    ld a,-16
.ChkFireFoundShpTiltX
    add a,b
    ld c,a
    ld (hl),c ; direction used by ball or ring to move
    dec l
    ld b,EBDirY/256 ; now bc points to direction lookup table for y
    ld a,(bc) ; get y move
    ld (hl),a ; y move set in ring table
    dec l
    inc b ; now bc points to x move
    ld a,(bc)
    ld (hl),a ; x move set
    ex de,hl
; sound trigger
    ld a,(SfxTrigger)
    set SFXBitNMERFire,a
    ld (SfxTrigger),a
; end sound trigger
    jp CheckFireLp

.CheckRingAction
    add a,4
    ld c,a ; save current ring y
    ld b,e ; save e
    inc e:inc e
    set 4,e ; now point to type of ring
    ld a,(de)
    or a
    jr z,CRACheckShrapnelDetonate ; if type = 0, is a ring, so check if needs to explode
; ring is a ball type, check timer for creating wake bullets
    inc a ; increase ball counter
    ld (de),a
    bit 0,a ; only fire bullets from ball every other check
    ret nz
; time to create bullets from ball location
    dec e
    ex de,hl
    ld b,(hl) ; put ball direction into b
    ex de,hl
; timer expired so send off two wake bullets
    inc e
    res 4,e ; point to ball x co-ord
    ex af,af' ; preserve counter
    ld a,(de)
    add a,2 ; have x for first bullet start position
; can now discard de
    ld e,b ; put current direction in for use in next part
    ld b,a ; bc now holds start pos of the wake bullets fired from ball
    ex af,af'
    ld d,a ; d holds counter, which is also used as a 'tilt' on the bullet direction fired by the ball
; create 2 wake bullets using standard creation routine
    ld a,e ; get direction back in a
    sub a,d ; sub counter to make arc
    sub a,64 ; alter direction by additional 45 degrees from direction of ball
    ld iy,ReturnBulletWake ; return back here when first bullet done
    push bc
    jp FindNewBullet
.ReturnBulletWake ; after first bullet created, returns here from address in iy
    ld iy,CheckFireLp ; repeat main loop when bullet created
    ld a,e ; get direction back again
    add a,d ; add counter to make arc
    add a,64 ; tile 45 degrees in opposite direction to first bullet
    push bc
    jp FindNewBullet ; and create second bullet, then will return to main loop

.CRACheckShrapnelDetonate
; ring is a shrapnel type, first check if triggered by being shot
    set 5,e
    res 4,e ; now point to shrapnel trigger
    ld a,(de) ; get trigger
    or a
    jr z,TimeToExplode ; if trigger byte cleared, has been shot and triggered prematurely
; now check if close enough to player to detonate yet
    ld a,(PlayerTestY) ; get current player y co-ord
    sub a,64
    jr c,TimeToExplodeProximity ; if player near top of screen, always explode shrapnel ring
    cp a,c ; compare player y to ring y
    ret nc ; if ring to far away, take no further action
.TimeToExplodeProximity
; first, clear collision byte
    xor a
    ld (de),a ; clear the collision byte
.TimeToExplode
    ld e,b ; get de back to the ring co-ords
; player close to ball, make it explode
    inc e
    inc e
    ld a,(de)
    add a,2 ; have ring x
    ld b,a ; bc now holds start point for a shrapnel explosion
; can now discard de, create bullets looping through entire lookup table, 2 more bytes give timer and timer reset
    push bc
; now before creating shrapnel, clear ring from the ring table
    xor a
    ex de,hl
    ld (hl),a
    dec l
    ld (hl),a
    dec l
    ld (hl),a
    dec l
    ld (hl),a
    set 4,l
    ld (hl),-1
    inc l
    ld (hl),a
    ex de,hl
; end clear ball from table
; sound trigger
    ld a,(SfxTrigger)
    set SFXBitDet,a
    ld (SfxTrigger),a
; end sound trigger
    exx
    pop bc ; bc hold start pos
    ld de,ShrapnelDirList
    exx
    jp FindNewBullet16

.ChkFireLaser
; fire a fast moving laser
    inc l ; point to next byte, will be sprite pointer
    ld e,(hl) ; get sprite pointer, will allready be calced up to point to status in EnemyData1 list
    ld d,EnemyData1/256
    ld a,(de)
    rla ; check if sprite is active
    jp c,ChkFStreamAbort ; can use same abort exit as stream fire, same number of parameters
; sprite still active, now need to check fire timer
    inc l
    dec (hl)
    jp nz,ChkFireSpreadAbort ; not time to fire another laser yet, so abort
; have timed out, need to restore timer, and create bullet spread
    inc l
    ld a,(hl) ; get reset value of fire timer
    dec l
    ld (hl),a ; and write the reset value back to the time
    inc l
    inc l ; leave hl pointing to next entry
; sprite is valid, so get co-ords as base for firing a fast laser
    ld a,e
    sub a,&20
    ld e,a ; de now points to co-ords of sprite
    ld a,(de)
    or a ; check if sprite still fully off screen at start of round
    jp z,CheckFireLp ; if sprite completely offscreen at top, dont fire
    add a,16 ; have y for new laser
    ld (NewFastShotY),a ; write to temp spot to be read by next laser fire routine in interrupt
;    ld c,a
    inc e
    ld a,(de) ; get sprite x
    add a,3 ; have x for new laser
    ld (NewFastShotX),a ; write to temp spot to be read by next laser fire routine in interrupt
; laser is actually created by PrintFastShot - see DA_FastShot4.asm
;    ld a,c
;    ld (NewFastShotY),a
; sound trigger
    ld a,(SfxTrigger)
    set SFXBitNMELFire,a
    ld (SfxTrigger),a
; end sound trigger
.ChkFLaserAbort
; this sprite destroyed, move pointers on and return to loop
    inc l
    jp CheckFireLp

.ChkFireNarrowStream
; this routine is almost identical to ChkFireStream above, the only difference is that
; the direction changes by less with each shot, and is fixed in a 90 degree arc facing
; downwards, rather than revolving through a full 180 degree arc
    inc l ; point to next byte, will be sprite pointer
    ld e,(hl) ; get sprite pointer, will allready be calced up to point to status in EnemyData1 list
    inc l ; move hl pointer to y offset for shot start
    ld d,EnemyData1/256
    ld a,(de)
    rla
    jp c,ChkFStreamAbort
; sprite is valid, so get co-ords as base for firing a bullet
    ld a,e
    sub a,&20
    ld e,a ; de now points to co-ords
    ld a,(de)
    or a
    jp z,ChkFStreamAbort ; if sprite completely offscreen at top, dont fire
    add a,(hl) ;16 ; have y
    ld c,a
    inc e
    ld a,(de)
    add a,4 ; have x
    ld b,a
    push bc
    inc l ; point to move dir through arc
    ld a,(hl)
    inc l ; point to pointer in arc list
    add a,(hl)
    ld (hl),a ; update pointer
; for narrow stream, restrict to 90 degrees downwards
    cp a,64
    jr nc,ChkFNSNotTooLeft
; time to make narrow stream swing right
    dec l
    ld (hl),4
    inc l
    jr ChkFNSCommon
.ChkFNSNotTooLeft
    cp a,193
    jr c,ChkFNSCommon
; time to make narrow stream swing left
    dec l
    ld (hl),-4
    inc l
.ChkFNSCommon
    inc l ; now point to next entry in the firing list
; can now discard de, create bullet using direction lookup table pointer and modifier to pointer as have now, 2 more bytes
    ld iy,CheckFireLp ; repeat main loop when bullet created
    jp FindNewBullet
