;;-----------------------------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
;;-------------------------------------------------------------------------------
; v5 major overhaul adds support for multiple dragon types
;list
.readmatrix
;nolist
; this routine scans the keyboard matrix via the PPI
    ld hl,KBmatrixbuf+9 ; scan backwards through list, KBMatrixBuf is at &b000 so l<0 ends loop
    ld bc,&f40e
    ld d,b
    out (c),c
    ld b,&f6
    ld e,b
    in a,(c)
    and &30
    ld c,a
    or &c0
    out (c),a
    out (c),c
    inc b
    ld a,&92
    out (c),a
    push bc
    ld a,&49
    or a,c
    ld c,a
.scankey
    ld b,e
    out (c),c
    ld b,d
    in a,(c)
    cpl
    ld (hl),a
    dec c
    dec l
    jp p,scankey ; repeat while l>=0
    pop bc
    ld a,&82
    out (c),a
    dec b
    out (c),c
    ret

.ReadJoystick
    jp UseJoystick ; this jump is modified on game start according to menu selection
.UseMultiPlay
; read multiplay port 1
    ld bc,&f890
    in a,(c)
    ld (KBmatrixbuf+9),a ; data from multiplay in same format as joystick
    jr InputReceived
.UseJoystick
; scan keys and get joystick 0 byte
    call readmatrix
    ld a,(KBmatrixbuf+9)
    jr InputReceived
.UseKeyboard
; keyboard control
; check bits for q,a,o,p & space and set up a state byte matching joystick read
    call readmatrix
    ld e,0 ; clear input byte
    ld a,(KBmatrixbuf+8) ;4
    bit 3,a              ;2
    jr z,RdJoyNotQ       ;2/3
    set 0,e              ;2
.RdJoyNotQ
    bit 5,a              ;2
    jr z,RdJoyNotA       ;2/3
    set 1,e              ;2
.RdJoyNotA
    ld hl,KBmatrixbuf+4  ;3
    bit 2,(hl)           ;3
    jr z,RdJoyNotO       ;2/3
    set 2,e              ;2
.RdJoyNotO
    dec l         ;1
    ld a,8        ;2
    and a,(hl)    ;2
    or a,e        ;1 ; want end product in a, will match format of input from joytick
    jr RdJoyCheckSpace ;2/3
; end keyboard read
.UseCursor ;
    call readmatrix
; check bits for cursor u,d,l,r & space and set up a state byte matching joystick read
    ld e,0 ; clear key input byte
    ld hl,KBmatrixbuf+1 ;3
    bit 0,(hl)          ;2
    jr z,RdJoyNotCurL   ;2/3
    set 2,e             ;2
.RdJoyNotCurL
;    dec hl
    dec l               ;1
    bit 2,(hl)          ;2
    jr z,RdJoyNotCurD   ;2/3
    set 1,e             ;2
.RdJoyNotCurD
    bit 1,(hl)          ;2
    jr z,RdJoyNotCurR   ;2/3
    set 3,e             ;2
.RdJoyNotCurR
    ld a,1        ;2
    and a,(hl)    ;2
    or a,e        ;1 ; want end product in a, will match format of input from joytick
; end cursor read
.RdJoyCheckSpace
    ld hl,KBmatrixbuf+5  ;3
    bit 7,(hl)           ;3
    jr z,InputReceived   ;2/3
    set 4,a              ;2
.InputReceived
    ld hl,KeyInY ; point to key read output list
    ld c,0
    ld (hl),c ; clear last output
    bit 0,a
    jr z,RdJySkipYUp
.PlayerMoveUpStep
    ld (hl),-2 ; if up pressed, write y move to list and check x move
    jr RdJySkipYDn
.RdJySkipYUp
; up not pressed, check down
    bit 1,a
    jr z,RdJySkipYDn
.PlayerMoveDownStep
    ld (hl),2 ; if down pressed, write y move to list and check x move
.RdJySkipYDn
    inc l ; move ptr to move list to x
    ld (hl),c ; clear current x move
    bit 2,a ; check if left pressed
    jr z,RdJySkipXLe
    ld (hl),255 ; left pressed so write left move and check fire
    jr RdJySkipXRi
.RdJySkipXLe
    bit 3,a ; if left not pressed, check right
    jr z,RdJySkipXRi
    ld (hl),1 ; right pressed so write in right move and then check fire
.RdJySkipXRi
    inc l ; move iput list to point to fire
    and a,16 ; mask out all but fire bit, fire either 0 or non zero
.RdJyInvert
    xor a,0 ; invert if selected in menu option
    ld (hl),a ; write fire state to list
    ret ; and exit player input read routine

.PlayerMovement
    ld a,(PlayerAutoPilot)
    or a
    jr z,PlyMoveNormal
; player completed game, time to fly off screen
    ld hl,PlayerYX+2 ; point to player y
    ld a,(hl) ; get player y
    or a ; if not already 0 - top of screen
    jr z,PlayerBulletMovement ; already done
    sub a,2 ; then subtract 2 from y to make player move up screen
    ld (hl),a ; update player y
    ld a,64+3
    ld (PlayerHitTimer),a ; keep player invulnerable, but not flashing
    jr nz,PlayerBulletMovement ; done all that's needed this time, go to bullet move
; player has reached top of screen, so set game exit
    ld a,128+34
    ld (PlayerHitTimer),a ; this sets game exit with one second delay, bit 7 causes game exit
    jr PlayerBulletMovement ; now go to bullet move
.PlyMoveNormal
; normal player movement
    ld hl,KeyInY ; point to filtered input changes
    ld bc,PlayerTestY ; point to player y in table
    ld a,(bc)
    add a,(hl)
    cp a,16+50
    jr c,PMCancelY ; player y co-ord too high, cancel y move
    cp a,256-10
    jr nc,PMCancelY ; player y co-ord to low, cancel
    ld (bc),a ; new y co-ord in valid range, write back
.PMCancelY
    inc hl ; point to x move
    inc bc ; point to current player x
    ld a,(bc)
    add a,(hl) ; add player x and move
    cp a,128-4 ; valid range is 0-123
    jr nc,PMCancelX
    ld (bc),a ; if in valid range, write back new x
.PMCancelX
; now go to player bullet movement
.PlayerBulletMovement
    ld hl,PlayerYX+4 ; first bullet y
    ld bc,&606 ; 6 player bullets, move up 6 pixels each move
.PMBullMvLp
    ld a,(hl) ; read y
    sub a,c
    jr c,SkipThisBullet
    cp a,13
    jr nc,ThisBulletInPlay ; bullet still on screen
; this bullet no longer in play, set x to 128
    inc l
    ld (hl),128
    dec l
    xor a ; and y = 0
.ThisBulletInPlay
    ld (hl),a ; write y back
.SkipThisBullet
    inc l
    inc l
    djnz PMBullMvLp
    ret

.PlayerShrapnelMove
; a contains PlayerExp on call of this routine
    cp a,26*4
    jr c,PlayerShrapMvNotFirst ; not first frame of player explosion, skip setup
; player explosion just triggered, so set up co-ords and move of shrapnel
; first step is to prime the co-ord offsets and move directions
    ld hl,PlayerExpList
    ld de,PlayerYXShrapnel
    ld bc,28
    ldir
; now get player co-ords and add to offsets
    ld a,(PlayerTestY)
    ld e,a
    ld a,(PlayerTestX)
    ld d,a
    ld hl,PlayerYXShrapnel
    ld b,6
.PlyExpSetupShrapLoop
    ld a,(hl)
    add a,e ; add player y to shrapnel offset
    ld (hl),a
    inc l
    ld a,(hl)
    add a,d ; add player x to shrapnel offset
    ld (hl),a
    inc l
    djnz PlyExpSetupShrapLoop
; now want to skip the move step on first round
    jr PlayerShrapnelCommon
.PlayerShrapMvNotFirst
; not first frame of player explosion, so move shrapnel pieces and set off screen if they leave
    ld b,6
    ld de,PlayerYXShrapnel
    ld hl,PlayerYXShrapnel+&10
.PlyShrapMvLp
    ld a,(de) ; get y
    add a,(hl) ; add move
    cp a,16
    jr c,PlyShrapYLimit ; if y<16 then shrapnel off screen, remove it
    ld (de),a ; write back new y
    inc l
    inc e
    ld a,(de) ; get x
    add a,(hl) ; add move
    bit 7,a
    jr nz,PlyShrapXLimit ; if x>127 then shrapnel off screen, remove it
    ld (de),a ; write back new x
.PlyShrapReturnLimit
    inc l
    inc e
    djnz PlyShrapMvLp    
.PlayerShrapnelCommon
; for this routine to be called, PlayerExp is at least 4
; first reduce playerexp counter to see if explosion will continue next frame
    ld a,(PlayerExp)
    sub a,4
    ld (PlayerExp),a
    cp a,4
    ret nc ; not last frame of player explosion
; is last frame of player explosion, so check lives and
; if there are lives left reset player position, set shield
; if there are no lives left, trigger game over
    ld a,(Lives)
    or a
    jr z,PlyrShrapSetGameOver ; if lives is 0, time to trigger game over
    call UpdateLivesDisplay ; update the lives display on score line
    ld a,100 ; set timer for player indicating temporary invulnerability
.PlyrShrapGOEntry
    ld (PlayerHitTimer),a
; reset player position
    ld a,62
    ld (PlayerTestX),a
    ld a,200
    ld (PlayerTestY),a
    ret
.PlyrShrapSetGameOver ; set timer for player indicating game over
    ld a,128 ; bit 7 will tell sprite not to display, and game to end
    jr PlyrShrapGOEntry

.PlyShrapXLimit
    dec e
    dec l
.PlyShrapYLimit
    xor a
    ld (hl),a ; ymv
    inc l
    ld (hl),a ; xmv
    ld (de),a ; ypos
    inc e
    ld a,128
    ld (de),a ; xpos
    jr PlyShrapReturnLimit

.PlayerExpList
    defb 2,2,4,1,4,3,6,1,6,3,8,2 ; format is y offset from player y, x offset from player x
    defs 4 ; 4 blank bytes
    defb -4,0,-2,-1,-2,1,2,-1,2,1,4,0 ; y,x movement for each piece of 'shrapnel'

; Enemy bullets are handled with 6 pages allowing a max of 256 bullets
; the code is lengthy but straight forward, the tables used are as follows
;EBYsp equ &6a00 - the sub pixel or fraction of a pixel for the current y co-ord
;EBY equ &6b00 - the pixel of the current y co-ord, valid range is 16-255
;EBX equ &6c00 - the pixel*2 of the current x co-ord, valid range is 0-127
;                bit 0 defines left or right pixel within a byte of screen ram
;EBXsp equ &6d00 - the sub pixel or fraction of a pixel for the current x co-ord
;EBMv (y) equ &6e00 - the sub pixel or fraction of a pixel step the bullet moves in y each frame
;     (x) equ &6700 - the sub pixel or fraction of a pixel step the bullet moves in x each frame
; for each pixel the move values are doubled and placed in the low byte of a register pair
; if bit 7 was set then it indicates the step is negative and the high byte is set to &ff
; the current co-ord is then read into another register pair with the actual co-ord in
; the high byte, the sub pixel/fraction placed in the low byte and the move added to it
; the result for the new co-ord has the high byte checked for valid range, and so on for the
; x and y co-ord for all 256 bullets

.MoveNBullets
; loop to update the 256 enemy bullets
; no check is made to see if bullets are currently on screen or not, so offscreen bullets
; are required to have move values that will cause them to exit screen boundary on first sum
; this is done because the worst case scenario is all bullets in play, so checking all 256
; being in play when they are all in play is a waste of cpu time.  So the principle is that
; it is better to waste cpu time on some unrequired maths for inactive bullets,
; than on unrequired maths for active bullets - this produces a lower peak cpu usage
    exx
    ld hl,EBXsp ; point to the x co-ord sub pixel offset
    exx
    ld hl,EBMv ; point to the bullets y move, stored in sub pixel steps
.MNBLoop
    ld a,(hl) ; get y move
    ex af,af'
    inc h
    ld a,(hl) ; get x move
    exx
    ld e,(hl) ; get x sub pixel offset
    dec h
    ld d,(hl) ; get x primary screen co-ord
    ld b,0
    add a,a
    jr nc,MNBNotNegX1
    dec b
;    ld b,&ff
.MNBNotNegX1
    ld c,a ; de now holds current x, bc holds move
    ex de,hl
    add hl,bc
    ex de,hl
    bit 7,d ; valid bullet x co-ord is 0-127
    jr nz,XMoveOffscreen1
; x pos valid, write back to table
    ld (hl),d
    inc h
    ld (hl),e
.SkipXMv1
    dec h
    dec h ; point to current y of the bullet
    ex af,af' ; get y move 1
    ld d,(hl)
    dec h ; point to sub pixel of the y co-ord
    ld e,(hl) ; de holds current y
    ld b,0
    add a,a ; bit 7 determines if bullet moving up or down
    jr nc,MNBNotNegY1
    dec b ; if moving up, make bc 'negative', or &ff
.MNBNotNegY1
    ld c,a
    ex de,hl ; put current y in hl
    add hl,bc
    add hl,bc ; double the y move
    ex de,hl
    ld a,d
    cp a,16 ; valid y co-ord is 16 to 255
    jr c,YMoveOffscreen1
    ld (hl),e
    inc h
    ld (hl),d ; write back new y
; now do second bullet move of the pair
; this repeats the process above, except that x & y co-ordinate pointers are going in the reverse order
.SkipYMv1
.MoveOffscreenReturn1
    inc l ; point to next bullets co-ords
    exx
    inc l ; point to next bullets move values
    ld a,(hl) ; get x move
    dec h
    ex af,af'
    ld a,(hl) ; get y move
    exx
    ld d,(hl)
    dec h
    ld e,(hl) ; de holds current y
    ld b,0
    add a,a ; bit 7 determines if bullet moving up or down
    jr nc,MNBNotNegY2
    dec b ; if moving up, make bc 'negative', or &ff
.MNBNotNegY2
    ld c,a
    ex de,hl ; put current y in hl
    add hl,bc
    add hl,bc ; double the y move
    ex de,hl
    ld a,d
    cp a,16
    jr c,YMoveOffscreen2
    ld (hl),e
    inc h
    ld (hl),d ; write back new y
.SkipYMv2
; now do x - almost identical to y
    ex af,af' ; get x move 2
    inc h ; point to NBXpos
    ld d,(hl)
    inc h
    ld e,(hl)
    ld b,0
    add a,a
    jr nc,MNBNotNegX2
    dec b
.MNBNotNegX2
    ld c,a
    ex de,hl
    add hl,bc
    ex de,hl
    bit 7,d
    jr nz,XMoveOffscreen2
    ld (hl),e
    dec h
    ld (hl),d
.SkipXMv2
    inc h
.MoveOffscreenReturn2
; now get to next bullet
    inc l
    exx
    inc l
    jr nz,MNBLoop
.MNBExit
; main bullets have been moved, move on to rings/balls
    jr MoveNBRings

.YMoveOffscreen1
.XMoveOffscreen1
    ld h,EBMv/256+1
.XMovOSSkip1
    xor a
    ld (hl),-1 ; mark x move of even bullet ids as an 'invalid' number to identify as not in use
    dec h
    ld (hl),a
    dec h
    ld (hl),a
    dec h
    ld (hl),a
    dec h
    ld (hl),a
    dec h
    ld (hl),a
    inc h
    jr MoveOffscreenReturn1

.XMoveOffscreen2
    ld h,EBYsp/256
.YMoveOffscreen2
.YMovOSSkip2
    xor a
    ld (hl),a
    inc h
    ld (hl),a
    inc h
    ld (hl),a
    inc h
    ld (hl),a
    inc h
    ld (hl),a
    inc h
    ld (hl),a ; for odd bullet ids, can use 0 rather than -1, due to order free bullet ids checked
    ld h,EBXsp/256
    jr MoveOffscreenReturn2

.MoveNBRings
    exx
    ld hl,EBRingYX+15 ; start with x and go down
    exx
    ld hl,EBRingYXMv+13
    ld b,4
.MNBRingLoop
    ld a,(hl) ; get y move
    ex af,af'
    dec l
    ld a,(hl) ; get x move
    exx
    ld b,0
    add a,a
    jr nc,MNBNotNegXRing
    dec b
.MNBNotNegXRing
    ld c,a ; bc now holds x move
    ld d,(hl)
    dec l
    ld e,(hl)
    ex de,hl
    add hl,bc
    ex de,hl
    bit 7,d
    jr nz,XMoveOffscreenRing
    ld (hl),e
    inc l
    ld (hl),d
    dec l
    dec l ; point to y
    ex af,af' ; get y move
    ld b,0
    add a,a
    jr nc,MNBNotNegYRing
    dec b
.MNBNotNegYRing
    ld c,a ; bc now holds y move
    ld d,(hl)
    dec l
    ld e,(hl)
    ex de,hl
    add hl,bc
    add hl,bc
    ex de,hl
    ld a,d
    cp a,16
    jr c,YMoveOffscreenRing
    ld (hl),e
    inc l
    ld (hl),d
    dec l
.MoveRingOffscreenReturn
    dec l ; point to x of next ring
    exx
    dec l
    dec l
    dec l ; point to x move of next ring
    djnz MNBRingLoop
    ret

.XMoveOffscreenRing
    inc l
    xor a
    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
    dec l
    res 4,l
    jr MoveRingOffscreenReturn
.YMoveOffscreenRing
    xor a
    ld (hl),a
    inc l
    ld (hl),a
    inc l
    ld (hl),a
    inc l
    ld (hl),a
    set 4,l
    dec l
    dec l
    ld (hl),a
    dec l
    ld (hl),-1
    res 4,l
    jr MoveRingOffscreenReturn

.MoveSprites
    ld ix,BossMvY
    ld iy,(DragonHeadPtr) ; points to y co-ord of current dragon's head
; next is common to all dragon types
    ld h,0
    ld b,h
    ld a,(ix+0) ; boss y move
    ld l,(ix+1) ; hl has current y subpixel offset
    add a,a ; bit 7 determines if head moving up or down
    jr z,MNSSkipHeadSprite ; if y move is 0, then check if tails move in case boss only moved offscreen
    jr nc,MNSNotNegY
    dec b ; if moving up, make bc 'negative', or b=&ff
.MNSNotNegY
    ld c,a ; bc holds direction
    add hl,bc
    ld (ix+1),l
    ld d,h ; will be 1 or -1 y move
; now do x
    ld h,0
    ld b,h
    ld a,(ix+2)
    ld l,(ix+3) ; hl has current sp offset
    add a,a ; bit 7 determines if bullet moving left or right
    jr nc,MNSNotNegX
    dec b ; if moving up, make bc 'negative', or &ff
.MNSNotNegX
    ld c,a ; bc holds direction
    add hl,bc
    ld (ix+3),l
    ld e,h ; will be 1 or -1 x move
; now apply move to head sprite
    ld a,(iy+1) ; get x pos
; no longer required due to subsequent changes
    add a,e ; add x move
    ld (iy+1),a
    ld a,(iy+0) ; get y pos
    add a,d ; add y move
    ld (iy+0),a
.MNSSkipHeadSprite
; head now moved if on screen, now write new y to TailYList and set remaining sprite co-ordinates
    exx
    ld c,a ; preserve new y, will be zero if y move is 0, which is set to 0 when head y = 0
    ld h,TailYList/256
    ld a,(TailYListPtr)
    dec a
    and a,&3f
    ld (TailYListPtr),a
    ld l,a ; hl' now points to tail list pointer new start
    ld (hl),c ; write new y value to tailylist
; at this point, need to perform different actions according to type
    ld a,(DragonType)
    or a
    jr z,MNSStdDragonMove
    dec a
    jr z,MNSLongDragonMove
; final boss type
    exx
; update other 2 head locations
    ld c,(iy+0)
    ld (iy+4),c
.MNSFinalBossHeadOS
    ld b,0
; centre head has offset, so determine how offset should be changed, if at all
    ld a,(ix+0) ; get y move
    or a
    jr z,MNSFinalBossExited
    ld a,b
    cp a,6
    jr nc,MNSFinalBossActive
    inc a
    jr MNSFinalBossActive
.MNSFinalBossExited
    ld a,b
    or a
    jr z,MNSFinalBossActive ; already minimum offset
    dec a
.MNSFinalBossActive
    ld (MNSFinalBossHeadOS+1),a
    add a,c ; add offset to base head y
    ld (iy+2),a
    ld a,(iy+1)
    add a,8
    ld (iy+3),a
    add a,8
    ld (iy+5),a
; tail y list updated, now loop through left and right tail sprite lists
    ld hl,EnemyYX+5 ;- left hand tail sprites
    ld bc,EnemyYX+13 ; point to right hand tail sprites
    ld a,3
    call MNSSetPosLp ; set co-ords of tail segments
    jr MNSMoveCap
.MNSLongDragonMove
    ld d,TailXList/256
    ld a,(TailXListPtr)
    dec a
    and a,&7f
    ld (TailXListPtr),a
    ld e,a ; de' now points to tail list pointer new start
    ld a,(iy+1) ; get current x value
    ld (de),a ; write new x value to tailxlist
    exx
; tail y list updated, now loop through left and right tail sprite lists
    ld hl,EnemyYX+15 ;- left hand tail sprites
    ld a,7
    call MNSSetLongPosLp ; set co-ords of tail segments
    jr MNSMoveCap
.MNSStdDragonMove
    exx
; tail y list updated, now loop through left and right tail sprite lists
    ld hl,EnemyYX+7 ;- left hand tail sprites
    ld bc,EnemyYX+11 ; point to right hand tail sprites
    ld a,4
    call MNSSetPosLp ; set co-ords of tail segments
.MNSMoveCap
; finally, cap the movement by checking bounds of the central core/head sprite
    ld a,(BossMvY)
    or a
    jr z,MNSCheckArmsExiting ; if y mv is 0, then boss core has exited, check arms
    ld de,(BossXLimits) ; e has left limit, d has right
; before checking y boundaries, check x move, if 0, will be special case (entering or leaving screen)
    ld a,(DragonLeaving)
    or a
    jr nz,MNSCheckExitIsLeaving ; if not 0 then is leaving the screen
    ld a,(BossMvX)
    or a
    jr nz,MNSSetYMv ; is normal boss move situation
    ld a,(iy+0) ; head y co-ord
    cp a,16
    ret c
; boss core sprite has fully entered screen, set x to be left or right according to current X
    ld a,(iy+1) ; get core sprite x
    cp a,62
    jr c,MNSSetXRightMv
    jr MNSSetXLeftMv
.MNSCheckExitIsLeaving
; if y=0 then stop moving
    ld a,(iy+0) ; get y
    or a
    ret nz
; core has reached top of screen
    ld (BossMvY),a ; a=0, stop moving
    ld a,50+24
    ld (RoundEnd),a ; trigger round end in 3+1.5 seconds
    ld (Int4Game_PlayMusic+1),a ; trigger fade
    set 7,(iy+32) ; set core to no collisions
    ret
.MNSSetYMv
; check y of core sprite first
    ld a,(iy+0)
    cp a,17
    jr nc,CheckYBottom
; y is highest it can get, set y move down
    ld a,64
    ld (BossMvY),a
    jr MNSSetXMv
.CheckYBottom
    cp a,48
    jr c,MNSSetXMv
; y is lowest it can get, set y move up
    ld a,-64
    ld (BossMvY),a
; when core hits bottom of screen, check timer, if 0, then zero x as well
    ld a,(RoundTime)
    or a
; check time out, boss now going to exit screen
    jr nz,MNSSetXMv ; if round time not expired, continue to x move as normal
    inc a
    ld (DragonLeaving),a
    dec a
    jr MNSetXZero
.MNSSetXMv
    ld a,(iy+1)
    cp a,e ;33
    jr nc,CheckXRight
; x is left as it can get, set x move to right
.MNSSetXRightMv
    ld a,64
.MNSetXZero
    ld (BossMvX),a
    ret
.CheckXRight
    cp a,d ;88
    ret c
; x is right as it can get, set x move to left
.MNSSetXLeftMv
    ld a,-64
    ld (BossMvX),a
    ret
.MNSSkipLeftSprite
    dec l
    jr MNSMvSprSkipLeftReturn

.MNSCheckArmsExiting
; core has exited screen, so just check other segments and set to no collision if also offscreen
    ld de,EnemyYX
    ld hl,EnemyData1
    ld b,9
.MNSChkAExitLp
    ld a,(hl)
    or a
    jr nz,MNSChkAExitSkip
; segment has reached top of screen
    set 5,l
    set 7,(hl) ; clear collision status
    res 6,l
.MNSChkAExitSkip
    inc l
    inc l
    djnz MNSChkAExitLp
    ret

; set arms for standard and boss dragons
; on entry a=segments, hl=ptr to left segment first x pos, de=ptr to first x pos of right tail
.MNSSetPosLp
    ex af,af' ; preserve loop counter
; for each pair of tail segments, retrieve previous y value
    exx
    ld a,l
    add a,6
    and a,&3f
    ld l,a
    ld a,(hl) ; got y from 6 steps ago
    exx
    ld d,a ; put current historical y coord in d, no longer require d for y move delta
; now check left segment
    ld a,(hl) ; get x pos
    bit 7,a
    jr nz,MNSSkipLeftSprite
    add a,e ; add x move
    ld (hl),a
    dec l
    ld (hl),d
.MNSMvSprSkipLeftReturn
    dec l
; now check right segment
    ld a,(bc) ; get x pos
    bit 7,a
    jr nz,MNSSkipRightSprite
    add a,e ; add x move
    ld (bc),a
    dec c
    ld a,d
    ld (bc),a
    inc c
.MNSSkipRightSprite
    inc c
    inc c
    ex af,af' ; get loop counter back
    dec a
    jr nz,MNSSetPosLp
    ret

; set arms for long dragons
; on entry a=segments, hl=ptr to left segment first x pos
.MNSSetLongPosLp
    ex af,af' ; preserve loop counter
; for this tail segment, retrieve previous x value
    exx
    ld a,e
    add a,16
    and a,&7f
    ld e,a
    ld a,(de) ; got x from 8 steps ago
    exx
    ld d,a ; put current historical x coord in d, no longer require d for y move delta
; now check left segment x pos for on screen
    ld a,(hl) ; get x pos
    bit 7,a
    jr nz,MNSSkipLeftSpriteLong
    ld (hl),d
    dec l
; for this tail segment, retrieve previous y value
    exx
    ld a,l
    add a,8
    and a,&3f
    ld l,a
    ld a,(hl) ; got y from 6 steps ago
    exx
    ld (hl),a
.MNSMvSprSkipLeftLongReturn
    dec l
    ex af,af' ; get loop counter back
    dec a
    jr nz,MNSSetLongPosLp
    ret
.MNSSkipLeftSpriteLong
    dec l
    jr MNSMvSprSkipLeftLongReturn
