;;-----------------------------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
;;-------------------------------------------------------------------------------
; v2 introduces compiled game display font
; v3 introduces multiplier and roundtime update only on change
; v4 add round complete time bonus display
;Display screen addresses of various score panel elements
ScoreDspTime equ &c040
ScoreDspScore equ &48
ScoreDspMult equ &c072
ScoreDspLives equ &c078
ScoreDspRound equ &c07c
ScoreDspProgLine1 equ &d864
ScoreDspProgLine2 equ &e064

.ClearScr
; called with A holding &c0 or &80 to clear high or low screen
    ld h,a
    ld l,0
    ld d,a
    ld e,1
    ld (hl),l
    ld bc,&3fff
    ldir
    ret

; below used to find the two digits of a byte number (max 99)
; used by timer and multiplier
.FindNumber8bit
; enter with a holding the byte to find the digits and c holding units (10 or 100)
; exits with b holding digit and a holding remainder
    ld b,0
.FiNu8bitLoop
    inc b
    sub a,c
    jr nc,FiNu8bitLoop
    dec b
    add a,c
    ret

; unroll find number for speed increase with reversed jump
.FindNumber16bit ; used for score, find a digit in a word of the score
; enter with hl holding score, de the digit to be found (as a negative number -1000,-100,-10)
; exits with a holding digit and hl holding remainder
; this routine must be used by finding most significant digit first, then work down to least significant
    xor a
    add hl,de
    jr nc,FiNu16bitExit ; 0
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 1
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 2
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 3
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 4
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 5
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 6
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 7
    inc a
    add hl,de
    jr nc,FiNu16bitExit ; 8
    inc a
    ret
.FiNu16bitExit
    sbc hl,de ; want hl to hold correct remainder
    ret

.PrintFastChar
; used to print an in game numeral in score line, not title screen printing
; enter with a = to 0-10
; de = screen address to print
    ld h,FastFontLU/256
    rlca
    add a,&40
    ld l,a
    ld a,(hl)
    inc l
    ld h,(hl)
    ld l,a
    jp (hl)

.UpdateScoreLine
; enters with a holding ScoreUpdater variable
; this routine updates 3 things, the score, the multiplier and progress bar, and the timer
; first use scoreupdater in a to determine which part of score it is time to update
    bit 0,a
    jp nz,USL_Convert_Score_Decimal ; convert current word to digits for printing
; otherwise, take converted score word and update score line display
.DisplayScore
; display one word of score segment, a holds 0,2,4
    add a,a
    add a,a
    add a,ScoreDspScore ; got screen address offset for this word
    ld e,a
    ld d,&c0 ; de holds screen address to write this word of the score to
    exx
    ld hl,BCD_Score_Segment
    ld a,(hl)
    inc l
    exx
    call PrintFastChar
    exx
    ld a,(hl)
    inc l
    exx
    call PrintFastChar
    exx
    ld a,(hl)
    inc l
    exx
    call PrintFastChar
    exx
    ld a,(hl)
    exx
    call PrintFastChar
; for second stage, find number for multiplier or timer
    ld a,(ScoreUpdater) ; will be 0,2,4
    or a
    jr nz,USL_Convert_Number_Timer
; convert number for multiplier one out of 3 times
    ld hl,Multiplier
    ld a,(hl)
    dec l
    cp a,(hl)
    jr z,USL_Display_Multi_Bar ; if multiplier not changed since last update, then skip
    ld (hl),a ; write current multipler to (hl) MultiplierDsp
    set 7,(hl) ; and trigger next multiplier write to update display
    ld c,10
    call FindNumber8bit
; b now holds 10's, a the units of the multiplier
    ld hl,BCD_Round_Mult
    ld (hl),b
    inc l
    ld (hl),a ; have written multiplier to display buffer for next frame
    jr USL_Display_Multi_Bar
.USL_Convert_Number_Timer
; convert timer display two out of 3 times
    ld hl,RoundTime
    ld a,(hl)
    dec l
    cp a,(hl)
    jr z,USL_Display_Multi_Bar ; if roundtime not changed since last update, then skip
    ld (hl),a ; write current roundtime to (hl) RoundTimeDsp
    set 7,(hl) ; and trigger next roundtime write to update display
    ld c,10
    call FindNumber8bit
; b now holds 10's, a the units
    ld hl,BCD_Round_Mult+1
    ld (hl),a
    dec l
; for timer, check for leading 0 and make a blank char (value 10) if so
    ld a,b
    or a
    jr nz,USL_NoLeading0inTimer
    ld a,10
.USL_NoLeading0inTimer
    ld (hl),a ; have written timer to display buffer for next frame
.USL_Display_Multi_Bar
; finally, update the multiplier bar on screen
    ld de,ScoreDspProgLine1 ; first line screen memory location start of multiplier progress bar
    ld hl,ProgBarDisplay ; bar worked out on previous frame
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ld de,ScoreDspProgLine2 ; first line screen memory location start of multiplier progress bar
    ld hl,ProgBarDisplay
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ret
.USL_Convert_Score_Decimal
; get a word of the 3 word score and convert to decimal bytes for display
    res 0,a ; a now holds 0,2 or 4
    ld l,a
    ld h,Score/256
    ld e,(hl)
    inc l
    ld d,(hl)
    ex de,hl ; put score in hl to extract the digits
    ld bc,BCD_Score_Segment ; each of the 4 digits placed in this table for subsequent display
    ld de,-1000 ; maximum score in a word is 9999
    call FindNumber16bit
    ld (bc),a
    inc c
    ld de,-100
    call FindNumber16bit
    ld (bc),a
    inc c
    ld de,-10
    call FindNumber16bit
    ld (bc),a
    inc c
    ld a,l ; hl still held remainder for units
    ld (bc),a
; now update timer or multiplier, depending on ScoreUpdater
    ld a,(ScoreUpdater) ; will be 1,3,5
    cp a,4
    jr nc,USL_Display_Number_Multiplier
; is time to update round timer on screen
    ld hl,RoundTimeDsp
    bit 7,(hl) ; check display trigger
    jr z,USL_Convert_Multi_Bar ; has not changed since last time, no need to update
    res 7,(hl) ; clear display trigger
    ld de,ScoreDspTime ; de holds screen address to write timer to
    jr USL_Display_Update_Common
.USL_Display_Number_Multiplier
    ld hl,MultiplierDsp
    bit 7,(hl) ; check display trigger
    jr z,USL_Convert_Multi_Bar ; has not changed since last time, no need to update
    res 7,(hl) ; clear display trigger
    ld de,ScoreDspMult ; de holds screen address to write multiplier to
.USL_Display_Update_Common
    exx
    ld hl,BCD_Round_Mult ; buffer used to hold the two digits for round or multiplier
    ld a,(hl)
    inc l
    exx
    call PrintFastChar
    exx
    ld a,(hl)
    exx
    call PrintFastChar
.USL_Convert_Multi_Bar
; finally, convert the multiplier bar for display on screen
    ld hl,ProgBarDisplay+9
    ld bc,&a04
; first step, if multiplier is max (99) then show no progress bar at all
    ld a,(Multiplier)
    cp a,99
    jr c,USL_Convert_Multi_Bar_Cont
; progress bar should not display as multiplier maxed, set up a blank progress bar
    xor a ; set bar length to 0
    jr USL_Blank_Prog_Bar ; use same setup as 0 length progress bar
.USL_Convert_Multi_Bar_Cont
; need to display the progress bar, work out current level for display
    ld a,(MultProgBar)
    inc a ; want even the smallest fraction to show on bar
    rrca
    ld e,&ff ; byte for full length of bar in a single byte - all pixels on
    and a,&3f ; cap bar
    jr nz,USLLengthLoop
.USL_Blank_Prog_Bar
; if progress bar is 0, show whole thing blank
    ld e,0 ; no pixels on
    dec a ; reduce a below 0 so does not carry on any future subtraction
.USLLengthLoop
    sub a,c
    jr c,USLPartByte
    jr z,USLLastFullByte
; all pixels in byte lit up, carry on
    ld (hl),e
    jr USLLoopNext
.USLPartByte
    cp a,255
    jr z,USLThreeQuarters
    cp a,254
    jr z,USLHalfByte
; must be one quarter byte
    ld (hl),17 ; data for one pixel byte
    ld e,0
    jr USLLoopNext
.USLHalfByte
    ld (hl),51 ; data for two pixel byte
    ld e,0
    jr USLLoopNext
.USLThreeQuarters
    ld (hl),119 ; data for three pixel byte
    ld e,0
    jr USLLoopNext
.USLLastFullByte
    ld (hl),e
    ld e,0
    dec a ; reduce below 0 so does not carry next subtraction
.USLLoopNext
    dec l ; point to next byte in progress bar display buffer
    djnz USLLengthLoop
    ret

.UpdateLivesDisplay
; only called when lives has been reduced
    ld a,(Lives)
    or a
    jr z,ULD_FoundLivesDisplayNumber ; display lives-1, unless lives is 0
    dec a
    cp a,10
    jr c,ULD_FoundLivesDisplayNumber
    ld a,9 ; cap displayed lives to 9, if over for some reason
.ULD_FoundLivesDisplayNumber
    ld de,ScoreDspLives
    jp PrintFastChar ; and exit from ret in PrintFastChar

.UpdateRoundDisplay
; only called when new round begun
    ld a,(Round)
    ld c,10
    call FindNumber8bit
; b now holds 10's, a the units
    ld de,ScoreDspRound+2 ; print units first, so screen address two bytes right on screen updates first
    call PrintFastChar
    ld a,b ; get 10's into a
    or a ; if 10's are 0, then set to blank char (10)
    jr nz,ULD_RoundDisplayNoLeading0
    ld a,10 ; still print blank char so at start of game, non zero 10's char is cleared
.ULD_RoundDisplayNoLeading0
    ld de,ScoreDspRound
    jp PrintFastChar ; and exit from ret in PrintFastChar

.UpdateScoring
; updates score with calculations for score this frame, and determines current multiplier
    ld a,(FrameHits)
    ld e,a ; set aside for use shortly
    xor a
    ld (FrameHits),a ; zero frame hits for next frame, this is called at start of new frame
    ld a,(Multiplier)
    cp a,99
    jr nc,UpdScoringMultDone ; multiplier maxed out, no further checks required
; mult progress bar not maxed, so frame hits added to it
    ld c,a ; preserve multiplier if needed later
    ld a,(MultProgBar)
    add a,e
    ld (MultProgBar),a
    cp a,80
    jr c,UpdScoringMultDone ; multiplier hasnt reached next level yet
    sub a,80 ; sub 80 rather than set to 0 to preserve overflow of any extra hits over 80 this frame
    ld (MultProgBar),a ; reduce prog bar by 80 steps (0-79)
    ld a,c ; get multiplier back and increment
    inc a
    ld (Multiplier),a
.UpdScoringMultDone
; now update minor score for hits landed last frame
; multiply hits this frame by current multiplier
; uses 'classic' multiplication as described on the CPCWiki, except that bit 7 is ignored as
; the maximum multiplier is 99
;;Input: H = Multiplier, E = Multiplicand, L = 0, D = 0
;;Output: HL = Product
; get multiplier and score from hits for this frame
    xor a
    ld l,a
    ld d,a ; e has been loaded with framehits above
    ld a,(Multiplier)
    ld h,a
; multiply them together
    sla h  ; msb will always be zero as maximum multiplier is 99, so ignore it
    sla h  ; optimised 1st iteration
    jr nc,$+3
    ld l,e
    add hl,hl  ; unroll 6 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
; now add to least significant score word
    ld bc,(Score+4)
    add hl,bc
    ld de,-10000
    call FindNumber16bit
    ld (Score+4),hl ; remainder is new low word
; a holds digit to add to middle score word, save it somewhere for later use
    ld (UpdateScore_NoHits_Return+1),a
; now get frame score for segments and frame multiplier bonus for simultaneous segments destroyed
    ld hl,FrameScore
; multiply hits this frame by current multiplier
;;Input: H = Multiplier, E = Multiplicand, L = 0, D = 0
;;Output: HL = Product
; get multiplier and score from hits for this frame
    ld d,0
    ld e,(hl) ; the multiplicand
    ld (hl),d ; zero the score so not counted twice
    inc l
    ld a,(hl) ; the multiplier
    ld (hl),d ; zero the frame multiplier so not counted twice
    ld (UpdateScore_Bump_Mulitplier+1),a ; save segment multiplier for end of rout for bonus
    or a
    jr z,UpdateScore_NoSegments_ThisFrame ; no need for extra maths this frame
    ld h,a ; put multiplier into h
    ld l,d ; zero l
; multiply them together - uses same multiplication approach as above
    sla h
    sla h
    sla h
    sla h  ; top nibble will always be zero as maximum multiplier is 9, so ignore them
    sla h  ; optimised 1st iteration
    jr nc,$+3
    ld l,e
    add hl,hl  ; unroll 3 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 3 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 3 times
    jr nc,$+3  ; ...
    add hl,de  ; ...
; hl now contains segment destruction score, so there needs to be a 16bit multiplication with the
; current score multiplier
;; Input: A = Multiplier, DE = Multiplicand, HL = 0
;; Output: HL = Product
    ex de,hl ; put multiplicand in de
    ld hl,0
    ld a,(Multiplier)
; first note that de is limited to 549, frame score to 61, so product will be 16 bit, not 24
; also note that multiplier is 7 bit, max of 99, so ignore msb of multiplier, as above
    rla   ; ignore msb, always 0
    add a,a  ; optimised 1st iteration
    jr nc,$+4
    ld h,d
    ld l,e
    add hl,hl  ; unroll 6 times
    rla   ; ...
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    rla   ; ...
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    rla   ; ...
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    rla   ; ...
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    rla   ; ...
    jr nc,$+3  ; ...
    add hl,de  ; ...
    add hl,hl  ; unroll 6 times
    rla   ; ...
    jr nc,$+3  ; ...
    add hl,de  ; ...
; hl now contains all up multiplied figure, time to add to overflow from hit score
.UpdateScore_NoHits_Return
    ld de,0 ; will be 0 or 1 depending on result of hits landed scoring performed above
    add hl,de
; now get current middle word score
    ld de,(Score+2)
    add hl,de
; find overflow
    ld de,-10000
    call FindNumber16bit
    ld (Score+2),hl ; remainder is new middle word of score
; a contains overflow to most significant word
    ld e,a
    ld d,0
    ld hl,(Score)
    add hl,de
    ld (Score),hl ; can assume this word will never overflow, so no check for it
.UpdateScore_Bump_Mulitplier
    ld a,0 ; this is the frame multiplier for destroying segments of a dragon saved here from
           ; earlier in this routine
    or a
    ret z
    dec a
    ret z ; if multiplier only 1, only 1 segment destroyed, no multiplier bonus
    ld c,a ; put aside segment bonus value for adding to current multiplier bonus
    ld a,(Multiplier)
    add a,c
    cp a,100
    jr c,UpdateScore_MultiNoCap
    ld a,99 ; need to cap multiplier at 99
.UpdateScore_MultiNoCap
    ld (Multiplier),a ; adding this frames segment bonus-1 to current multiplier bonus
    ret

.UpdateScore_NoSegments_ThisFrame
    ld hl,0
    jr UpdateScore_NoHits_Return

.DisplayTimeBonus
; enter with a holding current non zero timer value of time bonus display trigger
    dec a
    ld (TimeBonusDspTimer),a
    cp a,45
    jr c,DTBPrintText ; dont need to set colours this frame
    ret nz ; is 46, give time for last explosion frame to clear before changing boss colours for use by bonus text
; time to set bonus text colours when timer = 45
    ld hl,BossColours
    ld (hl),&5f ; hardware colour used by bonus text
    inc l
    ld (hl),&5b ; hardware colour used by bonus text
.DTBPrintText ; time to display, colours having been set
; firstly, always clear the two lines of text for current work screen
    ld hl,&c0d6
    ld a,(WorkScr)
    bit 6,a
    jr nz,DspTBHigh
    res 6,h ; set hl to low screen address
.DspTBHigh
    push hl ; preserve current start screen address
    call DspTBClearLine ; clear line of time bonus text on play area work screen
    pop hl
    ld (DspTBScrAddr+1),hl
    inc h
    res 7,l ; screen address now two char lines lower
    call DspTBClearLine
; have now cleared text if was displayed previously, now check if need to print it again this frame
    ld a,(TimeBonusDspTimer)
    cp a,2
    ret c ; only two frames left, so exit, only clear text on last two frames
.DspTBScrAddr
    ld de,0 ; loaded with screen address to write to from code just above
    push de
    call DspTBWriteFirstLine
    pop de
    inc d
    res 7,e
    inc e ; de now points two char lines below first, half a character across to the right
; read time bonus value from display buffer, point to it with hl
;.DspTBWriteSecondLine
    ld hl,BCD_TimeBonus
    ld a,(hl)
    cp a,10
    jr c,DspTBWriteFirstDigit
; ignore first digit, is single digit time bonus
    inc e ; move scr addr pointer right by one to even out alignment of text if single digit bonus
    jr DspTBWriteSkipFirstDigit
.DspTBWriteFirstDigit
; display first digit (10's), is double digit time bonus
    push hl
    call PrintTimeBonusChar
    pop hl
.DspTBWriteSkipFirstDigit
    inc l
    ld a,(hl) ; retrieve time bonus units, always non zero
    call PrintTimeBonusChar
    inc e:inc e ; create a space
    ld a,10 ; the 'x'
    call PrintTimeBonusChar
    inc e:inc e ; create a space
    ld a,1 ; print '1'
    call PrintTimeBonusChar
    xor a ; print '0'
    call PrintTimeBonusChar
    xor a ; print '0'
    call PrintTimeBonusChar
    xor a ; print '0'
    jr PrintTimeBonusChar ; print the final 0, and exit via the PrintTimeBonusChar routine

.DspTBClearLine
; enter with hl pointing to top of char line to clear 10 characters on game screen
    ld d,0
    ld c,7
    ld e,l ; preserve l starting point
.DspTBCLOutrLp
    ld b,5
.DspTBCLInnrLp ; have used slightly unrolled loop rather than ldi list or ldir because
    ld (hl),d  ; this averages out to 4 nops per byte rather than 5 or 6 of the ldi/ldir approach
    inc l
    ld (hl),d
    inc l
    ld (hl),d
    inc l
    ld (hl),d
    inc l
    djnz DspTBCLInnrLp ; clear 20 byte pixel line
    ld l,e ; put l starting point back in l, saved in e before loop
    ld a,8
    add a,h
    ld h,a ; make address line the next line below previous
    dec c ; repeat 7 times for whole character - 8th line unused by this text
    jr nz,DspTBCLOutrLp
    ret

.DspTBWriteFirstLine
    ld hl,TimeBonusText ; location of graphics for the word 'time bonus'
    ld c,19*7 ; 7 lines, 1 iteration plus 18 ldi's per line
    ld b,e ; preserve start of screen address line in b
.DspTBWFLLp
    ldi:ldi:ldi:ldi:ldi:ldi:ldi:ldi ; this copies 1 line of "time"
    inc e:inc e ; put in a space between the words
    ldi:ldi:ldi:ldi:ldi:ldi:ldi:ldi:ldi:ldi ; this copies 1 line of "bonus"
    ld e,b ; reset de to start of line
    ld a,8
    add a,d
    ld d,a ; de points to start of next pixel line of char
    dec c
    jr nz,DspTBWFLLp
    ret

.PrintTimeBonusChar
; used to print an in game time bonus numeral quickly
; enter with a = to 0-9
; de = location to print
    ld h,timebonus0/256 ; characters are stored at &5f50, so simple maths to find data for the characters
    rlca
    rlca
    rlca
    rlca ; multiplied digit by 16
    add a,&50 ; add base of first character to result
    ld l,a ; hl now points to data of digit to display
; now print top 7 lines
; have unrolled the display loop for extra speed, and character data is ordered so only 1 set
; or reset is used to traverse between pixel lines
    ldi
    ld a,(hl)
    ld (de),a
    inc l
    set 3,d
;
    ld a,(hl)
    ld (de),a
    inc l
    dec e
    ld a,(hl)
    ld (de),a
    inc l
    set 4,d
;
    ldi
    ld a,(hl)
    ld (de),a
    inc l
    res 3,d
;
    ld a,(hl)
    ld (de),a
    inc l
    dec e
    ld a,(hl)
    ld (de),a
    inc l
    set 5,d
;
    ldi
    ld a,(hl)
    ld (de),a
    inc l
    set 3,d
    res 4,d
;
    ld a,(hl)
    ld (de),a
    inc l
    dec e
    ld a,(hl)
    ld (de),a
    inc l
    res 3,d
;
    ldi
    ld a,(hl)
    ld (de),a
 ; set de for next char to print
    res 5,d
    inc e
;
    ret


