;-----------------------------------------------------------;
;                                                           ;
; Invaded                                                   ;
; Version 1.0                                               ;
; Main Game Loop Routines, etc.                             ;
;                                                           ;
;-----------------------------------------------------------;

newGame:
; Ini vars
        xor     a
        ld      (cpX),a
        sbc     hl,hl                           ; HL = 0
        ld      (score),hl
        ld      a,2
        ld      (lives),a
; Get all required pointers, etc.
        ld      hl,(dataPtr)                    ; HL => Start of Levelset header data
        ld      a,(hl)                          ; A = Number of levels
        ld      (noLevels),a
        inc     hl
        ld      a,(hl)                          ; A = Number of enemies
        ld      (noEnemies),a
        inc     hl
        ld      e,(hl)
        inc     hl
        ld      d,(hl)                          ; DE = High score
        ld      (hiScore),de
        ld      de,LEVELSET_HEADER_LENGTH-3
        add     hl,de                           ; HL => Start of compressed files
        ld      (cmpPtr),hl
        ld      ix,gbuf
        ld      de,enemyData
        ld      b,0
        call    huffExtr                        ; Extract enemy data
        xor     a
        ld      (level),a                       ; Start on first level
; Now calculate how many points each enemy is worth
        ld      hl,enemyData+18
        ld      ix,enemyScoreTable
        ld      a,(noEnemies)
        ld      b,a
calcEnemyPoints:
        push    hl
        ld      a,(hl)
        call    divAby8
        srl     a                               ; A = A / 16
        ld      l,a
        ld      h,0
        add     hl,hl
        ld      de,enemyPoints
        add     hl,de
        ld      a,(hl)
        ld      (ix),a
        inc     hl
        ld      a,(hl)
        ld      (ix+1),a
        pop     hl
        ld      de,ENEMY_DATA_LENGTH
        add     hl,de
        inc     ix
        inc     ix
        djnz    calcEnemyPoints

newLife:
; Ini some more vars, these vars are initialised after dying
        xor     a
        ld      (orb),a
        ld      (specialWeapon),a
iniLevel:
; Ini some vars, these ones are initialised at the start of a new level
        ld      a,(cpX)
        ld      (xBlock),a
        ld      l,a
        ld      h,0
        add     hl,hl
        add     hl,hl
        add     hl,hl
        ld      (xScr),hl
        ld      de,8
        add     hl,de
        ld      (x),hl
        xor     a
        ld      (beam),a
        ld      (frame),a
        ld      (guardian),a
        ld      (dead),a
        ld      (gEnemyCnt),a
        sbc     hl,hl                           ; HL = 0
        ld      (gAICnt1),hl                    ; Reset both AI1 & AI2 counters in one instruction ;)
        ld      a,3*8+2
        ld      (y),a
        call    clearAnimationArray
        call    clearBulletArray
        call    clearEBulletArray
        call    clearEnemyArray
        call    clearPickupArray
; Show screen before starting level
        bcall(_clrscrnfull)
        ld      hl,strPreLevel
        ld      de,0*256+0
        call    puts                            ; "LEVELSET:"
        ld      de,0*256+1
        call    puts                            ; "HI-SCORE:"
        ld      de,0*256+3
        call    puts                            ; "Level:"
        ld      de,0*256+4
        call    puts                            ; "Lives:"
        ld      de,0*256+5
        call    puts                            ; "Score:"
        ld      hl,levelsetName
        ld      de,11*256+0
        call    puts                            ; Show Levelset name
        ld      hl,(hiScore)
        ld      bc,5*256+1
        call    showHL
        ld      de,10*256+1
        call    puts                            ; Show Hi-Score
        ld      hl,(level)
        inc     l
        ld      h,0
        ld      bc,1*256+0
        call    showHL
        ld      de,15*256+3
        call    puts                            ; Show Level number
        ld      hl,(lives)
        ld      h,0
        ld      bc,1*256+0
        call    showHL
        ld      de,15*256+4
        call    puts                            ; Show Lives left
        ld      hl,(score)
        ld      bc,5*256+1
        call    showHL
        ld      de,10*256+5
        call    puts                            ; Show Score
        call    waitKeyA
; Now load the level and show it :)
        ld      a,(level)
        call    loadLevel                       ; Load level A
; Search through enemy placement data to find the checkpoint we're starting from
        ld      hl,(enemyIni)
        ld      a,(numEnemiesThisLevel)
        ld      b,a
        or      a
        jr      z,foundEnemyIni
        ld      a,(xBlock)
        ld      de,4
searchEnemyIni:
        cp      (hl)
        jr      c,foundEnemyIni
        add     hl,de
        djnz    searchEnemyIni
foundEnemyIni:
        ld      (enemyIni),hl
        ld      a,b
        ld      (numEnemiesThisLevel),a

mainGameLoop:
;------------------------------------------------
; MAIN GAME LOOP
;
; The main loop does the following things (in this order):
;   1. Reset APD timer, increment frame counter.
;   2. Check if player is dead, if so, ensure Orb is disabled
;   3. Checks to see if any new enemies are to be created.
;   4. Check to see if player is dead, if so and animation is complete, leave main loop.
;   5. Scroll screen (only every 4th frame).
;   6. Calculate player screen X Coorinate and save it (saves having to calc. it every time it's needed)
;   7. Show beam power on status bar.
;   8. Move Orb (if it's on screen)
;   9. Move item pickups.
;  10. Check to see if player is picking up any pickups
;  11. Move players bullets.
;  12. Check to make sure all player bullets are still on screen
;  13. Check to see if any player bullets are hitting solid tiles
;  14. Check to see if end-of-level boss if being damaged (if fighting one), if it's dead go to next level.
;  15. Move end-of-level boss (if fighting one), only every 2nd frame.
;  16. Produce a normal enemy from the end-of-level boss (if fighting one that does so).
;  17. Check to see if any enemies are being damaged.
;  18. Move enemies.
;  19. Check to see if any enemies have fallen off the screen, if so, remove them.
;  20. Move enemy bullets.
;  21. Check to see if any enemy bullets have fallen off the screen, if so, remove them.
;  22. Check to see if any enemy bullets are hitting solid tiles
;  23. Check to see if any enemy bullets have been hit by the Orb
;  24. Update explosion animations.
;  25. Check for collisions between player and enemies
;  26. Check for collisions between player and end-of-level boss (if fighting one)
;  27. Check for collisions between player and enemy bullets
;  28. Draw pickups.
;  29. Draw player bullets.
;  30. Draw enemy bullets.
;  31. Draw explosion animations.
;  32. Draw enemies.
;  33. Draw end-of-level boss if fighting one.
;  34. Draw player.
;  35. Draw orb (if it's on screen).
;  36. Copy video buffer to lcd.
;  37. Erase orb (if it's on screen).
;  38. Erase player.
;  39. Erase end-of-level boss if fighting one.
;  40. Erase enemies.
;  41. Erase explosion animations.
;  42. Erase enemy bullets.
;  43. Erase player bullets.
;  44. Erase pickups.
;  45. Check to see if player is dead, if so, skip key press checking.
;  46. Check for movement.
;  47. Check for teacher key.
;  48. Check for shooting off Orb.
;  49. Check for pausing.
;  50. Check for shooting.
;  51. Check for quitting.
;  52. If necessary, wait a bit to make frames even.
;  53. Return to start of loop.
;------------------------------------------------
        xor     a
        ld      (timer),a                       ; Reset APD timer
        ld      hl,frame
        inc     (hl)                            ; Increment frame counter

        ld      a,(dead)
        or      a
        jr      z,afterCheckDisableOrb
        xor     a
        ld      (orb),a
afterCheckDisableOrb:
; Search to see if there should be any enemies created
        ld      hl,(enemyIni)                   ; HL => Start of enemy initialisation array
        ld      a,(numEnemiesThisLevel)
        or      a
        jr      z,afterFindNewEnemies
findNewEnemies:
        ld      a,(xBlock)
        cp      (hl)
        jr      nz,afterFindNewEnemies
        call    newEnemy
        ld      a,(numEnemiesThisLevel)
        dec     a
        ld      (numEnemiesThisLevel),a
        jr      nz,findNewEnemies

afterFindNewEnemies:
        ld      (enemyIni),hl
        ld      a,(dead)                        ; A = Dead counter
        or      a
        jr      z,afterDecDead
        dec     a
        jp      z,playerDead
        ld      (dead),a                        ; If it wasn't 0 or 1, save the decremented value

afterDecDead:
;------------------------------------------------
; Scroll screen every 4th frame (unless in guardian mode)
;------------------------------------------------
        xor     a
        ld      (scrolled),a
        ld      a,(frame)
        and     $03
        call    z,scrollScreen

        call    getPlayerScreenX
        ld      (pScreenX),a

;------------------------------------------------
; Perform updates on enemies, bullets, etc. and some other stuff
;------------------------------------------------
        call    showBeamPower                   ; Show the Beam power
        call    moveOrb                         ; Move orb (if it's on screen)
        call    movePickups                     ; Move item pickups
        call    checkGetPickups                 ; Check to see if player is picking up any pickups
        call    moveBullets                     ; Move player bullets
        call    checkBulletsOnScreen            ; Check to see if any player bullets have fallen off the screen
        call    checkBulletsHitSolid            ; Check to see if any player bullets are hitting a solid tile
        call    checkGuardianDamage             ; Check to see if end-of-level boss is being damaged (if fighting one)
        jp      c,endLevel                      ; If end-of-level boss dead, go to next level
        call    moveGuardian                    ; Move end-of-level boss (if fighting one)
        call    guardianMakeEnemy               ; Make an enemy from the end-of-level boss (if fighting one that does so)
        call    checkEnemyDamage                ; Check to see if any enemies are being damaged
        call    moveEnemies                     ; Move enemies
        call    checkEnemiesOnScreen            ; Check to see if all enemies are still on screen
        call    moveEBullets                    ; Move enemy bullets
        call    checkEBulletsOnScreen           ; Check to see if all enemy bullets are still on screen
        call    checkEBulletsHitSolid           ; Chekc to see if any enemy bullets are hitting a solid tile
        call    checkOrbEBulletsCollisions      ; Check to see if any enemy bullets have been hit by the Orb
        call    updateAnimations                ; Update explosion animations
        call    checkPlayerEnemyCollisions      ; Check for collisions between player and enemies
        call    checkPlayerGuardianCollisions   ; Check for collisions between player and end-of-level boss (if fighting one)
        call    checkPlayerEBulletCollisions    ; Check for collisions between player and enemy bullets

;------------------------------------------------
; Put all sprites on gbuf, copy gbuf to video mem then remove sprites again
;------------------------------------------------
        call    drawPickups                     ; Draw pickups
        call    drawBullets                     ; Draw player bullets to gbuf
        call    drawEBullets                    ; Draw enemy bullets to gbuf
        call    drawAnimations                  ; Draw explosion animations
        call    drawEnemies                     ; Draw enemies to gbuf
        call    drawGuardian                    ; Draw end-of-level boss
        call    drawPlayer                      ; Draw player and orb if there is one
        call    drawOrb                         ; Draw orb (if it's on screen)
        call    ionFastCopy                     ; Copy gbuf to lcd
        ei                                      ; ionFastCopy disables interrupts
        call    eraseOrb                        ; Erase orb (if it's on screen)
        call    erasePlayer                     ; Erase player from gbuf
        call    eraseGuardian                   ; Erase end-of-level boss
        call    eraseEnemies                    ; Erase enemies from gbuf
        call    drawAnimations                  ; Erase explosion animations
        call    drawEBullets                    ; Erase enemy bullets from gbuf
        call    drawBullets                     ; Erase player bullets from gbuf
        call    drawPickups                     ; Erase pickups

        ld      a,(dead)
        or      a
        jp      nz,afterCheckShooting           ; If player dead, don't check movement/shooting

        ld      a,KG_ARROW
        call    directIn                        ; Get key presses from arrow pad
        push    af
        bit     DI_UP,a                         ; [Up]
        call    z,moveUp
        pop     af
        push    af
        bit     DI_DOWN,a                       ; [Down]
        call    z,moveDown
        pop     af
        push    af
        bit     DI_LEFT,a                       ; [Left]
        call    z,moveLeft
        pop     af
        bit     DI_RIGHT,a                      ; [Right]
        call    z,moveRight

        ld      a,KG_1
        call    directIn                        ; Get key pressed from 1st column (far left column)
        push    af
        bit     DI_MATH,a                       ; [MATH]
        call    z,teacherKey
        pop     af
        bit     DI_ALPHA,a                      ; [ALPHA]
        call    z,tryShootOffOrb

        ld      a,KG_TOP
        call    directIn                        ; Get key presses from top key group
        push    af
        bit     DI_MODE,a
        call    z,pause
        pop     af
        bit     DI_2ND,a                        ; [2nd]
        ld      hl,afterCheckShooting
        push    hl                              ; Save where to RET to
        jp      z,incBeamPower                  ; If [2nd] pressed, try to increment beam power
        pop     hl                              ; Get rid of RET value
        ld      hl,beam                         ; HL => Beam power
        ld      a,(hl)
        or      a                               ; Is it 0?
        jp      z,afterCheckShooting            ; If so, don't shoot
; Player is shooting, create necessary bullets
        call    divAby8
        srl     a                               ; A = A / 16
        inc     a                               ; Make sure it isn't 0
        ld      d,a                             ; D = Bullet type
        ld      (hl),0                          ; Reset beam power
        ld      a,(pScreenX)                    ; Get player X coordinate on screen
        ld      bc,(y)                          ; C = YCoord
        ld      b,a                             ; B = XCoord
        ld      a,d                             ; A = Bullet type
        ld      d,15                            ; D = Direction
        call    newBullet                       ; Try to create a new bullet
; If player isn't wearing Orb, it shoots as well :)
        ld      a,(orb)
        or      a                               ; Is the Orb on screen?
        jp      z,afterCheckShooting            ; If not, we've finished creating bullets
        bit     1,a                             ; Is player wearing Orb or is it floating around?
        ld      a,(specialWeapon)               ; A = Special weapon
        jr      nz,tryShootingPowerGun          ; If wearing Orb, try to shoot a special weapon
        or      a                               ; Is Orb armed with a special weapon?
        jr      nz,orbShootingLots              ; If so, shoot 5 bullets out
        ld      a,5
        ld      bc,(orbY)
        ld      d,15
        call    newBullet                       ; Create single bullet coming out of the front of the Orb
        jr      afterCheckShooting
orbShootingLots:
        ld      b,4
        ld      hl,orbBulletDirections
orbShootingLoop:
        push    bc
        push    hl
        ld      a,5                             ; A = Type
        ld      d,(hl)                          ; D = Direction
        ld      bc,(orbY)                       ; B = X, C = Y
        call    newBullet
        pop     hl
        pop     bc
        inc     hl
        djnz    orbShootingLoop
        jr      afterCheckShooting
tryShootingPowerGun:
        or      a                               ; Is Orb armed with a special weapon?
        jr      z,afterCheckShooting            ; If not, we're finished shooting
        dec     a                               ; Is it the Power Gun?
        jr      nz,tryShootingTwoWayGun         ; If not, try next weapon
        ld      bc,(orbY)
        ld      a,(orb)
        bit     2,a
        ld      d,15
        jr      z,shootPowerGun
        dec     d
shootPowerGun:
        ld      a,6
        call    newBullet                       ; Create Power Gun bullet
        jr      afterCheckShooting
tryShootingTwoWayGun:
        dec     a                               ; Is it the Two-Way Gun?
        jr      nz,shootVerticalGun             ; If not, it's the Vertical Gun
        ld      a,(orb)
        bit     2,a
        ld      de,7*256+1
        ld      a,7
        jr      z,shootTwoWayGun
        ld      de,10*256+4
        ld      a,8
shootTwoWayGun:
        ld      bc,(orbY)
        call    newBullet
        ld      d,e
        xor     $0F
        call    newBullet
        jr      afterCheckShooting
shootVerticalGun:
        ld      bc,(orbY)
        ld      a,9
        ld      d,12
        call    newBullet
        inc     a
        inc     d
        call    newBullet

afterCheckShooting:
        ld      a,KG_5
        call    directIn                        ; Get key presses from 5th column (far right column)
        bit     DI_CLEAR,a                      ; [CLEAR]
        jr      z,quitGame

        ld      hl,timer                        ; HL => APD timer
        jr      noHalt                          ; We might not need a HALT
mainLoopWait:
        halt                                    ; Wait a bit
noHalt:
        ld      a,(hl)                          ; A = Timer value
        neg                                     ; Negate it because it count's backwards
        cp      2                               ; Has a certain amount of time passed during this frame?
_speed          = $-1
        jr      c,mainLoopWait                  ; If not, wait a bit longer

        jp      mainGameLoop                    ; Back to start of loop

quitGame:
        xor     a
        ld      (lives),a
        jr      gameOver

endLevel:
        xor     a
        ld      (cpX),a
        ld      hl,level
        inc     (hl)
        ld      l,(hl)
        ld      h,200
        bcall(_htimesl)
        ex      de,hl
        call    updateScore                     ; Add heaps of points to score
; Now show end-of-level screen
        bcall(_grbufclr)
        ld      bc,44*256+30
        ld      hl,sprPlayer
        call    PutSprite                       ; Draw player ship
        call    ionFastCopy
        ld      hl,2*256+1
        ld      (currow),hl
        ld      hl,(level)
        ld      h,0
        bcall(_disphl)                          ; Show level number completed
        ld      de,0*256+1
        ld      hl,strLevelComplete
        call    puts
        ld      de,8*256+1
        call    puts
        ld      b,250
#ifdef MIRAGE
        call    delayB
#else
        ei
endLevelLoop:
        halt
        djnz    endLevelLoop
#endif
        ld      hl,level
        ld      a,(noLevels)
        cp      (hl)
        jp      nz,iniLevel

levelsetComplete:
        bcall(_clrscrnfull)
        ld      de,3*256+2
        ld      hl,strLevelsetComplete
        call    puts
        call    waitKey

gameOver:
        bcall(_clrscrnfull)
        ld      de,3*256+1
        ld      hl,strGameOver
        call    puts                            ; "GAME  OVER"
        ld      de,0*256+4
        call    puts                            ; "Score:"
        ld      hl,(lives)
        ld      h,200                           ; Get 2000 bonus points for each life left
        bcall(_htimesl)
        ld      de,(score)
        add     hl,de
        ld      (score),hl
        ld      bc,5*256+1
        call    showHL
        ld      de,10*256+4
        call    puts
        ld      de,(score)
        ld      hl,(hiScore)
        bcall(_cphlde)
        jr      nc,noNewHiScore
        ld      (hiScore),de
        ld      hl,(dataPtr)
        inc     hl
        inc     hl                              ; HL => Where to save hiScore in Levelset file
        ld      (hl),e                          ; Save LSB of hiScore
        inc     hl
        ld      (hl),d                          ; Save MSB of hiScore
        ld      de,1*256+6
        ld      hl,strNewHiScore
        call    puts
noNewHiScore:
        ld      b,100
#ifdef MIRAGE
        call    delayB
#else
        ei
gameOverLoop:
        halt
        djnz    gameOverLoop
#endif
        call    waitKeyA
        jp      startVAT

.end
