REM >.Src.Invaders REM Taito Space Invaders (Circa 1978) Clone REM (c) DynaByte Software 2007-2010 REM LibASH Extended heap manager (c)2008 DynaByte Software. REM Derived from ASH by Steve Revill (c)2004 7th Software. REM Keys | Title | In-Game REM ======+===============+============= REM SPACE | Start Game | REM S | Screenshot | Screenshot REM Z | | Left. REM X | | Right. REM Enter | | Fire. REM M | Music On/Off | Music On/Off REM + | Volume Up | Volume Up REM - | Volume Down | Volume Down REM < | Speed Down | Speed Down REM > | Speed Up | Speed Up REM P | | Pause/Resume Game. REM Q | Quit Program | Suicide. REM ************************************************************* REM Initialisation Part 1... (Files & Environment.) REM ************************************************************* REM Setup error trapping and announce ourselves... invaders_debug%=FALSE :REM Enable/Disable debug. ON ERROR PROCtrap_error:END MODE28:OFF :REM 800*600 (256 Colours) string$="Acorn Invaders. (c)2007-2010 DynaByte Software" PRINT string$:PRINT STRING$(LEN(string$),"-"):PRINT REM Check we have all our essential files... REM Our Hiscore table is a special case as it can be either REM generated if one doesn't exist, or we can carry on regardless REM if we can't create it. REM Set Directory pointers... app_dir$=".Resources." :REM Root resources. dat_dir$="." :REM Writable location base. sav_dir$=dat_dir$+"Data." :REM Location of saved data. scr_dir$=dat_dir$+"Screens." :REM Location of screenshots. REM dat_dir$ is now re-set to point at the read only data... gfx_dir$=app_dir$+"Graphics." :REM Graphics. dat_dir$=app_dir$+"Data." :REM Data, Lookups etc. snd_dir$=app_dir$+"Sound." :REM Sound. REM Locate files... (Mission critical...) bail%=FALSE PRINT"Checking files..." IF NOT FNfind(gfx_dir$+"Gfx0","Sprites") THEN bail%=TRUE IF NOT FNfind(gfx_dir$+"Gfx1","Charset") THEN bail%=TRUE IF NOT FNfind(gfx_dir$+"Gfx2","Numerics") THEN bail%=TRUE IF NOT FNfind(snd_dir$+"Intro","Music") THEN bail%=TRUE IF NOT FNfind(snd_dir$+"Teckno","Music") THEN bail%=TRUE IF NOT FNfind(dat_dir$+"Creds","Credits") THEN bail%=TRUE IF NOT FNfind(dat_dir$+"Msg","Messages") THEN bail%=TRUE IF NOT FNfind(dat_dir$+"ImgLUT","Lookups") THEN bail%=TRUE IF NOT FNfind(dat_dir$+"SWIs","Lookups") THEN bail%=TRUE IF NOT FNfind(dat_dir$+"AshLUT","Lookups") THEN bail%=TRUE IF bail% THEN ERROR 255,"File Missing!!" REM These two files are required before we can do anything else, so REM load them here... Other strings are further down with other file REM loading stuff. PRINT:PRINT"Loading LibASH Resources." :REM LibASH Resources. ashmsg_max%=FNstring_enumprocessed(dat_dir$+"AshLUT","ALUT") DIM ashmsg_num%(ashmsg_max%),ashmsg_msg$(ashmsg_max%) key%=FNstring_load(dat_dir$+"AshLUT","ALUT",ashmsg_num%(),ashmsg_msg$()) PROCash_init(key%) :REM Kick the Heap Manager. gamestate%=FNash_claimfilled(112,0) :REM Claim for state info. audiostate%=FNash_claimfilled(28,0) :REM Claim for audio info. REM Load Strings... PRINT"Loading Messages..."; :REM Text strings. string_max%=FNstring_enumprocessed(dat_dir$+"Msg","MSGS") DIM string_num%(string_max%),string_msg$(string_max%) key%=FNstring_load(dat_dir$+"Msg","MSGS",string_num%(),string_msg$()) gamestate%!68=key%:PRINTFNstring_get(14):PRINT REM ************************************************************* REM Initialisation Part 2... (Gamestate Initialisation.) REM ************************************************************* REM Setup our variables. (These are used all over...) REM A lot of misc values like timers and such are now held in the REM gamestate% object. Values held here are listed below. Music REM stuff was previously stored in it's own object, but didn't REM really warrant the extra overhead. A lot of the other values REM were bundled into the player% object, but didn't really REM belong there. This new object pulls all this stuff together REM in one place. REM gamestate% object contents... REM (Audio stuff moved to audiostate% - 28/05/10) REM Offs.| Usage Offs.| Usage REM -----+-------------------------------+-----+----------------------- REM 0 : Un-used (Was Snd Status) | 60 : Credit screen XOR hash. REM 4 : Un-used (Was Music Handle) | 64 : HS Table XOR hash. REM 8 : Un-used (Was Vol change delay)| 68 : Messages XOR hash. REM 12 : Last extra life score | 72 : Credit screen size. REM 16 : Extra life HUD counter | 76 : Sprite area pointer. REM 20 : Extra life frame toggle | 80 : Charset area pointer. REM 24 : Extra life frame timer | 84 : Charset2 area pointer. REM 28 : Pause Game toggle. | 88 : Current GFX area ptr. REM 32 : HUD Pause game status. | 92 : Config changed flag. REM 36 : HUD Pause game timer. | 96 : Un-used (Was Volume step.) REM 40 : Screenshot counter. | 100 : Has player achieved HiScore. REM 44 : Media writeable flag. | 104 : Have bunkers been invaded. REM 48 : Un-used (Was Music Volume) | 108 : VSync skip info. REM 52 : Number of inv. missiles | 112 : REM 56 : Number of bunkers. | : REM -----+-------------------------------+-----+----------------------- REM Number of invader missiles (Offset 52)... REM This value represents the maximum number of missiles currently REM allowed on screen. This increases up to a maximum of 10 as the REM player gets further down the line. For ASH allocation and REM freeing purposes, this number is ALWAYS 10 (The size of the REM array.) Slots higher than the number in this offset are masked REM off when it comes to the operation of the handler. REM Timers (Offsets 8, 24 & 36)... REM All timers are snapshots of OS_ReadMonotonicTime used for REM delay or animation events. REM Pointers (Offsets 76, 80, 84 & 88)... REM These are all pointers to blocks of memory, allocated by REM LibASH, that hang off this block. The only exception to this is REM offset 88 which always points to one of the other three and REM signifies the currently selected bank when bank-switching. When REM switching banks, we just change the value stored here to the REM desired bank. The graphics code uses the base address stored here REM when looking up graphics data. REM HiScore Flag (Offset 100)... REM Although this is strictly player related, it cannot go in the REM player% block, as it is used during the title sequence AFTER a game REM has finished, therefore player% has already been discarded. REM Bunkers Invaded Flag (Offset 104)... REM This is set if the remaining invaders have destroyed the bunkers on REM their way down to the player's base. If so, scores for each hit are REM halved until the end of the current rack. REM VSync skip info (Offset 108)... REM This is used to slow down the game when being played on fast REM hardware. The game plays at it's natural speed when played on an REM A7000+ (circa 48MHz). Anything faster than this and we need to REM throttle the hardware or we run waaay too fast. PRINTFNstring_get(12):PRINTFNstring_get(8)+" "+FNstring_get(2) PROCinit_memusage(gamestate%,206,0) :REM gamestate% alloc. PROCinit_memusage(audiostate%,210,0) :REM audiostate% alloc. gamestate%!0=1:gamestate%!4=-1 REM Screenshot Init... REM Set the initial value to -1 and then find the first free slot. If REM this value is still -1 when we return, then we're out of slots. gamestate%!40=FNinit_setdumpcounter(0) :REM Screendump counter. IF gamestate%!40=-1 THEN ERROR255,FNstring_get(109) PRINTFNstring_get(15)+STR$(gamestate%!40) REM Misc gamestate% setup... gamestate%!44=TRUE :REM TRUE to save HiTable. gamestate%!52=5 :REM Invader missiles. gamestate%!56=5 :REM Number of bunkers. gamestate%!92=FALSE :REM Configs changed flag. gamestate%!100=FALSE :REM Player on HiTable. gamestate%!104=FALSE :REM Bunkers Invaded flag. gamestate%!108=1 :REM VSync control (1:1). REM Audio State... REM This has been split from gamestate% for the time being, because REM it was becoming too fragmented. Offset 4 is used to store the REM value of a timer. We initialise it here for completeness, but REM written before it's read in any case. REM Offs | Usage REM -----+------------------------------- REM 0 : Sound Status. REM 4 : Volume change delay. REM 8 : Music Handle (Main). REM 12 : Music Handle (HiScore Entry). REM 16 : Current playing Handle. REM 20 : Volume REM 24 : Volume step. REM 28 : REM -----+------------------------------- audiostate%!0=1 :REM Audio Status. audiostate%!4=0 :REM Volume change delay. audiostate%!8=-1 :REM TimPlayer Handle (Main Theme) audiostate%!12=-1 :REM TimPlayer Handle (HiScore Entry) audiostate%!16=-1 :REM Currently playing Handle. audiostate%!20=128 :REM Default music volume. audiostate%!24=2 :REM Volume step. REM HiScore Table allocations... DIM scores_name$(10),scores_value%(10) :REM HiScore table. DIM scores_rack%(10) REM ************************************************************* REM Initialisation Part 3... (Object Offsets.) REM ************************************************************* REM Objects... Each object (Base, saucer, missile etc.) is REM given a block of memory up to 68 bytes long to hold all REM relevant data for that object. This, apart from being REM quicker, is far easier to code and saves memory. The reason REM for the memory saving is this... As long as we store like REM properties in the same offsets in each object, we can use the REM same tokens to access offsets in all like objects. REM For example... REM We could use seperate variables for base_x%, base_y%, base_spd% REM and base_dir%. We would then have to use another set of variables REM the saucer. Let's call this saucer_x%, saucer_y%, saucer_spd% & REM saucer_dir%. That makes a total of 8 variables. BBC BASIC allocates REM a memory word (4 bytes) to each one of these variables to store REM the data. For only a couple of objects, the memory overhead isn't REM too bad, but as you add more objects to the mix, the memory REM overhead becomes huge and the program becomes difficult to follow. REM However... If we allocate base% a block of memory large enough to REM hold all it's properties, likewise we do the same for saucer%. We REM only have 4 extra variables to worry about. These variables are REM just set to offsets that are common to both of the blocks of memory REM we've just setup. Therefore, we can use the same variables to REM read or write the properties of both of the objects. This is how REM Invaders works. REM These are those tokens to address offsets in each of the blocks REM of memory that represent sprites on the screen. These blocks REM don't actually contain any graphical data, just instance related REM information such as position, size and whether we've hit it or REM not. (See tutorial for how this works...) Contents for offsets REM 32-64 (flags1% - flags9%) will be different, depending on the REM nature of the object. Not all objects will have space for all 9 REM option flags. I'll define LOCAL variables with more meaningful REM names and point them to these in the object's handler routines. PRINTFNstring_get(8)+" "+FNstring_get(22) REM Object offsets... xpos%=0:ypos%=4:xsize%=8:ysize%=12 :REM Position & Size. dir%=16:speed%=20 :REM Movement. active%=24:beenhit%=28 :REM Object Status. flags1%=32:flags2%=36:flags3%=40 :REM Misc flags. flags4%=44:flags5%=48:flags6%=52 flags7%=56:flags8%=60:flags9%=64 REM Define offsets for screen and rail objects... REM We start at offset 16 here as the first 4 offsets that describe REM the position & size are common across ALL objects. x_eig%=16:y_eig%=20:setmode%=24 :REM Offsets in screen obj. xfont%=28:yfont%=32 toprail%=16:baserail%=20 :REM Offsets in rail obj. REM ************************************************************* REM Initialisation Part 4... (Load everything else in.) REM ************************************************************* PRINTFNstring_get(9)+" "+FNstring_get(3) :REM Sprite names. ilut_max%=FNstring_enumprocessed(dat_dir$+"ImgLUT","ILUT") DIM ilut_num%(ilut_max%),ilut_msg$(ilut_max%) key%=FNstring_load(dat_dir$+"ImgLUT","ILUT",ilut_num%(),ilut_msg$()) PRINTFNstring_get(26):PROCstring_bulkdecode(ilut_max%,key%,ilut_msg$()) PRINTFNstring_get(14):PRINT PRINTFNstring_get(9)+" "+FNstring_get(13) :REM SWI names. slut_max%=FNstring_enumprocessed(dat_dir$+"SWIs","SLUT") DIM slut_num%(slut_max%),slut_msg$(slut_max%) key%=FNstring_load(dat_dir$+"SWIs","SLUT",slut_num%(),slut_msg$()) PRINTFNstring_get(26):PROCstring_bulkdecode(slut_max%,key%,slut_msg$()) PRINTFNstring_get(14):PRINT REM Load configs if we can find them... REM This bit is not critical to us running, so just display a REM message if we can't and carry on regardless. They will be REM created the next time the program exits cleanly. Set our REM save configs flag (offset 92 in gamestate% object) TRUE REM in the second case so the file will be saved. PRINTFNstring_get(9)+" "+FNstring_get(24)+" "; IF FNfs_find(sav_dir$+"Configs")=1 THEN PROCgs_loadconfig:PRINTFNstring_get(18) ELSE PRINTFNstring_get(25):gamestate%!92=TRUE ENDIF REM Initialise Sound Sub-system and load music... PRINT:PRINTFNstring_get(8)+" "+FNstring_get(1)+" "; ver%=0:SYS FNswi_get(30) TO ver% PRINTFNstring_get(21)+" "+STR$(ver%/100) audiostate%!8=FNtimplay_songload(snd_dir$+"Intro") audiostate%!12=FNtimplay_songload(snd_dir$+"Teckno") PRINTFNstring_get(14):PRINT REM Initialise Graphics Sub-system... REM The variable we setup is gfx_spriteop% which is the base REM address of the OS call we'll use to draw graphics. Other REM values bunged into gamestate% here are the base addresses REM of the three sprite areas that hold the GFX and the two REM character sets. We set the bank pointer (offset 88 in REM gamestate% object) to the sprite area containing the GFX. PRINT:PRINTFNstring_get(17)+" "+FNstring_get(16) SYS FNswi_get(1),, FNswi_get(2) TO gfx_spriteop% PRINTFNstring_get(9)+" "+FNstring_get(4) gamestate%!76=FNgfx_load(gfx_dir$+"Gfx0") PROCinit_memusage(gamestate%!76,203,0) PRINTFNstring_get(14):PRINT PRINTFNstring_get(9)+" "+FNstring_get(5) gamestate%!80=FNgfx_load(gfx_dir$+"Gfx1") PROCinit_memusage(gamestate%!80,204,0) PRINTFNstring_get(14):PRINT PRINTFNstring_get(9)+" "+FNstring_get(23) gamestate%!84=FNgfx_load(gfx_dir$+"Gfx2") PROCinit_memusage(gamestate%!84,205,0) PRINTFNstring_get(14):PRINT gamestate%!88=gamestate%!76 REM Load Credit screen data... REM This is now done by a single call to PROCinit_credscreen. There REM were too many globals being defined by this one routine. Doing REM it this way allows me to define them as LOCAL. PRINT:PRINTFNstring_get(9)+" "+FNstring_get(6); PROCinit_credscreen:PRINTFNstring_get(14):PRINT REM HiScore Table... REM Here, we look for and load, if it exists, our Hiscores file. REM If we can't find one, we check whether we can write to the REM location before we attempt to create one. If we can't REM do that, then we can still run, but the user's scores will REM not be retained on exit. REM (This bit is crap, because I still get errors on certain REM configs. Is there a better way of doing this?) PRINT:PRINTFNstring_get(7) filespec$=sav_dir$+"HiTable" :REM Location of file. IF FNfs_find(filespec$)<>1 THEN IF NOT FNfs_writeable(0,sav_dir$) THEN gamestate%!44=FALSE :REM HiTable save flag. PRINTFNstring_get(19)+" "+FNstring_get(10)+" "+FNstring_get(20) ELSE PRINTFNstring_get(19)+" "+FNstring_get(11)+" "+FNstring_get(20) ENDIF PROChitable_create(gamestate%!44) :REM HiTable save flag. ELSE IF NOT FNfs_writeable(1,filespec$) THEN gamestate%!44=FALSE :REM HiTable save flag. PRINTFNstring_get(18)+" "+FNstring_get(10)+" "+FNstring_get(20) ELSE PRINTFNstring_get(18)+" "+FNstring_get(11)+" "+FNstring_get(20) ENDIF PROChitable_load ENDIF PRINT:PRINTFNstring_get(12):PROCwait(150) REM ************************************************************* REM Initialisation Part 5... (Screen, Rail & Box Objects.) REM ************************************************************* REM Reserve memory for global object blocks... screen%=FNash_claim(36):rail%=FNash_claim(24) :REM Global objects. box%=FNash_claim(16) REM Get the video display into a VGA 256 colour mode. This is REM also the same MODE as our sprites. Call to PROCget_screeninfo REM calls the OS to get some values and sets up our screen object REM for us. We still have to claim this screen% object as done REM above. CLS:PROCget_screeninfo :REM Setup screen object. screen%!xfont%=FNconv_units(8,1) screen%!yfont%=FNconv_units(8,2)+7 REM Define property values for other global objects. rail%!xsize%=FNconv_units(6,1) :REM Rail properties. rail%!ysize%=FNconv_units(6,2) rail%!toprail%=screen%!ysize%-50 rail%!baserail%=30 REM ************************************************************* REM Main Loop... (Title>Game>Title.) REM ************************************************************* REM Start music playing... REM Our last known status has already been loaded from the config REM file. If we don't have a config file, or the contents don't REM make sense, then the default values are assumed. Either way, REM they've already been bunged into the audiostate% object at REM offsets 0 & 20. Offset 8 was setup when we loaded the music. REM Offset 4 is used for fade in/outs and volume control. Offset REM 16 is a pointer to one of the other two handles and is used REM for bankswitching in exactly the same way as we manage the REM graphics. REM Offs. | Purpose | Value REM ------+---------+------ REM 0 | Status | 0=Mute, 1=Music & SFX, 2=SFX Only. REM 4 | Delay | Timer... (n=0 - Do it now, n>0 Wait n counts.) REM 8 | Handle | TimPlayer Handle (Main Theme) REM 12 | Handle | TimPlayer Handle (HiScore Entry) REM 16 | Handle | Pointer to handle currently playing. REM 20 | Volume | Music volume. (Loaded from configs file.) REM 24 | VStep | Fade in/out control. audiostate%!16=audiostate%!8 :REM Set to main theme. IF audiostate%!0=1 THEN PROCtimplay_fadein(0) REM Cycle attract mode and play game... REM At this point we're up and running. We drop out of this loop REM when the player quits the game. ("Q" From attract mode.) WHILE NOT FNtitle_loop CLS:PROCmain ENDWHILE REM ************************************************************* REM Shutdown & Clear up... (Player has quit the game.) REM ************************************************************* REM Enclose our goodbyes in a border... REM Setup our border size and position. Centre this on the screen REM and then drop it down by 3px. (Raising the text by 3px would REM break too many things!) Call PROCdraw_box() to produce our REM border and then display our text. Once we've done all that, we REM start to fade our music and bail back to the desktop. CLS:box%!xsize%=220:box%!ysize%=40 PROCcentre_sprite_xy(box%,screen%):box%!ypos%-=3 PROCdraw_box(box%):PROCcentre_text_xy(FNstring_get(31),10) REM Write back our current configuration... REM We re-check for writeability here, just in case the location REM IS writable, but the HiScore file wasn't. In that case, our REM flag would have been set FALSE when we loaded it in. REM IF FNfs_writeable(0,sav_dir$) THEN PROCgs_saveconfig IF gamestate%!44 THEN PROCgs_saveconfig REM Fade out & bail to desktop... We're done here. PROCtimplay_fadeout(4) :REM Fade-out current track. SYSFNswi_get(32),audiostate%!8 :REM Unload Music. (Main Theme) SYSFNswi_get(32),audiostate%!12 :REM Unload Music. (HiScore Entry) audiostate%!8=-1:audiostate%!12=-1 :REM Clear handles. audiostate%!16=-1 SYSFNswi_get(8),"FX 21,0" :REM Flush Keyboard buffer... PROCash_free(box%) :REM Release global objects... PROCash_free(rail%):PROCash_free(screen%) FOR i%=76 TO 84 STEP 4:PROCash_free(gamestate%!i%):NEXT i% PROCash_free(gamestate%) :REM Release gamestate%. PROCash_free(audiostate%) :REM Release audiostate%. PROCash_destroy :REM Tear down ASH Subsystem... END REM ************************************************************* REM Title Loop... (Display our title sequence.) REM ************************************************************* REM Animate in our initial screen layout and then perform a cycle REM between Credits, Score Advance, Keyboard Controls & HiScore REM Table screens. Do a keyboard scan for each screen and check REM for hotkeys and return from function accordingly... Object REM hwind% points to the area of the screen where where the REM current screen in the sequence gets placed in the attract REM mode. Although not a window in the true sense, in that it has REM no focus, it's easier to think of it that way. This is the REM area below "Invaders" and above "PRESS SPACE TO PLAY" text. REM Objects inv1% - inv3% are identical copies of each other, REM except for values of ypos%. Likewise, the key-% objects are REM copies, apart from the co-ordinates. Our saucer% object here, REM is only large enough to hold screen co-ordinates. It is made REM LOCAL to this function so we can re-define it for the main REM game loop. DEFFNtitle_loop LOCAL i%,space%,invt%,hwind%,str%,saucer%,inv1%,inv2%,inv3% LOCAL base% LOCAL keyz%,keyx%,keye%,keyq%,keym%,keys%,keyu%,keyd% LOCAL keylt%,keygt% LOCAL x%,y%,rc%,scores_save%,gfx_area%,hstab%,s$,string$ scores_save%=44:gfx_area%=80:hstab%=100 :REM gamestate% object offsets. REM Allocate & initialise title sequence objects using ASH... REM Space is allocated for keyx%, keye%, keyq%, keym%, keys%, REM keyu%, keyd%, inv2% and inv3% by FNash_blockcopy() all we REM need to do is remember to release it afterwards. space% =FNash_claimfilled(16,0) invt% =FNash_claimfilled(16,0) hwind% =FNash_claimfilled(16,0) saucer% =FNash_claimfilled(16,0) inv1% =FNash_claimfilled(16,0) keyz% =FNash_claimfilled(16,0) base% =FNash_claimfilled(16,0) REM Initialise LOCAL (title sequence) variables. space%!xsize%=FNconv_units(100,1) :REM Space text properties. space%!ysize%=FNconv_units(40,2) space%!xpos%=FNcentre_sprite(space%,screen%) space%!ypos%=800 invt%!xsize%=FNconv_units(150,1) :REM Invaders text properties. invt%!ysize%=FNconv_units(40,2) invt%!xpos%=FNcentre_sprite(invt%,screen%) invt%!ypos%=space%!ypos%-invt%!ysize% hwind%!xsize%=720:hwind%!ysize%=500:REM Window properties. hwind%!xpos%=FNcentre_sprite(hwind%,screen%) hwind%!ypos%=200 saucer%!xsize%=FNconv_units(40,1) :REM Saucer properties. saucer%!ysize%=FNconv_units(20,2) saucer%!xpos%=(hwind%!xpos%+hwind%!xsize%/2)-150 saucer%!ypos%=(hwind%!ypos%+hwind%!ysize%)-125 base%!xsize%=FNconv_units(30,1) :REM Base properties. base%!ysize%=FNconv_units(15,2) inv1%!xsize%=FNconv_units(20,1) :REM Common invader props. inv1%!ysize%=FNconv_units(20,2) inv1%!xpos%=(hwind%!xpos%+hwind%!xsize%/2)-150 inv1%!xpos%+=FNcentre_sprite(inv1%,saucer%) inv2%=FNash_blockcopy(inv1%) :REM Create two copies. inv3%=FNash_blockcopy(inv1%) :REM Unique invader props. inv3%!ypos%=saucer%!ypos%-40-inv3%!ysize% inv2%!ypos%=inv3%!ypos%-40-inv2%!ysize% inv1%!ypos%=inv2%!ypos%-40-inv1%!ysize% keyz%!xsize%=FNconv_units(20,1) :REM KB Controls "keys". keyz%!ysize%=FNconv_units(20,2) keyz%!xpos%=(hwind%!xpos%+hwind%!xsize%/2)-120 keyx%=FNash_blockcopy(keyz%) :REM Clone three. keye%=FNash_blockcopy(keyz%):keyq%=FNash_blockcopy(keyz%) keyq%!xpos%=hwind%!xpos% :REM Tweak & clone three. keyp%=FNash_blockcopy(keyq%):keys%=FNash_blockcopy(keyq%) keym%=FNash_blockcopy(keyz%):keylt%=FNash_blockcopy(keyq%) keym%!xpos%=hwind%!xpos% :REM Tweak & clone two. keym%!xpos%+=(hwind%!xsize%/2)+10 keyu%=FNash_blockcopy(keym%):keyd%=FNash_blockcopy(keym%) keygt%=FNash_blockcopy(keym%) :REM Unique "keys" props. keyz%!ypos%=(hwind%!ypos%+hwind%!ysize%)-90 keyx%!ypos%=keyz%!ypos%-60:keye%!ypos%=keyx%!ypos%-60 keys%!ypos%=keye%!ypos%-90:keyp%!ypos%=keys%!ypos%-60 keyq%!ypos%=keyp%!ypos%-60:keym%!ypos%=keye%!ypos%-90 keyu%!ypos%=keym%!ypos%-60:keyd%!ypos%=keyu%!ypos%-60 keylt%!ypos%=keyq%!ypos%-60:keygt%!ypos%=keyd%!ypos%-60 REM Animate the initial screen title "Space Invaders" & "PRESS REM SPACE TO PLAY" messages. CLS:PROCdraw_rail(rail%!toprail%):PROCdraw_rail(rail%!baserail%) PROChud_soundstate FOR i%=0-space%!xsize% TO space%!xpos% STEP 5 WAIT:PROCplot(FNsprite_get(100),i%,space%!ypos%) PROCwait(1) NEXT i% FOR i%=screen%!xsize% TO invt%!xpos% STEP -5 WAIT:PROCplot(FNsprite_get(101),i%,invt%!ypos%) PROCwait(1) NEXT i% string$=FNstring_get(32):str%=LEN(string$)*screen%!xfont%:y%=170 PROCprint(FNcentre_text(str%,screen%!xsize%),y%,string$,11,gamestate%!gfx_area%) string$=FNstring_get(33) str%=LEN(string$)*screen%!xfont%:y%-=screen%!yfont% PROCprint(FNcentre_text(str%,screen%!xsize%),y%,string$,11,gamestate%!gfx_area%) REM Cycle the remainder of the attract mode. Display the REM HiTable 1st if we've come from a game where the player REM has achieved a hi score. WHILE NOT INKEY(-99) AND NOT INKEY(-17) AND NOT INKEY(-82) REM Display HiScore table if we've just come from a game where the REM player has achieved a high score. (1st time only.) IF gamestate%!hstab% THEN gamestate%!hstab%=FALSE rc%=FNtitle_hstab IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) ENDIF REM Display Credits Screen... REM Text strings are held in cred_str$() in encrypted form. REM Line spacing info is held in the matching element of REM cred_spc%(). Negative values have special meanings... REM Value | Line spacing is... REM --------+--------------------- REM -1 | 1 * screen%!yfont% (Next line.) REM <-n> | n-1 * screen%!yfont% (Leave n-1 blank lines.) REM <+n> | Leave a gap of OS Units... y%=hwind%!ypos%+hwind%!ysize% FOR i%=1 TO gamestate%!72 string$=FNencode_string(cred_str$(i%),gamestate%!60) x%=FNtitle_stringcentre(string$) CASE SGN(cred_spc%(i%)) OF WHEN -1 : y%-=(ABS(cred_spc%(i%))*screen%!yfont%) WHEN 1 : y%-=cred_spc%(i%) OTHERWISE : ERROR255,FNstring_get(115)+" "+STR$(cred_spc%(i%)) ENDCASE PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) NEXT i% REM Wait for 750 counts or until SPACE or Q pressed... rc%=FNtitle_wait IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) REM Display Score Advance table... string$=FNstring_get(57)+" "+FNstring_get(34) x%=FNtitle_stringcentre(string$) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) string$=FNstring_get(35):y%-=screen%!yfont% x%=FNtitle_stringcentre(string$) PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) x%=saucer%!xpos%+saucer%!xsize%+10 PROCplot(FNsprite_get(11),saucer%!xpos%,saucer%!ypos%) string$=FNstring_get(36)+" "+FNstring_get(40) PROCprint(x%,saucer%!ypos%+10,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(24),inv3%!xpos%,inv3%!ypos%) string$=FNstring_get(37)+" "+FNstring_get(40) PROCprint(x%,inv3%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(22),inv2%!xpos%,inv2%!ypos%) string$=FNstring_get(38)+" "+FNstring_get(40) PROCprint(x%,inv2%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(20),inv1%!xpos%,inv1%!ypos%) string$=FNstring_get(39)+" "+FNstring_get(40) PROCprint(x%,inv1%!ypos%+12,string$,0,gamestate%!gfx_area%) str%=base%!xsize%/screen%!xfont%+2:s$=FNstring_get(66) string$=s$+STRING$(str%," ")+FNstring_get(67) x%=FNtitle_stringcentre(string$) y%=inv1%!ypos%-(3*screen%!yfont%) PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) base%!xpos%=x%+7+(LEN(s$)*screen%!xfont%):base%!ypos%=y% PROCplot(FNsprite_get(1),base%!xpos%,base%!ypos%) REM Wait for 750 counts or until SPACE or Q pressed... rc%=FNtitle_wait IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) REM Display Keyboard controls screen... string$=FNstring_get(41):x%=FNtitle_stringcentre(string$) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) REM Left, Right & Fire... x%=keyz%!xpos%+keyz%!xsize%+10 PROCplot(FNsprite_get(102),keyz%!xpos%,keyz%!ypos%) :REM (Z) string$=FNstring_get(42)+" "+FNstring_get(43) PROCprint(x%,keyz%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(103),keyx%!xpos%,keyx%!ypos%) :REM (X) string$=FNstring_get(42)+" "+FNstring_get(44) PROCprint(x%,keyx%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(104),keye%!xpos%,keye%!ypos%) :REM (Enter) PROCprint(x%,keye%!ypos%+12,FNstring_get(45),0,gamestate%!gfx_area%) REM Extra control keys (Left column)... x%=keyq%!xpos%+keyq%!xsize%+10 PROCplot(FNsprite_get(105),keyp%!xpos%,keyp%!ypos%) :REM (P) string$=FNstring_get(46)+" "+FNstring_get(49) PROCprint(x%,keyp%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(117),keys%!xpos%,keys%!ypos%) :REM (S) PROCprint(x%,keys%!ypos%+12,FNstring_get(76),0,gamestate%!gfx_area%) PROCplot(FNsprite_get(107),keyq%!xpos%,keyq%!ypos%) :REM (Q) string$=FNstring_get(48)+" "+FNstring_get(49) PROCprint(x%,keyq%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(118),keylt%!xpos%,keylt%!ypos%) :REM (<) string$=FNstring_get(78)+" "+FNstring_get(75) PROCprint(x%,keylt%!ypos%+12,string$,0,gamestate%!gfx_area%) REM Extra control keys (Right column)... x%=keym%!xpos%+keym%!xsize%+10 PROCplot(FNsprite_get(106),keym%!xpos%,keym%!ypos%) :REM (M) string$=FNstring_get(47)+" "+FNstring_get(73) PROCprint(x%,keym%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(115),keyu%!xpos%,keyu%!ypos%) :REM (+) string$=FNstring_get(47)+" "+FNstring_get(77)+" " string$+=FNstring_get(74) PROCprint(x%,keyu%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(116),keyd%!xpos%,keyd%!ypos%) :REM (-) string$=FNstring_get(47)+" "+FNstring_get(77)+" " string$+=FNstring_get(75) PROCprint(x%,keyd%!ypos%+12,string$,0,gamestate%!gfx_area%) PROCplot(FNsprite_get(119),keygt%!xpos%,keygt%!ypos%) :REM (>) string$=FNstring_get(78)+" "+FNstring_get(74) PROCprint(x%,keygt%!ypos%+12,string$,0,gamestate%!gfx_area%) REM Wait for 750 counts or until SPACE or Q pressed... rc%=FNtitle_wait IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) REM Display HiScore table... rc%=FNtitle_hstab IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) ENDWHILE REM This should never get executed... ERROR 255,FNstring_get(102) =FALSE REM Display HiScore table... DEFFNtitle_hstab LOCAL rc%,x%,y%,i%,gfx_area%,string$ gfx_area%=80 :REM gamestate% offsets... REM First line... (This is conditional on scores_save%) string$=FNstring_get(50) IF gamestate%!scores_save% THEN string$=FNstring_get(51) x%=FNtitle_stringcentre(string$) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) REM Second line... string$=FNstring_get(58)+" "+FNstring_get(52) x%=FNtitle_stringcentre(string$):y%-=screen%!yfont% PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) REM Table header... y%=(hwind%!ypos%+hwind%!ysize%)-100 string$=" "+FNstring_get(68)+" "+FNstring_get(69) string$+=STRING$(12," ")+FNstring_get(57)+"." string$+=STRING$(3," ")+FNstring_get(60)+"." x%=FNtitle_stringcentre(string$) PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) y%-=2*screen%!yfont% REM Loop through the HiScore table arrays displaying names and REM scores... FOR i%=1 TO 10 IF i%<10 THEN string$=" "+STR$(i%)+". " ELSE string$=" "+STR$(i%)+". " ENDIF s$=FNencode_string(scores_name$(i%),gamestate%!64) string$+=FNstr_pad(s$,8," ",FALSE):string$+=" .... " string$+=FNstr_rightalign(STR$(scores_value%(i%)),10) string$+=" ... "+FNstr_rightalign(STR$(scores_rack%(i%)),3) x%=FNtitle_stringcentre(string$) PROCprint(x%,y%-(i%*(screen%!yfont%+5)),string$,0,gamestate%!gfx_area%) NEXT i% REM Wait for 750 counts or until SPACE or Q pressed... =FNtitle_wait REM Wait for 750 counts or SPACE or Q to be pressed. Returns one REM of three values in rc% REM 0 -> Timeout period expired... (750 counts up.) REM 1 -> "Q" to Quit... REM 2 -> SPACE to Play... DEFFNtitle_wait LOCAL t%,rc% t%=TIME+750:rc%=FALSE REPEAT IF INKEY(-82) THEN PROCevnt_kb_screendump IF INKEY(-102) THEN PROCevnt_kb_togglemusic IF INKEY(-59) THEN PROCevnt_kb_volume(4) IF INKEY(-60) THEN PROCevnt_kb_volume(-4) IF INKEY(-103) THEN PROCevnt_kb_vsync(0) IF INKEY(-104) THEN PROCevnt_kb_vsync(1) UNTIL TIME>=t% OR INKEY(-17) OR INKEY(-99) IF INKEY(-17) THEN rc%=1 :REM Get outta here (Q)... IF INKEY(-99) THEN rc%=2 :REM Play a game... REM If rc% is TRUE (non zero value) then we either start a game REM or quit. Either way, we need to release the memory claimed REM by our objects. IF rc% THEN PROCash_free(space%):PROCash_free(invt%):PROCash_free(hwind%) PROCash_free(saucer%):PROCash_free(inv1%):PROCash_free(inv2%) PROCash_free(inv3%):PROCash_free(keyz%):PROCash_free(keyx%) PROCash_free(keye%):PROCash_free(keyq%):PROCash_free(keym%) PROCash_free(keyp%):PROCash_free(keys%):PROCash_free(keyu%) PROCash_free(keyd%):PROCash_free(base%):PROCash_free(keylt%) PROCash_free(keygt%) ENDIF =rc% REM Prepare a string for display... REM Centering a string requires a number of parameters to be setup REM before calling PROCprint(). The process is largely repetative REM All we do is change a few values. DEFFNtitle_stringcentre(string$) LOCAL str% str%=LEN(string$)*screen%!xfont% =hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) REM ************************************************************* REM Game Loop... (Play one instance of the game.) REM ************************************************************* REM Loop through one instance of the game. This basically means... REM Setup in-game stuff. -> Play until out of bases. -> Manage REM hiscore entry. -> Return. DEFPROCmain LOCAL base%,shell%,saucer%,rack%,player% :REM Objects. LOCAL inv% :REM Single Invader. LOCAL invstat%(),invfire%() :REM Invader status. LOCAL bunker%(),bunknum%,bunk% :REM Bunker status. LOCAL lives%,score%,racknum%,suicide% :REM Player properties. LOCAL paused%,pstatus%,ptime% :REM Game state (Paused) LOCAL elcount% :REM Game state (Extra) LOCAL vsync% :REM Game state (Misc) LOCAL colgap%,xnum%,ynum%,rownum%,frcap% :REM Rack properties. LOCAL str%,string$,x%,y%,i%,j% :REM Misc. REM Claim memory for local objects using ASH... base%=FNash_claim(32):shell%=FNash_claim(28) saucer%=FNash_claim(48):rack%=FNash_claim(68) inv%=FNash_claim(16):bunk%=FNash_claim(16) player%=FNash_claimfilled(16,0) REM Setup arrays and object pointers... lives%=0:score%=4:racknum%=8:suicide%=12 :REM Player offs. elcount%=16:elscore%=12:paused%=28:bunknum%=56:REM Gamestate offs. colgap%=flags2%:xnum%=flags3%:ynum%=flags4% :REM Rack offsets. rownum%=flags7%:frcap%=flags8% DIM invstat%(10,6),invfire%(10),bunker%(gamestate%!bunknum%) REM Initialise LOCAL (in-game) variables. base%!xsize%=FNconv_units(30,1) :REM Base properties. base%!ysize%=FNconv_units(15,2) base%!xpos%=FNcentre_sprite(base%,screen%) base%!ypos%=50:base%!speed%=6:base%!dir%=base%!speed% inv%!xsize%=FNconv_units(20,1) :REM Invader properties. inv%!ysize%=FNconv_units(20,2) saucer%!xsize%=FNconv_units(40,1) :REM Saucer properties. saucer%!ysize%=FNconv_units(20,2) saucer%!ypos%=rail%!toprail%-(10+saucer%!ysize%) saucer%!speed%=5:saucer%!active%=FALSE saucer%!flags3%=1 shell%!xsize%=FNconv_units(5,1) :REM Shell properties. shell%!ysize%=FNconv_units(20,2) shell%!active%=FALSE:shell%!speed%=10 player%!racknum%=1:player%!lives%=3 :REM Player properties. player%!suicide%=FALSE gamestate%!elcount%=-1 :REM Initial game state. gamestate%!elscore%=0 rack%!colgap%=FNconv_units(20,1) :REM Rack properties. rack%!xnum%=10:rack%!ynum%=6 rack%!active%=TRUE:rack%!frcap%=4 SYS FNswi_get(6) TO rack%!flags9% :REM MonotonicTime. PROCinvfire_init:PROCbunk_init :REM Object arrays. REM Initialise game screen... REM Draw the top and bottom rails, prepare and display the HUD & REM sort out the first rack of invaders. Finally... Display the REM player's base. PROCdraw_rail(rail%!toprail%):PROCdraw_rail(rail%!baserail%) PROChud_init(0,scores_value%(1),player%!lives%,player%!racknum%) PROCrack_gameinit :REM Invaders. PROCbunk_redrawall :REM Bunkers. PROCplot(FNsprite_get(1),base%!xpos%,base%!ypos%) :REM Base. REM Slow down fast hardware... SYSFNswi_get(8),"VSkip "+STR$(gamestate%!vsync%) REM Our event generator... REM This is the main polling loop. The program sits tight in this REM loop firing off events until the player runs out of lives. WHILE player%!lives%>0 REM Keyboard poll... REM We only check the top four if we haven't paused the game. REM The rest we always need to check. IF gamestate%!paused%=0 THEN IF INKEY(-74) THENPROCevnt_kb_basefire(base%!xpos%,base%!ypos%) IF INKEY(-98) THENPROCevnt_kb_baseleft :REM(Z)Base left... IF INKEY(-67) THENPROCevnt_kb_baseright :REM(X)Base right... IF INKEY(-17) THENPROCevnt_kb_suicide :REM(Q)Suicide... REMIF INKEY(-36) THENPROCevnt_kb_saucertest :REM(T)Test only... REMIF INKEY(-87) THENPROCevnt_kb_eltest :REM(L)Test only... REMIF INKEY(-35) THENPROCrack_endgame :REM(E)Test only... ENDIF IF INKEY(-102)THENPROCevnt_kb_togglemusic :REM(M)Music On/Off IF INKEY(-82) THENPROCevnt_kb_screendump :REM(S)Screenshot... IF INKEY(-56) THENPROCevnt_kb_togglepause :REM(P)Pause Game... IF INKEY(-59) THENPROCevnt_kb_volume(4) :REM(+)Volume Up... IF INKEY(-60) THENPROCevnt_kb_volume(-4) :REM(-)Volume Down... IF INKEY(-103)THENPROCevnt_kb_vsync(0) :REM(<)VSync faster... IF INKEY(-104)THENPROCevnt_kb_vsync(1) :REM(>)VSync slower... REM Update screen... REM This is our equivalent of an arcade game's VBlank interrupt. REM We've polled the input device (our keyboard) and triggered REM anything we need to do this time through. Now we need to deal REM with events that have either already been triggered previously REM or those that happen all the time. This means calling all the REM handlers that update positions of objects on the screen, so REM we can update the screen. If the game is currently paused REM (gamestate%!paused% =1) then we only need to trigger the game REM paused handler. We need to do this each time, so that the REM text flashing effect works. The delay for this is managed by REM code in the handler. REM Process evnt_fr events. (Frame Refresh.) IF gamestate%!paused%=0 THEN PROCevnt_fr_basefire :REM Animate player's shots. PROCevnt_fr_saucer :REM Saucer handling & AI. PROCevnt_fr_rackmove :REM Move rack & Collision detect. PROCevnt_fr_rackfire :REM Rack missile fire control. PROCevnt_fr_bunkers :REM Defense bunker collision stuff. PROCevnt_fr_extralife :REM "Extra Life" text control. ELSE PROCevnt_fr_pausegame :REM "Game Paused" text control. ENDIF REM Wait for next VBlank WAIT ENDWHILE REM Display GAME OVER!!! in a bordered rectangle in the middle REM of the screen. box%!xsize%=240:box%!ysize%=40 PROCcentre_sprite_xy(box%,screen%):box%!ypos%-=3 PROCwipe_sprite(box%):PROCdraw_box(box%) string$=FNstring_get(49)+" "+FNstring_get(53) PROCcentre_text_xy(string$,11):PROCwait(750) REM Check & Handle HiTable entry... IF NOT player%!suicide% THEN IF player%!score%>=scores_value%(10) THEN PROChitable_entry(player%) ENDIF ENDIF REM Free memory used for objects... PROCash_free(base%):PROCash_free(rack%):PROCash_free(saucer%) PROCash_free(shell%):PROCash_free(player%):PROCash_free(inv%) PROCash_free(bunk%) FOR i%=1 TO 10:PROCash_free(invfire%(i%)):NEXT i% FOR i%=1 TO gamestate%!bunknum%:PROCash_free(bunker%(i%)):NEXT i% SYSFNswi_get(8),"VSkip 1" ENDPROC REM ************************************************************* REM Proceedures (Keyboard events.) REM ************************************************************* REM Move base left. If we're already at left edge then bail... DEFPROCevnt_kb_baseleft REM Collision detection. (Base & Playfield.) IF base%!xpos%<=0 THEN base%!xpos%=0 :REM Don't move. ELSE base%!dir%=0-base%!speed% :REM Change direction. base%!xpos%+=base%!dir% PROCplot(FNsprite_get(1),base%!xpos%,base%!ypos%) ENDIF ENDPROC REM Move base right. If we're already at the right edge, bail... DEFPROCevnt_kb_baseright REM Collision detection. (Base & Playfield.) IF base%!xpos%>=screen%!xsize%-base%!xsize% THEN base%!xpos%=screen%!xsize%-base%!xsize% :REM Don't move. ELSE base%!dir%=0+base%!speed% :REM Change direction. base%!xpos%+=base%!dir% PROCplot(FNsprite_get(1),base%!xpos%,base%!ypos%) ENDIF ENDPROC REM Fire a shell. If we're already firing a shell bail. (We can REM only fire one shell at a time or else the animation gets REM confused.) shell%!active% is a property of the shell% object REM that is set boolean TRUE when player fires a shell and is REM cleared when either the shell scrolls off the top of the REM screen or hits something. DEFPROCevnt_kb_basefire(x%,y%) REM Bail if shell already active. IF NOT shell%!active% THEN shell%!active%=TRUE :REM Set active. shell%!xpos%=x%+(base%!xsize%/2) :REM Initial position. shell%!ypos%=y%+base%!ysize% PROCsfx_play(4) ENDIF ENDPROC REM Dump a screen to disc as ".Screens.Dump" REM Where is a number from 0 to 999. FNinit_setdumpcounter() REM when given the current value, returns the next free slot. REM When the "Screens" directory is empty, we start from 0. DEFPROCevnt_kb_screendump LOCAL num%,mflag% num%=40:mflag%=44 :REM Object offsets. IF gamestate%!mflag% THEN REPEAT:UNTIL NOT INKEY(-82) :REM De-bounce key. gamestate%!num%=FNinit_setdumpcounter(gamestate%!num%) IF gamestate%!num%=-1 THEN ERROR255,FNstring_get(109) SYSFNswi_get(8),"ScreenSave "+scr_dir$+"Dump"+STR$(gamestate%!num%) ENDIF ENDPROC REM Player has chickened out. (Committed suicide. 'Q' in-game.) REM Set lives to zero and flag it up as a suicide event in the REM player object. (Skip Hiscore entry if suicide set.) DEFPROCevnt_kb_suicide LOCAL i%,pcs%:pcs%=52 FOR i%=1 TO gamestate%!pcs%:invfire%(i%)!active%=FALSE:NEXT i% player%!lives%=0:player%!suicide%=TRUE ENDPROC REM Toggle Music on & off... REM Split music object data to new audiostate% object, which now REM also includes some of the values we used to store with the REM player% object that were never really specific to the player, REM but didn't belong anywhere else either. (04/07/08) REM Offs. | Purpose | Value REM -------+----------+------ REM 0 | Status | 0=Mute, 1=Music & SFX, 2=SFX Only. REM 4 | Delay | Snapshot of OS_ReadMonotonicTime last done REM 16 | Handle | Currently playing Handle. (TimPlayer) DEFPROCevnt_kb_togglemusic LOCAL mstatus%,mhandle%,cfgflag% mstatus%=0:mhandle%=16:cfgflag%=92 REPEAT:UNTIL NOT INKEY(-102) :REM De-bounce key. audiostate%!mstatus%+=1 IF audiostate%!mstatus%>2 THEN audiostate%!mstatus%=0 IF audiostate%!mstatus%=1 THEN SYSFNswi_get(33),audiostate%!mhandle% ELSE SYSFNswi_get(34),audiostate%!mhandle% ENDIF gamestate%!cfgflag%=TRUE :REM Config has changed. PROChud_soundstate ENDPROC REM Turn Music volume up/down... REM We only need to do this if music is actually playing... Don't REM change anything otherwise. We don't de-bounce the keys here REM as we don't exactly want a one-shot event. We do however, need REM to introduce a delay, otherwise we would get an on/off switch REM situation on fast hardware. During runtime, the value stored REM in audiostate%!gsvol% (Offset 20) is not actually used. It is REM there so we can write it and the current value of REM audiostate%!mstatus% (Offset 0) to the configs file and read REM it back next time the game is loaded. DEFPROCevnt_kb_volume(vol%) LOCAL mstatus%,mhandle%,mdelay%,gsvol%,mt%,cvol%,nvol%,cfgflag% mstatus%=0:mdelay%=4:gsvol%=20:mhandle%=16 :REM Object offsets. (audiostate%) cfgflag%=92 :REM Object offsets. (gamestate%) IF audiostate%!mstatus%=1 THEN SYSFNswi_get(6) TO mt% :REM OS_ReadMonotonicTime IF mt%-audiostate%!mdelay%>=10 THEN SYSFNswi_get(36),audiostate%!mhandle%,-1 TO ,cvol% nvol%=cvol%+vol% IF nvol%>=0 AND nvol%<=128 THEN SYSFNswi_get(36),audiostate%!mhandle%,nvol% audiostate%!gsvol%=nvol%:audiostate%!mdelay%=mt% gamestate%!cfgflag%=TRUE ENDIF ENDIF ENDIF ENDPROC REM Adjust the VSync skip ratio and apply... DEFPROCevnt_kb_vsync(by%) LOCAL vsync%,cfgflag% vsync%=108:cfgflag%=92 :REM Object offsets. IF by%=0 THEN REPEAT:UNTILNOTINKEY(-103) IF gamestate%!vsync%<64 THEN gamestate%!vsync%+=1 ELSE REPEAT:UNTILNOTINKEY(-104) IF gamestate%!vsync%>1 THEN gamestate%!vsync%-=1 ENDIF SYSFNswi_get(8),"VSkip "+STR$(gamestate%!vsync%) gamestate%!cfgflag%=TRUE ENDPROC REM Pause & Resume game... REM Toggle between pause and resume. The actual logic is done REM elsewhere. DEFPROCevnt_kb_togglepause LOCAL paused%,pstatus%,string$ paused%=28:pstatus%=32 REPEAT UNTIL NOT INKEY(-56) :REM De-bounce. string$=FNstring_get(49)+" "+FNstring_get(61) gamestate%!paused%=FNtoggle(gamestate%!paused%) :REM Toggle. gamestate%!pstatus%=gamestate%!paused% :REM Set visible. IF gamestate%!paused%=0 THEN string$=STRING$(LEN(string$)," ") PROChud_bannertext(string$) ENDIF ENDPROC REM ************************************************************* REM Proceedures (VBlank/Frame Events) REM ************************************************************* REM Continue animation of a player's shell... If one hasn't been REM created then we bail back to event loop. DEFPROCevnt_fr_basefire IF shell%!active% THEN REM Have we hit the top?? IF shell%!ypos%>=rail%!toprail%-shell%!ysize% THEN shell%!active%=FALSE :REM Clear event. shell%!ypos%-=2:PROCwipe_sprite(shell%) :REM Remove shell. ELSE REM Display shell at current position. PROCplot(FNsprite_get(5),shell%!xpos%,shell%!ypos%) ENDIF shell%!ypos%+=shell%!speed% :REM Advance frame. ENDIF ENDPROC REM Process saucer animation & generation events. A saucer can be REM randomely activated n% of the time, but not if one is REM currently on the screen. When activated, there is a 50% chance REM of it starting on the left hand side of the screen, likewise REM we have a 50% chance of it starting on the right. DEFPROCevnt_fr_saucer LOCAL hit%,toggle%,spr%,mt%,hittime%,racknum%,bunkinv%,fr$ LOCAL elscore%,elcount%,score%,lives% hit%=flags1%:toggle%=flags2%:spr%=flags3% :REM Object tokens. racknum%=8:elcount%=16:lives%=0:score%=4:bunkinv%=104 IF NOT saucer%!active% THEN IF FNpct(4) AND FNpct(5) THEN saucer%!active%=TRUE:saucer%!beenhit%=FALSE saucer%!toggle%=0 REM Decide which side of the screen to start from. IF FNpct(50) THEN saucer%!dir%=0-saucer%!speed% :REM Right edge. (50%) saucer%!xpos%=screen%!xsize% ELSE saucer%!dir%=0+saucer%!speed% :REM Left edge. (50%) saucer%!xpos%=0-saucer%!xsize% ENDIF ENDIF ELSE REM Has saucer gone off screen?? IF saucer%!xpos%<=0-saucer%!xsize%ANDSGN(saucer%!dir%)=-1 THEN saucer%!hit%=FALSE:saucer%!active%=FALSE ENDPROC ENDIF IF saucer%!xpos%>screen%!xsize%ANDSGN(saucer%!dir%)=1 THEN saucer%!hit%=FALSE:saucer%!active%=FALSE ENDPROC ENDIF REM Has saucer been hit by player shot?? REM Modified 15/01/10 to add penalty for bunkers being invaded. After REM bunkers have been invaded, player no longer has the oportunity of REM gaining an extra life on the current rack and the score for each REM saucer is halved. IF NOT saucer%!beenhit% THEN IF FNhit(shell%,saucer%) THEN PROCwipe_sprite(shell%) PROCplot(FNsprite_get(12),saucer%!xpos%,saucer%!ypos%) SYS FNswi_get(6) TO saucer%!hit%:saucer%!beenhit%=TRUE shell%!active%=FALSE IF gamestate%!bunkinv% THEN PROChud_scorehit(250) ELSE IF player%!racknum%>=5 AND FNpct(10) THEN player%!lives%+=1:PROChud_lives(player%!lives%) gamestate%!elcount%=0 ELSE PROChud_scorehit(500) ENDIF ENDIF ENDIF ELSE IF FNhit(shell%,saucer%) THEN PROCwipe_sprite(shell%):shell%!active%=FALSE ENDIF REM Animate saucer... IF NOT saucer%!beenhit% THEN REM Change image every 40 centi-seconds. SYS FNswi_get(6) TO mt% :REM OS_ReadMonotonicTime IF mt%-saucer%!spr%>=40 THEN saucer%!spr%=mt% saucer%!toggle%=FNtoggle(saucer%!toggle%) ENDIF REM Display one of two images. fr$=FNsprite_get(10)+STR$(saucer%!toggle%) PROCplot(fr$,saucer%!xpos%,saucer%!ypos%) saucer%!xpos%+=saucer%!dir% ELSE REM Animate/Remove explosion... SYS FNswi_get(6) TO mt% hittime%=mt%-saucer%!hit% IF hittime%>=300 THEN saucer%!active%=FALSE:saucer%!toggle%=1 PROCwipe_sprite(saucer%) ELSE IF hittime%>=250 THEN PROCplot(FNsprite_get(15),saucer%!xpos%,saucer%!ypos%) ELSE IF hittime%>=200 THEN PROCplot(FNsprite_get(14),saucer%!xpos%,saucer%!ypos%) ELSE IF hittime%>=100 THEN PROCplot(FNsprite_get(13),saucer%!xpos%,saucer%!ypos%) ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDPROC REM Check out the rack of invaders. Move left, right & down. Deal REM with collision detection between player missiles and invaders. DEFPROCevnt_fr_rackmove LOCAL rack_ext%,z%,col%,row%,eclr%,sclr%,rclr%,offset%,div% LOCAL frame%,scol%,ecol%,mt%,timer%,pcsinv%,pcsbnk% LOCAL rownum%,bunkinv% REM Setup meaningful pointers to our object flags... REM Vars colgap%, xnum%, ynum%, rownum% & frcap% have already REM been tied to flags2%, flags3%, flags4%, flags7% & flags8% REM respectively. frame%=flags1%:scol%=flags5% :REM rack% object offsets. ecol%=flags6%:timer%=flags9%:rownum%=flags7% pcsinv%=52:pcsbnk%=56:bunkinv%=104 :REM gamestate% object offsets. REM Alternate our animation frame... SYS FNswi_get(6) TO mt% :REM OS_ReadMonotonicTime IF mt%-rack%!timer%>=50 THEN rack%!timer%=mt%:rack%!frame%=FNtoggle(rack%!frame%) REM Deal with rack movement... REM Move rack left, right & down. Check each time we move down REM to make sure that the rack hasn't invaded the base. If it has REM then end the game. rack_ext%=rack%!xpos%+rack%!xsize% offset%=0-((rack%!scol%-1)*(inv%!xsize%+rack%!colgap%)) IF rack_ext%>=screen%!xsize% THEN rack%!dir%=0-rack%!speed%:PROCrack_advance IF rack%!xpos%<=offset% THEN rack%!dir%=0+rack%!speed%:PROCrack_advance REM Have we extended below the bottom of the bunkers? REM If so, then remove the bunkers... rack%!rownum% factored REM in to prevent bunkers being removed by destroyed invader REM rows. (15/01/10) IF rack%!ypos%+((rack%!rownum%-1)*inv%!ysize%)<=bunk%!ypos%+bunk%!ysize% THEN PROCbunk_clearall:gamestate%!bunkinv%=TRUE ENDIF REM Have we reached the base? offset%=rack%!ypos%+((rack%!rownum%-1)*inv%!ysize%) IF offset%<=base%!ypos%+base%!ysize% THEN PROCrack_endgame:ENDPROC REM Deal with collision detection... REM Firstly we check to see if the player's shell lays somewhere REM inside the rack of invaders. If this is not the case then we REM bail and take no further action this time. However, if we REM are inside the rack (FNhit(shell%,rack%)=TRUE) then we need REM to see what, if anything, we've actually hit. IF FNhit(shell%,rack%) THEN z%=inv%!xsize%+rack%!colgap% IF (shell%!xpos%-rack%!xpos%) MOD z% <= inv%!xsize% THEN REM We've hit something... Find out what. col%=((shell%!ypos%-rack%!ypos%) DIV inv%!ysize%)+1 row%=((shell%!xpos%-rack%!xpos%) DIV z%)+1 REM Act on it... REM Vars col% & row% now contain the index of the invader that REM the player hit. We now check the status of that invader REM and act accordingly. A value of zero indicates that it's REM already dead and buried, whilst a negative value is used REM as a countdown timer for the explosion sprite. We are only REM interested in positive non-zero values. Anything else, we REM take no action. IF SGN(invstat%(row%,col%))=1 THEN invstat%(row%,col%)=-5 :REM Set explosion timer. PROCwipe_sprite(shell%) :REM Remove shell. shell%!active%=FALSE :REM De-activate shell. rack%!beenhit%+=1 :REM Another one down. PROCsfx_play(1) REM Scoring... REM Modified 15/01/10 to allow for invaded bunkers. When scoring REM normally, div%=1 therefore 100/1=100 etc. If the bunkers REM have been invaded, then we set div%=2 and the scoring is halved. div%=1 :REM Normal scoring. IF gamestate%!bunkinv% THEN div%=2 CASE col% OF WHEN 1,2 : PROChud_scorehit(INT(50/div%)) :REM Bottom two rows. WHEN 3,4 : PROChud_scorehit(INT(75/div%)) :REM Middle two rows. WHEN 5,6 : PROChud_scorehit(INT(100/div%)) :REM Top two rows. ENDCASE ENDIF ENDIF ENDIF REM Have we destroyed the last invader in the current rack? REM If so, clear the play area and create and display the next REM wave. In the process of clearing the play area, we seem to REM wipe part of the HUD too. This happens when the cleared rows REM drop below the position of the rail marking the bottom of the REM play area. For the moment, I am just re-drawing this as and REM when it happens, but really it needs fixing. IF rack%!beenhit%>=rack%!xnum%*rack%!ynum% THEN player%!racknum%+=1 :REM Advance rack counter. PROCwipe_sprite(rack%) :REM Clear the last invader. PROCdraw_rail(rail%!baserail%) :REM Re-draw bottom rail. PROChud_lives(player%!lives%) :REM Update HUD. PROChud_rack(player%!racknum%) PROChud_soundstate PROCplot(FNsprite_get(1),base%!xpos%,base%!ypos%) PROCbunk_reset:PROCbunk_redrawall :REM Reset & redraw bunkers. PROCrack_init :REM Create new rack. REM Increase difficulty by allowing more invader missiles as REM number of racks destroyed increases. IF player%!racknum%>=5 THEN gamestate%!pcsinv%=7 IF player%!racknum%>=10 THEN gamestate%!pcsinv%=10 ENDIF REM Do we need to resize the rack? The rack is defined as a block REM of a fixed size. We display the invaders in this block. Each REM time we clear the left or rightmost column or the bottom row, REM we need to update the size of this block to keep the display REM accurate. eclr%=TRUE:sclr%=TRUE FOR col%=1 TO rack%!ynum% IF invstat%(rack%!ecol%,col%)<>0 THEN eclr%=FALSE IF invstat%(rack%!scol%,col%)<>0 THEN sclr%=FALSE NEXT col% rclr%=TRUE FOR row%=1 TO rack%!xnum% IF invstat%(row%,rack%!rownum%)<>0 THEN rclr%=FALSE NEXT row% IF sclr% THEN rack%!scol%+=1 IF eclr% THEN rack%!xsize%-=(inv%!xsize%+rack%!colgap%):rack%!ecol%-=1 IF rclr% THEN rack%!rownum%+=1 REM Finally... Redraw the rack... rack%!xpos%+=rack%!dir%:PROCrack_redraw ENDPROC REM Invader missile fire logic... REM This... Like all the rest of these animation routines is REM timer based. We call this event handler once every REM repetition of the game loop. All invader missile information REM is held in our invfire%() array. This array is just an array REM of object pointers. Data can be accessed using offsets just REM like all the others. Every call to this event advances the REM animation by one frame and then handles display & collision REM detection for that frame before returning. DEFPROCevnt_fr_rackfire LOCAL i%,j%,mt%,pcsinv%,pcsbnk% REM Assign meaningful names to object specific flags. pcsinv%=52:pcsbnk%=56 REM Loop through all invader missile slots... FOR i%=1 TO gamestate%!pcsinv% IF NOT invfire%(i%)!active% THEN PROCinvfire_create(i%) :REM Create a missile. ELSE PROCinvfire_animate(i%) :REM Animate and move down. REM Collision Detection... REM One of three events can happen here. Either we hit the REM bottom of the playfield, the player's base, or a shell REM hits us on the way up. REM Have we reached the bottom?? IF invfire%(i%)!ypos%<=rail%!baserail%+(rail%!ysize%+5) THEN invfire%(i%)!active%=FALSE:PROCwipe_sprite(invfire%(i%)) ENDIF REM Have we hit the player's base?? IF FNhit(invfire%(i%),base%) AND invfire%(i%)!active% THEN PROCinvfire_hitbase(i%) ENDIF REM Have we hit a bunker?? REM Currently just remove the missile. Replace this bit when REM we've worked out how to do a proper collision detection on REM it. FOR j%=1 TO gamestate%!pcsbnk% IF FNhit(invfire%(i%),bunker%(j%)) THEN PROCbunk_beenhit(j%,FALSE) PROCwipe_sprite(invfire%(i%)):invfire%(i%)!active%=FALSE ENDIF NEXT j% REM Has a shell hit us?? IF FNhit(shell%,invfire%(i%)) THEN PROCinvfire_beenhit(i%) ENDIF REM Explosion frame... IF invfire%(i%)!beenhit% THEN PROCinvfire_explode(i%) ENDIF ENDIF NEXT i% ENDPROC REM Handle the pause status and update the HUD... REM Text display is carried out by a HUD routine. We need to check REM and fire it here only... DEFPROCevnt_fr_pausegame LOCAL paused%,pstatus%,ptime%,mt%,string$ paused%=28:pstatus%=32:ptime%=36 :REM Object offsets. SYS FNswi_get(6) TO mt% IF mt%-gamestate%!ptime%>=50 THEN gamestate%!pstatus%=FNtoggle(gamestate%!pstatus%) gamestate%!ptime%=mt% ENDIF string$=FNstring_get(49)+" "+FNstring_get(61) :REM "GAME PAUSED" IF gamestate%!pstatus%=0 OR gamestate%!paused%=0 THEN string$=STRING$(LEN(string$)," ") ENDIF PROChud_bannertext(string$) ENDPROC REM Handle the extra life text status and update the HUD... REM This routine is fired if player%!elcount% is zero or greater. REM When our count (player%!elcount%) gets above 5, we set it to REM -1... It is then triggered by either the scoring routine or REM the PROCevnt_fr_saucer when an extra life condition occurs by REM setting our count to 0. Our current state (either blink on, or REM blink off) is held as 1 or 0 in player%!elframe%. DEFPROCevnt_fr_extralife LOCAL elcount%,elframe%,eltime%,mt%,string$ elcount%=16:elframe%=20:eltime%=24 :REM Object offsets. IF gamestate%!elcount%>=0 THEN SYS FNswi_get(6) TO mt% string$=FNstring_get(66)+" "+FNstring_get(72) :REM "EXTRA LIFE" IF mt%-gamestate%!eltime%>=50 THEN gamestate%!eltime%=mt% gamestate%!elframe%=FNtoggle(gamestate%!elframe%) IF gamestate%!elframe%=1 THEN gamestate%!elcount%+=1 ENDIF REM Are we in blink off state?? IF gamestate%!elframe%=0 THEN string$=STRING$(LEN(string$)," ") :REM Clear the line... ENDIF REM Have we blinked 5 times?? IF gamestate%!elcount%>5 THEN string$=STRING$(LEN(string$)," ") :REM Clear the line... gamestate%!elcount%=-1 :REM Disable event... ENDIF PROChud_bannertext(string$) :REM Update the HUD... ENDIF ENDPROC REM Deal with the bunkers... REM Currently we destroy the bunker after a combination of 10 REM hits by either the player or the invaders. Each time a bunker REM is hit, we bump it up or down by an offset of 5px accordingly. REM this is a temporary nudge like effect which returns the REM bunker to it's rightful place shortly afterwards. DEFPROCevnt_fr_bunkers LOCAL i%,mt%,pcsbnk%,timer%,blk% pcsbnk%=56:timer%=flags2% :REM Object offsets. blk%=FNash_claim(16) :REM Temp bunker object. FOR i%=1 TO gamestate%!pcsbnk% :REM Loop through each bunker. IF FNhit(shell%,bunker%(i%)) THEN PROCwipe_sprite(shell%):shell%!active%=FALSE PROCbunk_beenhit(i%,TRUE) ENDIF IF bunker%(i%)!beenhit% THEN SYSFNswi_get(6) TO mt% IF mt%-bunker%(i%)!timer%>=5 THEN blk%!xpos%=bunker%(i%)!xpos%:blk%!xsize%=bunker%(i%)!xsize% blk%!ypos%=bunker%(i%)!ypos%-5 blk%!ysize%=bunker%(i%)!ysize%+10:PROCwipe_sprite(blk%) PROCbunk_redraw(bunker%(i%)) bunker%(i%)!beenhit%=FALSE ENDIF ENDIF NEXT i% PROCash_free(blk%) ENDPROC REM ************************************************************* REM Functions & Proceedures. (HiScore Table/HiScore Entry.) REM ************************************************************* REM Create a clean HiScore table. The value passed to us in save% REM determines whether we can save the table to disc or not. If REM we can (save%=TRUE) then we call PROChitable_save to write the REM file back. DEFPROChitable_create(save%) LOCAL i%,n$,key% key%=64 :REM Offset into gamestate% obj. gamestate%!key%=RND(128)+127 FOR i%=1 TO 10 IF i% MOD 2=0 THEN n$=FNstring_get(71) ELSE n$=FNstring_get(70) scores_name$(i%)=FNencode_string(n$,gamestate%!key%) scores_value%(i%)=(11-i%)*1000:scores_rack%(i%)=0 NEXT i% IF save% THEN PROChitable_save ENDPROC REM Load our HiTable from disc. The names are stored in RAM in REM encoded form and are only decoded for display. Checksum added REM 02/06/09. DEFPROChitable_load LOCAL i%,hdl%,cplgot%,chfgot%,cpl%,chf%,key%,maggot$ hdl%=OPENIN(sav_dir$+"HiTable") INPUT#hdl%,maggot$ IF maggot$<>"xBATSH" THEN CLOSE#hdl%:ERROR255,FNstring_get(115) INPUT#hdl%,key%,chfgot%,cplgot% chfgot%=chfgot% EOR key%:cplgot%=cplgot% EOR key% FOR i%=1 TO 10 INPUT#hdl%,scores_name$(i%),scores_value%(i%),scores_rack%(i%) NEXT i% CLOSE#hdl% FOR i%=1 TO 10:chf%+=LEN(scores_name$(i%)):NEXT i% cpl%=chf%/10 IF chfgot%<>chf% OR cplgot%<>cpl% THEN ERROR255:FNstring_get(113) gamestate%!64=key% ENDPROC REM Save our HiTable back to disc. The names are stored in RAM in REM encoded form and are only decoded for display. Checksum added REM 02/06/09. DEFPROChitable_save LOCAL i%,hdl%,chf%,cpl%,key%,newhash%,name$ key%=64 :REM Offset into gamestate% obj. newhash%=RND(128)+127 :REM Calculate new hash value. REM Re-Hash HiScore Table... FOR i%=1 TO 10 name$=FNencode_string(scores_name$(i%),gamestate%!key%) scores_name$(i%)=FNencode_string(name$,newhash%) NEXT i% gamestate%!key%=newhash% :REM Update copy in gamestate% obj. REM Work out the checksums and write out the file... FOR i%=1 TO 10:chf%+=LEN(scores_name$(i%)):NEXT i% cpl%=chf%/10 chf%=chf% EOR gamestate%!key%:cpl%=cpl% EOR gamestate%!key% hdl%=OPENOUT(sav_dir$+"HiTable") PRINT#hdl%,"xBATSH",gamestate%!key%,chf%,cpl% FOR i%=1 TO 10 PRINT#hdl%,scores_name$(i%),scores_value%(i%),scores_rack%(i%) NEXT i% CLOSE#hdl% ENDPROC REM HiScore name entry... REM Sort through our hiscore array and find out where the player REM is. (We already know he is worthy.) Allow to enter their name REM and save the HiTable back to disk (Only if scores_save%=TRUE). REM then return... DEFPROChitable_entry(player%) LOCAL invt%,space%,done%,g%,i%,pos%,str%,x%,y%,string$ LOCAL scores_save%,gfx_area%,key%,hstab% space%=FNash_claim(16) :REM Space for objects. invt%=FNash_claim(16) scores_save%=44:gfx_area%=80 :REM gamestate% object offs. key%=64:hstab%=100 REM Setup graphical objects for later on... space%!xsize%=FNconv_units(100,1) :REM Space text properties. space%!ysize%=FNconv_units(40,2) space%!xpos%=FNcentre_sprite(space%,screen%) space%!ypos%=800 invt%!xsize%=FNconv_units(150,1) :REM Invaders text properties. invt%!ysize%=FNconv_units(40,2) invt%!xpos%=FNcentre_sprite(invt%,screen%) invt%!ypos%=space%!ypos%-invt%!ysize% REM Find out where we are and stick it in pos% pos%=0 FOR i%=10 TO 1 STEP -1 IF player%!score%>=scores_value%(i%) THEN pos%=i% NEXT i% REM Insert a new entry into the table and drop everything below REM us by one place... (We are 5th, so old 5th becomes 6th... 9th REM becomes 10th and 10th gets dropped.) We need to do this to REM all three arrays... FOR i%=9 TO pos% STEP -1 scores_name$(i%+1)=scores_name$(i%) scores_value%(i%+1)=scores_value%(i%) scores_rack%(i%+1)=scores_rack%(i%) NEXT i% REM Insert Player's achieved score & rack into the table... scores_value%(pos%)=player%!score% scores_rack%(pos%)=player%!racknum% PROCtimplay_crossfade(4) :REM Switch music track. REM Now deal with the graphics... REM Produce our basic Space Invaders layout a la title sequence. CLS:PROCdraw_rail(rail%!toprail%):PROCdraw_rail(rail%!baserail%) PROCplot(FNsprite_get(100),space%!xpos%,space%!ypos%) PROCplot(FNsprite_get(101),invt%!xpos%,invt%!ypos%) REM Build and display the text. string$=FNstring_get(54)+" "+STR$(player%!score%) string$+=" "+FNstring_get(55)+" "+STR$(pos%)+FNsuffix(pos%) str%=LEN(string$)*screen%!xfont% x%=FNcentre_text(str%,screen%!xsize%):y%=invt%!ypos%-170 PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) REM Free memory used by objects. PROCash_free(space%):PROCash_free(invt%) REM Create and display a box... box%!xsize%=165:box%!ysize%=40 PROCcentre_sprite_xy(box%,screen%):PROCdraw_box(box%) REM Handle player input... REM Code 13 is Return/Enter. Code 8 is BackSpace. All other codes REM are filtered with FNvalid() before being added to string$ REM to prevent the program breaking when we can't find a graphic. x%=box%!xpos%+20:y%=box%!ypos%+12:string$="":i%=1:done%=FALSE SYSFNswi_get(8),"FX 21,0" :REM Flush Keyboard buffer... REPEAT g%=GET :REM Get a char from Keyboard. REM Validate input... REM Only allow character input if our string length is less REM than 8 characters. Only accept Enter or Backspace for the REM 9th character. CASE g% OF WHEN 13 :done%=TRUE WHEN 8 :i%-=1:string$=LEFT$(string$,i%-1) OTHERWISE :IF i%<9 AND FNvalid(g%) THEN i%+=1:string$+=CHR$(g%) ENDCASE REM Update Display... REM Var i% will be less than 1 if the first character pressed REM was backspace. We need to trap that and make it 1. We also REM clear the input field before we re-display string$... This REM is so that when Backspace is pressed, the character is REM actually removed from the screen as well as from the input. REM (string$) REM We also need to check if we are trying to display an empty REM string, as our PROCprint() will fall over. This occurs if REM the first key typed was BackSpace or a character we can't REM display. (In the case of the latter, it is not added to REM string$, but we still come down here to update the display.) IF i%<1 THEN i%=1 PROCprint(x%,y%,STRING$(8," "),0,gamestate%!gfx_area%) IF string$<>"" THEN PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) UNTIL done% REM Add player's name to entry already prepared earlier and save REM if we can... (scores_save%=TRUE) IF string$="" OR string$=" " THEN string$=FNstring_get(56) scores_name$(pos%)=FNencode_string(string$,gamestate%!key%) gamestate%!hstab%=TRUE IF gamestate%!scores_save% THEN PROChitable_save PROCtimplay_crossfade(4) :REM Switch music back. ENDPROC REM Have we got the ability to display this character? REM Return TRUE if we can or FALSE if we can't. Evaluate this REM as an expression. DEFFNvalid(chr%) =chr%>=32 AND chr%<=126 REM ************************************************************* REM Functions & Proceedures (HUD & Scoring) REM ************************************************************* REM Initialise the HUD display... DEFPROChud_init(sc%,hi%,l%,r%) LOCAL x%,gfx_area%,string$:gfx_area%=80 string$=FNstring_get(57)+" "+FNstring_get(59) string$+=FNstr_pad(STR$(sc%),10," ",TRUE) PROCprint(0,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) string$=FNstring_get(58)+" "+FNstring_get(57) string$+=" "+FNstring_get(59)+FNstr_pad(STR$(hi%),10," ",TRUE) x%=screen%!xsize%-LEN(string$)*screen%!xfont% PROCprint(x%,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) PROChud_lives(l%):PROChud_rack(r%):PROChud_soundstate ENDPROC REM Update HUD lives display... DEFPROChud_lives(num%) LOCAL gfx_area%,string$:gfx_area%=80 PROCplot(FNsprite_get(1),0,0):string$=" = "+STR$(num%) PROCprint(base%!xsize%,0,string$,0,gamestate%!gfx_area%) ENDPROC REM Update HUD rack display... DEFPROChud_rack(num%) LOCAL x%,gfx_area%,string$:gfx_area%=80 string$=FNstring_get(60)+" "+FNstring_get(59) string$+=FNstr_pad(STR$(num%),3,"0",TRUE) x%=screen%!xsize%-LEN(string$)*screen%!xfont% PROCprint(x%,0,string$,0,gamestate%!gfx_area%) ENDPROC REM HUD banner text display routine... REM This is called by the Pause Game & Extra Life event refresh REM handlers at the moment and possibly others in the future. DEFPROChud_bannertext(string$) LOCAL x%,y%,str%,gfx_area%:gfx_area%=80 str%=LEN(string$)*screen%!xfont% x%=FNcentre_text(str%,screen%!xsize%) y%=screen%!ysize%-screen%!yfont% PROCprint(x%,y%,string$,0,gamestate%!gfx_area%) ENDPROC REM Score a hit... REM The first part updates the score display on the top left REM corner of the HUD. If the player's current score is greater REM than that of the top spot, this value is also reflected in the REM Hi Score display in the top right. For every 5k points the REM player scores, he/she gains an extra base. We check this and REM increment here too. DEFPROChud_scorehit(value%) LOCAL elscore%,elcount%,gfx_area%,x%,string$ elscore%=12:elcount%=16:gfx_area%=80 :REM Object offsets. player%!score%+=value% string$=FNstring_get(57)+" "+FNstring_get(59) string$+=FNstr_pad(STR$(player%!score%),10," ",TRUE) PROCprint(0,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) IF player%!score%-gamestate%!elscore%>=5000 THEN player%!lives%+=1:PROChud_lives(player%!lives%) gamestate%!elscore%=player%!score%:gamestate%!elcount%=0 ENDIF IF player%!score%>scores_value%(1) THEN string$=FNstring_get(58)+" "+FNstring_get(57)+" " string$+=FNstring_get(59) string$+=FNstr_pad(STR$(player%!score%),10," ",TRUE) x%=screen%!xsize%-LEN(string$)*screen%!xfont% PROCprint(x%,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) ENDIF ENDPROC REM Display sound status... REM Displays one of three icons in the HUD area depending on the REM current sound status. The placement of this needs to make REM sense during both game and attract modes. DEFPROChud_soundstate LOCAL mstatus%,blk%,fr$ blk%=FNash_claim(16) :REM Claim space for image block. mstatus%=0 :REM Object offsets. (audiostate%) blk%!xsize%=FNconv_units(20,1):blk%!ysize%=FNconv_units(10,2) blk%!xpos%=FNcentre_sprite(blk%,screen%):blk%!ypos%=0 REM Trap invalid sound state. REM by the time this is called, our status flag should be setup REM to something meaningful. Just in case it isn't, we sanity REM check it here. IF audiostate%!mstatus%<0 OR audiostate%!mstatus%>2 THEN ERROR255,FNstring_get(111)+" "+STR$(audiostate%!mstatus%) ENDIF REM Update display and free ASH block. fr$=FNsprite_get(120+audiostate%!mstatus%) PROCplot(fr$,blk%!xpos%,blk%!ypos%):PROCash_free(blk%) ENDPROC REM ************************************************************* REM Functions & Proceedures (Rack & Invaders) REM ************************************************************* REM Cold initialisation... REM Called directly by PROCmain once at the beginning of each REM game to initialise the first wave... DEFPROCrack_gameinit LOCAL frame%,scol%,ecol%,rownum% frame%=flags1%:scol%=flags5%:ecol%=flags6%:rownum%=flags7% PROCrack_init:PROCrack_redraw ENDPROC REM Initialise a rack of invaders... REM This is called at the beginning of the game and whenever REM rack%!beenhit%=rack%!xnum%*rack%!ynum% (All invaders hit.) REM to produce the next wave of the invasion force... We hold the REM status of each invader in the invstat%() array... REM Value Significance REM ======================== REM -n Just been hit. REM 0 Dead and buried. REM >0 Current graphic. DEFPROCrack_init LOCAL col%,row%,bunkinv% bunkinv%=104 :REM gamestate% object offsets. REM Rack properties... REM These are set here as they have to be reset at the beginning REM of each wave. rack%!xpos%=50:rack%!ypos%=550:rack%!frame%=0:rack%!beenhit%=0 rack%!speed%=3:rack%!dir%=rack%!speed%:rack%!scol%=1 rack%!ecol%=rack%!xnum%:rack%!rownum%=1 rack%!xsize%=(rack%!xnum%*inv%!xsize%)+((rack%!xnum%-1)*rack%!colgap%) rack%!ysize%=rack%!ynum%*inv%!ysize% REM Initialise invaders status. FOR row%=1 TO rack%!xnum% FOR col%=1 TO rack%!ynum% invstat%(row%,col%)=((col%+1)/2)+(rack%!frame%*10) NEXT col% NEXT row% REM Reset Bunkers Invaded flag. gamestate%!bunkinv%=FALSE ENDPROC REM Redraw the whole rack of invaders at once. DEFPROCrack_redraw LOCAL col%,row%,x%,y%,blk%:blk%=FNash_claim(16) FOR row%=rack%!scol% TO rack%!ecol% x%=rack%!xpos%+((row%-1)*(inv%!xsize%+rack%!colgap%)) FOR col%=rack%!rownum% TO rack%!ynum% y%=rack%!ypos%+((col%-1)*inv%!ysize%) IF invstat%(row%,col%)>0 THEN invstat%(row%,col%)=((col%+1)/2)+(rack%!frame%*10) ENDIF IF invstat%(row%,col%)<>0 THEN CASE SGN(invstat%(row%,col%)) OF WHEN -1 : PROCrack_checkinv(x%,y%,row%,col%) WHEN 1 : PROCplot(FNsprite_get(27)+STR$(invstat%(row%,col%)),x%,y%) ENDCASE ELSE blk%!xpos%=x%:blk%!ypos%=y%:blk%!xsize%=inv%!xsize% blk%!ysize%=inv%!ysize%:PROCwipe_sprite(blk%) ENDIF NEXT col% NEXT row% PROCash_free(blk%) ENDPROC REM We start our explosion at -5. Every time we redraw an invader REM we add to the count until zero is reached. When this happens REM we wipe the sprite by creating a temporary object and passing REM it to PROCwipe_sprite() in the normal way. REM +----+--------------------------+ REM | x% | Physical X co-ordinate. | REM | y% | Physical Y co-ordinate. | REM | r% | Row index into array. | REM | c% | Column index into array. | REM +----+--------------------------+ DEFPROCrack_checkinv(x%,y%,r%,c%) LOCAL blk% invstat%(r%,c%)+=1:PROCplot(FNsprite_get(26),x%,y%) IF invstat%(r%,c%)=0 THEN blk%=FNash_claim(16):blk%!xpos%=x%:blk%!ypos%=y% blk%!xsize%=inv%!xsize%:blk%!ysize%=inv%!ysize% PROCwipe_sprite(blk%):PROCash_free(blk%) ENDIF ENDPROC REM Advance rack down by one row... REM Object blk% hovers above the top row of invaders to allow me a REM quick use of PROCwipe_sprite() to remove the old top row. We REM drop a row by dropping 1/3 of a row three times to lessen the REM "jump". The original used to do something like this, but I'm REM guessing this was down to the lack of CPU power rather than by REM design. DEFPROCrack_advance LOCAL blk%,i%:blk%=FNash_claim(16) FOR i%=1 TO 3 rack%!ypos%-=inv%!ysize%/3:PROCrack_redraw NEXT i% blk%!xpos%=rack%!xpos% blk%!ypos%=rack%!ypos%+rack%!ysize% blk%!xsize%=rack%!xsize%:blk%!ysize%=inv%!ysize% PROCwipe_sprite(blk%):PROCash_free(blk%) ENDPROC REM End game... REM We are called when the rack of invaders has reached the REM player's base. Do a little animation before we return to the REM Hiscore entry etc... DEFPROCrack_endgame LOCAL inv_left%,inv_right%,mt%,frtime%,alt%,speed%,i%,img$ speed%=1:inv_ext%=24 REM Reserve space... inv_left%=FNash_claim(28):inv_right%=FNash_claim(28) REM Clear any in-game debris from the screen. (Invaders, saucers, REM missiles and other junk.) PROCwipe_sprite(rack%):PROCwipe_sprite(base%) IF shell%!active% THEN PROCwipe_sprite(shell%):shell%!active%=FALSE IF saucer%!active% THEN PROCwipe_sprite(saucer%):saucer%!active%=FALSE PROCinvfire_clearall:PROCbunk_clearall REM Setup initial values. base%!xpos%=FNcentre_sprite(base%,screen%) inv_left%!xpos%=0-inv%!xsize% :REM Left invader properties. inv_left%!ypos%=base%!ypos% inv_left%!dir%=speed% inv_right%!xpos%=screen%!xsize% :REM Right invader properties. inv_right%!ypos%=base%!ypos% inv_right%!dir%=0-speed% REM Zero any remaining lives & update bottom of HUD... player%!lives%=0:PROCdraw_rail(rail%!baserail%) PROChud_lives(player%!lives%):PROChud_rack(player%!racknum%) REM Hit it!! REM Two invaders, one from each side, approach the base and blow REM it up... PROCplot(FNsprite_get(1),base%!xpos%,base%!ypos%) REM The approach... frame%=0:alt%=FALSE REPEAT inv_left%!xpos%+=inv_left%!dir% inv_left%!inv_ext%=inv_left%!xpos%+inv%!xsize% inv_right%!xpos%+=inv_right%!dir% SYS FNswi_get(6) TO mt% :REM OS_ReadMonotonicTime IF mt%-frtime%>=50 THEN alt%=FNtoggle(alt%):frtime%=mt% IF alt% THEN img$=FNsprite_get(21) ELSE img$=FNsprite_get(20) PROCplot(img$,inv_left%!xpos%,inv_left%!ypos%) PROCplot(img$,inv_right%!xpos%,inv_right%!ypos%) PROCwait(2):WAIT UNTIL inv_left%!inv_ext%>=base%!xpos% REM The cheer... FOR i%=1 TO 10 PROCplot(FNsprite_get(20),inv_left%!xpos%,inv_left%!ypos%) PROCplot(FNsprite_get(20),inv_right%!xpos%,inv_right%!ypos%) PROCwait(20) PROCplot(FNsprite_get(21),inv_left%!xpos%,inv_left%!ypos%) PROCplot(FNsprite_get(21),inv_right%!xpos%,inv_right%!ypos%) PROCwait(20):WAIT NEXT i% REM The end... PROCsfx_play(2) FOR i%=1 TO 3 PROCplot(FNsprite_get(2)+STR$(i%),base%!xpos%,base%!ypos%) PROCwait(20):WAIT NEXT i% PROCwait(20):PROCwipe_sprite(base%) PROCash_free(inv_left%):PROCash_free(inv_right%) ENDPROC REM ************************************************************* REM Functions & Proceedures (Invader Fire) REM ************************************************************* REM Setup our array of invader missile objects... REM Like everything else, sizes are supplied in pixels. These are REM passed to FNconv_units() which converts them to OS Units. All REM system calls expect these values as OS Units. DEFPROCinvfire_init LOCAL i%,hittime%,frtime% hittime%=flags3%:frtime%=flags4% :REM Offsets. FOR i%=1 TO 10 invfire%(i%)=FNash_claimfilled(48,0) :REM Claim memory. invfire%(i%)!xsize%=FNconv_units(5,1) :REM Width (In px.) invfire%(i%)!ysize%=FNconv_units(20,2) :REM Height (In px.) SYS FNswi_get(6) TO invfire%(i%)!hittime% :REM Timer stores. SYS FNswi_get(6) TO invfire%(i%)!frtime% invfire%(i%)!active%=FALSE :REM Set inactive. NEXT i% ENDPROC REM If we haven't already got an active missile in this slot REM then we create one. The likelyhood of this happening is REM a percentage chance based on the player's current level +5. REM This is capped by a second call to FNpct(), the value of REM which is initially 4. This value is incremented by 1 for REM every 10 racks the player destroys. REM Prop.. | Value | Comments. REM -------+---------+-------------- REM type% | 1 or 2 | Missile type. (Type 1 can be hit.) REM frame% | 0,1,2,3 | Current frame. (2 & 3 For type 2 only.) REM Missile types are defined 75/25. Having a 75% chance of REM being type 1. Speed for type 1 is 4 OS Units per frame, for REM type 2, we half that. DEFPROCinvfire_create(slot%) LOCAL fpos%,firepos%,frate%,spd%,x%,y% LOCAL frame%,type% frame%=flags1%:type%=flags2% :REM Object properties. fpos%=RND(10):frate%=player%!racknum%+5 :REM Other constants. IF frate%>100 THEN frate%=100 IF player%!racknum% MOD 10=0 THEN rack%!frcap%+=1 ENDIF IF FNpct(frate%) AND FNpct(rack%!frcap%) THEN IF invstat%(fpos%,rack%!rownum%)>0 THEN x%=rack%!xpos%+(fpos%-1)*(inv%!xsize%+rack%!colgap%) y%=rack%!ypos%+((rack%!rownum%-1)*inv%!ysize%)-inv%!ysize% firepos%=(inv%!xsize%/2)-(invfire%(slot%)!xsize%/2) invfire%(slot%)!xpos%=x%+firepos% invfire%(slot%)!ypos%=y% invfire%(slot%)!active%=TRUE:invfire%(slot%)!beenhit%=FALSE invfire%(slot%)!type%=1:invfire%(slot%)!frame%=0:spd%=4 IF FNpct(25) THEN invfire%(slot%)!type%=2:spd%=spd%/2 invfire%(slot%)!speed%=spd% invfire%(slot%)!dir%=invfire%(slot%)!speed% PROCsfx_play(3) ENDIF ENDIF ENDPROC REM Continue animating down the screen... REM Handle the image swapping (for the animation) and the REM position of the currently selected invader missile. DEFPROCinvfire_animate(slot%) LOCAL mt%,frame%,frtime%,type%,fr$,fr2$ REM Assign meaningful names to object specific flags. frame%=flags1%:type%=flags2%:frtime%=flags4% SYS FNswi_get(6) TO mt% :REM Handle the timer... IF mt%-invfire%(slot%)!frtime%>=40 THEN invfire%(slot%)!frtime%=mt% invfire%(slot%)!frame%+=1 ENDIF :REM Limit frame counter... IF invfire%(slot%)!type%=1 AND invfire%(slot%)!frame%>1 THEN invfire%(slot%)!frame%=0 ENDIF IF invfire%(slot%)!type%=2 AND invfire%(slot%)!frame%>3 THEN invfire%(slot%)!frame%=0 ENDIF fr2$=STR$(invfire%(slot%)!frame%) :REM Prepare frame... IF invfire%(slot%)!type%=2 AND invfire%(slot%)!frame%=3 THEN fr2$="1" ENDIF fr$=FNsprite_get(30)+STR$(invfire%(slot%)!type%)+fr2$ REM Display frame... REM Only advance and display prepared frame if we haven't REM already been hit, otherwise we just display the explosion. IF NOT invfire%(slot%)!beenhit% THEN invfire%(slot%)!ypos%-=invfire%(slot%)!dir% PROCplot(fr$,invfire%(slot%)!xpos%,invfire%(slot%)!ypos%) ELSE fr$=FNsprite_get(30)+"3" PROCplot(fr$,invfire%(slot%)!xpos%,invfire%(slot%)!ypos%) ENDIF ENDPROC REM Missile has hit the player's base... DEFPROCinvfire_hitbase(slot%) LOCAL i% PROCsfx_play(2) FOR i%=1 TO 3 PROCplot(FNsprite_get(2)+STR$(i%),base%!xpos%,base%!ypos%) PROCwait(20):WAIT NEXT i% PROCwipe_sprite(base%) IF shell%!active% THEN PROCwipe_sprite(shell%):shell%!active%=FALSE ENDIF base%!xpos%=FNcentre_sprite(base%,screen%) player%!lives%-=1:PROChud_lives(player%!lives%) PROCwipe_sprite(invfire%(slot%)):invfire%(slot%)!active%=FALSE PROCplot(FNsprite_get(1),base%!xpos%,base%!ypos%) ENDPROC REM We've been hit by a player's shell... DEFPROCinvfire_beenhit(slot%) LOCAL type%,hittime% type%=flags2%:hittime%=flags3% :REM Object offsets. IF invfire%(slot%)!type%=1 THEN IF NOT invfire%(slot%)!beenhit% THEN invfire%(slot%)!beenhit%=TRUE:PROChud_scorehit(5) fr$=FNsprite_get(30)+"3" PROCplot(fr$,invfire%(slot%)!xpos%,invfire%(slot%)!ypos%) SYS FNswi_get(6) TO invfire%(slot%)!hittime% ENDIF ENDIF PROCwipe_sprite(shell%):shell%!active%=FALSE ENDPROC REM We have been destroyed... DEFPROCinvfire_explode(slot%) LOCAL hittime%,mt% hittime%=flags3% :REM Object offsets. SYS FNswi_get(6) TO mt% IF mt%-invfire%(slot%)!hittime%>=40 THEN PROCwipe_sprite(invfire%(slot%)) invfire%(slot%)!active%=FALSE ENDIF ENDPROC REM Clear all invader missiles off the screen... REM Used by PROCrack_endgame in preparation for the animation. DEFPROCinvfire_clearall LOCAL i%,pcsinv%:pcsinv%=52 FOR i%=1 TO gamestate%!pcsinv% IF invfire%(i%)!active% THEN PROCwipe_sprite(invfire%(i%)):invfire%(i%)!active%=FALSE ENDIF NEXT i% ENDPROC REM ************************************************************* REM Functions & Proceedures (Bunkers) REM ************************************************************* REM Setup an arbitary number of bunkers... REM This allocates memory and performs the calculations required REM to convert from pixels to OS Units to give us the correct REM sizes for all further display & collision detection etc... REM Bunker positions are calculated using the following formula... REM x=(s/n)+((c-1)*(s/n)) -> Gives us the position of the current REM bunker. Where x = X Co-ordinate. REM s = Screen width. REM n = Number of bunkers. REM c = Current bunker. DEFPROCbunk_init LOCAL i%,x%,gap%,hitcount%,pcsbnk% hitcount%=flags1%:pcsbnk%=56 :REM Object offsets. bunk%!xpos%=0:bunk%!ypos%=0 bunk%!xsize%=FNconv_units(30,1) :REM Width (In px.) bunk%!ysize%=FNconv_units(15,2) :REM Height (In px.) gap%=screen%!xsize%/gamestate%!pcsbnk% :REM Gap between bunkers. x%=gap%-150 :REM Left bunker pos. FOR i%=1 TO gamestate%!pcsbnk% bunker%(i%)=FNash_claimfilled(48,0) :REM Claim memory. bunker%(i%)!xsize%=bunk%!xsize% :REM Width. bunker%(i%)!ysize%=bunk%!ysize% :REM Height. bunker%(i%)!xpos%=x%+((i%-1)*gap%) :REM Position (X) bunker%(i%)!ypos%=base%!ypos%+100 :REM Position (Y) bunker%(i%)!active%=TRUE :REM Set active. bunker%(i%)!hitcount%=0 :REM Hit counter. NEXT i% bunk%!xpos%=bunker%(1)!xpos% :REM Not used. bunk%!ypos%=bunker%(1)!ypos% :REM Common Y co-ord. ENDPROC REM Reset all bunkers to active state... REM Perform a quick init. We already know where everything goes, REM we just need to get them all in a known condition. DEFPROCbunk_reset LOCAL i%,hitcount%,pcsbnk% hitcount%=flags1%:pcsbnk%=56 :REM Object offsets. FOR i%=1 TO gamestate%!pcsbnk% bunker%(i%)!active%=TRUE :REM Set active. bunker%(i%)!hitcount%=0 :REM Reset hit counter. NEXT i% ENDPROC REM Redraw all active bunkers... REM Does much the same as PROCbunk_init, but without all the setup REM as we already know where everything is. If the selected bunker REM is not active then remove it. DEFPROCbunk_redrawall LOCAL i%,pcsbnk% pcsbnk%=56 :REM gamestate% offset. FOR i%=1 TO gamestate%!pcsbnk% IF bunker%(i%)!active% THEN PROCbunk_redraw(bunker%(i%)) NEXT i% ENDPROC REM Redraw a single bunker... REM Using the old redraw routine, now defunct, did a blanket REM redraw whether the particular bunker needed updating or not. REM This cuts down the work by targetting the bunker that actually REM needs updating. DEFPROCbunk_redraw(slot%) LOCAL tx%,ty%,hits%,str%,hitcount%,gfx_area%,string$ hitcount%=flags1%:gfx_area%=84 :REM Properties hits%=10-slot%!hitcount% :REM Hits left. string$=STR$(hits%):str%=LEN(string$) :REM String tx%=slot%!xpos%+FNcentre_text(str%,slot%!xsize%) :REM Co-ords. ty%=slot%!ypos%+(slot%!ysize%/2)-(screen%!yfont%/2) ty%+=7:IF str%=1 THEN tx%-=7 ELSE tx%-=15 :REM Fine tune PROCwipe_sprite(slot%) :REM Wipe old. PROCplot(FNsprite_get(16),slot%!xpos%,slot%!ypos%):REM Display. PROCprint(tx%,ty%,string$,0,gamestate%!gfx_area%) ENDPROC REM Bunker collision detection... REM Both invader fire and player fire handlers call this routine REM if they detect a collision with a bunker. Bunkers can be hit REM 10 times by either invader fire or player's shots before they REM disappear until the beginning of the next rack. Var by% is REM TRUE if hit by player or FALSE otherwise. DEFPROCbunk_beenhit(slot%,by%) LOCAL y%,hitcount% hitcount%=flags1%:timer%=flags2% :REM Object offsets. bunker%(slot%)!hitcount%+=1 :REM Increment hit counter. IF bunker%(slot%)!hitcount%>=10 THEN PROCwipe_sprite(bunker%(slot%)):bunker%(slot%)!active%=FALSE ELSE PROCwipe_sprite(bunker%(slot%)) y%=bunker%(slot%)!ypos% IF by% THEN bunker%(slot%)!ypos%+=5 ELSE bunker%(slot%)!ypos%-=5 PROCbunk_redraw(bunker%(slot%)) bunker%(slot%)!beenhit%=TRUE:bunker%(slot%)!ypos%=y% SYSFNswi_get(6) TO bunker%(slot%)!timer% ENDIF ENDPROC REM Clear all bunkers off the screen... REM Used by PROCrack_endgame in preparation for the animation. DEFPROCbunk_clearall LOCAL i%,blk%,pcsbnk%:pcsbnk%=56 blk%=FNash_claim(16) FOR i%=1 TO gamestate%!pcsbnk% IF bunker%(i%)!active% THEN bunker%(i%)!active%=FALSE blk%!xpos%=bunker%(i%)!xpos%:blk%!ypos%=bunker%(i%)!ypos% blk%!xsize%=bunk%!xsize%:blk%!ysize%=bunk%!ysize% PROCwipe_sprite(blk%) ENDIF NEXT i% PROCash_free(blk%) ENDPROC REM ************************************************************* REM Functions & Proceedures (Core Graphics Routines) REM ************************************************************* REM Load and Decompress GFX data... REM Doing it this way allows us to use three different buffers, one REM for the sprites and the other two for the charsets. All Graphics REM are compressed using the Squash module. The data is loaded REM and decompressed to memory, with some munging and a bit of REM magic here, it becomes our sprite area. This code can also REM process raw sprite data as it did before. The code assumes REM that if the Squash header isn't present ("SQSH") then we're REM dealing with raw sprite data. No new checks are made as to REM the validity of the sprite data at this stage. We do a REM verify area at the end. The only disadvantage with this as it REM stands it that the file will be loaded & discarded and loaded REM again if it is already decompressed. Is there a better way of REM doing this??? REM Modified (11/06/10) to seperate decompression code from GFX REM loader. We now call two routines from this code to handle REM the decompression part that was embedded here. DEFFNgfx_load(filespec$) LOCAL size%,osize%,blk%,area% size%=FNfs_filesize(filespec$) :REM Input file size. blk% =FNash_claimfilled(size%,0) :REM Claim buffer. PROCinit_memusage(blk%,208,0) :REM Display alloc. SYSFNswi_get(3),16,filespec$,blk%,0 :REM Load file. osize%=FNsquash_init(blk%) :REM Init Squash. IF osize%>0 THEN area%=FNgfx_createdefaultarea(osize%+4) :REM Create a sprite area. PROCsquash_decompress(blk%,area%+4) :REM Decompress to memory. ELSE area%=FNgfx_createdefaultarea(size%+256) :REM Create a sprite area. SYSgfx_spriteop%,256+10,area%,filespec$ :REM Load raw data. ENDIF SYSgfx_spriteop%,256+17,area% :REM Verify data. =area% REM Create a default sprite area... REM This requires a few default parameters to be set up and is the same REM for both cases. Only the values differ. DEFFNgfx_createdefaultarea(size%) LOCAL area% area%=FNash_claimfilled(size%,0) area%!0=size%:area%!4=0:area%!8=16:area%!12=16 =area% REM Display a string character by character with an optional REM delay between each one. Each character is indexed to the REM 8*8 character set sprites using ASCII codes. DEFPROCprint(x%,y%,string$,delay%,bank%) LOCAL i%,chsize%,gfx_area%,gfx_sprs% gfx_area%=88:gfx_sprs%=76 :REM GFX bank ptrs. REM Check for bank garbage and switch... IF bank%<>gamestate%!80 AND bank%<>gamestate%!84 THEN ERROR 255,FNstring_get(112) ENDIF gamestate%!gfx_area%=bank% :REM Switch banks. REM Display our text... chsize%=screen%!xfont% FOR i%=1 TO LEN(string$) PROCplot(STR$(ASC(MID$(string$,i%,1))),x%+((i%-1)*chsize%),y%) IF delay%>0 THEN PROCwait(delay%) NEXT i% gamestate%!gfx_area%=gamestate%!gfx_sprs% :REM Switch back... ENDPROC REM Display a given image. Modified to use a local sprite area & REM system calls to speed things up dramatically. (02/01/08) REM sprite$ = Name of image to display. REM x% = X Co-ordinate. REM y% = Y Co-ordinate. DEFPROCplot(sprite$,x%,y%) LOCAL address%,gfx_area%:gfx_area%=88 SYS gfx_spriteop%, 256+24, gamestate%!gfx_area%, sprite$ TO ,, address% SYS gfx_spriteop%, 512+34, gamestate%!gfx_area%, address%, x%, y%, 0 ENDPROC REM Clear a sprite from the screen. (New method.) REM Before we created an identical bitmap in the sprite file REM and displayed this over our image. This should be faster, and REM result in a smaller sprite file. DEFPROCwipe_sprite(obj%) GCOL0,0 TINT0 RECTANGLE FILLobj%!xpos%,obj%!ypos%,obj%!xsize%,obj%!ysize% ENDPROC REM ************************************************************* REM Functions & Proceedures (Text & Graphics Alignment.) REM ************************************************************* REM Centre text horizontally... REM Where string_length% is the length of the string in multiples REM of OS Units, where each multiple is the width of a character. REM Var screen% is the screen width. REM width... DEFFNcentre_text(string_length%,screen%) =(screen%/2)-(string_length%/2) REM Centre text horizontally & vertically. DEFPROCcentre_text_xy(string$,delay%) LOCAL str%,x%,y%,gfx_area%:gfx_area%=80 str%=LEN(string$)*screen%!xfont%:x%=FNcentre_text(str%,screen%!xsize%) y%=(screen%!ysize%/2)-(screen%!yfont%/2) PROCprint(x%,y%,string$,delay%,gamestate%!gfx_area%) ENDPROC REM Centre sprite horizontally... REM Takes the two "object" blocks as parameters and extracts the REM required values from them. Then returns the centre position. DEFFNcentre_sprite(obj%,relative%) =(relative%!xsize%/2)-(obj%!xsize%/2) REM Centre sprite horizontally & vertically. REM Returns the new position at offsets xpos% & ypos% in obj%. DEFPROCcentre_sprite_xy(obj%,relative%) obj%!xpos%=(relative%!xsize%/2)-(obj%!xsize%/2) obj%!ypos%=(relative%!ysize%/2)-(obj%!ysize%/2) ENDPROC REM ************************************************************* REM Functions & Proceedures (Graphical Boxes & Lines.) REM ************************************************************* REM Repeat a 6*6 image across the screen to produce a "rail" like REM effect at position y%. DEFPROCdraw_rail(ypos%) LOCAL i%,reps% reps%=screen%!xsize%/rail%!xsize% FOR i%=1 TO reps%+1 PROCplot(FNsprite_get(108),(i%-1)*rail%!xsize%,ypos%) NEXT i% ENDPROC REM Produce a box to surround text in GAME OVER!!! & GOODBYE!!! REM texts. (Among others.) DEFPROCdraw_box(obj%) LOCAL x%,y%,cxsize%,cysize%,rxsize%,rysize%,i% REM Setup... cxsize%=FNconv_units(3,1):cysize%=FNconv_units(3,2) rxsize%=1:rysize%=3 REM The top & bottom... FOR i%=1 TO obj%!xsize% x%=obj%!xpos%+((i%-1)*rxsize%) PROCplot(FNsprite_get(113),x%,obj%!ypos%) PROCplot(FNsprite_get(113),x%,obj%!ypos%+(obj%!ysize%-(2*rysize%))) NEXT i% REM The two sides... x%=obj%!xpos%+(obj%!xsize%-cxsize%) FOR i%=1 TO obj%!ysize%-1 PROCplot(FNsprite_get(114),obj%!xpos%,obj%!ypos%+((i%-1)*rxsize%)) PROCplot(FNsprite_get(114),x%,obj%!ypos%+((i%-1)*rxsize%)) NEXT i% REM The four corners... PROCplot(FNsprite_get(111),obj%!xpos%,obj%!ypos%) PROCplot(FNsprite_get(109),obj%!xpos%,(obj%!ypos%+obj%!ysize%)-cysize%) PROCplot(FNsprite_get(112),obj%!xpos%+(obj%!xsize%-cxsize%),obj%!ypos%) x%=(obj%!xpos%+obj%!xsize%)-cxsize% y%=(obj%!ypos%+obj%!ysize%)-cysize% PROCplot(FNsprite_get(110),x%,y%) ENDPROC REM ************************************************************* REM Functions & Proceedures (String & Text De-coding) REM ************************************************************* REM These routines deal with our garbled strings. This is done REM this way for a couple of reasons... REM # BasCrunch output is smaller. REM # BasCrunch ouptut is harder to reverse engineer. REM Get number of messages in a processed file... DEFFNstring_enumprocessed(filespec$,magic$) LOCAL hdl%,count%,junk%,key%,maggot$ hdl%=OPENIN(filespec$) INPUT#hdl%,maggot$:maggot$=FNstring_reverse(maggot$) IF maggot$<>magic$ THEN CLOSE#hdl%:ERROR 255,"Bad Magic!" INPUT#hdl%,key%,junk%,junk%,count% CLOSE#hdl% =count% EOR key% REM Load a file off disc and return messages setup in arrays. DEFFNstring_load(filespec$,magic$,RETURN nums%(),RETURN strs$()) LOCAL hdl%,i%,count%,key%,chfgot%,cplgot%,chf%,cpl%,maggot$:chf%=0 hdl%=OPENIN(filespec$) INPUT#hdl%,maggot$:maggot$=FNstring_reverse(maggot$) IF maggot$<>magic$ THEN CLOSE#hdl%:ERROR 255,"Bad Magic!" INPUT#hdl%,key%,chfgot%,cplgot%,count% chfgot%=chfgot% EOR key%:cplgot%=cplgot% EOR key% count%=count% EOR key% FOR i%=1 TO count% INPUT#hdl%,nums%(i%),strs$(i%) NEXT i% CLOSE#hdl% FOR i%=1 TO count%:chf%+=LEN(strs$(i%)):NEXT i% cpl%=chf%/count% IF chf%<>chfgot% OR cpl%<>cplgot% THEN ERROR 255,"File Inconsistant with header!" ENDIF =key% REM Reverse a given string... REM Reverse the order of all characters in the given string. "ABC" REM becomes "CBA" etc. DEFFNstring_reverse(string$) LOCAL i%,out$ FOR i%=LEN(string$) TO 1 STEP-1 out$+=MID$(string$,i%,1) NEXT i% =out$ REM Decode SWIs and Sprite at runtime to increase speed... REM We don't store these strings as part of the listing, and we REM don't want them as plaintext on disc, but the overheads REM involved in decoding them on the fly are big. It doesn't REM matter so much if these are as plaintext in memory. DEFPROCstring_bulkdecode(max%,key%,RETURN str$()) LOCAL i% FOR i%=1 TO max% str$(i%)=FNencode_string(str$(i%),key%) NEXT i% ENDPROC REM The following functions are all identical, other than what is REM actually searched. (The arrays are different.) So... To avoid REM duplicating code, I've written a generic search routine with REM three wrappers passing the correct values to the generic code. REM Retrieve an encoded message for display... (Wrapper...) DEFFNstring_get(msg%) LOCAL key%:key%=gamestate%!68 =FNcore_get(msg%,string_max%,string_num%(),string_msg$(),key%,106) REM Retrieve an encoded Sprite name... (Wrapper...) DEFFNsprite_get(msg%) =FNcore_get(msg%,ilut_max%,ilut_num%(),ilut_msg$(),-1,107) REM Retrieve an encoded SWI name... (Wrapper...) DEFFNswi_get(msg%) =FNcore_get(msg%,slut_max%,slut_num%(),slut_msg$(),-1,108) REM Retrieve an encoded LibASH resource... (Wrapper...) DEFFNash_get(msg%) =FNcore_get(msg%,ashmsg_max%,ashmsg_num%(),ashmsg_msg$(),ash_key%,109) REM Generic core code for above get functions... REM If our XOR key (key%) is -1 then we are dealing with raw REM strings that have already been de-munged in memory. DEFFNcore_get(msg%,max%,num%(),str$(),key%,msg_err%) LOCAL i%,found%:found%=0 FOR i%=1 TO max% IF num%(i%)=msg% THEN found%=i%:i%=max% NEXT i% IF found%=0 THEN IF msg%>=106 AND msg%<=108 THEN ERROR 255,"Missing message!!!" ELSE IF msg_err%=109 THEN ERROR 255,"Can't find LibASH resource : "+STR$(msg%) ELSE ERROR 255,FNstring_get(msg_err%)+" "+STR$(msg%) ENDIF ENDIF ENDIF IF key%=-1 THEN =str$(found%) =FNencode_string(str$(found%),key%) REM ************************************************************* REM Functions & Proceedures (Sound Sub-system OS call wrappers.) REM ************************************************************* REM Load a song file, decompress into memory and return REM a handle... We use the generic decompression code extracted REM from our old FNgfx_load() for this. We only need to free the REM file buffer if osize% is zero. Likewise we only need to claim REM an output buffer if we're actually going to decompress anything! DEFFNtimplay_songload(filespec$) LOCAL size%,osize%,blk%,area%,hdl% size%=FNfs_filesize(filespec$) :REM Input file size. blk% =FNash_claimfilled(size%,0) :REM Claim buffer. IF invaders_debug% THEN PROCinit_memusage(blk%,208,0) :REM Disp. alloc. SYSFNswi_get(3),16,filespec$,blk%,0 :REM Load file. osize%=FNsquash_init(blk%) :REM Init Squash. REM Var. osize% is either a positive value, or zero. If zero then REM the SQSH magic has not been found at the beginning of the REM file, therefore it is assumed that the file contains raw data. REM If positive, it contains the size of the output buffer we need REM create. IF osize%>0 THEN area%=FNash_claimfilled(osize%,0) :REM Claim output buffer. PROCsquash_decompress(blk%,area%) :REM Decompress to memory. SYSFNswi_get(31),,0,area%,osize% TO hdl% :REM Copy to TimPlayer wksp. PROCinit_memusage(area%,0,1) :REM Disp. free. PROCash_free(area%) :REM Free output buffer. ELSE SYSFNswi_get(31),,0,blk%,size% TO hdl% :REM Copy to TimPlayer wksp. PROCinit_memusage(blk%,0,1) :REM Disp. free. PROCash_free(blk%) :REM Free file buffer. ENDIF =hdl% REM Fade out the current track whilst fading in the new one... DEFPROCtimplay_crossfade(delay%) LOCAL i%,j%,mstatus%,chandle%,mhandle%,ehandle%,gsvol%,mstep% mstatus%=0:mhandle%=8:ehandle%=12 :REM Object offsets. chandle%=16:gsvol%=20:mstep%=24 REM Only do this if music is currently playing. If not, skip to REM end and return to caller. We can't just bung an ENDPROC here REM or we'll barf the cruncher. IF audiostate%!mstatus%=1 THEN REM Check what we are currently playing and start playing the REM other one at zero volume. IF audiostate%!chandle%=audiostate%!mhandle% THEN SYSFNswi_get(36),audiostate%!ehandle%,0 :REM Play HiScore Entry SYSFNswi_get(33),audiostate%!ehandle% :REM track (fade-in) ELSE SYSFNswi_get(36),audiostate%!mhandle%,0 :REM Play Main Theme SYSFNswi_get(33),audiostate%!mhandle% :REM track (fade-in) ENDIF REM Start the cross-fade. FOR i%=0 TO audiostate%!gsvol% STEP audiostate%!mstep% j%=audiostate%!gsvol%-i% IF audiostate%!chandle%=audiostate%!mhandle% THEN SYSFNswi_get(36),audiostate%!ehandle%,i% SYSFNswi_get(36),audiostate%!mhandle%,j% ELSE SYSFNswi_get(36),audiostate%!mhandle%,i% SYSFNswi_get(36),audiostate%!ehandle%,j% ENDIF PROCwait(delay%) NEXT i% REM Stop the track we've just faded out. SYSFNswi_get(35),audiostate%!chandle% REM Update our current handle pointer. IF audiostate%!chandle%=audiostate%!mhandle% THEN audiostate%!chandle%=audiostate%!ehandle% ELSE audiostate%!chandle%=audiostate%!mhandle% ENDIF ENDIF ENDPROC REM Fade-in the current track... DEFPROCtimplay_fadein(delay%) LOCAL i%,mstatus%,chandle%,gsvol%,mstep% mstatus%=0:chandle%=16:gsvol%=20:mstep%=24 :REM Object offsets. SYS FNswi_get(36),audiostate%!chandle%,0 :REM Set volume to zero. SYS FNswi_get(33),audiostate%!chandle% :REM Start playing. FOR i%=0 TO audiostate%!gsvol% STEP audiostate%!mstep% SYS FNswi_get(36),audiostate%!chandle%,i% :REM Set volume. IF delay%>0 THEN PROCwait(delay%) :REM Wait if delay%>0 NEXT i% ENDPROC REM Fade-out the current track... DEFPROCtimplay_fadeout(delay%) LOCAL i%,mstatus%,chandle%,gsvol%,mstep% mstatus%=0:chandle%=16:gsvol%=20:mstep%=24 :REM Object offsets. IF audiostate%!mstatus%=1 THEN FOR i%=audiostate%!gsvol% TO 0 STEP 0-audiostate%!mstep% SYSFNswi_get(36),audiostate%!chandle%,i% IF delay%>0 THEN PROCwait(delay%) :REM Wait if delay%>0 NEXT i% SYSFNswi_get(34),audiostate%!chandle% ENDIF ENDPROC REM Trigger one of a number of loaded sound effects... REM The number passed in what% is the number of the sample to REM play. We currently only have 4 sound effects numbered 1-4. DEFPROCsfx_play(what%) LOCAL mstatus%,err$ mstatus%=0:err$=FNstring_get(110)+" "+STR$(what%) IF audiostate%!mstatus%>0 THEN IF what%<1 AND what%>4 THEN ERROR255,err$ SYSFNswi_get(8),"SPlay_fire0"+STR$(what%)+"mhh" ENDIF ENDPROC REM ************************************************************* REM Functions & Proceedures (Misc. Game Specific.) REM ************************************************************* REM Get various information about screen resolution. This includes REM viewing area and conversion factors. We cannot rely on fixed REM values because they change from mode to mode. So grab the REM current values from the OS and bung 'em into some variables. DEFPROCget_screeninfo LOCAL blk% blk%=FNash_claim(20) :REM Reserve space for parameter block. REM Setup parameter block... blk%!0=4 :REM XEigFactor (Convert.) blk%!4=5 :REM YEigFactor (Convert.) blk%!8=11 :REM XWindLimit (Width.) blk%!12=12 :REM YWindLimit (Height.) blk%!16=-1 :REM Termination byte. SYS FNswi_get(5), blk%, blk%:REM Call OS. REM Setup our screen% object... screen%!x_eig%=blk%!0 :REM X & Y EigFactor. screen%!y_eig%=blk%!4 screen%!xsize%=(blk%!8)+1< OS Units) REM 2 = Height (Pixels > OS Units) REM 3 = Width (OS Units > Pixels) REM 4 = Height (OS Units > Pixels) REM Returns REM conv% = Converted value. DEFFNconv_units(size%,op%) LOCAL conv%,mode% REM Check to see if the screen mode has changed. If so, then REM call PROCget_screeninfo to update the values. This is REM probably overkill and can be omitted, but still good practice REM for debugging. mode%=MODE IF mode%<>screen%!setmode% THEN PROCget_screeninfo REM Do the conversion. CASE op% OF WHEN 1 : conv%=size%<>screen%!x_eig% WHEN 4 : conv%=size%>>screen%!y_eig% OTHERWISE ERROR 255,FNstring_get(103)+" "+STR$(op%) ENDCASE =conv% REM Collision detection. Takes two objects and returns TRUE if REM one is inside or has hit the other, else returns FALSE. Vars REM tx_ext% & ty_ext% hold the two far edges of the sprite. We REM calculate these by adding the target object's size to it's REM position each time we're called. We then use these values to REM perform a bounds check, one for x (ix% is TRUE if in.) and REM one for y (iy% is TRUE if in.). Finally... We return an REM evaluation of ix% AND iy%. DEFFNhit(obj%,targ%) LOCAL tx_ext%,ty_ext%,ix%,iy% ix%=FALSE:iy%=FALSE IF obj%!active% AND targ%!active% THEN tx_ext%=targ%!xpos%+targ%!xsize% :REM Right hand edge. ty_ext%=targ%!ypos%+targ%!ysize% :REM Top edge. IF obj%!xpos%>=targ%!xpos% AND obj%!xpos%=targ%!ypos% AND obj%!ypos%-1 THEN SYS"TimPlayer_SongStop",audiostate%!i% SYS"TimPlayer_SongUnload",audiostate%!i% ENDIF NEXT i% REM Tear down ASH Sub-system. PROCash_destroy ENDPROC REM Display file info as we go... (Initialisation.) DEFFNfind(filespec$,msg$) LOCAL string$,s$,rc%,found% string$=STRING$(10," "):RIGHT$(string$,3)=" : " LEFT$(string$,LEN(msg$))=msg$:rc%=FNfs_find(filespec$) s$="Missing!!":found%=FALSE IF rc%=1 THEN s$="OK.":found%=TRUE string$+=s$ PRINTstring$ =found% REM Load the configs file... REM This currently contains the last known sound & vsync states. DEFPROCgs_loadconfig LOCAL hdl%,mstatus%,gsvol%,value%,vol%,cfgflag%,vsync%,sval% mstatus%=0:gsvol%=20 :REM Object offsets. vsync%=108:cfgflag%=92 hdl%=OPENIN(sav_dir$+"Configs") INPUT#hdl%,value%,vol%,sval% CLOSE#hdl% REM Do some sanity checking. IF value%>=0 AND value%<=2 THEN audiostate%!mstatus%=value% IF vol%>=0 AND vol%<=128 THEN audiostate%!gsvol%=vol% IF sval%>=1 AND sval%<=64 THEN gamestate%!vsync%=sval% gamestate%!cfgflag%=FALSE ENDPROC REM Save the configs file... REM This is performed on clean exit of the program. Currently REM contains the last known sound & vsync states. Only saves REM if config data has changed. (Offset 92 in gamestate% REM is set.) DEFPROCgs_saveconfig LOCAL hdl%,mstatus%,gsvol%,cfgflag%,vsync% mstatus%=0:gsvol%=20 :REM Object offsets. vsync%=108:cfgflag%=92 IF gamestate%!cfgflag% THEN hdl%=OPENOUT(sav_dir$+"Configs") PRINT#hdl%,audiostate%!mstatus%,audiostate%!gsvol% PRINT#hdl%,gamestate%!vsync% CLOSE#hdl% ENDIF ENDPROC REM ************************************************************* REM Functions & Proceedures (Data Decompression.) REM ************************************************************* REM Return our output buffer size requirements... REM Returns either a positive value indicating the size of the REM output buffer we need to create, or zero if the data is REM compressed. DEFFNsquash_init(in%) LOCAL size%,wsize%,osize%,i%,magic$ size%=FNash_blocksize(in%) :REM Get size of input buffer. PROCinit_memusage(in%,208,0) :REM Display alloc. REM Quick sanity check here. We return zero if data not SQSHed. FOR i%=0 TO 3:magic$+=CHR$(in%?i%):NEXT i% IF magic$<>"SQSH" THEN =0 REM OK to continue... Buffer contains what appears to be squashed REM data. PRINTFNstring_get(27)+"..." SYSFNswi_get(7),1<<3,size% TO wsize%,osize% :REM Init Squash. IF osize%=-1 THEN osize%=in%!4 :REM Squash bugfix. =osize% REM Decompress data... REM We are supplied with an input buffer and an output buffer. It REM is assumed that we can obtain sizes for both using LibASH REM calls. NOTE:- input buffer is clobbered on exit!! DEFPROCsquash_decompress(in%,out%) LOCAL size%,wksp%,wsize%,osize% size% =FNash_blocksize(in%) :REM Input buffer size. osize%=FNash_blocksize(out%) :REM Output buffer size. SYSFNswi_get(7),1<<3,size% TO wsize% :REM Init Squash. wksp%=FNash_claimfilled(wsize%,0) :REM Claim workspace. PROCinit_memusage(wksp%,207,0) :REM Display alloc. SYSFNswi_get(7),1<<2,wksp%,in%+20,size%-20,out%,osize% TO err% IF err%<>0 THEN ERROR255,FNstring_get(114)+" "+STR$(err%) PROCinit_memusage(wksp%,0,1):PROCinit_memusage(in%,0,1) PROCash_free(wksp%):PROCash_free(in%) :REM Free up memory. ENDPROC REM ************************************************************* REM Functions & Proceedures (Initialisation.) REM ************************************************************* REM Run through a counter checking names of files in the "Screens" REM directory inside the application until we have a file not REM found condition. Set the counter to this. This routine starts REM from count% and stops at the first gap in a chain. REM Because we fire it from PROCevnt_kb_screendump we effectively REM handle any possible breakage in a chain of existing numbers REM because it'll always find the next free slot. We continue to REM count up until "Dump" and the number of digits in our count REM exceed the ADFS file name limit (10 Chars.), at that point we REM flag up an error by returning -1. This gives us a max count REM of 999999. Chances are we'd exceed the max number of files REM allowed in a directory before we'd ever hit this limit!! DEFFNinit_setdumpcounter(count%) LOCAL dump_name$ dump_name$="Dump"+STR$(count%) IF FNfs_find(scr_dir$+dump_name$)<>0 THEN REPEAT count%+=1 dump_name$="Dump"+STR$(count%) UNTIL FNfs_find(scr_dir$+dump_name$)=0 OR LEN(dump_name$)>10 IF LEN(dump_name$)>10 THEN count%=-1 ENDIF =count% REM Displays helpful diagnostic information on startup. REM Used to build the memory allocation strings during the REM init sequence. Var blk% contains the base address REM returned by a LibASH claim and msg% contains the token REM for the unique part of the message. For displaying frees, REM the unique part of the message passed in msg% is always REM zero and is ignored. Allocs have an op% value of 0 and REM frees have an op% value of 1 DEFPROCinit_memusage(blk%,msg%,op%) LOCAL string$ IF invaders_debug% THEN IF op%=0 THEN string$=FNstring_get(200) ELSE string$=FNstring_get(209) string$+=" &":PRINTstring$;~FNash_blocksize(blk%); PRINT" "+FNstring_get(201)+" &";~blk%; IF op%=0 THEN string$=" "+FNstring_get(202)+" "+FNstring_get(msg%) ELSE string$="." ENDIF PRINTstring$ ENDIF ENDPROC REM This was originally set out as strings in the listing, but REM implementing this in a loop reading data from an array REM tidies up the listing somewhat. DEFPROCinit_credscreen LOCAL hdl%,chf%,cpl%,chfgot%,cplgot%,i%,gs%,magic$ :REM Misc LOCALs LOCAL key%,size% :REM gamestate% obj. key%=60:size%=72 :REM offsets. hdl%=OPENIN(dat_dir$+"Creds") INPUT#hdl%,magic$ :REM Read magic and verify. IF magic$<>"DERC" THEN CLOSE#hdl%:ERROR 255,FNstring_get(101):END ENDIF INPUT#hdl%,gamestate%!key%,gs%,chfgot%,cplgot% :REM File header. gamestate%!size%=gs% EOR gamestate%!key% chfgot%=chfgot% EOR gamestate%!key% cplgot%=cplgot% EOR gamestate%!key% DIM cred_str$(gamestate%!size%),cred_spc%(gamestate%!size%) FOR i%=1 TO gamestate%!size% INPUT#hdl%,cred_spc%(i%),cred_str$(i%) NEXT i% CLOSE#hdl% chf%=0 FOR i%=1 TO gamestate%!size%:chf%+=LEN(cred_str$(i%)):NEXT i% cpl%=chf%/gamestate%!size% IF cpl%<>cplgot% OR chf%<>chfgot% THEN ERROR 255,FNstring_get(113) ENDPROC REM ************************************************************* REM Functions & Proceedures (Generic & Portable Code) REM ************************************************************* REM Returns TRUE n% percent of the time... REM This is used all over the place, usually dictating the chances REM of a particular event occuring, or as a bias for properties. DEFFNpct(n%) =RND(100)<=n% REM Encode or decode a string using a simple XOR algo... This REM prevents hackers from peeking at and modifying strings in REM files. Strings are in reverse order in BASIC datafiles, but REM are still in plaintext format. REM string$ = String to mash up. REM Returns REM mashed$ = Mashed up string. DEFFNencode_string(string$,key%) LOCAL i%,out$ FOR i%=1 TO LEN(string$) out$+=CHR$(ASC(MID$(string$,i%,1)) EOR key%) NEXT i% =out$ REM Introduce a delay of n% hundredths of a second. DEFPROCwait(n%) LOCAL t% t%=TIME+n% REPEAT:UNTIL TIME>=t% ENDPROC REM Toggle a flag between 0 & 1. DEFFNtoggle(n%) =n% EOR 1 REM Returns a suffix (st, nd, rd etc...) when given a number. This REM number can be any number of digits long, we just deal with the REM last digit. DEFFNsuffix(n%) LOCAL out$,u% u%=VAL(RIGHT$(STR$(n%),1)) out$=FNstring_get(65) IF u%>=1 AND u%<=3 THEN out$=FNstring_get(u%+61) =out$ REM ************************************************************ REM Functions & Proceedures (LibSTR) REM ************************************************************ REM I've imported the following two routines from my string utils REM library. Normally I'd just include them using the LIBRARY REM command, but that would kill the cruncher, so I've dumped them REM here... REM Pad a string to l% characters by adding multiple copies of REM char$ to it. If start% is set TRUE then we will add padding to REM the start of the string, else we add it at the end. If longer REM than l% characters, then chop it to l% characters. DEFFNstr_pad(string$,l%,char$,start%) LOCAL diff%,out$ diff%=l%-LEN(string$) :REM How many to add. IF diff%<=0 THEN out$=LEFT$(string$,l%) ELSE IF start% THEN out$=STRING$(l%,char$):RIGHT$(out$,LEN(string$))=string$ ELSE out$=string$+STRING$(diff%,char$) ENDIF ENDIF =out$ REM Fix a string's length to l% characters and return it right REM aligned... DEFFNstr_rightalign(string$,l%) LOCAL out$ out$=STRING$(l%," ") RIGHT$(out$,LEN(string$))=string$ =out$ REM ************************************************************ REM Functions & Proceedures (LibFS) REM ************************************************************ REM I've imported the following two routines from my fs utils REM library. Normally I'd just include them using the LIBRARY REM command, but that would kill the cruncher, so I've dumped them REM here... REM Check for the prescence of a filing system object at a given REM location. I say object, as this can check for the existance of REM directories as well as files. REM Parameter block... REM filespec$ = Filespec to check for in current directory. REM ...On Exit. REM found% = 0 Object not found. REM = 1 Object is a file. REM = 2 Object is a directory. DEFFNfs_find(filespec$) LOCAL found% SYS"XOS_File",17,filespec$ TO found% =found% REM Return the size of a file. (In bytes.) DEFFNfs_filesize(filespec$) LOCAL size% SYS FNswi_get(3),17,filespec$ TO ,,,,size% =size% REM Can we write to this file? Return TRUE or FALSE... REM I really need to find a better way of doing this. The logic is REM screwy and doesn't pick up on a write protected floppy. I'm REM not sure if this is down to this part of the check, or the REM logic further up which deals with the result from this code. REM op%=0 -> File doesn't exist. Var filespec$ contains location. REM op%=1 -> File exists. Can we write/replace it? DEFFNfs_writeable(op%,filespec$) LOCAL hdl%,flg%,flags%,result% result%=FALSE REM Attempt to open or create a file depending on our opcode. CASE op% OF WHEN 0 : hdl%=OPENUP(filespec$+"chkfile") WHEN 1 : hdl%=OPENUP(filespec$) REM Unknown op% code. OTHERWISE : ERROR 255,FNstring_get(104)+" "+STR$(op%) ENDCASE REM If we get this far and we still haven't got a valid file REM handle then it's fairly safe to say that we're never gonna REM get one, so skip the next bit and return FALSE. IF hdl%<>0 THEN SYS FNswi_get(4),254,hdl% TO flags%;flg% IF (flg% AND 1) THEN CLOSE#hdl%:ERROR 255,FNstring_get(105) IF ((flags% AND (1<<7))<>0) THEN result%=TRUE CLOSE#hdl% ENDIF =result% REM ************************************************************ REM Functions & Proceedures (Ash Heap manager for BASIC) REM Copyright (c)2004 7th Software. REM ************************************************************ REM Initialise ASH Subsystem... DEFPROCash_init(key%) LOCAL err% ash_heap%=0:ash_total%=0:ash_key%=key% SYS FNash_get(210), -1, -1 TO ash_slot% SYS FNash_get(211) TO ash_page% ; err% IF err% AND 1 THEN ash_page%=16384 PROCash_create(ash_page%) ENDPROC REM Release the memory used for our heap DEFPROCash_destroy IF ash_heap% THEN SYS FNash_get(210), ash_heap%-&8000,-1 TO ash_slot% ash_heap%=0:ash_total%=0 ENDIF ENDPROC REM Claim a block from the heap - generate an error if claim fails REM Our block's size is now the requested size + 3 extra locations REM @ 4 bytes per location + 3 extra bytes. REM Offs | Significance. REM --------+--------------- REM 0 | Low marker. REM 4 | Block user area size in bytes. REM 8 | User area base address. (This is what is returned.) REM | High marker. DEFFNash_claim(bytes%) LOCAL block%,size% size%=(bytes%+15) AND &FFFFFFFC block%=FNash_alloc(size%) IF block% ELSE ERROR 254,FNash_get(110) block%!0=&DEADDEAD :REM Store low marker... block%!4=bytes% :REM Store user area size... !(block%-8+(block%!-4))=&DEADDEAD :REM Store high marker... =block%+8 REM Claim a block of memory, as above, but fill it with a REM specified value before returning. DEFFNash_claimfilled(bytes%,value%) LOCAL block% block%=FNash_claim(bytes%) :REM Claim requested space... PROCash_blockfill(block%,value%) :REM Fill with spec'd value... =block% REM Free a block into the heap DEFPROCash_free(RETURN block%) CASE FNash_blockintegrity(block%) OF WHEN 1 :ERROR 254,FNash_get(111) WHEN 2 :ERROR 254,FNash_get(112) WHEN 3 :ERROR 254,FNash_get(113) ENDCASE block%-=8:ash_total%-=block%!-4 SYS FNash_get(212), 3, ash_heap%, block% block% = 0 ENDPROC REM Return the size (in bytes) of a specified block. DEFFNash_blocksize(block%) LOCAL blk% blk%=block%-4 =blk%!0 REM Fill a block of memory with a specified value... DEFPROCash_blockfill(block%,value%) LOCAL offset%,size% size%=FNash_blocksize(block%) FOR offset%=0 TO size%-4 STEP 4 block%!offset%=value% NEXT offset% ENDPROC REM Create a block with identical contents to the original. DEFFNash_blockcopy(block%) LOCAL blk%,offset%,size% size%=FNash_blocksize(block%) blk%=FNash_claim(size%) FOR offset%=0 TO size%-4 STEP 4 blk%!offset%=block%!offset% NEXT offset% =blk% REM Returns integrity status of a given block... REM 0 -> No Errors REM 1 -> Low marker (Under-run) REM 2 -> High marker (Over-run) REM 3 -> Both markers!?! DEFFNash_blockintegrity(block%) LOCAL hi%,lo% hi%=FALSE:lo%=FALSE :REM Default. (No Errors) block%-=8 IF block%!0<>&DEADDEAD THEN lo%=TRUE IF !(block%-8+(block%!-4))<>&DEADDEAD THEN hi%=TRUE IF lo% AND hi% THEN =3 :REM Both markers trashed. IF hi% THEN =2 :REM High marker trashed. IF lo% THEN =1 :REM Low marker trashed. =0 :REM Integrity OK. REM *** LibASH Internals *** REM Create a heap in the memory above HIMEM DEFPROCash_create(size%) IF ash_heap% THEN ERROR 254,FNash_get(114) size%=(size%+ash_page%-1) AND NOT (ash_page%-1) ash_heap%=HIMEM SYS FNash_get(210), ash_slot%+size%, -1 TO ash_slot% size%=ash_slot%+&8000-ash_heap% IF size%<1 THEN ash_heap%=0 ERROR 254,FNash_get(115) ELSE SYS FNash_get(212), 0, ash_heap%,, size% ENDIF ash_total%=0 ENDPROC REM Attempt to add some more memory into the end of our heap DEFPROCash_grow(bytes%) LOCAL prev% prev%=ash_slot% SYS FNash_get(210), ash_slot%+bytes%, -1 TO ash_slot% bytes%=ash_slot%-prev% IF bytes% THEN SYS FNash_get(212),5,ash_heap%,,bytes% TO ,,,bytes% ENDPROC REM Claim a block from the heap (grow it if need be) - return REM zero if claim fails. DEFFNash_alloc(bytes%) LOCAL block%, err% SYS FNash_get(213), 2, ash_heap%,, bytes% TO ,, block% ; err% IF err% AND 1 THEN PROCash_grow((bytes%+ash_page%) AND NOT (ash_page%-1)) SYS FNash_get(213), 2, ash_heap%,, bytes% TO ,, block% ; err% IF err% AND 1 THEN =0 ENDIF ash_total% += block%!-4 =block%