;;-----------------------------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
;;-------------------------------------------------------------------------------
; v7 adds player explosion

.Start_Interrupt
; must be called with interrupts disabled
; prime first interrupt
    ld hl,int_1_title
    ld (Int_sub_ptr+1),hl

; wait for 2 vsyncs to allow it to stabalise so that we can sync with that
    ld e,2
wait_two_vsyncs:
    ld b,&f5
wait_vsync_end:
    in a,(c)
    rra
    jr c,wait_vsync_end
wait_vsync_start:
    in a,(c)
    rra
    jr nc,wait_vsync_start
    dec e
    jr nz,wait_two_vsyncs
; synchronised with start of vsync and we are synchronised with CRTC too, and we have the 
; same number of lines to next screen as we want.
    ei
    ret

; called to sync game and menu loops to particular interrupt, which will set int_flag to 0
.wait_int
    ld a,1
    ld (int_flag),a
.wait_int_lp
.int_flag equ $ + 1
    ld a,0
    or a
    jr nz,wait_int_lp
    ret

; called to sync game exit to particular interrupt, which will set exit_flag to 0
.wait_exit
    ld a,1
    ld (exit_flag),a
.wait_exit_lp
.exit_flag equ $ + 1
    ld a,0
    or a
    jr nz,wait_exit_lp
    ret

; every interrupt begins with this code
.Int_start
    push bc
    push hl
    push af
; each interrupt in turn modifies the following jump to the next routine in the sequence
.Int_sub_ptr
    jp Int_1_Game


;--------- these following interrupt routines are used in-game --------
; first interrupt after vsync
.Int_1_Game
; set vsync position to turn it off
    ld bc,&bc07
    out (c),c
    ld a,&ff ; preserve c for later in this interrupt
    inc b
    out (c),a
; manage refresh counter to lock main game engine to 17fps
.int_1_game_FrameCtr
    ld a,0
    inc a
    cp a,3
    jr c,Int1_NoNewFrame ; if not been 3 screen rfreshes yet, no new frame
    ld hl,int_flag
    bit 0,(hl)
    jr z,Int1_NoNewFrame
; time to swap display screens
    ld a,(WorkScr)
    ld e,a
    xor a,&40 ; swap a between &c0 and &80
    ld (WorkScr),a
    ld a,e
    rrca
    rrca ; convert a to to hardware ptr value, needs to be &30 or &20
    ld (Int2ScrBase+1),a ; and write to code in next interrupt
; move multiplier frame counters so can be handled safely by main engine
; without interference by interrupts
    ld hl,MultProgBarFrame
    ld a,(hl)
    ld (hl),0
    dec l
    cp a,128
    jr c,Int1_Game_NoMultiRST
; player just died, need to reset the multiplier and progress bar
    ld (hl),0
    ld a,(Multiplier)
    sra a
    jr nc,Int1_Game_NC
; round up
    inc a
.Int1_Game_NC
    ld (Multiplier),a
    jr Int1_Game_SkipAdd
.Int1_Game_NoMultiRST
    add a,(hl)
    ld (hl),a ; interrupt based multiplier additions handled, main code can add hit based increments freely
.Int1_Game_SkipAdd
; now reset int counter and clear wait int so next game frame starts
    xor a
    ld (int_flag),a
.Int1_NoNewFrame ; jumped to here from above if no new game frame started
    ld (int_1_game_FrameCtr+1),a ; set zeroed or incremented ctr for next int1

; set height of score line part of screen, crtc regiter 6 = 2
    dec b ; get b back to &bc
    dec c ; c = 6
    out (c),c
    ld a,2
    inc b
    out (c),a

; screen address for score line part of screen, always crtc reg 12 = &30, crtc reg 13 = 0
; this sets score line screen to read &c000 all the time
    ld bc,&bc0c
    out (c),c
    inc b
    ld a,&30
    out (c),a
    dec b
    inc c
    out (c),c
    inc b
    xor a
    out (c),a

; use gate array to set the score line colours
; set backround black - want the score line to appear to be 'in the border', not play area
    ld bc,&7f00
    out (c),c
    ld a,&54
    out (c),a
; set col 1 grey
    inc c
    out (c),c
    ld a,&40
    out (c),a
; set col 2 white
    inc c
    out (c),c
    ld a,&4b
    out (c),a
; set col 3 orange
    inc c
    out (c),c
    ld a,&4e
    out (c),a

; set gate array to display in mode 1
    ld bc,&7F8d
    out (c),c

; preserve all other registers
    push de
    push iy
    push ix
    ex af,af'
    push af
    exx
    push de
    push bc
    push hl
; clearplayer performs clear of player and player bullets, handles player collision
; and then calculates new screen position and redisplays player and their bullets
; this all occurs to the active screen, not the buffer, so needs to happen during first interrupt
; to avoid sprite flicker
    call ClearPlayer ; see DA_PlayerSpriteDisplay9.asm
; as for the player, the enemy 'fast' lasers move at 50fps, so they are updated on the active
; screen.  printfastshot updates the lasers to their current position after moving them down, if active
    call PrintFastShot ; see DA_FastShot2.asm
; restore all registers preserved above
    pop hl
    pop bc
    pop de
    exx
    pop af
    ex af,af'
    pop ix
    pop iy
    pop de
; load hl with address of subsequent interrupt routine
    ld hl,Int_2_Game
    jp int_end ; exit to common interrupt exit code

;---------
.Int_2_Game
; in this int routine, the player bullet collision is performed
; also do a mode and colour change for the play area
; at this point the score line screen has not completed, so need to set a few things up
; first set crtc reg 4 = 1 - vertical total is number of char lines less 1
    ld bc,&bc04
    out (c),c
    ld bc,&bd00+1
    out (c),c
; now set crtc reg 12 & 13 to point to the active screen for the play area
; will be &c080 or &8080 in memory, writen to the crtc registers as &3040 or &2040
    ld bc,&bc0c
    out (c),c
.Int2ScrBase ; this is set in interrupt 1
    ld bc,&bd00+&30
    out (c),c
    ld bc,&bc0d
    out (c),c
    ld bc,&bd00+64
    out (c),c

; at this point, the last pixel line of the score line has commenced, so the gate array can
; be programmed to show mode 0 for the next display line, as this will not take effect until
; the next line starts
; set mode 0
    ld bc,&7F8c
    out (c),c
; preserve some registers used by the collision rout later in this interrupt
; this is not required yet, but burns some cpu time so we can wait until near the end of the 
; last score line to set the play area colours, as colour changes take effect immediately
    push de
    ex af,af'
    push af
    exx
    push bc
    push hl
; use gate array to set up boss colours 2 and 3
    ld hl,BossColours
    ld a,(hl)
    ld bc,&7f02
    out (c),c
    out (c),a ; first colour specific to this round read from table sent to gate array
; set background grey now, want to make background colour change appear seamless
    xor a
    out (c),a ; write to pen 0
    ld a,&40 ; grey
    out (c),a
; now get second level specific colour, change may be delayed and slightly visible
; on screen along top left corner of play area, but should be hardly noticeable
    inc l ; inc the colour list pointer
    ld a,(hl)
    inc c ; point to pen 3
    out (c),c
    out (c),a ; second colour specific to this round
; set col 1 black
    ld a,1
    out (c),a
    ld a,&54
    out (c),a

; at this point, the second screen has begun, so the height of the current screen & vsync can be set
; set display height of screen with crtc reg 6 = 30 (character lines)
    ld bc,&bc06
    out (c),c
    ld bc,&bd00+30
    out (c),c

; set total height of screen crtc reg 4 = 36 - this gives 37 chars high for this part of the display
; added to the score line (set to 1 for 2 char lines in int 1) this gives the normal 39 char high display
    ld bc,&bc04
    out (c),c
    ld bc,&bd00+36
    out (c),c

; set vsync position crtc reg 7 = 32 determines when vsync signal begins
    ld bc,&bc07
    out (c),c
    ld bc,&bd00+32
    out (c),c

; do player bullet collision
    call CollisionPBullets ; see DA_collision5.asm
; restore registers preserved at start of int 2
    pop hl
    pop bc
    exx
    pop af
    ex af,af'
    pop de
; set up jump to next interrupt and exit
    ld hl,Int_3_Game
    jp int_end

;---------
.Int_3_Game
; only one task in int 3 - if this is the first screen rerfresh of a new game frame
; then clear the fast laser shots from the newly hidden buffer
; is only performed here and not in the main game loop so the stack can be used to
; refrence the screen address lu table without clashing with interrupts, which would corrupt the table
    ld a,(int_1_game_FrameCtr+1)
    or a
    jr nz,Int3_SkipClearFastShot ; if non zero, skip this process as it has been done previously
    ld c,a ; preload c with 0 for use in clear routine below
    exx
    push de
    push bc
    push hl
    call ClearFastShot ; see DA_FastShot1.asm
    pop hl
    pop bc
    pop de
    exx
.Int3_SkipClearFastShot
; set up jump to next interrupt and exit
    ld hl,Int_4_Game
    jp int_end

;--------- this interrupt plays music at 25hz, and triggers sfx or song changes
;--------- also handles score line update sometimes - see below for conditions
.Int_4_Game
; preserve all remaining registers for music player
    push de
    exx
    push hl
    push de
    push bc
    ex af,af'
    push af
    push ix
    push iy
; invert 25hz toggle for playing the music
.int_4_game_toggle
    ld a,0
    xor a,1
    ld (int_4_game_toggle+1),a
    jr z,Int4SkipMusic ; this frame, do not call music player, handle sfx instead
; this frame, call the music player
.Int4Game_MusicControl
    ld a,0 ; this value is written to trigger new music or normal playing
    or a
    jr z,Int4Game_PlayMusic ; if a=0, continue playing
; if non zero, alter a to point to music lu table at &0040 in ram
    dec a
    add a,a
    add a,&40
    ld l,a
; clear then new music trigger and any fade setting
    xor a
    ld (Int4Game_MusicControl+1),a ; clear the trigger
    ld (Int4Game_PlayMusic+1),a ; and the fade
    exx
    call PLY_SetFadeValue ; clear fade in player code, see ArkosTrackerPlayer_CPC_MSX.asm 
    exx
    ld h,a
; now load new music address pointed to by hl into de for player
    ld e,(hl)
    inc l
    ld d,(hl) ; de points to new music to play
    call Ply_Init ; see ArkosTrackerPlayer_CPC_MSX.asm 
    ld de,SoundFX ; test sound effects SFXMusicAddress
    call PLY_SFX_Init ;to initialise the SFX Song. see ArkosTrackerPlayer_CPC_MSX.asm 
.Int4Game_PlayMusic
; first need to check if music fading has been set
    ld a,0
    or a
    jr z,Int4Game_NoFading ; a=0 means no music fade
; need to fade, base fade value on round end timer
    ld a,(RoundEnd)
    sra a
    sra a
    sra a
    neg
    add a,6
    jp m,Int4Game_NoFading ; if round end timer too high, dont change player fade value yet
    call PLY_SetFadeValue ; see ArkosTrackerPlayer_CPC_MSX.asm
.Int4Game_NoFading
    call Ply_Play ; see ArkosTrackerPlayer_CPC_MSX.asm
    jr Int4SkipScoreUpdate ; skip to end off interrupt 4
.Int4SkipMusic
; this frame, trigger sound effects
    call HandleSfx ; see DA_SoundFX3.asm
; as the music is 25hz, but the game is 17fps, the music plays twice one game frame and once another
; to balance the cpu load, the score line graphics are only updated on the game frame when the music
; is only played once that game frame
; ie, update score line if framectr = 0 or 2+, music played on 1, but not when
; framectr = 1, as music played on 0 and 2
    ld a,(int_1_game_FrameCtr+1)
    cp a,1
    jr z,Int4SkipScoreUpdate ; if this game frame has two music updates, no score update
; decrement the marker for which part of score to update, rotates around 0-5
    ld a,(ScoreUpdater)
    dec a
    jp p,Int4G_NoRst
    ld a,5 ; start from 5 again
.Int4G_NoRst
    ld (ScoreUpdater),a ; write back new score update ptr
    call UpdateScoreLine ; see DA_Display6.asm
.Int4SkipScoreUpdate
; restore all previously preserved registers
    pop iy
    pop ix
    pop af
    ex af,af'
    pop bc
    pop de
    pop hl
    exx
    pop de
; set up jump to next interrupt and exit
    ld hl,Int_5_Game
    jp int_end

;--------- handles enemy sprite printing and round timer
.Int_5_Game
; if main code has determined new enemy positions and addresses, then print them
; as with clearing the fast enemy shots, this only done here so sp can be used to
; read addresses quickly with interrupts corrupting the lists being read
    ld a,(PrnEnemy)
    or a
    jr z,NotReadyToPrintEnemySpr
; ready to print new sprite frames
    xor a
    ld (PrnEnemy),a ; clear enemy print trigger
; preserve required registers
    push de
    exx
    push bc
    push hl
    call PrintEnemySprites ; see BH_EnemySpriteDisplay2.asm
; restore register
    pop hl
    pop bc
    exx
    pop de
.NotReadyToPrintEnemySpr
; if round timer has not expired, decrement the 50ths of a second and timer if needed
    ld hl,RoundTime
    ld a,(hl)
    or a
    jr z,Int_5_Game_RoundTimerExpired ; round timer expired, no further action
; round time not expired, so check 50th of a sec
    inc l
    dec (hl)
    jr nz,Int_5_Game_RoundTimerExpired ; 50th second timer still non zero, so no further action
; have ticked down a whole second, reset 50th sec timer
    ld (hl),50
    dec l
    dec (hl) ; and decrement round timer
; round has expired if boss still alive, checks made in main game engine when boss
; reaches the bottom of it's movement zone
.Int_5_Game_RoundTimerExpired
; set up jump to next interrupt and exit
    ld hl,Int_6_Game
    jp int_end

;--------- scan the keyboard
.Int_6_Game
    push de
    ld a,(PlayerExp)
    cp a,4
    jr c,Int6Game_PlayerLive
; player is exploding, skip key reading and perform explosion shrapnel movement
    call PlayerShrapnelMove ; see DA_Move6.asm
; still need to move player bullets
    call PlayerBulletMovement ; see DA_Move6.asm
    jr Int6Game_SkipMv
.Int6Game_PlayerLive
; player is alive, so read control input and act on it
    call ReadJoystick ; see DA_Move6.asm
    call PlayerMovement ; includes PlayerBulletMovement - see DA_Move6.asm
    call FirePlayer ; see DA_Fire5.asm
.Int6Game_SkipMv
    pop de
; check for trigger to return to menu
.Int_6_Game_EndTrigger
    ld a,0
    or a
    jr z,Int_6_Game_Cont
; game end has been triggered
    xor a
    ld (Int_6_Game_EndTrigger+1),a ; reset trigger
    ld a,2 ; trigger stop of current music at first menu music interrupt
    ld (TitleMusic_Control),a
    ld hl,BlankMenuCols+3 ; and set up menu with blank colours for initial screen build
    ld (MenuColourPtr),hl
; set border grey for menu
    ld bc,&7f10
    out (c),c
    ld a,&40
    out (c),a
; set up next interrupt to be the first menu interrupt
    ld hl,Int_1_Title
    jr int_end
.Int_6_Game_Cont
; game continues, set up next interrupt to be the first game interrupt
    ld hl,Int_1_Game

; common interrupt exit routine
.int_end
; write next jump for next interrupt
    ld (Int_sub_ptr+1),hl
; restore preserved registers
    pop af
    pop hl
    pop bc
    ei
    ret

;--------- the interrupts used by the menu system follow
;--------- the screen split has identical timings, just different code in the interrupts
; first interrupt after vsync
.Int_1_Title
; set vsync position to turn it off
    ld bc,&bc07
    out (c),c
    ld a,&ff
    inc b
    out (c),a

; set height of main part of screen - crtc reg 6 = 2
    dec b
    dec c
    out (c),c
    ld a,2
    inc b
    out (c),a

; screen address for top two lines of screen, always crtc reg 12 = &30, crtc reg 13 = 0
; this sets score line screen to read &c000 all the time
    ld bc,&bc0c
    out (c),c
    inc b
    ld a,&30
    out (c),a
    dec b
    inc c
    out (c),c
    inc b
    xor a
    out (c),a
; set colours, colour list will be all grey when first building screen
    ld hl,(MenuColourPtr)
    push de
    call SetColoursMode1
    pop de
; set GA to use mode 1
    ld bc,&7F8d
    out (c),c
; clear wait int so next frame starts in menu loop
    xor a
    ld (int_flag),a
; set up next interrupt and exit
    ld hl,Int_2_Title
    jp int_end

;;---------
.Int_2_Title
; check exit trigger to see if interrupt needs to end
    ld a,(exit_flag)
    or a
    jr z,Int2_Title_Cont
; time to exit the interrupt for paging in the ROM
    xor a
    ld (exit_flag),a ; clear trigger
; interrupts will be disabled while rom paged in, as interrupt &38 would revert to firmware version
; so must make stable screen with no interrupts
; set the height of the screen for full frame
; set display height of screen with crtc reg 6 = 32 (character lines)
    ld bc,&bc06
    out (c),c
    ld bc,&bd00+32
    out (c),c

; set vsync position crtc reg 7 = 32 determines when vsync signal begins
; affects vertical position of the display window within the monitor display
    ld bc,&bc07
    out (c),c
    ld bc,&bd00+34
    out (c),c
; set total height of screen crtc reg 4 = 38 - this gives 39 chars high for the display
    ld bc,&bc04
    out (c),c
    ld bc,&bd00+38
    out (c),c
; once this interrupt has exited, the main code will issue a di, so interrupt 3 will not execute
    jr Int2_Title_Common
.Int2_Title_Cont
; if menu is to continue, simply set up screen address for the second screen at &c080
; using crtc reg 12/13, setting to &3040
; and set the height for the first part of the screen to 2 characters high
; less 1 when writting to crtc reg 4
    ld bc,&bc04
    out (c),c
    ld bc,&bd00+1
    out (c),c

    ld bc,&bc0c
    out (c),c
    ld bc,&bd00+&30
    out (c),c
    ld bc,&bc0d
    out (c),c
    ld bc,&bd00+64
    out (c),c

; set mode 1
    ld bc,&7F8d
    out (c),c

; wait to past end of first two lines of screen
    ld b,16
.dummylptitle1
    djnz dummylptitle1

; at this point, the second screen has begun, so the height of the current screen & vsync can be set
; set display height of screen with crtc reg 6 = 30 (character lines)
    ld bc,&bc06
    out (c),c
    ld bc,&bd00+30
    out (c),c

; set total height of screen crtc reg 4 = 36 - this gives 37 chars high for this part of the display
; added to the score line (set to 1 for 2 char lines in int 1) this gives the normal 39 char high display
    ld bc,&bc04
    out (c),c
    ld bc,&bd00+36
    out (c),c

; set vsync position crtc reg 7 = 32 determines when vsync signal begins
    ld bc,&bc07
    out (c),c
    ld bc,&bd00+32
    out (c),c

.Int2_Title_Common
    ld hl,Int_3_Title
    jp int_end

;--------- title screen interrupt 3 does nothing except set up interrupt 4
.Int_3_Title
    ld hl,Int_4_Title
    jp int_end

;;---------
.Int_4_Title
; preserve all remaining registers for music player
    push de
    exx
    push hl
    push de
    push bc
    ex af,af'
    push af
    push ix
    push iy
; invert menu music control toggle, all the music is 25hz
.int_4_title_toggle
    ld a,0
    xor a,1
    ld (int_4_title_toggle+1),a
    jr z,Int4TitleSkipMusic ; no music played this frame
; this frame, call the music player
; the control byte is a fixed point in memory rather than self mod code - see Dragon_Attack_Build6.asm
; this is so decompressed menu code can reference it
    ld a,(TitleMusic_Control)
    or a
    jr z,Int4Title_PlayMusic ; if a=0 no new music is triggered
; find which change condition has been triggered and act accordingly
    dec a
    jr z,Int4Title_MusicStart
    dec a
    jr z,Int4Title_MusicStop
    dec a
    jr z,Int4Title_NameEntryStart
    dec a
    jr z,Int4Title_MusicStartImmediate ; used to switch from high score table to menu immediately
    dec a
    jr z,Int4Title_GameWonMusicStart
    dec a
    jr z,Int4Title_GameWonMusicBadEndStart
; if a>6 then dont play anything yet
    jr Int4TitleSkipMusic
.Int4Title_MusicStop
; time to stop the music
    call Ply_Stop
    ld a,MenuMusicOff
    ld (TitleMusic_Control),a ; set title music control to skip
    jr Int4TitleSkipMusic
.Int4Title_GameWonMusicBadEndStart
; start victory music this frame for bad ending
    xor a
    ld (TitleMusic_Control),a ; set title music control to play
    call PLY_SetFadeValue ; see ArkosTrackerPlayer_CPC_MSX.asm 
    ld de,VictoryMusicBadEnd ; new music to play
    jr Int4TitleCommonStart
.Int4Title_GameWonMusicStart
; start victory music this frame for good ending
    xor a
    ld (TitleMusic_Control),a ; set title music control to play
    call PLY_SetFadeValue ; see ArkosTrackerPlayer_CPC_MSX.asm 
    ld de,VictoryMusic ; new music to play
    jr Int4TitleCommonStart
.Int4Title_NameEntryStart
; start name entry music this frame
    xor a
    ld (TitleMusic_Control),a ; set title music control to play
    call PLY_SetFadeValue ; see ArkosTrackerPlayer_CPC_MSX.asm 
    ld de,NameEntryMusic ; new music to play
    jr Int4TitleCommonStart
.Int4Title_MusicStartImmediate
    call Ply_Stop ; see ArkosTrackerPlayer_CPC_MSX.asm 
.Int4Title_MusicStart
; start menu music this frame
    xor a
    ld (TitleMusic_Control),a ; set title music control to play
    call PLY_SetFadeValue ; see ArkosTrackerPlayer_CPC_MSX.asm 
    ld de,MenuMusic ; music to play
.Int4TitleCommonStart
    call Ply_Init ; see ArkosTrackerPlayer_CPC_MSX.asm 
    ld de,SoundFX ; set sound effects file - none used on menu
    call PLY_SFX_Init ;to initialise the SFX Song.
.Int4Title_PlayMusic
    call Ply_Play ; see ArkosTrackerPlayer_CPC_MSX.asm 
.Int4TitleSkipMusic
; restore all the preserved registers
    pop iy
    pop ix
    pop af
    ex af,af'
    pop bc
    pop de
    pop hl
    exx
    pop de
; set up the next interrupt and exit this interrupt
    ld hl,Int_5_Title
    jp int_end

;--------- title screen interrupt 5 does nothing except set up interrupt 6
.Int_5_Title
    ld hl,Int_6_Title
    jp int_end

;;---------
.Int_6_Title
; read the keyboard for menu input
    push de
    call readmatrix ; see DA_Move6.asm
    pop de
.Int_6_Title_StartTrigger
    ld a,0
    or a
    jr z,Int_6_Title_Cont ; if game not started, wrap up int6
; game start has been triggered
    xor a
    ld (Int_6_Title_StartTrigger+1),a ; reset game start trigger
; set next interrupt to divert to first game interrupt
    ld hl,Int_1_Game
; set game music control to begin the music for the first round
    inc a
    ld (Int4Game_MusicControl+1),a
; set border black for game so play area boundaries clear
    ld bc,&7f10
    out (c),c
    ld a,&54
    out (c),a
; and exit interrupt
    jp int_end
.Int_6_Title_Cont
; set up first interrupt for title interrupt and exit
    ld hl,Int_1_Title
    jp int_end

