TND Productions, C64 News + Updates

Proudly presents
DARK FORCE - DISSECTED
(REQUIRES C64STUDIO by Endurion or ACME cross-compiler)

Introduction / Setting up the enhancements / Title screen, Get Ready + Game Over / End Sequence / High Score Table




DOWNLOAD BINARIES AND SOURCE



INTRODUCTION



Who remembers the classic C64 game 'Light Force' by FTL?. It was a stunning vertical scrolling shoot 'em up. After discovering some old SEUCK tips in a C64 magazine in the 1990's, the SEUCK Vault and also pushing the SEUCK limitations even further. I decided to work during Christmas 2013 - January 2014 on a Light Force style SEUCK game called (surprisingly) 'Dark Force. The main game was created using the standard version of the Shoot 'Em Up Construction Kit on my Commodore 64. Alf Yngve has been really helpful setting up the graphics and the levels. I also did some of the graphics myself. Anyway here's a bit more about the game and what it features.

First of all, the game was saved into FINISHED GAME form directly from the shoot 'em up construction kit. This saved a standalone version of the game data. Therefore you can load up the game data from disk using the LOAD "*",8,1 prompt. However, if I were to enhance the game to push SEUCK games limitations even further. I would have needed to prepare additional graphics, music and other data.

The first thing I did was stripped the SEUCK data into 2 files, using Action Replay's machine code monitor using the command:

S "DARKF08A",8,0900,6580
S "DARKF08B",8,B6C0,FFFA

If you want to enhance a SEUCK game on its own, it is wise to save the 2 files as I did above. Since $6580-$B6C0 carries unwanted data which was left in the SEUCK.

The next job I did was prepare a new charset, and also the music. JSL did a logo for the game.

Now what about enhancing the game part?. Well, I used C64Studio by Endurion. A great C64 developing suite for the .NET framework. It also makes compiling of and compression of programs much quicker compared to native C64 format. I also needed to use VICE and EXOMIZER. I created a folder to put all of the program files in, and created a new solution, which is obviously called 'Dark_Force.s64'. Now here comes the source code.

The first source code is the main binary importing (Graphics, Bitmaps, Music, etc) and in game enhancement code, as well as some self-mode SEUCK pokes, which replaces the old title screen and game run source with new source. The enhancements include:

- Linked players and deaths




The linked players to one joystick is called by POKE 16578,2 (or LDA #$02 : STA 16578 (or $40c2 ). This will position both players on to the screen and allow both players to be moved with the aid of one joystick. This may have been an Easter Egg which was accidentally left in SEUCK for some reason. A great and handy feature this is as well.

The most important feature in this game is that PLAYER 1 and PLAYER 2 always has to be placed apart next to each other. In order to fix this, I made a simple routine which automatically flips between PLAYER / BACKGROUND CHAR collision. So that after one player is hit by an enemy, the background becomes DEADLY all the way through. The player settings then gets changed from STOP to DIE.

After the player death, and re spawn phase. The background char value gets reset to how it was originally. Then STOP is restored. The player then gets re-spawned on to the screen (linked together with the default settings as when starting a new game) and then it can be moved about once again. 

- Object killed detection



A subroutine is called in order to check which enemy has been shot. A chapter on object/enemy detection has already been mentioned in the main SEUCK school page, but somewhere in the source code, it will show you the actual object killed detection. This method was used in order to count the number of generators shot in game. Dark Force has a total of 20 generators, which much be destroyed in order to advance on to the next level. I also made it possible that for every certain amount of generators are destroyed, the player is rewarded an extra life. Then the amount to destroy (for extra lives) get reset again.

- Linked player scoring

Scoring inside a SEUCK game is usually separate to the players. However, this game uses both players linked together to form one space ship. The score panel data gets replaced with a custom share score, where both player's scores are borrowed from the scoring data, then stored as an overall score in a new area. What used to be the player's original score then gets transferred to a subroutine that displays the score panel sprites in the lower border.

- Level colour setup (according to level which is triggered)

This trick is set inside the main game loop to check whether or not the player is set on a certain level. The value of each level is based on the Level Parameters * 7. There are 22 levels in total inside the main SEUCK.



- New sound capabilities

Dark Force has two different game options, they are either the standard SEUCK in game sound effects or in game music.

- In game background animation



There is also in game background animation, in which scrolls some of the characters used inside the background downwards. For example the metallic voids, and the water. A charset consists of 8 bytes, and in order to call the char scroll you can push all 8 bytes of the charset left (to move them up) or right (to move them down) inside a loop. You'd need to create a temp byte to store the last byte and then after a loop, store it to the first byte - or vice-versa, depending on char scrolling direction. SEUCK's background charset data is at $F800. In order to cycle the charset

- Not used: Smart bomb effect

I also accidentally left the smart bomb effect into the source, but didn't quite use it. When Alf Yngve had created some level bosses into this game, more than one of the same boss was used later on in the game. So it wasn't worth the effort of creating the smart bomb effect. Therefore the bosses had to be blasted in segments.

- 2 endings

There are 2 endings in Dark Force. There is a subroutine which will check for the number of generators that have been destroyed (in the custom object detection subroutine). If all 20 generators have been destroyed, the subroutine will jump directly to the true ending code (mentioned in the ending source), which flashes the screen and then does a full screen explosion effect. Then jumps to the well done end message.

The second ending in Dark Force will scroll all the way to the very last level (Set by the player in SEUCK) where a giant laser gets fired, and then the screen explodes with a bad ending.



SETTING UP THE ENHANCEMENTS

Before we go straight on to the source code, here's a list of areas which have been used for Dark Force.

$0400-$0900 - Additional code for enhancements, one time routines (before starting the game). The area is being used to setup the new POKES in order to BYPASS the old SEUCK title screen, and be able to use the NEW title screen instead. It also sets up POKEs for other enhancement features as well. The startup code doesn't always have to be at $0400-$0900 you can easily use a DIFFERENT address if you want, but you would need to setup the De-Cruncher to execute the NEW address if you did. Otherwise $0400 is the jump address. The source code in C64Studio has already set the Exomizer (In the C:\EXOMIZERWIN32\ directory) and when assembled from the current tab. It will compress the assembled data+code together using $0400 as jump address. Therefore you can load the DARKFORCE.PRG file with VICE, CCS or port to your C64 into a disk.

$0900 - $6580 - SEUCK Map data, and main game code. Used for scoring, controlling the player, scrolling maps, etc. This area needs to remain in the source code, otherwise the program will not work, and will probably crash if left out.

$6580 - $6800 - In game enhancements code / pokes. This contains the new features in the game, including a new infinite loop (Which was originally used for playing SEUCK SFX inside a player at $4503), and also calls a few other new in game subroutines. Including background animation, possibility to play in game, music. etc...

$6800 - $7800 - Imports the title screen code, which will display a logo at the top of the screen, the credits/high score, sound options, in game music, etc. Get Ready and Game Over Music is also stored.

$7800 - $8000 - Text charset for the title screen. BANK 2 is being used to display the character set (LDA #$02 STA $DD00 : LDA #$FE STA $D018). Be aware that the title code uses $7C00 - $7FE8 for the actual screen RAM. Which means if a larger sized character set is used in that area.

$8000 - $8800 - Title screen logo Colour + Video RAM (Colour RAM at $8000, Video RAM at $8400).

$8800 - $9A00 - Music player data. Dark Force music was composed using GoatTracker and will init $8800 and play $8803. The music data consists of 5 different tracks. Which are in game, title music, get ready, game over, well done music/ hiscore music.

$9A00 - $A000 - End screen code and pointers.

$A000 - $AC00 - Logo bitmap data. Although a full screen bitmap would be $a000-$bf70, we don't have enough memory to use this, so instead the assembler should OVERWRITE the memory $b6c0 + with the last part of the SEUCK data file.

$AC00 - $B6C0 - Text data - Scroll text, title screen credits, end text, etc.

$B6C0 - $FFFA - Second half of the SEUCK game data - SFX, Sprites, Graphics and level setup data 

 This source is pretty much self-explained slightly. Read the ; prompt to find out what happens in the source. If you want to try out the source an build in C64Studio, click on the DISK icon to download the source.


SOURCE: DarkforceCode.asm


;
====================================
;
;
DARK FORCE
;
----------
;
Game Design, Graphics and SFX by
;
;
Richard Bayliss and Alf Yngve
;
;
Music arranged and composed by
;
;
RICHARD BAYLISS
;
;
COPYRIGHT (C)2014 THE NEW DIMENSION
;
=====================================

;
DARK-FORCE by Richard Bayliss
;
(C)2014 THE NEW DIMENSION
;
-------------------------------------

;
Variables:
GenAmount
= 20 ;Total number of generators to be
;destroyed.


MusicInit
= $8800 ;Initialize music from relocated tune
MusicPlay
= $8803 ;Play music from relocated tune.

!to "darkforce.prg",cbm ;Target filename

;
Use VIC BANK 3, screen RAM data at $0400 to
;
install the full enhancements into the split
;
up raw SEUCK game files. (Using EXOMIZER or
;
PUCRUNCH, set JUMP address at $0400.


;ONE TIME RUN ... Always execute at $0400
;at run of main program.

;exomizer sfx $9d00 darkforcce.prg -o darkforce.prg -n
;
; or
;
;pucrunch darkforce.prg -s darkforce.prg -x $9d00
;
; or use a native C64 packer which can pack up to $0400-$ffff
; and does not use a depacker inside the $0400-$07e8 memory.

*=$0400
sei
lda #0
sta 16964 ;Disable SEUCK editor (POKE 16964)
lda #2
sta 16578 ;Bolt 2 player to one joystick control

lda #$35 ;Disable KERNAL ROM for being able to
sta $01 ;use $e000-$fffa

;Sometimes in a SEUCK game, with enhancements
;the status panel gets inverted. We need to
;reverse the status panel back to its normal
;state, when initializing the game.

;Inverting charset data is called by setting
;itself with EOR #$ff and storing it back
;by itself.

ldx #$00
InvertStatus

lda $f400,x
eor #$ff
sta $f400,x
lda $f500,x
eor #$ff
sta $f500,x
inx
bne InvertStatus

lda #$37 ;Switch KERNAL back on (if you need to).
sta $01

lda #4 ;Starting amount of lives.
sta $40ad ;- for player 1
sta $40c0 ;- for player 2

lda #<store ;Store low/hi bits of the total value of the
ldx #>store ;total score into the score display code.
sta $5a2a
stx $5a2b


;Force the old title screen to jump directly to the
;brand new title screen, by skipping the standard
;SEUCK title code, and executing the new routines
;instead.

;This is done by storing the value of #$4c (JMP)
;and the low-hi bytes of the Front End code into
;what used to be to set the BORDER colour ($D020) in
;$40dd.


lda #$4c
ldx #<.FrontEnd
ldy #>.FrontEnd
sta $40dd
stx $40de
sty $40df

;Where the game normally loops, after completion of
;the game. Replace the game loop/restart with a JMP
;.EndScreen2 prompt. So instead of looping, the game
;will execute the game ending code.

;Dark Force has 2 endings. This one is for when the
;player has failed to destroy all 20 generators, and
;the game is over.

lda #$4c
ldx #<.EndScreen2
ldy #>.EndScreen2
sta $47a1
stx $47a2
sty $47a3

;Where the original SFX player is stored, replace the
;SFX player, with an additional routine for playing
;new in game subroutines (as well as sounds). This is
;done by storing the low + hi bytes of the in game
;enhancement code. (.InGameLoop).

lda #<.InGameLoop
ldx #>.InGameLoop
ldy #$20
sty $4503
sta $4504
stx $4505

;Where player 1 loses a life, instead of exploding
;one player we want to explode both. A new JSR (LDA #$20)
;needs to be stored into the original player life lost code.
;This is so that we have both players killed at the same time.
;This will also trigger a deadly background trick on the
;second player, and make the linked ship explode in one go.

lda #$20
ldx #<.LifeLostEnhancement
ldy #>.LifeLostEnhancement
sta $4b0f
stx $4b10
sty $4b11


;Where player 2 loses a life, instead of exploding
;one player we want to explode both. A new JSR (LDA #$20)
;needs to be stored into the original player life lost code.
;This is so that we have both players killed at the same time.
;This will also trigger a deadly background trick on the
;second player, and make the linked ship explode in one go.


lda #$20
ldx #<.LifeLostEnhancement2
ldy #>.LifeLostEnhancement2
sta $4e1e
stx $4e1f
sty $4e20

;Where player 1 respawns. Set the low-hi bytes to an additional
;subroutine to JSR .DefaultPlayer(x). Where both players respawn at
;the same time. Also disable the deadly background trick, and restore
;the settings of sprite/char collision.

lda #$20
ldx #<.DefaultPlayer2
ldy #>.DefaultPlayer2
sta $4e7c
stx $4e7d
sty $4e7e

;Where player 2 respawns. Set the low-hi bytes to an additional
;subroutine to JSR .DefaultPlayer(x). Where both players respawn at
;the same time. Also disable the deadly background trick, and restore
;the settings of sprite/char collision.

lda #$20

ldx #<.DefaultPlayer1
ldy #>.DefaultPlayer1
sta $4b6c
stx $4b6d
sty $4b6e

;After the last life has been lost, instead of waiting for the
;screen delay to finish. This next subroutine will make the
;game jump directly to the GAME OVER screen.

lda #$4c
sta $42bf
lda #<.GameOver
sta $42c0
lda #>.GameOver
sta $42c1

;Default sounds in the game is going to be set as MUSIC, so
;BIT out (LDA #$2C) the sound effects voices to make them
;not interfere with the in game music.

lda #$2c
sta $5c0d
sta $5c10
sta $5c13
sta $5c18
sta $5c24
sta $5c27
sta $5c2a
sta $5c2f
sta $5c52
sta $5c55
sta $5c58

;Force low-hi byte of the address .ObjectHit into the enemy
;killed subroutine (JSR .ObjectHit). This will link the
;code, which will detect which enemy object has been killed,
;in order to generate some effects. This method can also be
;used for other tricks, such as power ups. Darkforce has no
;need for power ups, so instead this is being used for the
;Generators counter.

lda #$20
sta $55c3
lda #<.ObjectHit
sta $55c4
lda #>.ObjectHit
sta $55c5

;Fix the common SEUCK scoring bug, by jumping to the
;FixScoring routine. This will allow the correct player
;to own the correct score.

lda #$4c
ldx #<FixScoring
ldy #>FixScoring
sta $54a2
stx $54a3
sty $54a4

;Setup the new position of player 1 and player 2's score panel.
;NOTE: A table is required to do this.

ldx #$00
.CentrePlot
lda .PlotTable,x
sta $5eaf,x
lda #$55
sta $5eb7,x

inx
cpx #$09
bne .CentrePlot

;Finished the one-time routine, so now EXECUTE to the main game.

jmp $4245


;
Linked explosions routine.

.DestroyAll

lda #$0a ;Set the value of the linked explosion
sta ExplodeTimer ;counter, in order to produce a smart bomb
;lda #$00 ;feature.
;sta FlashPTR
lda $bd06,y

rts

.PlotTable
!byte $03,$89,$07,$a1,$07,$b9,$07,$d1,$07,$d9

;
Fix player 1's score and player 2 score so
;
that the scoring bug doesn't occur (One player
;
kills and object, and the opponent scored)

FixScoring


sta $5dbb
lda #$00
sta $09
jmp $54a5

;
Level colour settings. Accoring to the level value * 7

FixLevelSetup

;inc $d020

ldx $5dc9 ;Read current level from SEUCK set level parameters, and multiply value by 7
;SEUCK uses 22 levels in total

cpx #2*7 ;Level 2?
beq .SetGreen ;Make green background scheme
cpx #3*7 ;Level 3?
beq .SetBlue ;Make blue background scheme
cpx #7*7 ;Level 7?
beq .SetBrownWhite ;Make brown background scheme
cpx #14*7 ;Level 14?
beq .SetRedPink ;Make red background scheme
cpx #17*7 ;Level 17?
beq .SetGrey ;Make grey background scheme
cpx #20*7 ;Level 20?
beq .FlashLaser ;If ending, make lazer flash

cpx #21*7 ;Level 21?
beq .FlashLaser ;If ending make laser flash
cpx #22*7 ;Level 22?
beq .FlashLaser ;If ending make laser flash
cpx #23*7 ;Level 23? - Actually it is level 22
beq .FlashLaser ;If ending make laser flash
rts


;
Set the background colour scheme for each level
;
chosen.

.SetGreen
;Green + light green scheme
lda #$05 ;BG colour 2 - green
ldx #$0d ;BG colour 3 - light green
sta $d022 ;BG colour 2 - stored
stx $d023 ;BG colour 3 - stored
rts

.SetBlue
;Blue + light blue scheme
lda #$06 ;BG colour 2 - blue
ldx #$0e ;BG colour 3 - light blue
sta $d022 ;BG colour 2 - stored
stx $d023 ;BG colour 3 - stored
rts

.SetBrownWhite
;Brown + green scheme
lda #$09 ;BG colour 2 - brown
ldx #$05 ;BG colour 3 - green
sta $d022 ;BG colour 2 - stored
stx $d023 ;BG colour 3 - stored
rts

.SetRedPink
;Red+Pink scheme
lda #$02 ;BG colour 2 - red
ldx #$0a ;BG colour 3 - pink
sta $d022 ;BG colour 2 - stored
stx $d023 ;BG colour 3 - stored
rts

.SetGrey
;Grey scheme
lda #$0b ;BG colour 2 - dark grey
ldx #$0c ;BG colour 3 - medium grey
sta $d022 ;BG Colour 2 - stored
stx $d023 ;BG Colour 3 - stored
rts

.FlashLaser


;
Flash laser gives out a flickering effect, to give
;
the laser an effect. This uses the EOR values to
;
flash the backround colours of the laser firing.

lda $d022
eor #$04
sta $d022
lda $d023
eor #$03
sta $d023
rts


;
Import the first segment of the finished SEUCK game data from $0900 - $6580

*=$0900
!bin "DARK08A.prg",,2

;
Initialize the game. This is always activated when called from
;
the GET READY screen.

*=$6580
.InitGameTune
lda #$00
jsr MusicInit ;Initialise music for in game
jsr .Fix1 ;Fix player 1 position
jsr .Fix2 ;Fix player 2 position
jsr .InitPlayerStartPositions

;
Initialise the player starting positions
;
according to the SEUCK starting positions
;
you originally set in the SEUCK game.
.InitPlayerStartPositions


;When initialising each player, allow the player
;to move around everywhere, by disabling the DIE
;command, and setting the collision char value as
;255.

lda #$00 ;Player 2 Die/Stop = Stop
sta $40be

lda #255 ;Player 2 collision with character
sta $40bf

lda #$00 ;Player 1 Die/Stop = Stop
sta $40ab
lda #255 ;Player 1 collision with character
sta $40ac

;Default player positions. In order
;to work out the X, XMSB and Y values of
;the player's position. You will need to use a
;M/C monitor. Simply by using:

;
M 40a3
;
M 40a4
;
M 40a5
;...
etc.

;Initialized start positions for player 1

lda #$92 ;Player 1 X-Start position
sta $40a3 ;Store to player 1 X start position
lda #$00 ;Player 1 X-MSB Start position
sta $40a4 ;Store to player 1 X MSB start position
lda #$e5 ;Player 1 Y-Start Position
sta $40a5 ;Store to player 1 Y start position

lda #$00 ;Player 1 stop
sta $40ab ;Store to player 1 stop / die parameters
lda #255 ;Player 1 collision with character
sta $40ac ;Store to player 1 collision character

;Initialized start positions for player 2

lda #$aa ;Player 2 X-Start position
sta $40b6 ;Store to player 2 X-start position
lda #$00 ;Player 2 X-MSB Start Position
sta $40b7 ;Store to player 2 X-MSB start position
lda #$e5 ;Player 2 Y-Start Position
sta $40b8 ;Store to player 2 Y-start position

lda #$00 ;Tune NO.0 - In game music
jsr MusicInit ;Initialise music

ldx #$00 ;Silence the SID chip, using a loop
.SilenceChip
;that zero fills all SID registers
lda #$00 ;between $d400-$d418.
sta $d400,x
inx
cpx #$18
bne .SilenceChip

jmp $41a4 ;SEUCK title screen exit routine ... Inits game start.

;
This is the LIFE LOST enhancement, which will trigger an effect
;
on both players. It stores the player death routine, and also
;
will trigger a deadly sprite/background collision, so that both
;
players die simultaneously.

.LifeLostEnhancement


sta $5dbf ;Store player 1's death routine.
lda #$00 ;Set deadly char value for sprite/background.
sta $40bf ;collision for player 2.
lda #$01 ;Set STOP/DIE for player 2 to DIE ...
sta $40be ;and store it.

rts

.LifeLostEnhancement2


sta $5dc0 ;Store player 2's death routine.
lda #$00 ;Set deadly char value for sprite/background.
sta $40ac ;collision for player 1.
lda #$01 ;Set STOP/DIE for player 1 to DIE ...
sta $40ab ;and store it.
rts


;
Players lose a life, so positions are reset

.DefaultPlayer1

sta $5db7 ;This is where lives are lost for player 1

.Fix1

lda #$00 ;Set STOP/DIE for player 1 to STOP ...
sta $40ab ;and store it.
lda #255 ;Set player 1 collision with char to 255, so
sta $40ac ;that the player has free movement when alive.
lda $40a4 ;Load default X starting position of player 1
sta $bc01 ;and force it to actual X position
lda $40a5 ;Load default XMSB starting position of player 1
sta $bc02 ;and force it to actual XMSB position
lda $40a6 ;Load default Y starting position of player 1
sta $bc03 ;and force it to actual Y position

;Initialized start positions for player 1

rts

.DefaultPlayer2

sta $5db8 ;This is where lives are lost for player 2

.Fix2

lda #$00 ;Set STOP/DIE for player 2 tio stop ...
sta $40be ;and store it.
lda #255 ;Set player 2 collision with char to 255, so
sta $40bf ;that the player has free movement when alive.
lda $40b6 ;Load default X starting position of player 2
sta $bc31 ;and force it to actual X position
lda $40b7 ;Load default XMSB starting position of player 2
sta $bc32 ;and force it to actual YMSB position
lda $40b8 ;Load default Y starting position of player 2
sta $bc33 ;and force it to actual Y position
rts

;
The main in game loop, which is linked to where the subroutine used
;
to play in game sound effects.

.InGameLoop
lda $5db7 ;Share the lives of player 1 and
sta $5db8 ;give to player 2.
lda $5db7 ;Check amount of lives, for player 1 to
cmp #$00 ;see if LIVES = 0.
beq GameLost ;Game over

;The game is not over so ...
jsr .BackgroundAnim ;Call subroutine to animate background
jsr .BorrowScore ;Call subroutine to share score
jsr .FixPositionsXY ;Call subroutine to fix X+Y position of player
jsr FixLevelSetup ;Call subroutine to check colour scheme
.SoundPlayer
jsr MusicPlay ;Selfmod music or sfx player
rts ;end of loop subroutine.

GameLost
jmp .GameOver ;Jump to the Game Over subroutine

;
Fix player position, so that it is stuck together all the time

;
.FixPositionsXY is a calculation subroutine, which will constantly
;
bolt both player 1 and player 2 together permanently, in order to
;
make a really big ship. The subroutine places the actual Y position
;
of player 2, to where player 1 is. Also the X position has to be
;
moved $18 bytes in order for player 2's ship to be connected to
;
player 1 properly.

.FixPositionsXY


lda $bc01 ;Load actual X position of player 1.
clc ;add $18 bytes (For the linked ship
adc #$18 ;trick).
sta $bc31 ;Store it to player 2's actual X position
lda $bc03 ;Load actual Y position of player 1.
sta $bc33 ;Store player 2 on the same Y position.
rts

;
Score sharing routine. Which will share both player 1's score
;
and player 2's score. Add it all together and give out an overall
;
calculation.

.BorrowScore


lda #0 ;Initialise borrow pointer
sta borrow
ldx #$05 ;Loop to call 6 digits of the score object
calc1
lda score1,x ;Pick up player one's score
clc ;Calculate
adc score2,x ;Also add player 2's score with player 1
clc ;Calculate
adc borrow ;Add to borrow
ldy #0 ;Init borrow again
sty borrow
and #$1f
cmp #$0a ;Check if digit is over 10
bcc noborrow ;else, don't add 1 to borrow.
inc borrow ;Increase pointer, borrow by 1
sec ;
sbc #$0a ;Subtract 10.
; ora #$30
noborrow
sta result,x ;Store the result of the overall score
dex ;Else keep on with the calculation.
bpl calc1

ldx #$00 ;Otherwise clear the table


ldx #$05
lda #$00
calc0
sta borrow,x
dex
bpl calc0
ldx #$00
calc2
lda result,x ;Then put the result into the
sta store,x ;actual score panel sprites.
inx
cpx #$06
bne calc2

rts

;
This next subroutine will animate the background, simply by
;
picking certain chars and rolling those downwards.

.BackgroundAnim

lda .scrDelay ;Delay of the single char scroll
cmp #3 ;is set to 3. If not then wait
beq .slowEnough ;a bit more, and add 1 to the
inc .scrDelay ;pointer .scrDelay.
rts
.slowEnough
lda #0 ;Reset the delay of the colour
sta .scrDelay ;roll.

;A C64 character consists of 8 bytes in total.
;If you wanted to scroll a byte up / down, you would
;need to but all 8 bits inside a loop.

lda $fa30+7 ;Last value of the 8-byte character
sta $9ff0 ;position. Gets stored to a temp byte 1
lda $fb20+7 ;Same as above, but with a different
sta $9ff1 ;character ...
lda $fb28+7 ;
sta $9ff2 ;Same here ... Etc.!
lda $fb30+7
sta $9ff3
lda $fbc0+7
sta $9ff4

;
Scroll the 8-byte of the character downwards inside a
;
loop.
ldx #7 ;Start counter with X
.scrChar
lda $fa30-1,x ;Always use -1 to roll chars downwards
sta $fa30,x ;then store to the next byte of each character
lda $fb20-1,x
sta $fb20,x
lda $fb28-1,x
sta $fb28,x
lda $fb30-1,x
sta $fb30,x
lda $fbc0-1,x
sta $fbc0,x
dex ;Subtract a byte
bpl .scrChar ;Not = 0 yet, loop .scrChar
lda $9ff0 ;Load tempbyte1

sta $fa30 ;Store to first byte of char
lda $9ff1 ;Load tempbyte2
sta $fb20 ;Store to second byte of char
lda $9ff2 ;... etc ...
sta $fb28
lda $9ff3
sta $fb30
lda $9ff4
sta $fbc0
rts

;
Some quick variables to represent the object values (not enemy values), which
;
represent the generator object (The goal of the game is to
;
simply destroy them all in order to save the universe from
;
catastrophe).

Generator1
= 46 ;Object (not enemy) value for generator 1
Generator2
= 47 ;Object value for generator 2
Generator3
= 48 ;Object value for generator 3

;
This subroutine will check for which object has been hit.
;
If an object is either of the generators, the code should
;
add 1 to the amount of generators destroyed.

.ObjectHit

lda $bc00,y ;Load actual object hit
sta ObjectDestroyed ;store to pointer ObjectDestroyed
lda ObjectDestroyed ;load the pointer ObjectDestroyed

cmp #Generator1 ;Was object Generator1?
beq .AddGen ;call .AddGen to add 1 to generator counter
cmp #Generator2 ;Was object Generator2?
beq .AddGen ;call .AddGen to add 1 to generator counter
cmp #Generator3 ;Was object Generator3?
beq .AddGen ;call .AddGen to add 1 to generator counter
lda $bd06,y ;Load back the object pointers
rts

;
Generators are hit, so add 1 to the amount of generators
;
that have been destroyed.

.AddGen
inc GeneratorsToDestroy ;Add amount of generators to be destroyed
inc GeneratorsDestroyed ;Add amount of generators that have been destroyed
lda GeneratorsToDestroy ;Load amount of generators to be destroyed
cmp #GenAmount ;does amount add up to total value GenAmount = 20?
beq .AllGeneratorsDestroyed ;Yes ... Skip to all .GeneratorsDestroyed subroutine

;
Check for 6 generators destroyed? If 6 are destroyed
;
the player gains an extra life.

lda GeneratorsDestroyed ;Load amount of generators destroyed
cmp #$06 ;Have 6 been destroyed?
beq .AwardExtraLife ;Yes, call subroutine to award extralife
lda $bd06,y ;No. Just load back object pointers and exit routine
rts

.AwardExtraLife
inc $5db7 ;Add 1 value to player 1's lives counter
lda #$00 ;Reset the number of GeneratorsDestroyed
sta GeneratorsDestroyed ;before next extra life can be awarded.
lda $bd06,y ;Load back object pointers
rts

;
All generatores have been destroyed, so now, execute the happy
;
ending subroutine. Where the screen flashes and explodes the
;
alien complex.

.AllGeneratorsDestroyed

lda $bd06,y ;Finish object check
jmp .EndScreen1 ;Execute the happy ending.


;
In game pointers ... Loads of them ;)

ObjectDestroyed
!byte $00 ;Pointer to check which object value is destroyed
GeneratorsDestroyed
!byte $00 ;Counter for amount of generators destroyed for extra life
GeneratorsToDestroy
!byte $00 ;Counter for how many generators to destroy to complete the game

score1
= $5ea3 ;Player 1's score data
score2
= $5ea9 ;Player 2's score data
ExplodeTimer
!byte 0 ;Explosion pointer
.scrDelay
!byte 0 ;Delay for 8-byte background char scroll
.scrPointer
!byte 0 ;Counter for 8-byte background char scroll

;
Pointers to represent score values for the score sharing subroutine

borrow
!byte 0,0,0,0,0,0
result
!byte 0,0,0,0,0,0
store
!byte 0,0,0,0,0,0

tempScore
!byte 0,0,0,0,0,0 ;We will be using the
;temp score for the


Sync
!byte 0 ;Synchronized timer for the title screen, and other
;bits.

animpointer
!byte 0 ;Animation pointer

objectHit
!byte 0 ;Pointer for which object has been it

explodeColourDelay
!byte 0 ;Explosion colour delay pointer
explodeColourTimer
!byte 0 ;Explosion actual colour pointer

;Explosion colour table.
explodeColours
!byte $0b,$0c,$0f,$07,$01,$07,$0f,$0c,$0b,$0f,$0f,$0f,$0f,$0f,$0f,$0f
!byte 0

;Import / link main front end code. The whole front end
;code is in 'frontend.asm'
*=$6800
!source "frontend.asm"

;Import / link custom title screen character set. Make
;sure your character set is NOT more than 3 blocks otherwise
;you may need to trim the char or use a different free
;memory location for the charset. If you do, the value of
;the charset and VIC2 BANK memory may need to be altered.
;SCREEN RAM $7c00-$7fe8 in VIC BANK #$02 is being used for
;the front end graphics.

*=$7800
!bin "charset.prg",,2



;Import logo colour RAM data. Do not overflow memory
*=$8000
!bin "dfcolram.prg",,2

;Import logo video RAM data. Do not overflow memory
*=$8400
!bin "dfvidram.prg",,2

;Import title music (Composed from Goat Tracker)
*=$8800
!bin "dfmus.prg",,2

;Link end code for the game ending. The end code is based
;in 'endcode.asm'.
*=$9a00
!source "endcode.asm"


;Import bitmap data of logo into higher memory.
*=$a000
!bin "newdfbitmap.prg",,2

;Some space available for a scroll text. So here
;it is. Write any odd bunf if you want. :)
*=$ac00

ScrollText


!ct scr ;Turn text into C64 screencodes

!text " ... it is the year 2173, the entire solar system is under threat from "
!text "an intergalactic alien race, the cygons ... they are planning a huge "
!text "attack against all planets of the solar system ... to make matters wo"
!text "rse, they have brought out a huge atomic laser beam, and threaten to us"
!text "e it on all the planets of the solar system, via reflectors ... if th"
!text "is laser gets fired, the planet will get destroyed ... you have been "
!text "assigned as a fearless pilot of the 'dark force' supercraft ... it's "
!text "big, it's cool and it is very powerful ... you enter the ship, begin "
!text "to take off and you make it into space, ready for a huge battle against"
!text " the cygons ... during your mission, you must battle through space an"
!text "d other enemy planets - fighting your way against the enemies ... you"
!text " must also destroy all 20 of the generators, which contain the energy of"
!text " the huge laser beam ... destroying six generators will give you an extra ship ... can you destroy them all, or will the cygons"
!text " be too tough for you ... good luck pilot, you need it! ... - dark"
!text " force - special edition - is a s.e.u.c.k tribute to hewson's awesome game 'light force' "
!text " by faster than light... created using the shoot 'em up construction kit, a tool written by jon hare and chris yates of sensible software ... game design by rich"
!text "ard bayliss and alf yngve ... front end and enhancements programming "
!text "were done by richard bayliss ... dark force logo and loading picture was painted by johan "
!text "'jsl' janssen ... finally the awesome music was arranged and composed "
!text "by richard bayliss ... - check out http://tnd64.unikat.sk/darkforce.html for the game data and source - ... "
!text "huge thank you to everyone involved ... use joystick in port 2 ... press fire to play! ... "
!byte 0 ;Scroll reset marker.

;
The still text. The 6 lines below indicates the text for the BAD ending. Basically
;
if the laser is fired from the laser cannon, the mission is failed.

EndFailText1
!text " mission failed "
EndFailText2
!text " you did not shoot enough generators to "
EndFailText3
!text " save all planets of the solar system "
EndFailText4
!text " the laser was activated by the cygons. "
EndFailText5
!text "all planets of the solar system are gone"
EndFailText6
!text " game over "

;
The still text. The 6 lines below indicate the happy ending for Dark Force. The
;
players destroyed all of the generators that appeared on screen and saved the
;
universe, and everything. Wahey :)

EndPassText1
!text " congratulations "
EndPassText2
!text " all generators have been destroyed "
EndPassText3
!text " the laser didn't work, and the cygons "
EndPassText4
!text " finally admit defeat. "
EndPassText5
!text " peace has now been restored. "
EndPassText6
!text " press fire to continue "
GetReadyText1
!text " prepare to kick cygon butt "
GetReadyText2
!text " get ready for action "
GetReadyText3
!text " press fire to commence battle "

;
The next 3 lines are the game over text.

GameOverText1
!text " dark force has been wiped out "
GameOverText2
!text " and so has the entire solar system "
GameOverText3
!text " game over "

;
Also the final score output. We make FinalScore as a self-mod text
;
label in order for new scores to be added.

FinalScoreMess
!text " your final score was: "
FinalScore
!text "000000 "

;
Self-mod text variable PlayerName for hi-score entry.

PlayerName
!text "abcdefghi"

;
No hi-score achieved message.

HiFailMessage1
!text " sorry, but not a high score "
HiFailMessage2
!text " press fire to continue "

;
Hi-score achieved message
HiPassMessage1
!text " well done, you are one of the top 3! "
HiPassMessage2
!text " please enter your name. "

;Second bulk segment of the SEUCK file data. Basically SEUCK
;game sprites, graphics and other bits of data which is very
;important for the SEUCK game.

*=$b6c0
!bin "dark08b.prg",,2

;
Finished.

BACK TO TOP:

THE TITLE SCREEN

As like any other game, a title screen presentation should be created. The source code below does exactly that. When you set up your own title screen, it can be based any way you like. This front end consists of a custom character set (read from $7800-$7c00), a logo (Bitmap data at $a000, Colram at $8000, and Video RAM at $8400), some star sprites, colour bars, and other bits. All which have been put together into several routines.The title screen looks something like THIS:



Each row (R) is setup by using a raster split, inside an IRQ interrupt. IRQ interrupts can do many amazing things, but sometimes when you try and implement your own rasters, things don't always go according to plan. This is because raster splits can misbehave quite a lot. In order to make them behave better, you can move the raster line across slightly more, using a TIMING approach. The source code below gives an example of timing rasters. Adding NOPs or some fake codes can time those blighters out. Anyway, let's describe about the Rasters used.

If using the multiple interrupt routine, although I set a raster in the first IRQ as the value of R2, the settings need to updated to values of R1. Basically the position of R1 ends where R2 starts. We have to setup the hardware values after the previous raster. Then set the low/hi-bytes of the next IRQ interrupt into the interrupt flag. This might not make much sense here, but the code below will show you how it is done.

R1 represents the colour bar at the bottom and ends at raster line R2
R2 represents the colour bar at the top and ends at raster line R3
R3 represents the logo and ends at raster line R4
R4 represents the presentation text, and ends at raster line R5
R5 represents the scroll text code and ends at raster line R1

The main title screen outside the multiple IRQ willl call subroutines to wash colours, and scroll text, flip pages, etc. The source code already tells you what everything does.

Get Ready and Game Over is also linked to the Title Screen Source.

Anyway here's the source:

The main title code:
SOURCE: frontend.asm

RasterLine1 = $00           ;Position of raster line 1
RasterLine2
= $79 ;Position of raster line 2
RasterLine3
= $ea ;Position of raster line 3

;
Front End

;
The main front end routine

.FrontEnd
sei ;Set INIT interrupt flags
lda #$00 ;Black border and background
sta $d020 ;set.
sta $d021

sta PageDelay ;Set delay for title screen
sta PageDelay+1 ;page flip routine
sta PageCounter ;Zero page routines

;
Init starting position for the starfield in the title screen

ldx #$00
SetPosition
lda PosTable,x ;Load X,Y positions for sprites
sta ObjPos,x ;store those in virtual sprite position
inx ;next one, until 16 bytes is
cpx #$10 ;read.
bne SetPosition

lda #$01 ;Place a dot in blank sprite
sta $7bbf ;area at $7bbf to make star.

;
Setup the star sprite frames, and set star colours as light blue.

ldx #$00
SetSpriteTypes
lda #$ef ;Load value of sprite type
sta $7ff8,x ;store it to actual sprite type.
lda #$0e ;Light blue
sta $d027,x ;Store to sprite colours (all 8)
inx
cpx #$08
bne SetSpriteTypes

lda #$ff ;Switch on all sprites (the title screen stars)
sta $d015 ;Store to sprites enabled hardware
lda #$00 ;No multicolour sprites required
sta $d01c ;Store to sprites multicolour hardware
lda #$ff ;All sprites behind background
sta $d01b ;Store to sprite/background priorities hardware
lda #$00 ;No expanded sprites please.
sta $d01d ;Store to expand sprite X
sta $d017 ;Store to expand sprite Y

ldx #$00 ;Reset colour pointer, using X
stx ColourPointer ;Store to self-mod colour pointer

lda #$00 ;Destroy existing interrupts.
sta $d019 ;Store them to the IRQ vectors
sta $d01a

lda #$81 ;Switch CIA timer OFF
sta $dc0d

lda #$36 ;C64 Kernal switched back on
sta $01 ;for the timebeing

jsr .ClearScreen ;Clear the screen subroutine

lda #$02 ;Selfmod fixup somewhere in pic.
sta PicPos

;
Setup the Front End Logo colour / video DATA (Steps of 40 cols)

ldx #$00
.SetupLogo
lda $8000,x ;Where logo colour data is read from
sta $d800,x ;then store it to Colour RAM
lda $8028,x ;($d800-$dbe8). We just copy the
sta $d828,x ;memory from the logo colour + screen
lda $8050,x ;data and then paste it in rows on
sta $d850,x ;to the main screen.
lda $8078,x ;
sta $d878,x ;Logo colour RAM: $8000,x
lda $80a0,x ;Screen colour RAM: $D800-$DBE8
sta $d8a0,x ;
lda $80c8,x ;Logo video RAM $8400,x
sta $d8c8,x ;No change to the screen RAM as the
lda $80f0,x ;logo video RAM is already stored
sta $d8f0,x ;and the raster split (later in this source)
lda $8118,x ;will set the CORRECT screen RAM/char RAM value
sta $d918,x ;for the logo.
lda $8140,x
sta $d940,x
inx
cpx #$28
bne .SetupLogo

;
Setup the credits page, by default. Read the text
;
data and then store it to the screen row position
;
starting from the first column. Read 40 bytes ($28)
;
and store.
;
Screen RAM in VIC BANK 2 is being used

;
A line row = 40 chars

ldx #$00
.PutCreds
lda Credits1,x ;Read first line of credits
sta $7d68,x ;store it to screen ROW position $7D68,x
lda Credits2,x ;Read second line of credits
sta $7db8,x ;store it to the next screen ROW position $7DB8
lda Credits3,x ;Read third line of credits
sta $7de0,x ;store it to the next screen ROW position ($7de0,x), etc ...
lda Credits4,x
sta $7e30,x
lda Credits5,x
sta $7e58,x
lda Credits6,x
sta $7ea8,x
lda Credits7,x
sta $7ed0,x
lda Credits8,x
sta $7f20,x
lda Credits9,x
sta $7f70,x

;White text ...
lda #$01 ;Set colour white on each row where the credits are.
sta $d968,x ;store to the colour RAM.
sta $d9b8,x
sta $d9e0,x
sta $da30,x

sta $da58,x
sta $daa8,x
sta $dad0,x
sta $db20,x
sta $db70,x
inx ;Move to the next char on each row.
cpx #$28 ;Until last char (40) has been read.
bne .PutCreds ;Otherwise just loop back to .PutCreds.
;until the last character on each row
;is complete.

;
Initialise the scroll text by storing a low and hi byte of
;
ScrollText into the self-mod pointer .MessRead. So that any
;
time the title screen restarts, the scroll text does as well.

lda #<ScrollText ;Lo-byte of the scroll text message
sta .MessRead+1 ;Store to low byte scroll read memory
lda #>ScrollText ;Hi-byte of the scroll text message
sta .MessRead+2 ;Store to hi-byte scroll read memory

;
Set up / initialise the title screen IRQ raster interrupts. For
;
this title screen, there will be multiple interrupts.

lda #<.Irq1 ;Low byte of interrupt IRQ1
sta $0314 ;Store to kernal IRQ low-byte read
lda #>.Irq1 ;Hi byte of interrupt IRQ
sta $0315 ;Store to kernal IRQ hi-byte read
lda #$7f ;CIA interrupt set
sta $dc0d ;Store CIA interrupt
sta $dd0d ;and enable it
lda #$00 ;Starting raster position
sta $d012 ;Store raster position
lda #$1b ;Screen on
sta $d011 ;Store screen on
lda #$01 ;ACK IRQ vector
sta $d01a ;Store SYNC IRQ vector
lda #$01 ;Title music value
jsr MusicInit ;Initialise title music player

lda #0 ;Initialise fire on both joysticks
sta FirePort1 ;Init joystick port 1 fire button
sta FirePort2 ;Init joystick port 2 fire button

lda #3
sta $7bdf
sta $7bff

cli

;Synchronize timer ... and front end loop

.FrontEndLoop
lda #$00 ;Interrupt sets 1 to the Sync timer, if
sta Sync ;the Sync timer = 0 then it should not synchronize
cmp Sync ;the subroutines below until it reads the timer in the interrupt
beq *-3

;Some subroutines to do various infinite tricks unless
;the fire button has been pressed to start the game.

jsr .SpriteField ;Subroutine to move the star sprites
jsr .DoScroll ;Scrolltext subroutine
jsr .ColourScroll ;Colour washing subroutine
jsr .CheckSoundOption ;Sound option check subroutine
jsr .SetPages ;Page flipping subroutine
lda $dc00 ;Check for joystick port 2
lsr ;up
lsr ;down
lsr ;left
bcs .CheckRight1 ;not left
jsr .SetAsMusic ;Set music option
jmp .FireButton1 ;Test fire
.CheckRight1
lsr ;right
bcs .FireButton1b ;not right
jsr .SetAsSfx ;Set sound effects option
.FireButton1b
jmp .FireButton1 ;jump to check fire button subroutine


;
Sound option must be set as either music or in game sound effects
;
0 = music
;
1 = sfx

;
Set music option
.SetAsMusic
lda #$00 ;Value 0 = in game music
sta SoundOption ;Store to the sound option pointer
rts
;
Set sfx option
.SetAsSfx
lda #$01 ;Value 1 = sound effects
sta SoundOption ;Store to the sound option pointer
rts
;
Check sound option to see whether or not music or sound effects
;
should be forced into the SEUCK game code?

.CheckSoundOption
lda SoundOption ;Load SoundOption pointer
cmp #$00 ;Check for music
beq .MusicOnly ;Yes, can only play music
cmp #$01 ;Check for sound effects
beq .SFXOnly ;Yes, can only play sfx
rts

;
Force music only by changing low/hi bytes of SoundPlayer, by
;
replacing the SFX pointers with the low/hi bytes of music play pointers

.MusicOnly
lda #$2c ;BIT out the SFX SID
sta $5c0d ;registers. This is a very cheap
sta $5c10 ;and most simplest way in order
sta $5c13 ;to have just in game music, uninterrupted
sta $5c18 ;by the SEUCK sfx. It is extremely difficult to
sta $5c24 ;mix actual SEUCK sfx with in game music
sta $5c27 ;That is why many SEUCK games I enhanced have
sta $5c2a ;either just SFX or in game music.
sta $5c2f
sta $5c52
sta $5c55
sta $5c58

lda #<MusicPlay ;Set lowbyte of MusicPlay (into A)
ldx #>MusicPlay ;and hibyte of MusicPlay (into X)
sta .SoundPlayer+1 ;Store low byte (into A)
stx .SoundPlayer+2 ;Store hi byte (into X)

;Place characters 'MUSIC' on to the screen

ldx #$00
.PutMusicText
lda OptionMusic,x ;Read label 'Music' and then
sta $7f39,x ;store it to the title screen
inx
cpx #$06 ;read 6 chars
bne .PutMusicText ;Loop if not 6th char read
rts

.SFXOnly
; sta $dbe7
lda #$8d ;LOAD in all sound fx
sta $5c0d ;pointers, so that in
sta $5c10 ;game music is enabled.
sta $5c13
sta $5c24
sta $5c27
sta $5c2a
lda #$9d
sta $5c2f
sta $5c52
sta $5c55
sta $5c58
lda #<$5c94 ;SEUCK SFX player low-byte
ldx #>$5c94 ;SEUCK SFX player hi-byte
sta .SoundPlayer+1 ;Store to low-byte of the sound player subroutine
stx .SoundPlayer+2 ;Store to hi-byte of the sound player subroutine

;Place SFX option text on to the title screen

ldx #$00
.PutSFXText
lda OptionSfx,x ;read label OptionSfx
sta $7f39,x ;Store to screen.
inx
cpx #$06 ;read 6 chars
bne .PutSFXText ;loop if not 6th char read.
rts

;Wait for fire button in joystick
;port 2 to be pressed ...

.FireButton1
lda $dc00
lsr ;command UP
lsr ;command DOWN
lsr ;command LEFT
lsr ;command RIGHT
lsr ;command FIRE
bit FirePort2 ;Avoid fire from being HELD
ror FirePort2 ;Avoid fire from being HELD
bmi .CheckJoy1 ;Ignore fire
bvc .CheckJoy1 ;-----------
jmp .GetReady ;Run GET READY screen
jmp .FrontEndLoop ;Otherwise continue
;the main loop inside
;the title screen.

;Or maybe wait for fire button in
;joystick port 1 to be pressed ...
;(Just like above)


.CheckJoy1
lda $dc01
lsr
lsr
lsr
lsr
lsr
bit FirePort1
ror FirePort1
bmi .FrontEndLoop2
bvc .FrontEndLoop2
jmp .GetReady
.FrontEndLoop2
jmp .FrontEndLoop


;
Switch off all of the interrupts, reset amount of
;
generators to be destroyed (Zero the GeneratorsToDestroy
;
counter). Reset the fire button on the joystick

.GetReady
sei
lda #$00 ;Switch off interrupts $D019 + $D01A
.ClearRAM
sta $d019 ;and also all sprites
sta $d01a
sta $d015

sta GeneratorsToDestroy ;Init amount of generators to destroy
sta GeneratorsDestroyed ;Init amount of generators destroyed
sta FirePort1 ;Refresh fire button in joystick port 1
sta FirePort2 ;Refresh fire button in joystick port 2

lda #$1b ;Screen on
sta $d011
lda #$81 ;CIA interrupt disabled
sta $dc0d
lda #$02 ;VIC2 BANK #2
sta $dd00 ;Store VIC2 Bank

lda #$1b ;Screen ON
sta $d011

lda #$fe ;Charset SET to display text charset.
sta $d018
lda #$08 ;No multicolour set.
sta $d016
jsr .ClearScreen ;Clear the screen


;Display the GET READY text. Simply by reading
;each line of 40 characters from GetReadyText1,x etc...
;then storing it to the chosen screen ROW in VIC2 BANK 2
;

ldx #$00
.PutGetReady
lda GetReadyText1,x ;Read line 1 of the Get Ready Text
sta $7d90,x ;Store to screen ROW $7d90,x
lda GetReadyText2,x ;... You might know the rest of this
sta $7de0,x ;it is basically the same, but different
lda GetReadyText3,x ;screen address. :)
sta $7e30,x
inx ;Move to next char
cpx #$28 ;check 40 chars read per row
bne .PutGetReady ;if not loop back to .PutGetReady


;Setup the interrupts (IRQ4) for thye Get Ready screen. IRQ 4 is
;a single IRQ.

lda #<.Irq4 ;Read low-byte of single IRQ (.Irq4)
sta $0314 ;store to read low-byte IRQ pointer
lda #>.Irq4 ;Read hi-byte of single IRQ (.Irq4)
sta $0315 ;store to read hi-byte IRQ pointer
lda #$7f ;Enable CIA interrupt
sta $dc0d ;Store CIA interrupt A
sta $dd0d ;and B
lda #$1b ;Screen switched on, char mode
sta $d011
lda #$00 ;Initialised raster position
sta $d012 ;store raster position

lda #$01 ;Enable IRQ
sta $d01a ;store it

lda #$02 ;Initialise sub tune (Get Ready tune)
jsr MusicInit
cli

.GetReadyLoop
lda #0 ;Sync loop (as before)
sta Sync
cmp Sync
beq *-3
jsr .FlashGetReady ;Call subroutine to flash text

;Wait for fire button response

lda $dc00
lsr
lsr
lsr
lsr
lsr
bit FirePort2
ror FirePort2
bmi .H2
bvc .H2
jmp .SetUpGame ;Set up the game defaults

.H2
lda $dc01
lsr
lsr
lsr
lsr
lsr
bit FirePort1
ror FirePort1
bmi .GetReadyLoop
bvc .GetReadyLoop

;Setup the game defaults before the main game is
;activated with the new enhancements.

.SetUpGame
lda #$00 ;In game music
jsr MusicInit

;Zero fill the SID chip so for a split second
;no sounds play, while setting up other routines.

ldx #$00
KillM
lda #$00
sta $d400,x
inx
cpx #$18
bne KillM

;Initialise the linked explosion timer
lda #0
sta ExplodeTimer

;Initialise player start positions. Note player 2
;MUST be linked to player 1 to make a 2 sprite space ship.

jmp .InitPlayerStartPositions


;Game over subroutine. Init everything as
;before for Get Ready screen.
.GameOver
sei
lda #$00 ;Disable IRQs
sta $d019
sta $d01a
sta FirePort1 ;Init joystick button press
sta FirePort2
sta $d015 ;No sprites
lda #$1b ;Screen on
sta $d011
lda #$81 ;Disable interrupt
sta $dc0d
lda #$36 ;Switch KERNAL back on
sta $01
lda #$02 ;Set VIC2 bank as 2
sta $dd00
lda #$1b ;Screen on
sta $d011
lda #$fe ;Set text charset $7800-$8000 and screen $7c00-$7fe8
sta $d018 ;NOTE if charset is more than 3 blocks on disk, the screen will
;overwrite the second half.
lda #$08
sta $d016 ;Disable screen multicolour

jsr .ClearScreen ;Call subroutine to clear the screen

;Please refer to the GET READY screen subroutine.
;\/
ldx #$00
.PutGameOver
lda GameOverText1,x
sta $7d90,x
lda GameOverText2,x
sta $7de0,x
lda GameOverText3,x
sta $7e30,x
inx
cpx #$28
bne .PutGameOver

;Initialise single IRQ interrupt for Game over screen.
lda #<.Irq4
sta $0314
lda #>.Irq4
sta $0315
lda #$7f
sta $dc0d
sta $dd0d
lda #$1b
sta $d011
lda #$00
sta $d012
lda #$01
sta $d01a

lda #$03 ;Initialise Game Over subtune
jsr MusicInit
cli

;Sync timer again
.GameOverLoop
lda #0
sta Sync
cmp Sync
beq *-3
jsr .FlashGetReady ;Exactly the same type of sub routine for flash
;game over. Recreated.

;Read joystick in port 2, and check for firebutton
;(as done before).
lda $dc00
lsr
lsr
lsr
lsr
lsr
bit FirePort2
ror FirePort2
bmi .H3
bvc .H3
jmp .HiScoreCheck

;Same for joystick port 1

.H3
lda $dc01
lsr
lsr
lsr
lsr
lsr
bit FirePort1
ror FirePort1
bmi .GameOverLoop
bvc .GameOverLoop
jmp .HiScoreCheck

;
Interrupt 1 - The smooth scroll text mesage

.Irq1
lda $d019
and #1
sta $d019
lda #$29
sta $d012
ldx #$0a ;Time it out a little
dex
bne *-1

;Setup the lower raster colour bar. And also
;time it.

ldx #$00
.SetRasterColours1
ldy RasTime2,x ;read raster timing table to Y
nop ;delay a bit
dey ;decrement delay
bne *-1
lda RasterCol2,x ;read colour table
sta $d020 ;Store to VIC2 border colour
sta $d021 ;and VIC2 background colour
inx ;until read 8 bytes from the
cpx #$08 ;table. Otherwise loop.
bne .SetRasterColours1
ldx #0 ;Last raster set to black, in
stx $d021 ;the value of X of border and
stx $d020 ;background colour.

lda #$01 ;VIC2 BANK set to #$01 for bitmap
sta $dd00
lda #$18 ;VIC2 charset / screen at BANK 1 ($8400-$87e8)
sta $d018
lda #$3b ;VIC2 Screen set to bitmap
sta $d011
lda #$18 ;VIC2 charset multicolour ON
sta $d016


lda #<.Irq2 ;Call low byte of next IRQ
sta $0314 ;Store to Kernal IRQ vector low byte
lda #>.Irq2 ;Call hi byte of next IRQ
sta $0315 ;Store to Kernal IRQ vector hi byte
lda #1 ;Enable Sync timer
sta Sync
jsr MusicPlay ;Play the music
jmp $ea7e ;Infinite interrupt loop (Use this instead of JMP $EA31).

;
Interrupt 2 - The logo displayer

.Irq2
lda $d019
and #$01
sta $d019
lda #RasterLine2 ;Setup next raster line
sta $d012 ;and store it.
ldx #$0a ;Time out the raster line
dex
bne *-1
ldx #$00
.SetRasterColours2
ldy RasTime1,x ;Read the RasTime1 table for the upper raster
nop ;Delay it
dey
bne *-1

lda RasterCol1,x ;Read the colour table for the upper raster
sta $d020 ;Store it to Border and Background colour
sta $d021 ;inside the raster split, until repeated 8
inx ;Times.
cpx #$08
bne .SetRasterColours2
nop ;More timing bunf ...
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop

ldx #0 ;Black border and background colour
stx $d021 ;stored to X.
stx $d020

lda #$01 ;VIC Bank 1 for bitmap
sta $dd00
lda #$3b ;Bitmap mode enabled
sta $d011
lda #$18 ;Set Bitmap screen ($A000-$BF70) although not a full bitmap
sta $d018
lda #$18 ;Screen multicolour enabled.
sta $d016

;lda #3
;sta $d020
lda #<.Irq3 ;Set low-byte of next raster interrupt
sta $0314 ;Store IRQ low byte
lda #>.Irq3 ;Set hi-byte of next raster interrupt
sta $0315 ;Store IRQ hi byte
jmp $ea7e ;Loop interrupt

;
Interrupt 3 - The static text displayer

.Irq3
lda $d019
and #$01
sta $d019
lda #$ea ;End position for static text displayer
sta $d012
lda #$02 ;VIC2 Bank #2 set
sta $dd00
lda #$1b ;Normal screen mode / Bitmap disabled
sta $d011
lda #$fe ;Set and display font ($7800-$8000) in raster
sta $d018
lda #$08 ;Disable screen multicolour
sta $d016
;lda #$4
;sta $d020
lda #<.Irq4a ;Call next IRQ low-byte
sta $0314 ;Store to low byte interrupt
lda #>.Irq4a ;Call next IRQ hi-byte
sta $0315 ;Store to hi byte interrupt

jmp $ea7e ;Loop

.Irq4a
lda $d019
and #$01
sta $d019
lda #$fa ;Set end position of scroll text raster
sta $d012
lda #<.Irq1 ;Call low-byte of first IRQ
sta $0314 ;Store to low-byte interrupt
lda #>.Irq1 ;Call hi-byte of first IRQ
sta $0315 ;Store to hi-byte interrupt
lda xpos ;Load in pointer XPOS into HSP
sta $d016 ;store to HSP / Screen Horizonal Position to form
;a smooth scroll.


jmp $ea7e ;Infinite IRQ loop.


;
Single IRQ ... This will only enable synchronized timer
;
and play music. Suitable for Get Ready, Game Over, End
;
text and Hi-Score table .name entry routines.

.Irq4
lda $d019
and #$01
sta $d019
lda #$fa
sta $d012
lda #$01
sta Sync
jsr MusicPlay

jmp $ea7e


;
Clear screen - MEMORY $7c00-$7fe8
;
This is always required as a JSR statement, to reduce the amount
;
of code where possible.

.ClearScreen
ldx #$00
.ClearLoop
lda #$20 ;Read spacebar character
sta $7c00,x ;Store it 256 timers in
sta $7d00,x ;each location on screen.
sta $7e00,x
sta $7ee8,x
inx
bne .ClearLoop
rts

;
Scrolling text subroutine.

.DoScroll
lda xpos ;Read pointer xpos
sec ;
sbc #$02 ;Subtract it by 2 to get the speed.
and #$07 ;Only allow values $00-$07 for HSP
sta xpos ;Store HSP value for smooth scroll delay
bcs .EndScroll ;Else exit to .EndScroll and terminate.

;This next bit will pull the next char column to the previous one, giving
;it a scroll effect with the text. (Marquee trick)
ldx #$00
.MoveScroll
lda $7fc1,x ;Fetch next char in current row to the end
sta $7fc0,x ;Store it to the previous char in current row behind

inx ;Read next char
cpx #40 ;Do it 40 times else
bne .MoveScroll ;loop to .MoveScroll

;Check scroll text address / value

.MessRead
lda ScrollText ;read value of scroll text address from self-mod label
cmp #$00 ;If value 00 (@ char) is not detected, then move on to
bne .StoreCharacter ;subroutine .StoreCharacter.

lda #<ScrollText ;Initialise the low-byte of the scroll text
sta .MessRead+1 ;Store to self-mod value of the message read subroutine
lda #>ScrollText ;Initialise the hi-byte of the scroll text
sta .MessRead+2 ;Store to self-mod value of the message read subroutine
jmp .MessRead ;Now wrapped text, jump back to .MessRead and read again.

.StoreCharacter
sta $7fe7 ;Store letter/symbol to the last column in the char's row
inc .MessRead+1 ;Add 1 to the selfmod low-byte addr of .MessRead,
bne .EndScroll ;not exceeded $ff
inc .MessRead+2 ;Add 1 to the selfmod hi-byte addr of .MessRead
.EndScroll
rts ;Scroll routine finished.


;Subroutine for colour washing and scrolling, flashing, etc. This creates
;a flashing effect, inside the colour pointer. Reads a table from a
;loop and then stores the colour from the table into the ColourStore byte.
;In order to get the colours on to the screen to flash, the .ColourScroll
;subroutine should be called using JSR .ColourScroll, and then
;another subroutine to place the colour scroll on to the actual char screen colour.
.ColourScroll

ldx ColourPointer ;Read byte value of ColourPointer in X loop
lda ColourTable,x ;Read byte from ColourTable
sta ColourStore ;Store it to byte ColourStore
inx ;Move to next byte on table until
cpx #$70 ;Until 112 bytes of colours are read
beq .ResetColourPointer ;If so, jump .ResetColourPointer
inc ColourPointer ;Otherwise store next byte value in ColourPointer
jsr .FillScreen ;Call colour washing to char subroutine
rts ;Exit subroutine

.ResetColourPointer
ldx #$00 ;Zero the byte ColourPointer so that the
stx ColourPointer ;colour table gets read from the start again.
rts

;This next sub routine will fill the screen colour data
;first by plotting a byte of the colours, and then
;calling a loop to constantly wash the colours over the
;existing text.

.FillScreen
lda ColourStore ;Read byte ColourStore
sta $d968+39 ;Place byte at last char at row $D968
sta $d9b8 ;Place byte at first char at row $D9B8
sta $d9e0+39 ;Place byte at last char at row $D9E0
sta $da30 ;Place byte at first char at row $DA30
sta $da58+39 ;Place byte at last char at row $DA58
sta $daa8 ;Place byte at first char at row $DAA8
sta $dad0+39 ;Place byte at last char at row $DAD0
sta $db20 ;Place byte at first char at row $DB20
sta $db70+39 ;Place byte at last char at row $DB70
sta $dbc0 ;Place byte at first char at row $DBC0
jsr .ColScrollForward ;Call subroutine to move colour chars forwards
jsr .ColScrollBackward ;Call subroutine to move colour chars backwards
rts ;Exit

;Subroutine which moves coloured chars forwards.

.ColScrollForward
ldx #$27 ;Set starting value of X to 39 (We are moving backwards this time)
.ColScrLoop1
lda $d9b7,x ;Fetch all chars from colour char row-1
sta $d9b8,x ;Store all chars to char row +0.
lda $da2f,x ;
sta $da30,x ;Same procedure follows for the rest of the chars
lda $daa7,x ;
sta $daa8,x ;
lda $db1f,x ;
sta $db20,x ;
dex ;Decrement value of X, to lower value until X=0
bne .ColScrLoop1 ;Otherwise continue the moving each colour char until reached 0
rts ;Exit


;Now scroll the colours of selected rows backwards.

.ColScrollBackward
ldx #$00 ;Set X loop pointer as zero
.ColScrLoop2
lda $d969,x ;Read one char after the first char in current row
sta $d968,x ;Store to previous char, to give the washing effect.
lda $d9e1,x ;
sta $d9e0,x ;Same procedure follows for the rest of the chars
lda $da59,x
sta $da58,x
lda $dad1,x
sta $dad0,x
lda $db71,x
sta $db70,x
lda ColourStore ;Instead of colour was, use ColourStore for flashing row $DBC0
sta $dbc0,x

inx ;Increment next char
cpx #$28 ;until it reaches 40 characters. Otherwise
bne .ColScrLoop2;Loop back to .ColScrLoop2 and do the next char
rts ;Done ...

;The get Ready Screen uses colour flashing instead of colour
;rolling. A similar method is used, to the loop above, apart
;from no colours get move across the character. Instead the
;variable FlashStore - FlashStore+2 is stored to the char colours.

.FlashGetReady
jsr .FlashMain ;Call subroutine .FlashMain (Used for Get Ready + Game Over)

ldx #$00 ;X=0 - Starting amount of colours to read
.PutGetReadyColours
lda FlashStore ;Read byte FlashStore
sta $d990,x ;Store it to the current chosen char row
lda FlashStore+1 ;Read the next byte of FlashStore
sta $d9e0,x ;Store it to the next chosen char row
lda FlashStore+2 ;Read the next byte of FlashStore
sta $da30,x ;Store it to the next chosen char row
inx ;Move on to the next colour
cpx #40 ;Has the colour reached 40 chars?
bne .PutGetReadyColours ;Keep repeating until it has
rts ;Exit

;A new flash routine for Get Ready+Game Over ... I think you may have already
;noticed what this will do.

.FlashMain
ldx FlashPointer ;Read set value of X-Loop FlashPointer
lda FlashTable1,x ;Read bytes from FlashTable1
sta FlashStore ;Store to byte FlashStore
lda FlashTable2,x ;Read bytes from FlashTable2
sta FlashStore+1 ;Store to next byte of FlashStore
lda FlashTable3,x ;Read bytes from FlashTable3
sta FlashStore+2 ;Store to next byte of FlashStore
lda FlashTable4,x ;Read bytes from FlashTable4
sta FlashStore+3 ;Store to next byte of FlashStore
inx ;Increment the value of byte FlashPointer
cpx #16 ;If 16 colours are read from the table
beq .ResetFlash ;Jump to the subroutine .ResetFlash (To zero the FlashPointer)
inc FlashPointer ;Otherwise increment to next value of loop FlashPointer
rts ;Exit

;Reset FlashPointer
.ResetFlash
ldx #$00 ;Set value to 0
stx FlashPointer ;Store to X-loop FlashPointer
rts

;
TITLE SCREEN - Page flipping subroutine. This will first
;
read the delay value before checking which page
;
should be displayed on the title screen. Should it be
;
credits or Hi-score table?

;Page delay subroutine ...
.SetPages
lda PageDelay ;Read byte PageDelay
cmp #$ca ;Delay value expired?
beq DelayEnd ;Yes, go to subroutine DelayEnd
inc PageDelay ;No, increment byte PageDelay by 1
rts ;then exit from subroutine.

;Delay of the first part of the delay has
;expired. So now call subroutine DelayEnd in
;order to call another delay to make the delay
;of the page flipping last longer.

DelayEnd
lda #$00 ;Reset first byte of PageDelay byte setting 0
sta PageDelay ;to it.
lda PageDelay+1 ;Now do the same trick with the next byte of PageDelay
cmp #$02 ;Delay value expired
beq DelayEnd2 ;YES, move to the next subroutine DelayEnd2
inc PageDelay+1 ;Otherwise increment the second byte of PageDelay
rts ;and return from subroutine.

;Delays all have expired. Reset the second byte of PageDelay
;and then test the page counter, to see which page the title
;screen should flip to.

DelayEnd2
lda #0 ;Zero second byte of PageDelay
sta PageDelay+1


inc PageCounter ;Increment value of page counter.
lda PageCounter ;Read PageCounter
cmp #$01 ;If PageCounter = 1
beq .Page2 ;call subroutine .Page2
cmp #$02 ;If PageCounter = 2
beq .Page1 ;call subroutine .Page1
rts ;Exit


;Page 2 subroutine - Jump to subroutine .AddHiScores.
;This displays the hi-score table.
.Page2
jmp .AddHiScores

.Page1
;Page 1 subroutine - zero's byte PageCounter and then
;jumps to subroutine .AddCredits, which will display the
;game credits.

lda #$00 ;Set Zero byte PageCounter
sta PageCounter ;Store to byte PageCounter

jmp .AddCredits ;Jump to subroutine .AddCredits
rts

.AddCredits
;Display credits on to the screen (You should already
;know the text display routine. No explanation necessary.

ldx #$00
.PutCreds2
lda Credits1,x
sta $7d68,x
lda Credits2,x
sta $7db8,x
lda Credits3,x
sta $7de0,x
lda Credits4,x
sta $7e30,x
lda Credits5,x
sta $7e58,x
lda Credits6,x
sta $7ea8,x
lda Credits7,x
sta $7ed0,x
inx
cpx #$28
bne .PutCreds2
rts

;Show hi-score table on to the screen.

.AddHiScores

ldx #$00
.PutHis
lda Hi1,x
sta $7d68,x
lda Hi2,x
sta $7db8,x
lda Hi3,x
sta $7de0,x
lda Hi4,x
sta $7e30,x
lda Hi5,x
sta $7e58,x
lda Hi6,x
sta $7ea8,x
lda Hi7,x
sta $7ed0,x
inx
cpx #$28
bne .PutHis
rts

;Star sprites subroutine

.SpriteField
jsr .Expand ;Call subroutine to expand X-MSB sprite position
ldx #$00 ;
MoveObject
lda ObjPos+$00,x;Read all X-position of sprites using GhostBytes
sec ;
sbc ObjSpeed,x ;Subtract position by the value of speed (going backwards)
sta ObjPos+$00,x;Store back new X-Position to sprites using GhostBytes
inx ;
inx ;Increment byte twice, and move on to next sprite
cpx #$10 ;Until 16 sprites GhostBytes have been counted.
bne MoveObject ;Otherwise return to loop MoveObject
rts

;Read sprite GhostBytes and then store these positions to the
;actual hardware. Expand the area for X-Position so that the
;sprites can use the WHOLE screen area, rather than 2 thirds
;of the screen.

.Expand
ldx #$00 ;Read pointer value X
.ExpandLoop
lda ObjPos+$01,x ;Read sprite Y-position from GhostByte table ObjPos (1,3,5,7,9,11,13,15)
sta $D001,x ;Store it to the hardware sprite position Y
lda ObjPos+$00,x ;Read sprite X-position from GhostByte table ObjPos (0,2,4,6,8,10,12,14)
asl ;multiply area size
ror $d010 ;rotate sprite MSB to the right
sta $d000,x ;Store it to the hardware sprite position X
inx ;Increment pointer value X twice
inx ;
cpx #$10 ;Until 16 bytes read in total (2x8)
bne .ExpandLoop ;otherwise loop back to .ExpandLoop
rts

;Import source hi-score table + name entry routines
!source "hiscore.asm"

;Default starting sprite position tables for the moving star sprites.
PosTable
!byte $20,$80,$60,$98,$a0,$b0,$40,$c8,$c0,$88,$00,$a8,$e0,$c0,$80,$d8,$00

;GhostByte position self-mod table for storing the 8 star sprites
ObjPos
!byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;Speed table (X+Y) for sprites
ObjSpeed
!byte 1,0,3,0,2,0,4,0,3,0,1,0,2,0,4,0,0

xpos
!byte $00 ;$D016 Scroll control counter

PageDelay
!byte $00,$00 ;Self mod delay pointers for page flipping on title screen
PageCounter
!byte $00,$00 ;Page counter for page flipping on title screen
PicPos
!byte $00
FirePort1
!byte $00 ;Control for firebutton on joystick port 1
FirePort2
!byte $00 ;Control for firebutton on joysstick port 2
OptionType
!byte $00 ;Game options

Credits
!ct scr
Credits1
!text " #2014-2016 the new dimension "
Credits2
!text " game design, graphics and ideas by "
Credits3
!text " richard bayliss and alf yngve "
Credits4
!text " logo bitmap artwork drawn by "
Credits5
!text " johan 'jsl' janssen "
Credits6
!text " front end, enhancements and music by "
Credits7
!text " richard bayliss "
Credits8
!text " current option: "
OptionText
!text " "
Credits9
!text " press -fire- to start your mission "
HiScores
!ct scr
Hi1
!text " today's leaders of the dark force "
Hi2
!text " -- 1 -- "
Hi3
!text " "
Name1
!text "alf yngve ............... "
HiScore1
!text "014000 "
Hi4
!text " -- 2 -- "
Hi5
!text " "
Name2
!text "richard b ............... "
HiScore2
!text "007200 "
Hi6
!text " -- 3 -- "
Hi7
!text " "
Name3
!text "jsl ............... "
HiScore3
!text "005000 "



OptionMusic
!text "music " ;Text for music option
OptionSfx
!text " sfx " ;Text for sfx option
SoundOption
!byte 0 ;Sound option pointer

ColourPointer
!byte 0 ;Colour pointer counter
ColourStore
!byte 0,0,0,0,0 ;4-byte storing for flashing colours
FlashPointer
!byte 0 ;Colour pointer counter
FlashStore
!byte 0,0,0,0 ;4-byte storing for flashing colours

RasterCol1
!byte $09,$02,$08,$0a,$07,$01,$00,$00 ;Colour table for the top raster colour bar
RasterCol2
!byte $00,$01,$07,$0a,$08,$02,$09,$00 ;Colour table for the bottom raster colour bar

RasTime1
!byte $01,$08,$08,$08,$08,$08,$08,$08 ;Timing table for top raster colour bar
RasTime2
!byte $01,$08,$08,$08,$08,$08,$08,$08 ;Timing table for bottom raster colour bar

FlashTable1
!byte $00,$0b,$09,$02,$08,$0a,$0f,$07 ;Colour table for flashing sequence 1
!byte $01,$07,$0f,$0a,$08,$02,$09,$00 ;GET READY + GAME OVER

FlashTable2
!byte $0b,$09,$02,$08,$0a,$0f,$07 ;Colour table for flashing sequence 2
!byte $01,$07,$0f,$0a,$08,$02,$09,$00,$00 ;GET READY + GAME OVER

FlashTable3

!byte $09,$02,$08,$0a,$0f,$07 ;Colour table for flashing sequence 3
!byte $01,$07,$0f,$0a,$08,$02,$09,$00,$00,$0b ;GET READY + GAME OVER


FlashTable4
!byte $02,$08,$0a,$0f,$07 ;Colour table for flashing sequence 4
!byte $01,$07,$0f,$0a,$08,$02,$09,$00,$00,$0b,$09 ;GET READY + GAME OVER

ColourTable
;Colour table for the title screen colour-wash routine
!
byte $01,$01,$01,$01,$07,$0f,$0a,$08,$02,$09,$0b,$00,$0b,$09,$02,$08
!
byte $0a,$07,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
!
byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01

BACK TO TOP:

THE GAME ENDING

The game ending uses a single IRQ interrupt, which will do nothing special, apart from display the end text and play music. Before all this is kicked in, a count of the generators is detected and will flash the screen multicolours - IF in the game all generator sprites have been destroyed. Otherwise the explosion effect is triggered and then jumps straight on to the bad ending.



Source for game ending:
SOURCE: endcode.asm

;Dark Force happy ending - Flash the game screen ($d022, $d023).
;
The player flies off screen.


.EndScreen1
sei ;Disable all interrupts, and reset the value of the the Explosion
lda #$00 ;counter.
sta $d019
sta $d01a
sta .ExplodeDelay
sta .ExplodePointer
sta .ScenarioCounter

lda #$81
sta $dc0d
sta $dd0d
lda #$00
sta $d015
lda #$36
sta $01
lda #<.Irq4
sta $0314
lda #>.Irq4
sta $0315
lda #$00
sta $d012
; sta $d015
lda #$7f
sta $dc0d
lda #$1b
sta $d011
lda #$00
sta $d020
sta $d021
lda #$01
sta $d019
sta $d01a
lda #$06 ;Siren effect
jsr MusicInit

cli
;Scene 1 ... Flash the background inside the SEUCK graphics ...
.Scene1
lda #$00
sta Sync
cmp Sync
beq *-3

jsr .FlashBackground
jmp .Scene1

;Call tables to flash the background and store
;to screen multicolour 1 and 2 ($D022, $D023).
;Basically all generators have been destroyed, so
;we want to give this effect, in order to destabilize
;the laser beam.

.FlashBackground
ldx .ExplodePointer ;read byte .ExplodePointer
lda .ExplodeColourTable,x ;Read the .ExplodeColourTable
sta $d022 ;Store it to screen Multi colour 1
lda .ExplodeColourTable+1,x ;Read the next byte of .ExplodeColourTable
sta $d023 ;Store it to screen Multi colour 2
inx ;
cpx #$0e ;14 colours read?
beq .ResetBGExp ;YES ...
inc .ExplodePointer ;Guess not, move on to the next colour and loop
rts

.ResetBGExp
ldx #$00 ;Flash finished, so reset ExplodePointer for it
stx .ExplodePointer ;to flash again.

lda .ScenarioCounter ;Read the scenario counter. (It should flash colours 20 times)
cmp #20 ;Check if 20 flashes have taken place.
beq .Scenario2 ;Yes, move on to the next scenario (SCREEN EXPLOSION)
inc .ScenarioCounter ;No. increment value of ScenarioCounter and loop.
rts

;Scenario 2

.Scenario2
lda #$05 ;Explosion sound
jsr MusicInit ;init music/sound
ldx #$00 ;Reset .ExplodePointer
stx .ExplodePointer
lda #$00 ;Reset .ExplodeDelay
sta .ExplodeDelay
lda #$00 ;Switch off screen
sta $d011
sta .ScenarioCounter ;Reset .ScenarioCounter
.Scenario2Loop
lda #0 ;Sync timer again
sta Sync
cmp Sync
beq *-3
jsr .ExplodeFullScreen ;Call ExplodeFullScreen subroutine
jmp .Scenario2Loop ;Loop

;Assuming the screen is switched off. Delay the colour explosion
;then read the exploding colours and place them to the border colour ($D020)
;to give a screen exploding effect.

.ExplodeFullScreen

lda .ExplodeDelay ;Call a delay to control the speed of
cmp #$02 ;the colour flash explosion.
beq .ExpSlowEnough ;$02 is the set speed. If speed of 2 has been read. Call .ExpSlowEnough

inc .ExplodeDelay ;otherwise increment delay by 1 byte.
rts

;Main explosion colours routine. It will flash the whole screen

.ExpSlowEnough
lda #$00 ;Reset .ExplodeDelay
sta .ExplodeDelay
ldx .ExplodePointer ;Read .ExplodePointer inside loop
lda .ExplodeColourTable,x ;Read .ExplodeColourTable
sta $d020 ;Store to VIC2 Border
inx ;Next byte
cpx #$0d ;Check if 13 bytes of the table has been read
beq .ScreenExpFinished ;Yes... Explosion has finished
inc .ExplodePointer ;No ... Move on to next byte of colour pointer until reached 13
rts

;Explosion has finished, so setup the previous explosion position
;in order to display the black border/background and add a little
;delay before displaying the WELL DONE text.

.ScreenExpFinished

ldx #$0c ;Put value of 12 to .ExplodePointer (One byte before last from table)
stx .ExplodePointer

lda .ScenarioCounter ;Calculate delay (.ScenarioCounter)
cmp #24 ;until delay = 24
beq .FinishedExpRoutine2 ;Skip to finished explosion subroutine
inc .ScenarioCounter ;increment value of delay
rts
.FinishedExpRoutine2

lda #$04 ;Init Hi Score/Well done tune
jsr MusicInit ;Music Init

;IMPORTANT FEATURE: Check how many generators should be destroyed.
;If amount is all 20, then run the happy ending. Otherwise run the Sad ending.

lda GeneratorsToDestroy ;How many generators destroyed?
cmp #GenAmount ;Are they all destroyed?
beq .HappyEnding ;Yes. Run happy ending.

;Otherwise display SAD ending.


lda #$02 ;VIC BANK $02 set
sta $dd00
lda #$fe ;$7C00-$7FE8 screen memory, $7800-$8000 char memory set
sta $d018
lda #$08 ;Disable screen multicolour / default HSP
sta $d016
lda #$00 ;Black border/background
sta $d020
sta $d021
jsr .ClearScreen ;Clear the screen subroutine
;Display failed text ...
ldx #$00
.PutMessageEnd2
lda EndFailText1,x
sta $7cc8,x
lda EndFailText2,x
sta $7d40,x
lda EndFailText3,x
sta $7d90,x
lda EndFailText4,x
sta $7de0,x
lda EndFailText5,x
sta $7e30,x
lda EndFailText6,x
sta $7ea8,x
inx
cpx #$28
bne .PutMessageEnd2
jmp PutWhite

;Display well done text.

.HappyEnding
lda #$02
sta $dd00
lda #$fe
sta $d018
lda #$08
sta $d016
lda #$00
sta $d020
sta $d021
jsr .ClearScreen ;Clear the screen subroutine
ldx #$00
.PutMessageEnd1
lda EndPassText1,x
sta $7cc8,x
lda EndPassText2,x
sta $7d40,x
lda EndPassText3,x
sta $7d90,x
lda EndPassText4,x
sta $7de0,x
lda EndPassText5,x
sta $7e30,x
lda EndPassText6,x
sta $7ea8,x
inx
cpx #$28
bne .PutMessageEnd1

;Fill colour screen to WHITE
PutWhite

ldx #$00
MakeWhite
lda #$01
sta $d800,x
sta $d900,x
sta $da00,x
sta $dae8,x
inx
bne MakeWhite
.ContRout

;Reset joystick Port 2 and wait
;for the fire button to be pressed.

lda #$00
sta FirePort2
sta $d020
sta $d021
lda #$1b ;Switch screen back on
sta $d011
.TheFlashingRoutine

lda #$00 ;Sync timer again
sta Sync
cmp Sync
beq *-3

lda $dc00
lsr
lsr
lsr
lsr
lsr
bit FirePort2
ror FirePort2
bmi .TheFlashingRoutine
bvc .TheFlashingRoutine
jmp .HiScoreCheck

;usual text flashing
.FlashAllText
ldx #$00
.StoreIt
lda FlashStore
sta $d8c8,x
sta $d940,x
sta $d990,x
sta $d9e0,x
sta $da30,x
sta $daa8,x
inx
cpx #$28
bne .StoreIt
rts

;End screen 2 - The sad ending. Similar to above, apart from
;just the full screen explosion and the sad ending text.

.EndScreen2
sei
lda #$00
sta $d019
sta $d01a
sta $d015
sta .ExplodeDelay
sta .ExplodePointer
sta .ScenarioCounter
lda #$81
sta $dc0d
sta $dd0d
lda #$00
sta $d015
lda #$36
sta $01
lda #<.Irq4
sta $0314
lda #>.Irq4
sta $0315
lda #$00
sta $d012
; sta $d015
lda #$7f
sta $dc0d
lda #$1b
sta $d011
lda #$00
sta $d020
sta $d021
lda #$01
sta $d019
sta $d01a

cli
jmp .Scenario2











.ScenarioCounter
!byte 0 ;Byte for scenario
.ExplodeCounter
!byte 0 ;Byte for reading amount of colours for exploding screen
.ExplodeDelay
!byte 0 ;Explosion delay control
.ExplodePointer
!byte 0 ;Explosion counter
.ExplodeColourTable
;Actual colours for the explosion effect

!byte $00,$09,$02,$08,$0a,$07,$01,$07,$0a,$08,$02,$09,$00




BACK TO TOP:

THE HIGH SCORE / NAME ENTRY

The  listing below will check for the player's current score (via pointers) and see whether or not the 3 hi-scores are actually LOWER than the player's final score. If it is, another routine gets called to allow the player to enter his/her name in to the high score table. Otherwise the hi-score detection will display 'NOT A HIGH SCORE', and prompts the player to press fire to return to the front end.



The name entry routine is called inside another Sync loop, where moving the joystick up / down will advance or go back a letter. A subroutine is also called to check whether the letter value is too high or too low and will reset the letter to the lowest or highest. Up Arrow and Left Arrow are set as the End and Delete chars. When either of the two arrows are spotted at the press of the FIRE BUTTON. A routine will either delete the arrow char and delete the char before it, or the routine will REMOVE the UP arrow, and copy the player's final score and name to one of the top 3 hi-scores. Then jumps straight back to the title screen. 
The chosen letter flashes to indicate the position of the cursor for the player to enter its name.

Finally the hi-score routine / name entry routine:
SOURCE: hiscore.asm
;Hi score check routine.

;Disable interrupts from previous routines
;silence SID chip.

.HiScoreCheck
sei
lda #$00
Silence
lda #$00
sta $d400,x
inx
cpx #$18
bne Silence
jsr .ClearScreen ;Clear the screen subroutine
lda #$00
sta $d01a
sta $d019
lda #$81
sta $dc0d
sta $dd0d
lda #$31
sta $0314
lda #$ea
sta $0315

;Call single IRQ interrupt in order to be
;able to flash text and play music.

lda #<.Irq4
sta $0314
lda #>.Irq4
sta $0315
lda #$00
sta $d012
lda #$7f
sta $dc0d
lda #$1b
sta $d011
lda #$01
sta $d01a
sta $d019
lda #$04 ;Well done / ending music
jsr MusicInit ;Init mussic

cli


;Clear the screen visible, by calling subroutine.
jsr .ClearScreen

;Now copy and convert the 6 bytes score from SEUCK
;and then convert it into numerical characters.
;Then store it into table FinalScore.

ldx #$00
.CopyScore
lda result,x
clc
adc #$30
sta FinalScore,x
inx
cpx #$06

bne .CopyScore

;Display the text which will show the
;final score message.

ldx #$00
.PutScoreMessage
lda FinalScoreMess,x
sta $7d18,x
lda #$01
sta $d918,x
inx
cpx #$28
bne .PutScoreMessage

;THIS CODE IS OLD, AND PRETTY LENGTHY
;but it should be easy enough to study.

;Check the value of the final score (byte by byte)
;and compare it to the hi-score in first place.
.CheckFirstPlace

lda FinalScore ;Read first byte of FinalScore
sec ;
lda HiScore1+5 ;Check if 6th digit of hi-score is
sbc FinalScore+5 ;Lower than 6th digit of FinalScore
lda HiScore1+4 ;Do the same for the 5th digit
sbc FinalScore+4
lda HiScore1+3 ;The 4th digit ...
sbc FinalScore+3
lda HiScore1+2 ;3rd ...
sbc FinalScore+2
lda HiScore1+1 ;2nd ...
sbc FinalScore+1
lda HiScore1 ;1st ...
sbc FinalScore+0
bpl .NotFirstPlace ;Not first place ... skip to .NotFirstPlace to check next position
jsr .NameEntryRoutine ;Call subroutine .NameEntryRoutine for player to enter his/her name
jmp .SetFirstPlace ;THEN call subroutine to move names.

;See .CheckFirstPlace to know what is happening here ... It is
;basically the same, but using HiScore2 to check for second place.

.NotFirstPlace
lda FinalScore
sec
lda HiScore2+5
sbc FinalScore+5
lda HiScore2+4
sbc FinalScore+4
lda HiScore2+3
sbc FinalScore+3
lda HiScore2+2
sbc FinalScore+2
lda HiScore2+1
sbc FinalScore+1
lda HiScore2
sbc FinalScore
bpl .NotSecondPlace ;Not second place read ...
jsr .NameEntryRoutine ;Enter name
jmp .SetSecondPlace ;Set new position to 2nd place.


;Check for position in hi-score - 3rd place

.NotSecondPlace
lda FinalScore
sec
lda HiScore3+5
sbc FinalScore+5
lda HiScore3+4
sbc FinalScore+4
lda HiScore3+3
sbc FinalScore+3
lda HiScore3+2
sbc FinalScore+2
lda HiScore3+1
sbc FinalScore+1
lda HiScore3
sbc FinalScore
bpl .NoHiScore ;Not third place ... No high score found.
jsr .NameEntryRoutine ;Call subroutine to enter name
jmp .SetThirdPlace ;Then set new position to 3rd place.

;No hi score has been achieved, so display a FAIL message to
;show the play failed to get a high score.

.NoHiScore
ldx #$00
.PutFailScore
lda HiFailMessage1,x ;Read text HiFailMessage1,x
sta $7d90,x ;Store to line ...
lda HiFailMessage2,x
sta $7e08,x
lda #$01 ;White
sta $d990,x ;Fill chars that colour
sta $da08,x
inx
cpx #$28 ;40 chars
bne .PutFailScore


lda #$00 ;Reset fire button pointer for joystick
sta FirePort2 ;port 2

;Loop while waiting for the fire button to be
;pressed to return back to the title screen.

.WaitTitle
lda $dc00
lsr
lsr
lsr
lsr
lsr
bit FirePort2
ror FirePort2
bmi .WaitTitle
bvc .WaitTitle
jmp .FrontEnd ;back to title screen.

;Player can enter his/her name ... Display well done text
;and paint it white.

.NameEntryRoutine
ldx #$00
.PutNameText
lda HiPassMessage1,x
sta $7d90,x
lda HiPassMessage2,x
sta $7e08,x
lda #$01
sta $d990,x
sta $da08,x
inx
cpx #$28
bne .PutNameText

ldx #$00
.Putline
lda #$2e ;Draw a row of 9 dashes on to the screen
sta $7eb8,x ;and set colour white.
lda #$01
sta $dab8,x
inx
cpx #$09
bne .Putline

;Set low-byte address of which character screen position
;in BANK 2, $7c00-$7fe8 the first char for name entry should
;start from.

lda #<$7e90 ;Low byte of char screen position
sta CharPlot+1 ;Store to self-mod low byte CharPlot1
lda #>$7e90 ;Hi byte of